SSB_HighSpeed_Modem/hsmodem/voiceio.cpp

463 lines
14 KiB
C++
Executable File

/*
* High Speed modem to transfer data in a 2,7kHz SSB channel
* =========================================================
* Author: DJ0ABR
*
* (c) DJ0ABR
* www.dj0abr.de
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* voiceio.c ... interface to libsoundio für DV audio
*
*/
#include "hsmodem.h"
struct SoundIo* voice_soundio = NULL;
struct SoundIoDevice* io_ls_device = NULL;
struct SoundIoDevice* io_mic_device = NULL;
struct SoundIoOutStream* outlsstream = NULL;
struct SoundIoInStream* inmicstream = NULL;
bool lsrawdev = true;
bool micrawdev = true;
float latency = 0.1f;
void read_voicecallback(struct SoundIoInStream* instream, int frame_count_min, int frame_count_max)
{
int err;
//printf("cap: %d %d\n", frame_count_min, frame_count_max);
//int chans = instream->layout.channel_count;
struct SoundIoChannelArea* areas;
// samples are in areas.ptr
int frames_left = frame_count_max; // take all
while (keeprunning)
{
int frame_count = frames_left;
if ((err = soundio_instream_begin_read(instream, &areas, &frame_count)))
{
fprintf(stderr, "begin read error: %s", soundio_strerror(err));
exit(1);
}
if (!frame_count)
break;
for (int frame = 0; frame < frame_count; frame += 1)
{
for (int ch = 0; ch < instream->layout.channel_count; ch += 1)
{
if (micrawdev == false)
{
float frxdata;
memcpy(&frxdata, areas[ch].ptr, instream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
if (ch == 0)
{
float f = frxdata;
f *= softwareMICvolume;
io_mic_write_fifo(f);
}
}
else
{
int16_t rxdata;
memcpy(&rxdata, areas[ch].ptr, instream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
if (ch == 0)
{
float f = rxdata;
f /= 32768;
f *= softwareMICvolume;
io_mic_write_fifo(f);
}
}
}
}
// needs to sleep or it will not work correctly, no idea why
sleep_ms(1);
//measure_speed_bps(frame_count);
if ((err = soundio_instream_end_read(instream)))
{
fprintf(stderr, "end read error: %s", soundio_strerror(err));
exit(1);
}
frames_left -= frame_count;
if (frames_left <= 0)
break;
}
}
void overflow_voicecallback(struct SoundIoInStream* instream)
{
static int count = 0;
printf("voice overflow %d\n", ++count);
}
void write_sample_s16ne(char* ptr, double sample) {
int16_t* buf = (int16_t*)ptr;
double range = (double)INT16_MAX - (double)INT16_MIN;
double val = sample * range / 2.0;
*buf = (int16_t)val;
}
void write_sample_float32ne(char* ptr, double sample) {
float* buf = (float*)ptr;
*buf = (float)sample;
}
#define MAXCAPCHUNKLEN 50000
//static const double PI = 3.14159265358979323846264338328;
//static double seconds_offset = 0.0;
static void write_voicecallback(struct SoundIoOutStream* outstream, int frame_count_min, int frame_count_max)
{
//printf("pb: %d %d\n", frame_count_min, frame_count_max);
//double float_sample_rate = outstream->sample_rate;
//double seconds_per_frame = 1.0 / float_sample_rate;
struct SoundIoChannelArea* areas;
int err;
int frames_left = 4800;
if (frame_count_max < frames_left)
frames_left = frame_count_max;
for (;;) {
int frame_count = frames_left;
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) {
fprintf(stderr, "unrecoverable stream error: %s\n", soundio_strerror(err));
return;
}
if (!frame_count)
break;
//printf("ck: %d\n", frame_count);
//float f[MAXCAPCHUNKLEN];
float *f = (float *)malloc(frame_count * sizeof(float));
int fiforet = io_ls_read_fifo_num(f, frame_count);
if (fiforet == 0)
{
// elements not available, fill with zeroes
//printf("only %d not enough data, send zeroes\n", io_ls_fifo_usedspace());
memset(f, 0, sizeof(float) * frame_count);
}
const struct SoundIoChannelLayout* layout = &outstream->layout;
//double pitch = 440.0;
//double radians_per_second = pitch * 2.0 * PI;
for (int frame = 0; frame < frame_count; frame += 1)
{
//double sample = sin((seconds_offset + frame * seconds_per_frame) * radians_per_second);
for (int channel = 0; channel < layout->channel_count; channel += 1)
{
float ftx = f[frame] * softwareLSvolume;
//write_sample_float32ne(areas[channel].ptr, sample);
if(lsrawdev == false)
write_sample_float32ne(areas[channel].ptr, ftx);
else
write_sample_s16ne(areas[channel].ptr, ftx);
areas[channel].ptr += areas[channel].step;
}
}
//seconds_offset = fmod(seconds_offset + seconds_per_frame * frame_count, 1.0);
free(f);
//measure_speed_bps(frame_count);
if ((err = soundio_outstream_end_write(outstream))) {
if (err == SoundIoErrorUnderflow)
return;
fprintf(stderr, "unrecoverable stream error: %s\n", soundio_strerror(err));
return;
}
frames_left -= frame_count;
if (frames_left <= 0)
break;
}
}
void underflow_voicecallback(struct SoundIoOutStream* outstream)
{
static int count = 0;
printf("voice underflow %d\n", count++);
}
int io_init_voice(char* lsname, char* micname)
{
int err;
init_voice_result = 0;
printf("\n ==== IO INIT VOICE devices ====\n");
printf("requested:\nPB :<%s>\nCAP:<%s>\ncapture rate:%d\n", lsname, micname, VOICE_SAMPRATE);
io_close_voice();
// prepare and connect to libsoundio
voice_soundio = soundio_create();
if (!voice_soundio) {
printf("vsoundio_create: out of memory\n");
return 0;
}
#ifdef _WIN32_
if ((err = soundio_connect_backend(voice_soundio, SoundIoBackendWasapi))) {
printf("vsoundio_connect: %s\n", soundio_strerror(err));
return 0;
}
#endif
#ifdef _LINUX_
if ((err = soundio_connect(voice_soundio))) {
printf("vsoundio_connect: %s\n", soundio_strerror(err));
return 0;
}
#endif
if (lsname == NULL || micname == NULL || strlen(lsname) < 3 || strlen(micname) < 3) // no devices defined yet
{
printf("no devices specified\n");
return 0;
}
char* lsdevid = getDevID(lsname, 1);
if (lsdevid == NULL)
{
printf("no lsdevid for %s\n",lsname);
return 0;
}
char* micdevid = getDevID(micname, 0);
if (micdevid == NULL)
{
printf("no micdevid for %s\n", micname);
return 0;
}
soundio_flush_events(voice_soundio);
// under Windows we usually use raw devices. This does not work with
// virtual sound cards due to problems in libsoundio
// for VACs we use shared devices, otherwise raw
lsrawdev = true;
if (strstr(lsname, "irtual") || strstr(lsname, "VAC"))
lsrawdev = false;
micrawdev = true;
if (strstr(micname, "irtual") || strstr(micname, "VAC"))
micrawdev = false;
if (VoiceAudioMode != VOICEMODE_LISTENAUDIOIN)
{
// define the capture device
for (int i = 0; i < soundio_input_device_count(voice_soundio); i++)
{
io_mic_device = NULL;
struct SoundIoDevice* device = soundio_get_input_device(voice_soundio, i);
if (strcmp(device->id, micdevid) == 0
#ifdef _WIN32_
&& device->is_raw == micrawdev
#endif
)
{
io_mic_device = device;
break;
}
soundio_device_unref(device);
}
if (!io_mic_device)
{
printf("Invalid device id: %s\n", micdevid);
sleep_ms(2000);
return 0;
}
if (io_mic_device->probe_error)
{
printf("Unable to probe voice device: %s\n", soundio_strerror(io_mic_device->probe_error));
sleep_ms(2000);
return 0;
}
// create capture callback
inmicstream = soundio_instream_create(io_mic_device);
if (!inmicstream) {
printf("out of memory\n");
return 0;
}
if (micrawdev)
inmicstream->format = SoundIoFormatS16NE;
else
inmicstream->format = SoundIoFormatFloat32NE;
inmicstream->sample_rate = VOICE_SAMPRATE;
inmicstream->software_latency = latency;
inmicstream->read_callback = read_voicecallback;
inmicstream->overflow_callback = overflow_voicecallback;
inmicstream->userdata = NULL;
if ((err = soundio_instream_open(inmicstream))) {
printf("unable to open voice input stream: %s", soundio_strerror(err));
return 0;
}
if ((err = soundio_instream_start(inmicstream))) {
printf("unable to start voice input device: %s", soundio_strerror(err));
return 0;
}
init_voice_result |= 2;
printf("selected MICROPHONE device:\nname:%s\nid :%s\n", micname, micdevid);
printf("physical capture rate:%d\n", VOICE_SAMPRATE);
printf("format: %s\n\n", soundio_format_string(inmicstream->format));
// the CAP callback is running now
}
// define the playback device
for (int i = 0; i < soundio_output_device_count(voice_soundio); i++)
{
io_ls_device = NULL;
struct SoundIoDevice* device = soundio_get_output_device(voice_soundio, i);
if (strcmp(device->id, lsdevid) == 0
#ifdef _WIN32_
&& device->is_raw == lsrawdev
#endif
)
{
io_ls_device = device;
break;
}
soundio_device_unref(device);
}
if (!io_ls_device)
{
printf("Invalid device id: %s\n", lsdevid);
return 0;
}
if (io_ls_device->probe_error)
{
printf("Unable to probe device: %s\n", soundio_strerror(io_ls_device->probe_error));
return 0;
}
// create playback callback
outlsstream = soundio_outstream_create(io_ls_device);
if (!outlsstream) {
printf("soundio_outstream_create: out of memory\n");
return 0;
}
if (lsrawdev)
outlsstream->format = SoundIoFormatS16NE;
else
outlsstream->format = SoundIoFormatFloat32NE;
outlsstream->sample_rate = VOICE_SAMPRATE;
outlsstream->software_latency = latency;
outlsstream->write_callback = write_voicecallback;
outlsstream->underflow_callback = underflow_voicecallback;
outlsstream->userdata = NULL;
if ((err = soundio_outstream_open(outlsstream))) {
printf("unable to open voice output stream: %s", soundio_strerror(err));
return 0;
}
if ((err = soundio_outstream_start(outlsstream))) {
fprintf(stderr, "unable to start voice output device: %s", soundio_strerror(err));
return 0;
}
init_voice_result |= 1;
printf("selected LOUDSPEAKER device:\nname:%s\nid :%s\n", lsname, lsdevid);
printf("physical capture rate:%d\n", VOICE_SAMPRATE);
printf("format: %s\n\n", soundio_format_string(outlsstream->format));
return init_voice_result;
}
void io_close_voice()
{
printf("close Voice\n");
if (inmicstream) soundio_instream_destroy(inmicstream);
inmicstream = NULL;
if (outlsstream) soundio_outstream_destroy(outlsstream);
outlsstream = NULL;
if (io_ls_device) soundio_device_unref(io_ls_device);
io_ls_device = NULL;
if (io_mic_device) soundio_device_unref(io_mic_device);
io_mic_device = NULL;
if (voice_soundio) soundio_destroy(voice_soundio);
voice_soundio = NULL;
}
void io_saveStream(float f)
{
static FILE* fw = NULL;
static int old_VoiceAudioMode = 0;
if (VoiceAudioMode != old_VoiceAudioMode)
{
if (old_VoiceAudioMode == VOICEMODE_OFF && VoiceAudioMode == VOICEMODE_RECORD)
{
char fn[500];
snprintf(fn, 499, "%s/oscardata/intro/intro.pcm", homepath);
fn[499] = 0;
// audio was switched on, open file to save PCM stream
if (fw) fclose(fw);
fw = fopen(fn, "wb");
if (!fw) printf("cannot open pcm file:%s\n",fn);
else printf("AUDIO RECORDING:%s ...\n Speak, then switch off\n",fn);
}
if (VoiceAudioMode == VOICEMODE_OFF)
{
// audio was switched off, stop recording
if (fw)
{
fclose(fw);
printf("AUDIO RECORDING off\n");
}
fw = NULL;
}
old_VoiceAudioMode = VoiceAudioMode;
}
if (fw)
{
if (f > 1) f = 1;
if (f < -1) f = -1;
int16_t sh = (int16_t)(f * 32768.0f);
fwrite(&sh, sizeof(int16_t), 1, fw);
}
}