409 lines
14 KiB
C++
409 lines
14 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sndfile.h>
|
|
extern "C"
|
|
{
|
|
#include "rds.h"
|
|
#include "fm_mpx.h"
|
|
#include "control_pipe.h"
|
|
}
|
|
#include <librpitx/librpitx.h>
|
|
ngfmdmasync *fmmod;
|
|
#define DATA_SIZE 5000
|
|
static void terminate(int num)
|
|
{
|
|
delete fmmod;
|
|
fm_mpx_close();
|
|
close_control_pipe();
|
|
exit(num);
|
|
}
|
|
|
|
static void fatal(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
terminate(0);
|
|
}
|
|
|
|
typedef struct tx_data {
|
|
uint32_t carrier_freq;
|
|
char *audio_file;
|
|
uint16_t pi;
|
|
char *ps;
|
|
char *rt;
|
|
char *control_pipe;
|
|
int pty;
|
|
int *af_array;
|
|
int raw;
|
|
int drds;
|
|
double preemp;
|
|
int power;
|
|
int rawSampleRate;
|
|
int rawChannels;
|
|
int deviation;
|
|
int ta;
|
|
int tp;
|
|
float cutoff_freq;
|
|
float audio_gain;
|
|
float compressor_decay;
|
|
float compressor_attack;
|
|
float compressor_max_gain_recip;
|
|
int enablecompressor;
|
|
int rds_ct_enabled;
|
|
float rds_volume;
|
|
int disablestereo;
|
|
int log;
|
|
float limiter_threshold;
|
|
} tx_data;
|
|
|
|
int tx(tx_data *data) {
|
|
struct sigaction sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = terminate;
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGQUIT, &sa, NULL);
|
|
sigaction(SIGKILL, &sa, NULL);
|
|
sigaction(SIGHUP, &sa, NULL); //https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
|
|
sigaction(SIGPWR, &sa, NULL);
|
|
sigaction(SIGTSTP, &sa, NULL);
|
|
sigaction(SIGSEGV, &sa, NULL); //seg fault
|
|
|
|
// Data structures for baseband data
|
|
float data[DATA_SIZE];
|
|
float devfreq[DATA_SIZE];
|
|
int data_len = 0;
|
|
int data_index = 0;
|
|
|
|
int generate_multiplex = 1;
|
|
int dstereo = data->disablestereo;
|
|
int drds = data->drds;
|
|
float audio_gain = data->audio_gain;
|
|
float compressor_decay = data->compressor_decay;
|
|
float compressor_attack = data->compressor_attack;
|
|
float compressor_max_gain_recip = data->compressor_max_gain_recip;
|
|
int enablecompressor = data->enablecompressor;
|
|
int rds_ct_enabled = data->rds_ct_enabled;
|
|
float rds_volume = data->rds_volume;
|
|
float limiter_threshold = data->limiter_threshold;
|
|
|
|
//set the power
|
|
padgpio gpiopad;
|
|
gpiopad.setlevel(data->power);
|
|
|
|
// Initialize the baseband generator
|
|
if(fm_mpx_open(data->audio_file, DATA_SIZE, data->raw, data->preemp, data->rawSampleRate, data->rawChannels, data->cutoff_freq) < 0) return 1;
|
|
|
|
// Initialize the RDS modulator
|
|
char myps[9] = {0};
|
|
set_rds_pi(data->pi);
|
|
set_rds_ps(data->ps);
|
|
set_rds_rt(data->rt);
|
|
set_rds_pty(data->pty);
|
|
set_rds_ab(0);
|
|
set_rds_ms(1); // yes
|
|
set_rds_tp(data->tp);
|
|
set_rds_ta(data->ta);
|
|
if(dstereo == 1) {
|
|
set_rds_di(0);
|
|
} else {
|
|
set_rds_di(1);
|
|
}
|
|
uint16_t count = 0;
|
|
uint16_t count2 = 0;
|
|
if(data->log) {
|
|
if(drds == 1) {
|
|
printf("RDS Disabled (you can enable with control fifo with the RDS command)\n");
|
|
} else {
|
|
printf("PI: %04X, PS: \"%s\".\n", pi, ps);
|
|
printf("RT: \"%s\"\n", rt);
|
|
|
|
if(af_array[0]) {
|
|
set_rds_af(data->af_array);
|
|
printf("AF: ");
|
|
int f;
|
|
for(f = 1; f < data->af_array[0]+1; f++) {
|
|
printf("%f Mhz ", (float)(data->af_array[f]+875)/10);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the control pipe reader
|
|
if(data->control_pipe) {
|
|
if(open_control_pipe(data->control_pipe) == 0) {
|
|
if(data->log) printf("Reading control commands on %s.\n", data->control_pipe);
|
|
} else {
|
|
if(data->log) printf("Failed to open control pipe: %s.\n", data->control_pipe);
|
|
data->control_pipe = NULL;
|
|
}
|
|
}
|
|
if(data->log) printf("Starting to transmit on %3.1f MHz.\n", carrier_freq/1e6);
|
|
float deviation_scale_factor;
|
|
// The deviation specifies how wide the signal is (from its lowest bandwidht to its highest, but not including sub-carriers).
|
|
// Use 75kHz for WFM (broadcast radio, or 50khz can be used)
|
|
// and about 2.5kHz for NFM (walkie-talkie style radio)
|
|
deviation_scale_factor= 0.1 * (data->deviation);
|
|
int paused = 0;
|
|
for (;;)
|
|
{
|
|
if(data->control_pipe) {
|
|
ResultAndArg pollResult = poll_control_pipe(log);
|
|
if(pollResult.res == CONTROL_PIPE_RDS_SET) {
|
|
drds = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_PWR_SET) {
|
|
padgpio gpiopad;
|
|
gpiopad.setlevel(pollResult.arg_int);
|
|
} else if(pollResult.res == CONTROL_PIPE_DEVIATION_SET) {
|
|
data->deviation = std::stoi(pollResult.arg);
|
|
deviation_scale_factor= 0.1 * (data->deviation);
|
|
} else if(pollResult.res == CONTROL_PIPE_STEREO_SET) {
|
|
dstereo = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_GAIN_SET) {
|
|
audio_gain = std::stof(pollResult.arg);
|
|
} else if(pollResult.res == CONTROL_PIPE_COMPRESSORDECAY_SET) {
|
|
compressor_decay = std::stof(pollResult.arg);
|
|
} else if(pollResult.res == CONTROL_PIPE_COMPRESSORATTACK_SET) {
|
|
compressor_attack = std::stof(pollResult.arg);
|
|
} else if(pollResult.res == CONTROL_PIPE_CT_SET) {
|
|
rds_ct_enabled = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_RDSVOL_SET) {
|
|
rds_volume = std::stof(pollResult.arg);
|
|
} else if(pollResult.res == CONTROL_PIPE_PAUSE_SET) {
|
|
paused = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_MPXGEN_SET) {
|
|
generate_multiplex = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_COMPRESSOR_SET) {
|
|
enablecompressor = pollResult.arg_int;
|
|
} else if(pollResult.res == CONTROL_PIPE_COMPRESSORMAXGAINRECIP_SET) {
|
|
compressor_max_gain_recip = std::stof(pollResult.arg);
|
|
} else if(pollResult.res == CONTROL_PIPE_LIMITERTHRESHOLD_SET) {
|
|
limiter_threshold = std::stof(pollResult.arg);
|
|
}
|
|
}
|
|
fm_mpx_data *data2;
|
|
data2 = (fm_mpx_data *)malloc(sizeof(fm_mpx_data));
|
|
data2->drds = drds;
|
|
data2->compressor_decay = compressor_decay;
|
|
data2->compressor_attack = compressor_attack;
|
|
data2->compressor_max_gain_recip = compressor_max_gain_recip;
|
|
data2->dstereo = dstereo;
|
|
data2->audio_gain = audio_gain;
|
|
data2->enablecompressor = enablecompressor;
|
|
data2->rds_ct_enabled = rds_ct_enabled;
|
|
data2->rds_volume = rds_volume;
|
|
data2->paused = paused;
|
|
data2->generate_multiplex = generate_multiplex;
|
|
data2->limiter_threshold = limiter_threshold;
|
|
if(fm_mpx_get_samples(data, data2) < 0 ) terminate(0);
|
|
data_len = DATA_SIZE;
|
|
for(int i=0;i< data_len;i++) {
|
|
devfreq[i] = data[i]*deviation_scale_factor;
|
|
}
|
|
fmmod->SetFrequencySamples(devfreq,data_len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
tx_data data = {
|
|
.audio_file = NULL,
|
|
.control_pipe = NULL,
|
|
.carrier_freq = 100000000,
|
|
.ps = "Pi-FmSa",
|
|
.rt = "Broadcasting on a Raspberry Pi: Simply Advanced",
|
|
.pi = 0x00ff,
|
|
.pty = 0,
|
|
.compressor_decay = 0.999995,
|
|
.compressor_attack = 1.0,
|
|
.compressor_max_gain_recip = 0.01,
|
|
.enablecompressor = 1,
|
|
.rds_volume = 1.0,
|
|
.limiter_threshold = 0.9,
|
|
.log = 1,
|
|
.ta = 0,
|
|
.tp = 0,
|
|
.af_array = {0},
|
|
.raw = 0,
|
|
.drds = 0,
|
|
.disablestereo = 0,
|
|
.power = 7,
|
|
.audio_gain = 1,
|
|
.rawSampleRate = 44100,
|
|
.rawChannels = 2,
|
|
.preemp = 50e-6,
|
|
.deviation = 75000,
|
|
.rds_ct_enabled = 1,
|
|
.cutoff_freq = 15000,
|
|
};
|
|
|
|
int af_size = 0;
|
|
int gpiopin = 4;
|
|
int compressorchanges = 0;
|
|
int limiterchanges = 0;
|
|
|
|
int alternative_freq[100] = {};
|
|
int bypassfreqrange = 0;
|
|
// Parse command-line arguments
|
|
for(int i=1; i<argc; i++) {
|
|
char *arg = argv[i];
|
|
char *param = NULL;
|
|
if(arg[0] == '-' && i+1 < argc) param = argv[i+1];
|
|
if((strcmp("-audio", arg)==0) && param != NULL) {
|
|
i++;
|
|
data.audio_file = param;
|
|
} else if(strcmp("-freq", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.carrier_freq = (uint32_t)(atof(param)*1e6);
|
|
if(data.carrier_freq < 64e6 || data.carrier_freq > 108e6) fatal("Incorrect frequency specification. Must be in megahertz, of the form 107.9, between 64 and 108.\n");
|
|
} else if(strcmp("-pi", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.pi = atoi(param);
|
|
} else if(strcmp("-ps", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.ps = param;
|
|
} else if(strcmp("-rt", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.rt = param;
|
|
} else if(strcmp("-compressordecay", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.compressor_decay = atof(param);
|
|
compressorchanges = 1;
|
|
} else if(strcmp("-compressorattack", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.compressor_attack = atof(param);
|
|
compressorchanges = 1;
|
|
} else if(strcmp("-compressormaxgainrecip", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.compressor_max_gain_recip = atof(param);
|
|
compressorchanges = 1;
|
|
} else if(strcmp("-limiterthreshold", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.limiter_threshold = atof(param);
|
|
limiterchanges = 1;
|
|
if(data.limiter_threshold > 3.5) {
|
|
fatal("Limiter threshold too high!\n");
|
|
}
|
|
} else if(strcmp("-rdsvolume", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.rds_volume = atof(param);
|
|
} else if(strcmp("-pty", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.pty = atoi(param);
|
|
} else if(strcmp("-gpiopin", arg)==0 && param != NULL) {
|
|
i++;
|
|
printf("GPIO pin setting disabled, mod librpitx and pifmsa (pifm simply advanced) for this\n");
|
|
// int pinnum = atoi(param);
|
|
// if (!(pinnum == 4 || pinnum == 20 || pinnum == 32 || pinnum == 34 || pinnum == 6)) {
|
|
// fatal("Invalid gpio pin, allowed: 4, 20, 32, 34, 6");
|
|
// } else {
|
|
// gpiopin = pinnum;
|
|
// }
|
|
} else if(strcmp("-disablelogging", arg)==0) {
|
|
i++;
|
|
data.log = 0;
|
|
} else if(strcmp("-ta", arg)==0) {
|
|
i++;
|
|
data.ta = 1;
|
|
} else if(strcmp("-bfr", arg)==0) {
|
|
i++;
|
|
bypassfreqrange = 1;
|
|
} else if(strcmp("-tp", arg)==0) {
|
|
i++;
|
|
data.tp = 1;
|
|
} else if(strcmp("-ctl", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.control_pipe = param;
|
|
} else if(strcmp("-deviation", arg)==0 && param != NULL) {
|
|
i++;
|
|
if(strcmp("mono", param)==0) {
|
|
data.deviation = 50000;
|
|
} else if(strcmp("nfm", param)==0) {
|
|
data.deviation = 2500;
|
|
}
|
|
else {
|
|
data.deviation = atoi(param);
|
|
}
|
|
} else if(strcmp("-rawchannels", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.rawChannels = atoi(param);
|
|
} else if(strcmp("-rawsamplerate", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.rawSampleRate = atoi(param);
|
|
} else if(strcmp("-cutofffreq", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.cutoff_freq = atof(param);
|
|
} else if(strcmp("-audiogain", arg)==0 && param != NULL) {
|
|
i++;
|
|
data.audio_gain = atof(param);
|
|
} else if(strcmp("-power", arg)==0 && param != NULL) {
|
|
i++;
|
|
int tpower = atoi(param);
|
|
if(tpower > 7 || tpower < 0) fatal("Power can be between 0 and 7");
|
|
data.power = tpower;
|
|
} else if(strcmp("-raw", arg)==0) {
|
|
i++;
|
|
data.raw = 1;
|
|
} else if(strcmp("-disablerds", arg)==0) {
|
|
i++;
|
|
data.drds = 1;
|
|
} else if(strcmp("-disablestereo", arg)==0) {
|
|
i++;
|
|
data.disablestereo = 1;
|
|
} else if(strcmp("-disablecompressor", arg)==0) {
|
|
i++;
|
|
data.enablecompressor = 0;
|
|
} else if(strcmp("-disablect", arg)==0) {
|
|
i++;
|
|
data.rds_ct_enabled = 0;
|
|
} else if(strcmp("-preemphasis", arg)==0 && param != NULL) {
|
|
i++;
|
|
if(strcmp("us", param)==0) {
|
|
data.preemp = 75e-6;
|
|
} else if(strcmp("eu", param)==0) {
|
|
printf("premp eu default but ok\n");
|
|
data.preemp = 50e-6;
|
|
} else if(strcmp("off", param)==0 || strcmp("0", param)==0) {
|
|
data.preemp = 0;
|
|
} else {
|
|
data.preemp = atof(param) * 1e-6;
|
|
}
|
|
} else if(strcmp("-af", arg)==0 && param != NULL) {
|
|
i++;
|
|
af_size++;
|
|
alternative_freq[af_size] = (int)(10*atof(param))-875;
|
|
if(alternative_freq[af_size] < 1 || alternative_freq[af_size] > 204)
|
|
fatal("Alternative Frequency has to be set in range of 87.6 Mhz - 107.9 Mhz\n");
|
|
data.af_array = alternative_freq;
|
|
}
|
|
else {
|
|
fatal("Unrecognised argument: %s.\n"
|
|
"Syntax: pi_fm_rds [-freq freq] [-audio file] [-pi pi_code]\n"
|
|
" [-ps ps_text] [-rt rt_text] [-ctl control_pipe] [-pty program_type] [-raw play raw audio from stdin] [-disablerds] [-af alt freq] [-preemphasis us] [-rawchannels when using the raw option you can change this] [-rawsamplerate same business] [-deviation the deviation, default is 75000] [-tp] [-ta]\n", arg);
|
|
}
|
|
}
|
|
|
|
alternative_freq[0] = af_size;
|
|
int FifoSize=DATA_SIZE*2;
|
|
//fmmod=new ngfmdmasync(carrier_freq,228000,14,FifoSize, false, gpiopin); //you can mod
|
|
fmmod=new ngfmdmasync(data.carrier_freq,228000,14,FifoSize, false);
|
|
int errcode = tx(&data);
|
|
terminate(errcode);
|
|
}
|