SSB_HighSpeed_Modem/hsmodem/soundio.cpp

681 lines
20 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.
*
* soundio.c ... interface to libsoundio
*
*/
#include "hsmodem.h"
void io_cap_write_fifo(float sample);
#define MAXAUDIODEVICES 50
struct SoundIo* soundio = NULL;
struct SoundIoDevice* io_pb_device = NULL;
struct SoundIoDevice* io_cap_device = NULL;
struct SoundIoInStream* instream = NULL;
struct SoundIoOutStream* outstream = NULL;
float latenz = 0.1f; // long (some seconds) delay can be caused by SDR console (not this program and not the VAC)
typedef struct _AUDIODEV_ {
int in_out = 0; // 0=in, 1=out
char name[1000] = { 0 };
char id[1000] = { 0 };
int minsamprate = 44100;
int maxsamprate = 48000;
int stereo_mono = 2; // 1=mono, 2=stereo
} AUDIODEV;
AUDIODEV audiodev[MAXAUDIODEVICES];
int audiodevidx = 0;
bool pbrawdev = true;
bool caprawdev = true;
void print_devs()
{
printf("\n ==== AUDIO devices ====\n");
for (int i = 0; i < audiodevidx; i++)
{
if(i>0) printf(" -----------------\n");
printf("Name: %s\n", audiodev[i].name);
printf("ID : %s\n", audiodev[i].id);
printf("I/O : %s\n", (audiodev[i].in_out == 0) ? "record":"playback");
printf("Chan: %s\n", (audiodev[i].stereo_mono == 2) ? "stereo" : "mono");
printf("minR: %d\n", audiodev[i].minsamprate);
printf("maxR: %d\n", audiodev[i].maxsamprate);
}
printf("\n =======================\n");
}
static void get_channel_layout(const struct SoundIoChannelLayout* layout)
{
if (layout->name)
{
if (strstr(layout->name, "ereo"))
audiodev[audiodevidx].stereo_mono = 2;
if (strstr(layout->name, "ono"))
audiodev[audiodevidx].stereo_mono = 1;
}
}
int print_device(struct SoundIoDevice* device)
{
if (soundio == NULL) return 0;
if (!device->probe_error)
{
// ignore if exists
for (int i = 0; i < audiodevidx; i++)
if (!strcmp(device->id, audiodev[i].id)) return 0;
if (strstr(device->name, "onitor")) return 0;
strncpy(audiodev[audiodevidx].id, device->id, 999);
audiodev[audiodevidx].id[999] = 0;
strncpy(audiodev[audiodevidx].name, device->name, 999);
audiodev[audiodevidx].name[999] = 0;
for (int i = 0; i < device->layout_count; i++)
get_channel_layout(&device->layouts[i]);
for (int i = 0; i < device->sample_rate_count; i++)
{
struct SoundIoSampleRateRange* range = &device->sample_rates[i];
if (range->min < audiodev[audiodevidx].minsamprate)
audiodev[audiodevidx].minsamprate = range->min;
if (range->max > audiodev[audiodevidx].maxsamprate)
audiodev[audiodevidx].maxsamprate = range->max;
}
if (audiodev[audiodevidx].minsamprate > 44100)
return 0;
if (audiodev[audiodevidx].maxsamprate < 48000)
return 0;
return 1;
}
return 0;
}
static int scan_devices(struct SoundIo* soundio)
{
if (soundio == NULL) return 0;
audiodevidx = 0;
for (int i = 0; i < soundio_input_device_count(soundio); i++)
{
struct SoundIoDevice* device = soundio_get_input_device(soundio, i);
if (print_device(device) == 1)
{
audiodev[audiodevidx].in_out = 0;
audiodevidx++;
}
soundio_device_unref(device);
}
for (int i = 0; i < soundio_output_device_count(soundio); i++)
{
struct SoundIoDevice* device = soundio_get_output_device(soundio, i);
if (print_device(device) == 1)
{
audiodev[audiodevidx].in_out = 1;
audiodevidx++;
}
soundio_device_unref(device);
}
return 0;
}
// build string of audio device name, to be sent to application as response to Broadcast search
// starting with PB devices, sperarator ^, capture devices
// separator between devices: ~
uint8_t io_devstring[MAXDEVSTRLEN + 100];
void io_buildUdpAudioList()
{
memset(io_devstring, 0, sizeof(io_devstring));
io_devstring[0] = ' '; // placeholder for ID for this UDP message
io_devstring[1] = '0' + init_audio_result;
io_devstring[2] = '0' + init_voice_result;
// playback devices
for (int i = 0; i < audiodevidx; i++)
{
if (audiodev[i].in_out == 1)
{
strcat((char*)io_devstring, audiodev[i].name);
strcat((char*)io_devstring, "~"); // audio device separator
}
}
strcat((char*)(io_devstring + 1), "^"); // PB, CAP separator
// capture devices
for (int i = 0; i < audiodevidx; i++)
{
if (audiodev[i].in_out == 0)
{
strcat((char*)io_devstring, audiodev[i].name);
strcat((char*)io_devstring, "~"); // audio device separator
}
}
io_devstring[0] = 3; // ID for this UDP message
}
uint8_t* io_getAudioDevicelist(int* len)
{
// update Status
io_devstring[1] = '0' + init_audio_result;
io_devstring[2] = '0' + init_voice_result;
*len = strlen((char*)(io_devstring + 1)) + 1;
return io_devstring;
}
void io_readAudioDevices()
{
if (soundio == NULL) return;
// to get actual data
soundio_flush_events(soundio);
scan_devices(soundio); // read devices
io_buildUdpAudioList();
//print_devs();
}
char* getDevID(char* devname, int io)
{
for (int i = 0; i < audiodevidx; i++)
{
if (!strcmp(devname, audiodev[i].name) && io == audiodev[i].in_out)
{
return audiodev[i].id;
}
}
return NULL;
}
int min_int(int a, int b)
{
return (a < b) ? a : b;
}
void read_callback(struct SoundIoInStream* instream, int frame_count_min, int frame_count_max)
{
int err;
if (instream == NULL || soundio == NULL) return;
//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));
restart_modems = 1;
return;
}
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 (caprawdev == false)
{
// shared device
float frxdata;
memcpy(&frxdata, areas[ch].ptr, instream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
if (ch == 0)
{
float f = frxdata;
f *= softwareCAPvolume;
io_cap_write_fifo(f);
}
}
else
{
// raw device
// shared device
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 *= softwareCAPvolume;
io_cap_write_fifo(f);
}
}
}
}
//printf("%d into fifo\n", frame_count);
// needs to sleep or it will not work correctly on Windows, 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));
restart_modems = 1;
return;
}
frames_left -= frame_count;
if (frames_left <= 0)
break;
}
}
void overflow_callback(struct SoundIoInStream* instream)
{
static int count = 0;
printf("overflow %d\n", ++count);
}
#define MTXCHECK 48000
void getTXMax(float fv)
{
static float farr[MTXCHECK];
static int idx = 0;
static int f = 1;
if (f)
{
f = 0;
for (int i = 0; i < MTXCHECK; i++)
farr[i] = 1;
}
farr[idx] = fv;
idx++;
if (idx == MTXCHECK)
{
idx = 0;
float max = 0;
for (int i = 0; i < MTXCHECK; i++)
{
if (farr[i] > max) max = farr[i];
}
maxTXLevel = (uint8_t)(max * 100);
//printf("TX max: %10.6f\n", max);
}
}
// #define SINEWAVETEST
#ifdef SINEWAVETEST
static const double PI = 3.14159265358979323846264338328;
static double seconds_offset = 0.0;
#endif
static void write_callback(struct SoundIoOutStream* outstream, int frame_count_min, int frame_count_max)
{
if(outstream == NULL || soundio == NULL) return;
//printf("pb: %d %d\n", frame_count_min, frame_count_max);
#ifdef SINEWAVETEST
double float_sample_rate = outstream->sample_rate;
double seconds_per_frame = 1.0 / float_sample_rate;
double pitch = 440.0;
double radians_per_second = pitch * 2.0 * PI;
#endif
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, "write_callback unrecoverable soundio_outstream_begin_write error: %s\n", soundio_strerror(err));
restart_modems = 1;
return;
}
if (!frame_count)
break;
//printf("ck: %d\n", frame_count);
float* f = (float*)malloc(frame_count * sizeof(float));
int fiforet = io_pb_read_fifo_num(f, frame_count);
if (fiforet == 0)
{
// elements not available, fill with zeroes
memset(f, 0, sizeof(float) * frame_count);
}
const struct SoundIoChannelLayout* layout = &outstream->layout;
for (int frame = 0; frame < frame_count; frame += 1)
{
#ifdef SINEWAVETEST
double sample = sin((seconds_offset + frame * seconds_per_frame) * radians_per_second);
#endif
for (int channel = 0; channel < layout->channel_count; channel += 1)
{
float ftx = f[frame] * softwarePBvolume;
getTXMax(ftx);
if (pbrawdev == false)
{
#ifdef SINEWAVETEST
write_sample_float32ne(areas[channel].ptr, sample); // sine wave test tone
#endif
write_sample_float32ne(areas[channel].ptr, ftx);
}
else
write_sample_s16ne(areas[channel].ptr, ftx);
areas[channel].ptr += areas[channel].step;
}
}
#ifdef SINEWAVETEST
seconds_offset = fmod(seconds_offset + seconds_per_frame * frame_count, 1.0);
#endif
free(f);
if ((err = soundio_outstream_end_write(outstream))) {
if (err == SoundIoErrorUnderflow)
return;
fprintf(stderr, "unrecoverable stream error: %s\n", soundio_strerror(err));
restart_modems = 1;
return;
}
frames_left -= frame_count;
if (frames_left <= 0)
break;
}
}
void underflow_callback(struct SoundIoOutStream* outstream)
{
static int count = 0;
printf("underflow %d\n", count++);
}
int io_init_sound(char *pbname, char *capname)
{
int err;
init_audio_result = 0;
printf("\n ==== IO INIT AUDIO devices ====\n");
//printf("requested\nTX:<%s>\nRX:<%s>\ncapture rate:%d\n\n",pbname,capname,caprate);
io_close_audio();
// prepare and connect to libsoundio
soundio = soundio_create();
if (!soundio) {
printf("soundio_create: out of memory\n");
return 0;
}
#ifdef _WIN32_
if ((err=soundio_connect_backend(soundio, SoundIoBackendWasapi))) {
printf("soundio_connect: %s\n", soundio_strerror(err));
return 0;
}
#endif
#ifdef _LINUX_
if ((err = soundio_connect(soundio))) {
printf("soundio_connect: %s\n", soundio_strerror(err));
return 0;
}
#endif
io_readAudioDevices();
io_init_pipes();
if (pbname == NULL || capname == NULL || strlen(pbname) < 3 || strlen(capname) < 3) // no devices defined yet
{
printf("no devices specified\n");
return 0;
}
char* pbdevid = getDevID(pbname,1);
if (pbdevid == NULL) return 0;
char* capdevid = getDevID(capname,0);
if (capdevid == NULL) return 0;
// define the capture device
soundio_flush_events(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
pbrawdev = true;
if (strstr(pbname, "irtual") || strstr(pbname, "VAC"))
pbrawdev = false;
caprawdev = true;
if (strstr(capname, "irtual") || strstr(capname, "VAC"))
caprawdev = false;
for (int i = 0; i < soundio_input_device_count(soundio); i++)
{
io_cap_device = NULL;
struct SoundIoDevice* device = soundio_get_input_device(soundio, i);
if (strcmp(device->id, capdevid) == 0
#ifdef _WIN32_
&& device->is_raw == caprawdev
#endif
)
{
io_cap_device = device;
break;
}
soundio_device_unref(device);
}
if (!io_cap_device)
{
printf("Invalid device id: %s\n", capdevid);
return 0;
}
if (io_cap_device->probe_error)
{
printf("Unable to probe device: %s\n", soundio_strerror(io_cap_device->probe_error));
return 0;
}
// create capture callback
instream = soundio_instream_create(io_cap_device);
if (!instream) {
printf("out of memory\n");
return 0;
}
// raw devices: 16bit LE, shared devices float
if (caprawdev)
{
instream->format = SoundIoFormatS16NE;
instream->sample_rate = caprate;
physRXcaprate = caprate;
}
else
{
// a VAC needs these settings or it will not work with 44100
instream->format = SoundIoFormatFloat32NE;
instream->sample_rate = AUDIO_SAMPRATE;
physRXcaprate = AUDIO_SAMPRATE;
}
instream->software_latency = latenz;
instream->read_callback = read_callback;
instream->overflow_callback = overflow_callback;
instream->userdata = NULL;
if ((err = soundio_instream_open(instream))) {
printf("unable to open input stream: %d: %s", err, soundio_strerror(err));
return 0;
}
if ((err = soundio_instream_start(instream))) {
fprintf(stderr, "unable to start input device: %s", soundio_strerror(err));
return 0;
}
init_audio_result |= 2;
printf("selected CAPTURE device:\nname:%s\nid :%s\n", capname, capdevid);
printf("physical capture rate:%d, logical capture rate:%d\n", physRXcaprate, caprate);
printf("format: %s\n\n", soundio_format_string(instream->format));
// the CAP callback is running now
// define the playback device
for (int i = 0; i < soundio_output_device_count(soundio); i++)
{
io_pb_device = NULL;
struct SoundIoDevice* device = soundio_get_output_device(soundio, i);
if (strcmp(device->id, pbdevid) == 0
#ifdef _WIN32_
&& device->is_raw == pbrawdev
#endif
)
{
io_pb_device = device;
break;
}
soundio_device_unref(device);
}
if (!io_pb_device)
{
printf("Invalid device id: %s\n", pbdevid);
return 0;
}
if (io_pb_device->probe_error)
{
printf("Unable to probe device: %s\n", soundio_strerror(io_pb_device->probe_error));
return 0;
}
// create playback callback
outstream = soundio_outstream_create(io_pb_device);
if (!outstream) {
printf("soundio_outstream_create: out of memory\n");
return 0;
}
// raw devices: 16bit LE, shared devices float
if (pbrawdev)
outstream->format = SoundIoFormatS16NE;
else
outstream->format = SoundIoFormatFloat32NE;
outstream->sample_rate = caprate;
outstream->software_latency = latenz;
outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;
outstream->userdata = NULL;
if ((err = soundio_outstream_open(outstream))) {
printf("unable to open output stream: %s", soundio_strerror(err));
return 0;
}
if ((err = soundio_outstream_start(outstream))) {
fprintf(stderr, "unable to start output device: %s", soundio_strerror(err));
return 0;
}
init_audio_result |= 1;
printf("selected PLAYBACK device:\nname:%s\nid :%s\n", pbname, pbdevid);
printf("physical capture rate:%d, logical capture rate:%d\n", caprate, caprate);
printf("format: %s\n\n", soundio_format_string(outstream->format));
init_tune();
return init_audio_result;
}
void io_close_audio()
{
printf("close Audio\n");
if(instream) soundio_instream_destroy(instream);
instream = NULL;
if (outstream) soundio_outstream_destroy(outstream);
outstream = NULL;
if(io_pb_device) soundio_device_unref(io_pb_device);
io_pb_device = NULL;
if (io_cap_device) soundio_device_unref(io_cap_device);
io_cap_device = NULL;
if (soundio) soundio_destroy(soundio);
soundio = NULL;
}
void io_setPBvolume(int v)
{
// the volume comes in % 0..99
softwarePBvolume = ((float)v) / 50.0f;
}
void io_setCAPvolume(int v)
{
// the volume comes in % 0..99
softwareCAPvolume = ((float)v) / 50.0f;
}
// set volume
void io_setVolume(int pbcap, int v)
{
if (pbcap == 0) io_setPBvolume(v);
else io_setCAPvolume(v);
}
void io_setLSvolume(int v)
{
// the volume comes in % 0..99
softwareLSvolume = ((float)v) / 50.0f;
}
void io_setMICvolume(int v)
{
// the volume comes in % 0..99
softwareMICvolume = ((float)v) / 50.0f;
}
void setVolume_voice(int pbcap, int v)
{
}