mirror of
https://github.com/radio95-rnt/fm95.git
synced 2026-02-26 19:23:51 +01:00
534 lines
17 KiB
C
534 lines
17 KiB
C
#include <getopt.h>
|
|
#include <liquid/liquid.h>
|
|
#include "../inih/ini.h"
|
|
#include <stdbool.h>
|
|
|
|
#define DEFAULT_INI_PATH "/etc/fm95.conf"
|
|
|
|
#define buffer_maxlength 12288
|
|
#define buffer_tlength_fragsize 12288
|
|
#define buffer_prebuf 8
|
|
|
|
#define DEFAULT_STEREO 1
|
|
#define DEFAULT_RDS_STREAMS 2
|
|
#define DEFAULT_CLIPPER_THRESHOLD 1.0f
|
|
#define DEFAULT_PREEMPHASIS_TAU 50e-6 // Europe, the freedomers use 75µs (75e-6)
|
|
#define DEFAULT_MPX_POWER 3.0f // dbr, this is for BS412, simplest bs412
|
|
#define DEFAULT_MPX_DEVIATION 75000.0f // for BS412
|
|
#define DEFAULT_DEVIATION 75000.0f // another way to set the volume
|
|
|
|
#include "../dsp/oscillator.h"
|
|
#include "../filter/iir.h"
|
|
#include "../modulation/stereo_encoder.h"
|
|
#include "../filter/bs412.h"
|
|
#include "../filter/gain_control.h"
|
|
|
|
#define DEFAULT_SAMPLE_RATE 192000
|
|
|
|
#define INPUT_DEVICE "FM_Audio.monitor"
|
|
#define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback"
|
|
#define RDS_DEVICE "RDS.monitor"
|
|
#define MPX_DEVICE "FM_MPX.monitor"
|
|
|
|
#define BUFFER_SIZE 2048
|
|
|
|
#include "../io/audio.h"
|
|
|
|
#define DEFAULT_MASTER_VOLUME 1.0f // Volume of everything combined, for calibration
|
|
|
|
#define DEFAULT_MONO_VOLUME 0.45f // 45%
|
|
#define DEFAULT_PILOT_VOLUME 0.09f // 9%
|
|
#define DEFAULT_STEREO_VOLUME 0.3f // 30%
|
|
#define DEFAULT_RDS_VOLUME 0.0475f // 4.75%
|
|
#define DEFAULT_RDS_VOLUME_STEP 0.9f // 90%, so RDS2 stream 4 is 90% of stream 3 which is 90% of stream 2, which again is 90% of stream 1...
|
|
|
|
static volatile sig_atomic_t to_run = 1;
|
|
|
|
inline float hard_clip(float sample, float threshold) { return fmaxf(-threshold, fminf(threshold, sample)); }
|
|
|
|
typedef struct
|
|
{
|
|
float mono;
|
|
float pilot;
|
|
float stereo;
|
|
float rds;
|
|
float rds_step;
|
|
} FM95_Volumes;
|
|
typedef struct
|
|
{
|
|
FM95_Volumes volumes;
|
|
uint8_t stereo;
|
|
|
|
uint8_t rds_streams;
|
|
|
|
float clipper_threshold;
|
|
float preemphasis;
|
|
float tilt;
|
|
uint8_t calibration;
|
|
float mpx_power;
|
|
float mpx_deviation;
|
|
float audio_deviation;
|
|
float master_volume;
|
|
float audio_volume;
|
|
float audio_preamp;
|
|
|
|
uint32_t sample_rate;
|
|
|
|
// ini dont edit
|
|
char ini_config_path[64];
|
|
|
|
uint8_t lpf_order;
|
|
float preemp_unity_freq;
|
|
float agc_target;
|
|
float agc_attack;
|
|
float agc_release;
|
|
float agc_max;
|
|
float agc_min;
|
|
float bs412_attack;
|
|
float bs412_release;
|
|
float bs412_max;
|
|
float lpf_cutoff;
|
|
} FM95_Config;
|
|
|
|
typedef struct
|
|
{
|
|
PulseInputDevice input_device, mpx_device, rds_device;
|
|
PulseOutputDevice output_device;
|
|
} FM95_Runtime;
|
|
|
|
typedef struct {
|
|
char input[64];
|
|
char output[64];
|
|
char mpx[64];
|
|
char rds[64];
|
|
} FM95_DeviceNames;
|
|
typedef struct {
|
|
FM95_Config* config;
|
|
FM95_DeviceNames* devices;
|
|
} FM95_SetupContext;
|
|
|
|
static void stop(int signum) {
|
|
(void)signum;
|
|
printf("\nReceived stop signal.\n");
|
|
to_run = 0;
|
|
}
|
|
|
|
void show_help(char *name) {
|
|
printf(
|
|
"Usage: \t%s\n"
|
|
"\t-c,--config\tOverride the default config path (%s)\n",
|
|
name,
|
|
DEFAULT_INI_PATH
|
|
);
|
|
}
|
|
|
|
void cleanup_runtime(FM95_Runtime *rt, bool mpx_on, bool rds_on) {
|
|
free_PulseInputDevice(&rt->input_device);
|
|
if (mpx_on) free_PulseInputDevice(&rt->mpx_device);
|
|
if (rds_on) free_PulseInputDevice(&rt->rds_device);
|
|
free_PulseOutputDevice(&rt->output_device);
|
|
}
|
|
|
|
int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
|
|
bool mpx_on = (runtime->mpx_device.initialized == 1);
|
|
bool rds_on = (runtime->rds_device.initialized == 1);
|
|
|
|
if(config.calibration != 0) {
|
|
Oscillator osc;
|
|
init_oscillator(&osc, (config.calibration == 2) ? 60 : 400, config.sample_rate);
|
|
|
|
int pulse_error;
|
|
float output[BUFFER_SIZE];
|
|
|
|
while(to_run) {
|
|
for (int i = 0; i < BUFFER_SIZE; i++) {
|
|
float sample = get_oscillator_sin_sample(&osc);
|
|
if(config.calibration == 2) sample = (sample > 0.0f) ? 1.0f : -1.0f; // Sine wave to square wave filter
|
|
output[i] = sample*config.master_volume;
|
|
}
|
|
if((pulse_error = write_PulseOutputDevice(&runtime->output_device, output, sizeof(output)))) { // get output from the function and assign it into pulse_error, this comment to avoid confusion
|
|
if(pulse_error == -1) fprintf(stderr, "Main PulseOutputDevice reported as uninitialized.");
|
|
else fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error));
|
|
to_run = 0;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Oscillator osc;
|
|
init_oscillator(&osc, (config.stereo == 2) ? 7812.5 : 4750, config.sample_rate);
|
|
|
|
iirfilt_rrrf lpf_l, lpf_r;
|
|
if(config.lpf_cutoff != 0) {
|
|
lpf_l = iirfilt_rrrf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_LOWPASS, LIQUID_IIRDES_SOS, config.lpf_order, (config.lpf_cutoff/config.sample_rate), 0.0f, 1.0f, 60.0f);
|
|
lpf_r = iirfilt_rrrf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_LOWPASS, LIQUID_IIRDES_SOS, config.lpf_order, (config.lpf_cutoff/config.sample_rate), 0.0f, 1.0f, 60.0f);
|
|
}
|
|
|
|
ResistorCapacitor preemp_l, preemp_r;
|
|
if(config.preemphasis != 0) {
|
|
init_preemphasis(&preemp_l, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
|
|
init_preemphasis(&preemp_r, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
|
|
}
|
|
|
|
BS412Compressor bs412;
|
|
init_bs412(&bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.sample_rate);
|
|
|
|
TiltCorrectionFilter tilter;
|
|
if(config.tilt != 0) tilt_init(&tilter, config.tilt);
|
|
|
|
StereoEncoder stencode;
|
|
init_stereo_encoder(&stencode, 4.0f, &osc, (config.stereo == 2), config.volumes.mono, config.volumes.pilot, config.volumes.stereo);
|
|
|
|
AGC agc;
|
|
if(config.agc_max != 0.0) initAGC(&agc, config.sample_rate, config.agc_target, config.agc_min, config.agc_max, config.agc_attack, config.agc_release);
|
|
|
|
int pulse_error;
|
|
|
|
float audio_stereo_input[BUFFER_SIZE*2]; // Stereo
|
|
|
|
float *rds_in = malloc(sizeof(float) * BUFFER_SIZE * config.rds_streams);
|
|
memset(rds_in, 0, sizeof(float) * BUFFER_SIZE * config.rds_streams);
|
|
|
|
float mpx_in[BUFFER_SIZE] = {0};
|
|
float output[BUFFER_SIZE];
|
|
|
|
while (to_run) {
|
|
if((pulse_error = read_PulseInputDevice(&runtime->input_device, audio_stereo_input, sizeof(audio_stereo_input)))) { // get output from the function and assign it into pulse_error, this comment to avoid confusion
|
|
fprintf(stderr, "Error reading from input device: %s\n", pa_strerror(pulse_error));
|
|
to_run = 0;
|
|
break;
|
|
}
|
|
if(mpx_on) {
|
|
if((pulse_error = read_PulseInputDevice(&runtime->mpx_device, mpx_in, sizeof(mpx_in)))) {
|
|
fprintf(stderr, "Error reading from MPX device: %s\nDisabling MPX.\n", pa_strerror(pulse_error));
|
|
mpx_on = 0;
|
|
}
|
|
}
|
|
if(rds_on) {
|
|
if((pulse_error = read_PulseInputDevice(&runtime->rds_device, rds_in, sizeof(float) * BUFFER_SIZE * config.rds_streams))) {
|
|
fprintf(stderr, "Error reading from RDS95 device: %s\nDisabling RDS.\n", pa_strerror(pulse_error));
|
|
rds_on = 0;
|
|
}
|
|
}
|
|
|
|
for (uint16_t i = 0; i < BUFFER_SIZE; i++) {
|
|
float mpx = 0.0f;
|
|
|
|
float l = audio_stereo_input[2*i+0]*config.audio_preamp;
|
|
float r = audio_stereo_input[2*i+1]*config.audio_preamp;
|
|
|
|
if(config.agc_max != 0.0) {
|
|
float agc_gain = process_agc(&agc, 0.5f * (fabsf(l) + fabsf(r)));
|
|
l *= agc_gain;
|
|
r *= agc_gain;
|
|
}
|
|
|
|
if(config.lpf_cutoff != 0) {
|
|
iirfilt_rrrf_execute(lpf_l, l, &l);
|
|
iirfilt_rrrf_execute(lpf_r, r, &r);
|
|
}
|
|
|
|
if(config.preemphasis != 0) {
|
|
l = apply_preemphasis(&preemp_l, l);
|
|
r = apply_preemphasis(&preemp_r, r);
|
|
}
|
|
|
|
l = hard_clip(l*config.audio_volume, config.clipper_threshold);
|
|
r = hard_clip(r*config.audio_volume, config.clipper_threshold);
|
|
|
|
mpx = stereo_encode(&stencode, config.stereo, l, r);
|
|
|
|
if(rds_on && !(config.stereo == 2)) { // disable rds on polar stereo
|
|
float rds_level = config.volumes.rds;
|
|
for(uint8_t stream = 0; stream < config.rds_streams; stream++) {
|
|
uint8_t osc_stream = 12 + stream;
|
|
if(osc_stream == 13) osc_stream++;
|
|
|
|
mpx += (rds_in[config.rds_streams * i + stream] * get_oscillator_cos_multiplier_ni(&osc, osc_stream)) * rds_level;
|
|
|
|
rds_level *= config.volumes.rds_step; // Prepare level for the next stream
|
|
}
|
|
}
|
|
|
|
mpx = bs412_compress(&bs412, mpx+mpx_in[i]);
|
|
if(config.tilt != 0) mpx = tilt(&tilter, mpx);
|
|
|
|
output[i] = hard_clip(mpx*config.master_volume, 1.0); // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly
|
|
advance_oscillator(&osc);
|
|
}
|
|
|
|
if((pulse_error = write_PulseOutputDevice(&runtime->output_device, output, sizeof(output)))) {
|
|
fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error));
|
|
to_run = 0;
|
|
break;
|
|
}
|
|
}
|
|
if(config.lpf_cutoff != 0) {
|
|
iirfilt_rrrf_destroy(lpf_l);
|
|
iirfilt_rrrf_destroy(lpf_r);
|
|
}
|
|
|
|
free(rds_in);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int parse_arguments(int argc, char **argv, FM95_Config* config) {
|
|
int opt;
|
|
const char *short_opt = "c:h";
|
|
struct option long_opt[] =
|
|
{
|
|
{"config", required_argument, NULL, 'c'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
|
|
switch(opt) {
|
|
case 'c':
|
|
memcpy(config->ini_config_path, optarg, 63);
|
|
break;
|
|
case 'h':
|
|
show_help(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int config_handler(void* user, const char* section, const char* name, const char* value) {
|
|
FM95_SetupContext* ctx = (FM95_SetupContext*)user;
|
|
FM95_Config* pconfig = ctx->config;
|
|
FM95_DeviceNames* dv = ctx->devices;
|
|
|
|
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
|
|
|
|
if (MATCH("fm95", "stereo")) {
|
|
pconfig->stereo = atoi(value);
|
|
} else if (MATCH("devices", "input")) {
|
|
strncpy(dv->input, value, 63);
|
|
dv->input[63] = '\0';
|
|
} else if (MATCH("devices", "output")) {
|
|
strncpy(dv->output, value, 63);
|
|
dv->output[63] = '\0';
|
|
} else if (MATCH("devices", "mpx")) {
|
|
strncpy(dv->mpx, value, 63);
|
|
dv->mpx[63] = '\0';
|
|
} else if (MATCH("devices", "rds")) {
|
|
strncpy(dv->rds, value, 63);
|
|
dv->rds[63] = '\0';
|
|
} else if (MATCH("fm95", "rds_streams")) {
|
|
pconfig->rds_streams = atoi(value);
|
|
if(pconfig->rds_streams > 4) {
|
|
printf("RDS Streams more than 4? Nuh uh\n");
|
|
return 0;
|
|
}
|
|
} else if (MATCH("fm95", "clipper_threshold")) {
|
|
pconfig->clipper_threshold = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "preemphasis")) {
|
|
pconfig->preemphasis = strtof(value, NULL) * 1.0e-6f;
|
|
} else if (MATCH("fm95", "calibration")) {
|
|
pconfig->calibration = atoi(value);
|
|
} else if (MATCH("fm95", "mpx_power")) {
|
|
pconfig->mpx_power = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "mpx_deviation")) {
|
|
pconfig->mpx_deviation = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "master_volume")) {
|
|
pconfig->master_volume = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "audio_volume")) {
|
|
pconfig->audio_volume = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "audio_preamp")) {
|
|
pconfig->audio_preamp = strtof(value, NULL);
|
|
} else if (MATCH("fm95", "deviation")) {
|
|
pconfig->audio_deviation = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "lpf_order")) {
|
|
pconfig->lpf_order = atoi(value);
|
|
} else if(MATCH("advanced", "preemp_unity")) {
|
|
pconfig->preemp_unity_freq = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "sample_rate")) {
|
|
pconfig->sample_rate = atoi(value);
|
|
} else if(MATCH("advanced", "agc_target")) {
|
|
pconfig->agc_target = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "agc_attack")) {
|
|
pconfig->agc_attack = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "agc_release")) {
|
|
pconfig->agc_release = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "agc_min")) {
|
|
pconfig->agc_min = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "agc_max")) {
|
|
pconfig->agc_max = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "bs412_attack")) {
|
|
pconfig->bs412_attack = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "bs412_release")) {
|
|
pconfig->bs412_release = strtof(value, NULL);
|
|
} else if(MATCH("volumes", "mono")) {
|
|
pconfig->volumes.mono = strtof(value, NULL);
|
|
} else if(MATCH("volumes", "pilot")) {
|
|
pconfig->volumes.pilot = strtof(value, NULL);
|
|
} else if(MATCH("volumes", "stereo")) {
|
|
pconfig->volumes.stereo = strtof(value, NULL);
|
|
} else if(MATCH("volumes", "rds")) {
|
|
pconfig->volumes.rds = strtof(value, NULL);
|
|
} else if(MATCH("volumes", "rds_step")) {
|
|
pconfig->volumes.rds_step = strtof(value, NULL);
|
|
} else if(MATCH("fm95", "tilt")) {
|
|
pconfig->tilt = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "bs412_max")) {
|
|
pconfig->bs412_max = strtof(value, NULL);
|
|
} else if(MATCH("advanced", "lpf_cutoff")) {
|
|
pconfig->lpf_cutoff = strtof(value, NULL);
|
|
if(pconfig->lpf_cutoff > (pconfig->sample_rate * 0.5)) {
|
|
pconfig->lpf_cutoff = (pconfig->sample_rate * 0.5);
|
|
fprintf(stderr, "LPF cutoff over niquist, limiting.\n");
|
|
}
|
|
} else {
|
|
return 0; // Unknown section/name
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int parse_config(FM95_Config* config, FM95_DeviceNames* dv) {
|
|
FM95_SetupContext ctx = {
|
|
.config = config,
|
|
.devices = dv
|
|
};
|
|
return ini_parse(config->ini_config_path, &config_handler, &ctx);
|
|
}
|
|
|
|
int setup_audio(FM95_Runtime* runtime, const FM95_DeviceNames dv_names, const FM95_Config config, bool mpx_on, bool rds_on) {
|
|
pa_buffer_attr input_buffer_atr = {
|
|
.maxlength = buffer_maxlength,
|
|
.fragsize = buffer_tlength_fragsize
|
|
};
|
|
pa_buffer_attr output_buffer_atr = {
|
|
.maxlength = buffer_maxlength,
|
|
.tlength = buffer_tlength_fragsize,
|
|
.prebuf = buffer_prebuf
|
|
};
|
|
|
|
int opentime_pulse_error;
|
|
|
|
printf("Connecting to input device... (%s)\n", dv_names.input);
|
|
opentime_pulse_error = init_PulseInputDevice(&runtime->input_device, config.sample_rate, 2, "fm95", "Main Audio Input", dv_names.input, &input_buffer_atr, PA_SAMPLE_FLOAT32NE);
|
|
if (opentime_pulse_error) {
|
|
fprintf(stderr, "Error: cannot open input device: %s\n", pa_strerror(opentime_pulse_error));
|
|
return 1;
|
|
}
|
|
|
|
if(mpx_on) {
|
|
printf("Connecting to MPX device... (%s)\n", dv_names.mpx);
|
|
|
|
opentime_pulse_error = init_PulseInputDevice(&runtime->mpx_device, config.sample_rate, 1, "fm95", "MPX Input", dv_names.mpx, &input_buffer_atr, PA_SAMPLE_FLOAT32NE);
|
|
if (opentime_pulse_error) {
|
|
fprintf(stderr, "Error: cannot open MPX device: %s\n", pa_strerror(opentime_pulse_error));
|
|
free_PulseInputDevice(&runtime->input_device);
|
|
return 1;
|
|
}
|
|
}
|
|
if(rds_on) {
|
|
printf("Connecting to RDS95 device... (%s)\n", dv_names.rds);
|
|
|
|
opentime_pulse_error = init_PulseInputDevice(&runtime->rds_device, config.sample_rate, config.rds_streams, "fm95", "RDS95 Input", dv_names.rds, &input_buffer_atr, PA_SAMPLE_FLOAT32NE);
|
|
if (opentime_pulse_error) {
|
|
fprintf(stderr, "Error: cannot open RDS device: %s\n", pa_strerror(opentime_pulse_error));
|
|
free_PulseInputDevice(&runtime->input_device);
|
|
if(mpx_on) free_PulseInputDevice(&runtime->mpx_device);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
printf("Connecting to output device... (%s)\n", dv_names.output);
|
|
|
|
opentime_pulse_error = init_PulseOutputDevice(&runtime->output_device, config.sample_rate, 1, "fm95", "Main Audio Output", dv_names.output, &output_buffer_atr, PA_SAMPLE_FLOAT32NE);
|
|
if (opentime_pulse_error) {
|
|
fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(opentime_pulse_error));
|
|
free_PulseInputDevice(&runtime->input_device);
|
|
if(mpx_on) free_PulseInputDevice(&runtime->mpx_device);
|
|
if(rds_on) free_PulseInputDevice(&runtime->rds_device);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
printf("fm95 (an FM Processor by radio95) version 2.2\n");
|
|
|
|
FM95_Config config = {
|
|
.volumes = {
|
|
.mono = DEFAULT_MONO_VOLUME,
|
|
.pilot = DEFAULT_PILOT_VOLUME,
|
|
.stereo = DEFAULT_STEREO_VOLUME,
|
|
.rds = DEFAULT_RDS_VOLUME,
|
|
.rds_step = DEFAULT_RDS_VOLUME_STEP
|
|
},
|
|
.stereo = DEFAULT_STEREO,
|
|
|
|
.rds_streams = DEFAULT_RDS_STREAMS,
|
|
|
|
.clipper_threshold = DEFAULT_CLIPPER_THRESHOLD,
|
|
.preemphasis = DEFAULT_PREEMPHASIS_TAU,
|
|
.tilt = 0,
|
|
.calibration = 0,
|
|
.mpx_power = DEFAULT_MPX_POWER,
|
|
.mpx_deviation = DEFAULT_MPX_DEVIATION,
|
|
.audio_deviation = DEFAULT_DEVIATION,
|
|
.master_volume = DEFAULT_MASTER_VOLUME,
|
|
.audio_volume = 1.0f,
|
|
.audio_preamp = 1.0f,
|
|
|
|
.sample_rate = DEFAULT_SAMPLE_RATE,
|
|
|
|
.ini_config_path = DEFAULT_INI_PATH,
|
|
|
|
.lpf_order = 17,
|
|
.preemp_unity_freq = 15250.0f,
|
|
.agc_target = 0.625f,
|
|
.agc_attack = 0.03f,
|
|
.agc_release = 0.225f,
|
|
.agc_min = 0.1f,
|
|
.agc_max = 1.75f,
|
|
.bs412_attack = 0.03f,
|
|
.bs412_release = 0.02,
|
|
.bs412_max = 1.0f,
|
|
.lpf_cutoff = 15000,
|
|
};
|
|
|
|
FM95_DeviceNames dv_names = {
|
|
.input = INPUT_DEVICE,
|
|
.output = OUTPUT_DEVICE,
|
|
.mpx = MPX_DEVICE,
|
|
.rds = RDS_DEVICE
|
|
};
|
|
|
|
int err;
|
|
err = parse_arguments(argc, argv, &config);
|
|
if(err != 0) return err;
|
|
|
|
err = parse_config(&config, &dv_names);
|
|
if(err != 0) {
|
|
printf("Could not parse the config file. (error code as return code)\n");
|
|
return err;
|
|
}
|
|
|
|
config.master_volume *= config.audio_deviation/75000.0f;
|
|
|
|
FM95_Runtime runtime;
|
|
memset(&runtime, 0, sizeof(runtime));
|
|
|
|
bool mpx_on = (strlen(dv_names.mpx) != 0);
|
|
bool rds_on = (strlen(dv_names.rds) != 0 && config.rds_streams != 0);
|
|
|
|
err = setup_audio(&runtime, dv_names, config, mpx_on, rds_on);
|
|
if(err != 0) return err;
|
|
|
|
signal(SIGINT, stop);
|
|
signal(SIGTERM, stop);
|
|
|
|
int ret = run_fm95(config, &runtime);
|
|
printf("Cleaning up...\n");
|
|
cleanup_runtime(&runtime, mpx_on, rds_on);
|
|
return ret;
|
|
} |