Hi, Here is my "beta" freebeacon code.
Decoded audio is sent out by UDP prepended by 0x4141 and (short) length to a settable IP address to port 21234 as per the original freebeacon. The UDP in freebeacon receiver then receives, checks for the 0x4141 "signature" and places this on the input audio fifo. The freebeacon receive side, audio from the radio, is tested working. Conceivably, two systems could make a full duplex repeater. (not yet) Next step is, to me the hardest. "mvoice" being written in C++ baffles me in how to add another UDP "receiver" and making it "press PTT". (And sending out audio received from the M17 network) I'd like to use "mvoice" because it's mature and being a GUI app, displays nicely what's happening. Things to note: HF reception of FreeDV modes such as 700D and 700E do not convey accurate TxtMSGs so to rely on anything more than the trigger string will convey inaccurate callsigns. So my thought is that "mvoice" will add it's callsign when sending audio to the M17 network. So it's necessary for the HF transmitting station to announce their callsign verbally. Any help here would be greatly appreciated. Alan VK2ZIW On Mon, 4 Jan 2021 14:16:11 +1000, Al Beard via Freetel-codec2 wrote > Hi all, > > On looking into "freebeacon" there is a UDP listener. > I'm about to add "send this audio". > > To make sure this new audio packet is OK for sending, > I intend to prepend the packet with say 0x4141 and the next > short, the length of the audio samples. > > Then to end transmission, "length" to be zero. > (and a timeout) > > Similarly, adding a UDP sender for the Rx audio. > > Noting M17's "mvoice" is written with samplerate 8000, > no samplerate conversion is necessary and the UDP packets > will be under 1500 bytes. > > I'm completely new to app development, has anyone any > thoughts on this? > > The M17 network has at present plenty of listeners. > > --------------------------------------------------- > Alan VK2ZIW > Before the Big Bang, God, Sela. > OpenWebMail 2.53, nothing in the cloud. > > _______________________________________________ > Freetel-codec2 mailing list > Freetel-codec2@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/freetel-codec2 --------------------------------------------------- Alan VK2ZIW Before the Big Bang, God, Sela. OpenWebMail 2.53, nothing in the cloud.
/* freebeacon.c David Rowe VK5DGR Hamlib PTT and 700C support Bob VK4YA 700E Alan VK2ZIW FreeDV Beacon. */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <ctype.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h> #include <pthread.h> #ifdef _WIN32 #include <windows.h> #else #include <termios.h> #include <sys/ioctl.h> #endif #include <samplerate.h> #include <getopt.h> #include "sndfile.h" #include "portaudio.h" #include "codec2_fifo.h" #include "modem_stats.h" #include "freedv_api.h" #include "hamlib/rig.h" #define MAX_CHAR 80 #define FS8 8000 // codec audio sample rate fixed at 8 kHz #define FS48 48000 // 48 kHz sampling rate rec. as we can trust accuracy of sound card #define SYNC_TIMER 2.0 // seconds of valid rx sync we need to see to change state #define UNSYNC_TIMER 2.0 // seconds of lost sync we need to see to change state #define COM_HANDLE_INVALID -1 #define LOG_TIMER 1.0 #define SNR_MIN 3.0 // 700E, sync is problematic without SQ enabled #define SQ_LEVEL 1.0 #define SERVICE_PORT 21234 #define IP_ADDRESS "192.168.20.122" /* globals used to communicate with async events and callback functions */ volatile int keepRunning, runListener, recordAny; char txtMsg[MAX_CHAR], *ptxtMsg, triggerString[MAX_CHAR]; int triggered, txFromNet; float snr_est, snr_sample; int com_handle, verbose; struct FIFO *nfifo; /* state machine defines */ #define SRX_IDLE 0 /* listening but no FreeDV signal */ #define SRX_MAYBE_SYNC 1 /* We have sync but lets see if it goes away */ #define SRX_SYNC 2 /* We have sync on a valid FreeDV signal */ #define SRX_MAYBE_UNSYNC 3 /* We have lost sync but lets see if it's really gone */ #define STX 4 /* transmitting reply */ char *state_str[] = { "Rx Idle", "Rx Maybe Sync", "Rx Sync", "Rx Maybe UnSync", "Tx" }; int openComPort(const char *name); void closeComPort(void); void raiseDTR(void); void lowerDTR(void); void raiseRTS(void); void lowerRTS(void); pthread_t start_udp_listener_thread(void); /* hamlib static vars */ hamlib_port_t myport; RIG *my_rig; rig_model_t myrig_model; // int int retcode; /*--------------------------------------------------------------------------------------------------------*\ FUNCTIONS \*--------------------------------------------------------------------------------------------------------*/ /* Called on Ctrl-C */ void intHandler(int dummy) { keepRunning = 0; fprintf(stderr,"\nShutting Down ......\n"); } /* returns number of output samples generated by resampling */ int resample(SRC_STATE *src, short output_short[], short input_short[], int output_sample_rate, int input_sample_rate, int length_output_short, // maximum output array length in samples int length_input_short ) { SRC_DATA src_data; float input[length_input_short]; float output[length_output_short]; assert(src != NULL); src_short_to_float_array(input_short, input, length_input_short); src_data.data_in = input; src_data.data_out = output; src_data.input_frames = length_input_short; src_data.output_frames = length_output_short; src_data.end_of_input = 0; src_data.src_ratio = (float)output_sample_rate/input_sample_rate; //printf("%d %d src_ratio: %f \n", length_input_short, length_output_short, src_data.src_ratio); src_process(src, &src_data); assert(src_data.output_frames_gen <= length_output_short); src_float_to_short_array(output, output_short, src_data.output_frames_gen); return src_data.output_frames_gen; } void listAudioDevices(void) { const PaDeviceInfo *deviceInfo = NULL; int numDevices, devn; numDevices = Pa_GetDeviceCount(); printf("Num Name API InCh OutCh DefFs\n"); printf("====================================================================================\n"); for (devn = 0; devn<numDevices; devn++) { deviceInfo = Pa_GetDeviceInfo(devn); if (deviceInfo == NULL) { fprintf(stderr, "Couldn't open devNum: %d\n", devn); return; } printf(" %2d %50s %8s %6d %6d %6d\n", devn, deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name, deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels, (int)deviceInfo->defaultSampleRate); } } void printHelp(const struct option* long_options, int num_opts, char* argv[]) { int i; char *option_parameters = NULL; fprintf(stderr, "\nFreeBeacon - FreeDV Beacon\n" "usage: %s [OPTIONS]\n\n" "Options:\n" "\t-b (Enable beacon)\n" "\t-c (comm port for Tx or CAT PTT)\n" "\t-r (Hamlib CAT model number [use rigctl -l to see list])\n" "\t-l --list (audio devices)\n" "\t-m --mode 1600|700C|700D|700E\n" "\t-t (tx on start up, useful for testing)\n" "\t-u (Stop UDP listener)\n" "\t-v (verbose)\n", argv[0]); for(i=0; i<num_opts-1; i++) { if(long_options[i].has_arg == no_argument) { option_parameters=""; } else if (strcmp("dev", long_options[i].name) == 0) { option_parameters = " DeviceNumber (-l --list to list devices)"; } else if (strcmp("trigger", long_options[i].name) == 0) { option_parameters = " textString (used to trigger beacon)"; } else if (strcmp("callsign", long_options[i].name) == 0) { option_parameters = " callsign (returned in text str to tx)"; } else if (strcmp("txfilename", long_options[i].name) == 0) { option_parameters = " wavefile (to use for source audio on tramsmit)"; } else if (strcmp("samplerate", long_options[i].name) == 0) { option_parameters = " sampleRateHz (audio device sample rate)"; } else if (strcmp("wavefilewritepath", long_options[i].name) == 0) { option_parameters = " pathToWaveFiles (path to where wave files are written)"; } else if (strcmp("rpigpio", long_options[i].name) == 0) { option_parameters = " GPIO (BCM GPIO number on Raspberry Pi for Tx PTT)"; } else if (strcmp("rpigpioalive", long_options[i].name) == 0) { option_parameters = " GPIO (BCM GPIO number on Raspberry Pi for alive blinker)"; } else if (strcmp("statuspagefile", long_options[i].name) == 0) { option_parameters = " statusPageFileName (where to write status web page)"; } else if (strcmp("ip_address", long_options[i].name) == 0) { option_parameters = " ipAddressString (where to send Rx to via port 21234)"; } fprintf(stderr, "\t--%s%s\n", long_options[i].name, option_parameters); } exit(0); } /* text message callbacks */ void callbackNextRxChar(void *callback_state, char c) { /* if we hit end of buffer wrap around to start */ if ((ptxtMsg - txtMsg) < (MAX_CHAR-1)) *ptxtMsg++ = c; else ptxtMsg = txtMsg; /* if end of string let see if we have a match for the trigger string. Note tx may send trigger string many times. We only need to receive it once to trigger a beacon tx cycle. */ if (c == 13) { *ptxtMsg++ = c; *ptxtMsg = 0; ptxtMsg = txtMsg; if (verbose) fprintf(stderr, " RX txtMsg: %s:\n", txtMsg); if (strstr(txtMsg, triggerString) != NULL) { triggered = 1; snr_sample = snr_est; if (verbose) fprintf(stderr, " Tx triggered!\n"); } } } char callbackNextTxChar(void *callback_state) { if ((*ptxtMsg == 0) || ((ptxtMsg - txtMsg) >= MAX_CHAR)) ptxtMsg = txtMsg; //fprintf(stderr, "TX txtMsg: %d %c\n", (int)*ptxtMsg, *ptxtMsg); return *ptxtMsg++; } SNDFILE *openPlayFile(char fileName[], int *sfFs) { SF_INFO sfInfo; SNDFILE *sfPlayFile; sfInfo.format = 0; sfPlayFile = sf_open(fileName, SFM_READ, &sfInfo); if(sfPlayFile == NULL) { const char *strErr = sf_strerror(NULL); fprintf(stderr, " %s Couldn't open: %s\n", strErr, fileName); } *sfFs = sfInfo.samplerate; return sfPlayFile; } SNDFILE *openRecFile(char fileName[], int sfFs) { SF_INFO sfInfo; SNDFILE *sfRecFile; sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; sfInfo.channels = 1; sfInfo.samplerate = sfFs; sfRecFile = sf_open(fileName, SFM_WRITE, &sfInfo); if(sfRecFile == NULL) { const char *strErr = sf_strerror(NULL); fprintf(stderr, " %s Couldn't open: %s\n", strErr, fileName); } return sfRecFile; } /* Use the Linux /sys/class/gpio system to access the RPis GPIOs */ void sys_gpio(char filename[], char s[]) { FILE *fgpio = fopen(filename, "wt"); //fprintf(stderr,"%s %s\n",filename, s); if (fgpio == NULL) { fprintf(stderr, "\nProblem opening %s\n", filename); exit(1); } fprintf(fgpio,"%s",s); fclose(fgpio); } void getTimeStr(char timeStr[]) { time_t ltime; /* calendar time */ struct tm *loctime; ltime=time(NULL); /* get current cal time */ loctime = localtime (<ime); strftime(timeStr, MAX_CHAR, "%F-%H-%M-%S",loctime); } void hamlib_ptt_on() { printf("Freebeacon: Setting rig PTT ON.\n"); retcode = rig_set_ptt(my_rig, RIG_VFO_A, RIG_PTT_ON); if (retcode != RIG_OK) { printf("rig_set_ptt: error = %s \n", rigerror(retcode)); } } void hamlib_ptt_off() { printf("Freebeacon: Setting rig PTT OFF.\n"); retcode = rig_set_ptt(my_rig, RIG_VFO_A, RIG_PTT_OFF); if (retcode != RIG_OK) { printf("rig_set_ptt: error = %s \n", rigerror(retcode)); } } int hamlib_init(rig_model_t myrig_model, char *commport) { // rig_load_all_backends(); // rig_set_debug(RIG_DEBUG_VERBOSE); // rig_set_debug(RIG_DEBUG_TRACE); rig_set_debug(RIG_DEBUG_ERR); fprintf(stderr,"Freebeacon: Calling Rig Init\n"); my_rig = rig_init(myrig_model); if (!my_rig) { fprintf(stderr,"Freebeacon: Hamlib Rig Init FAILED\n"); return(1); } strncpy(my_rig->state.rigport.pathname, commport, FILPATHLEN - 1); retcode = rig_open(my_rig); if (retcode != RIG_OK) { fprintf(stderr,"Freebeacon: Hamlib rig_open: error = %s\n", rigerror(retcode)); return(2); } fprintf(stderr,"Freebeacon: Hamlib Rig opened okay\n"); return(0); } /*--------------------------------------------------------------------------------------------------------* \ MAIN \*--------------------------------------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { struct freedv *f2; PaError err; PaStreamParameters inputParameters, outputParameters; const PaDeviceInfo *deviceInfo = NULL; PaStream *stream = NULL; int j, src_error, inputChannels, nin, devNum; int outputChannels; int state, next_state; SRC_STATE *rxsrc, *txsrc; SRC_STATE *playsrc; struct FIFO *fifo; char txFileName[MAX_CHAR]; SNDFILE *sfPlayFile, *sfRecFileFromRadio, *sfRecFileDecAudio; int sfFs; int fssc; int triggerf, txfilenamef, callsignf, sampleratef, wavefilepathf, rpigpiof, rpigpioalivef; int statuspagef, ipAddressf; int sync, haveRecording, beaconTimer; char commport[MAX_CHAR]; char callsign[MAX_CHAR]; //FILE *ftmp; float syncTimer, logTimer; unsigned int tnout=0,mnout; short peak = 0; char waveFileWritePath[MAX_CHAR]; char rpigpio[MAX_CHAR], rpigpio_path[MAX_CHAR], rpigpioalive[MAX_CHAR], rpigpioalive_path[MAX_CHAR]; char statusPageFileName[MAX_CHAR], ipAddressString[MAX_CHAR]; FILE *fstatus; int gpioAliveState = 0; int freedv_mode; int ident_en, udp_en; struct MODEM_STATS g_stats; /* debug raw file */ //ftmp = fopen("t.raw", "wb"); //assert(ftmp != NULL); /* Defaults -------------------------------------------------------------------------------*/ devNum = 0; fssc = FS48; sprintf(triggerString, "M17"); sprintf(txFileName, "txaudio.wav"); sprintf(callsign, "PARROT"); verbose = 0; com_handle = COM_HANDLE_INVALID; mnout = 60*FS8; state = SRX_IDLE; *txtMsg = 0; sfRecFileFromRadio = NULL; sfRecFileDecAudio = NULL; sfPlayFile = NULL; strcpy(waveFileWritePath, "."); *rpigpio = 0; *rpigpioalive = 0; *statusPageFileName = 0; *ipAddressString = 0; sprintf(ipAddressString, IP_ADDRESS); freedv_mode = FREEDV_MODE_700E; recordAny = 1; myrig_model = 0; my_rig = NULL; beaconTimer = 0; ident_en = 1; udp_en = 1; if (Pa_Initialize()) { fprintf(stderr, "Port Audio failed to initialize"); exit(1); } /* Process command line options -----------------------------------------------------------*/ char* opt_string = "hlvc:u:tm:"; struct option long_options[] = { { "dev", required_argument, &devNum, 1 }, { "trigger", required_argument, &triggerf, 1 }, { "txfilename", required_argument, &txfilenamef, 1 }, { "callsign", required_argument, &callsignf, 1 }, { "samplerate", required_argument, &sampleratef, 1 }, { "wavefilewritepath", required_argument, &wavefilepathf, 1 }, { "statuspagefile", required_argument, &statuspagef, 1 }, { "ip_address", required_argument, &ipAddressf, 1 }, { "rpigpio", required_argument, &rpigpiof, 1 }, { "rpigpioalive", required_argument, &rpigpioalivef, 1 }, { "list", no_argument, NULL, 'l' }, { "mode", required_argument, NULL, 'm'}, { "help", no_argument, NULL, 'h' }, { NULL, no_argument, NULL, 0 } }; int num_opts=sizeof(long_options)/sizeof(struct option); while(1) { int option_index = 0; int opt = getopt_long(argc, argv, opt_string, long_options, &option_index); if (opt == -1) break; switch (opt) { case 0: if (strcmp(long_options[option_index].name, "dev") == 0) { devNum = atoi(optarg); } else if(strcmp(long_options[option_index].name, "trigger") == 0) { strcpy(triggerString, optarg); } else if(strcmp(long_options[option_index].name, "txfilename") == 0) { strcpy(txFileName, optarg); } else if(strcmp(long_options[option_index].name, "callsign") == 0) { strcpy(callsign, optarg); } else if (strcmp(long_options[option_index].name, "samplerate") == 0) { fssc = atoi(optarg); } else if (strcmp(long_options[option_index].name, "wavefilewritepath") == 0) { strcpy(waveFileWritePath, optarg); } else if (strcmp(long_options[option_index].name, "statuspagefile") == 0) { strcpy(statusPageFileName, optarg); } else if (strcmp(long_options[option_index].name, "ip_address") == 0) { strcpy(ipAddressString, optarg); } else if (strcmp(long_options[option_index].name, "rpigpio") == 0) { strcpy(rpigpio, optarg); sys_gpio("/sys/class/gpio/unexport", rpigpio); sys_gpio("/sys/class/gpio/export", rpigpio); usleep(100*1000); /* short delay so OS can create the next device */ char tmp[MAX_CHAR]; sprintf(tmp,"/sys/class/gpio/gpio%s/direction", rpigpio); sys_gpio(tmp, "out"); sprintf(rpigpio_path,"/sys/class/gpio/gpio%s/value", rpigpio); sys_gpio(rpigpio_path, "0"); } else if (strcmp(long_options[option_index].name, "rpigpioalive") == 0) { strcpy(rpigpioalive, optarg); sys_gpio("/sys/class/gpio/unexport", rpigpioalive); sys_gpio("/sys/class/gpio/export", rpigpioalive); usleep(100*1000); /* short delay so OS can create the next device */ char tmp[MAX_CHAR]; sprintf(tmp,"/sys/class/gpio/gpio%s/direction", rpigpioalive); sys_gpio(tmp, "out"); sprintf(rpigpioalive_path,"/sys/class/gpio/gpio%s/value", rpigpioalive); sys_gpio(rpigpioalive_path, "0"); } break; case 'b': ident_en = 0; break; case 'c': strcpy(commport, optarg); if (openComPort(commport) != 0) { fprintf(stderr, "Can't open comm port: %s\n",commport); exit(1); } break; case 'r': // hamlib CAT rig myrig_model = atoi(optarg); // rig number not its name for now if ((myrig_model == 0 ) || (com_handle == COM_HANDLE_INVALID)) { fprintf(stderr,"No Comm port? use 'c' option before 'u' option\n"); fprintf(stderr,"Rig Hamlib model numbers found use rigctl -l to see list\n"); exit(1); } closeComPort(); // let hamlib use it now fprintf(stderr,"Freebeacon: Opening Hamlib with model %d\n",myrig_model); if (hamlib_init(myrig_model, commport)) { fprintf(stderr,"Hamlib failed to initilise: [use rigctl -l to see list]\n"); exit(1); } break; case 'h': printHelp(long_options, num_opts, argv); break; case 'u': udp_en = 0; break; case 'v': verbose = 1; break; case 't': sprintf(txtMsg,"tx Test"); state = STX; break; case 'l': listAudioDevices(); exit(0); break; case 'm': if (strcmp(optarg, "1600") == 0) freedv_mode = FREEDV_MODE_1600; else if (strcmp(optarg, "700C") == 0) { fprintf(stderr, "700C doesn't support text, so there is no trigger word. " " So we just record the received file every time we get sync"); recordAny = 1; freedv_mode = FREEDV_MODE_700C; } else if (strcmp(optarg, "700D") == 0) freedv_mode = FREEDV_MODE_700D; else if (strcmp(optarg, "700E") == 0) freedv_mode = FREEDV_MODE_700E; else { fprintf(stderr, "Unknown mode: %s\n", optarg); exit(1); } break; default: /* This will never be reached */ break; } } /* Open Sound Device and start processing --------------------------------------------------------------*/ if ((freedv_mode == FREEDV_MODE_700D) || (freedv_mode == FREEDV_MODE_700E) || (freedv_mode == FREEDV_MODE_2020)) { // 700D and 700E have some init time stuff so treat it special struct freedv_advanced adv; adv.interleave_frames = 1; f2 = freedv_open_advanced(freedv_mode, &adv); freedv_set_sync(f2, FREEDV_SYNC_AUTO); freedv_set_eq(f2, 0); freedv_set_clip(f2, 1); freedv_set_tx_bpf(f2, 1); } else { f2 = freedv_open(freedv_mode); } assert(f2 != NULL); if ((freedv_mode == FREEDV_MODE_700D) || (freedv_mode == FREEDV_MODE_2020)) { freedv_set_phase_est_bandwidth_mode(f2, 0); freedv_set_dpsk(f2, 0); } freedv_set_squelch_en(f2, 1); freedv_set_snr_squelch_thresh(f2, SQ_LEVEL); int fsm = freedv_get_modem_sample_rate(f2); /* modem sample rate */ int n8m = freedv_get_n_nom_modem_samples(f2); /* nominal modem sample buffer size at fsm sample rate */ int n48 = n8m*fssc/fsm; /* nominal modem sample buffer size at 48kHz */ float dT = (float)n48/fssc; /* period of each sound buffer */ if (verbose) fprintf(stderr, "fsm: %d n8m: %d n48: %d port: %d\n", fsm, n8m, n48, SERVICE_PORT); short stereo[2*n48]; /* stereo I/O buffer from port audio */ short rx48k[n48], tx48k[n48]; /* signals at 48 kHz */ short rxfsm[n48]; /* rx signal at modem sample rate */ freedv_set_callback_txt(f2, callbackNextRxChar, callbackNextTxChar, NULL); fifo = codec2_fifo_create(4*n8m); assert(fifo != NULL); /* fifo to smooth out variation in demod nin */ nfifo = codec2_fifo_create(4*n8m); assert(fifo != NULL); /* fifo for network audio in */ /* states for sample rate converters */ rxsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(rxsrc != NULL); txsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(txsrc != NULL); playsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(playsrc != NULL); /* Open Port Audio device and set up config structures -----------------------------------------------------*/ deviceInfo = Pa_GetDeviceInfo(devNum); if (deviceInfo == NULL) { fprintf(stderr, "Couldn't get device info from Port Audio for device: %d\n", devNum); exit(1); } if (deviceInfo->maxInputChannels == 1) inputChannels = 1; else inputChannels = 2; /* input device */ inputParameters.device = devNum; inputParameters.channelCount = inputChannels; inputParameters.sampleFormat = paInt16; inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency; inputParameters.hostApiSpecificStreamInfo = NULL; /* output device */ if (deviceInfo->maxOutputChannels == 1) outputChannels = 1; else outputChannels = 2; outputParameters.device = devNum; outputParameters.channelCount = outputChannels; outputParameters.sampleFormat = paInt16; outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; /* open port audio for full duplex operation */ err = Pa_OpenStream( &stream, &inputParameters, &outputParameters, fssc, n48, /* changed from 0 to n48 to get Rpi audio to work without clicks */ paClipOff, NULL, /* no callback, use blocking API */ NULL ); if (err != paNoError) { fprintf(stderr, "Couldn't initialise sound device\n"); exit(1); } err = Pa_StartStream(stream); if (err != paNoError) { fprintf(stderr, "Couldn't start sound device\n"); exit(1); } /* init UDP sender */ int sockfd = 0; struct sockaddr_in addr_con; int addrlen = sizeof(addr_con); addr_con.sin_family = AF_INET; addr_con.sin_port = htons(SERVICE_PORT); addr_con.sin_addr.s_addr = inet_addr(ipAddressString); sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* Start UDP listener thread */ if (udp_en) start_udp_listener_thread(); /* Init for main loop ----------------------------------------------------------------------------*/ fprintf(stderr, "\nCtrl-C to exit\n"); fprintf(stderr, "freedv_mode: %d\n", freedv_mode); fprintf(stderr, "trigger string: %s\ntxFileName: %s\n", triggerString, txFileName); fprintf(stderr, "PortAudio devNum: %d\nsamplerate: %d\n", devNum, fssc); fprintf(stderr, "WaveFileWritePath: %s\n", waveFileWritePath); fprintf(stderr, "statusPageFile: %s\n", statusPageFileName); fprintf(stderr, "ip_address: %s\n", ipAddressString); if (com_handle != COM_HANDLE_INVALID) { fprintf(stderr, "Comm Port for PTT: %s\n", commport); } if (*rpigpio) { fprintf(stderr, "Raspberry Pi BCM GPIO for PTT: %s\n", rpigpio); } if (*rpigpioalive) { fprintf(stderr, "Raspberry Pi BCM GPIO for Alive Indicator: %s\n", rpigpioalive); } signal(SIGINT, intHandler); /* ctrl-C to exit gracefully */ keepRunning = 1; ptxtMsg = txtMsg; triggered = 0; txFromNet = 0; logTimer = 0; syncTimer = 0; haveRecording = 0; if (com_handle != COM_HANDLE_INVALID) { lowerRTS(); lowerDTR(); } /* -t flag: we are leaping straight into TX */ if (state == STX) { if (com_handle != COM_HANDLE_INVALID) { raiseRTS(); raiseDTR(); } if (*rpigpio) { sys_gpio(rpigpio_path, "1"); } if (my_rig) hamlib_ptt_on(); sfPlayFile = openPlayFile(txFileName, &sfFs); } modem_stats_open(&g_stats); nin = freedv_get_n_max_speech_samples(f2); /* Main loop -------------------------------------------------------------------------------------*/ while(keepRunning) { if (state != STX) { short demod_in[freedv_get_n_max_modem_samples(f2)]; short speech_out[freedv_get_n_speech_samples(f2) + 4]; COMP rx_fdm[freedv_get_n_max_modem_samples(f2)]; /* Read samples from sound card, resample to modem sample rate */ Pa_ReadStream(stream, stereo, n48); if (inputChannels == 2) { for(j=0; j<n48; j++) rx48k[j] = stereo[2*j]; /* left channel only */ } else { for(j=0; j<n48; j++) rx48k[j] = stereo[j]; } //fwrite(rx48k, sizeof(short), n8m, ftmp); int n8m_out = resample(rxsrc, rxfsm, rx48k, fsm, fssc, n8m, n48); /* crude input signal level meter */ peak = 0; for (j=0; j<n8m_out; j++) if (rxfsm[j] > peak) peak = rxfsm[j]; codec2_fifo_write(fifo, rxfsm, n8m_out); /* demodulate to decoded speech samples */ memset(speech_out, 0, sizeof(short)*freedv_get_n_speech_samples(f2)); while (codec2_fifo_read(fifo, demod_in, nin) == 0) { int nout = 0; for(int i=0; i<nin; i++) { rx_fdm[i].real = (float)demod_in[i]; rx_fdm[i].imag = 0.0; } nout = freedv_comprx(f2, &speech_out[2], rx_fdm); // was nout = freedv_rx(f2, speech_out, demod_in); freedv_get_modem_stats(f2, &sync, &snr_est); sync += freedv_get_sync(f2); // to be sure if ((freedv_mode == FREEDV_MODE_700E) && ( snr_est > SNR_MIN)) sync++; if (sfRecFileFromRadio) sf_write_short(sfRecFileFromRadio, demod_in, nin); nin = freedv_nin(f2); if (sfRecFileDecAudio) sf_write_short(sfRecFileDecAudio, &speech_out[2], nout); /* Send UDP */ if ((sockfd > 0) && (triggered > 0) && (nout > 100)) { speech_out[0] = 0x4141; speech_out[1] = (short)nout; sendto(sockfd, (char*)speech_out, (nout * 2) + 4, 0, (struct sockaddr*)&addr_con, addrlen); } tnout += nout; if (tnout > mnout) { sf_close(sfRecFileFromRadio); sf_close(sfRecFileDecAudio); char finished[16] = {0x41, 0x42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; sendto(sockfd, finished, 16, 0, (struct sockaddr*)&addr_con, addrlen); } } } if (state == STX) { short mod_out[freedv_get_n_max_modem_samples(f2)]; short speech_in[freedv_get_n_speech_samples(f2)]; /* get audio from network fifo */ if (txFromNet == 1) { if (codec2_fifo_read(nfifo, speech_in, n8m) != 0) { txFromNet = 0; } } if (sfPlayFile != NULL) { /* resample input sound file as can't guarantee 8KHz sample rate */ unsigned int nsf = freedv_get_n_speech_samples(f2)*sfFs/FS8; short insf[nsf]; unsigned int n = sf_read_short(sfPlayFile, insf, nsf); resample(playsrc, speech_in, insf, FS8, sfFs, freedv_get_n_speech_samples(f2), nsf); //fwrite(speech_in, sizeof(short), freedv_get_n_nom_modem_samples(f2), ftmp); if (n != nsf) { /* end of file - this signals state machine we've finished */ sf_close(sfPlayFile); sfPlayFile = NULL; beaconTimer = 0; } } // if sfPlayFile freedv_tx(f2, mod_out, speech_in); //fwrite(mod_out, sizeof(short), freedv_get_n_nom_modem_samples(f2), ftmp); int n48_out = resample(txsrc, tx48k, mod_out, fssc, fsm, n48, n8m); //printf("n48_out: %d n48: %d n_nom: %d\n", n48_out, n48, n8m); //fwrite(tx48k, sizeof(short), n48_out, ftmp); for(j=0; j<n48_out; j++) { if (outputChannels == 2) { stereo[2*j] = tx48k[j]; // left channel stereo[2*j+1] = tx48k[j]; // right channel } else { stereo[j] = tx48k[j]; // mono } } Pa_WriteStream(stream, stereo, n48_out); } /* state machine processing */ next_state = state; switch(state) { case SRX_IDLE: if (sync > 0) { next_state = SRX_MAYBE_SYNC; syncTimer = 0.0; *txtMsg = 0; ptxtMsg = txtMsg; freedv_set_total_bit_errors(f2, 0); freedv_set_total_bits(f2, 0); } break; case SRX_MAYBE_SYNC: if (sync > 0) { syncTimer += dT; if (syncTimer >= SYNC_TIMER) { /* OK we really are in sync */ next_state = SRX_SYNC; } } else { next_state = SRX_IDLE; triggered = 0; haveRecording = 0; } break; case SRX_SYNC: syncTimer += dT; if (!sync) { syncTimer = 0; next_state = SRX_MAYBE_UNSYNC; } /* if triggered kick off recording of two files */ if ((triggered || recordAny) && !haveRecording) { char timeStr[MAX_CHAR]; char recFileFromRadioName[MAX_CHAR], recFileDecAudioName[MAX_CHAR]; getTimeStr(timeStr); sprintf(recFileFromRadioName,"%s/%s_from_radio.wav", waveFileWritePath, timeStr); sprintf(recFileDecAudioName,"%s/%s_decoded_speech.wav", waveFileWritePath, timeStr); sfRecFileFromRadio = openRecFile(recFileFromRadioName, fsm); sfRecFileDecAudio = openRecFile(recFileDecAudioName, FS8); haveRecording = 1; if (freedv_mode != FREEDV_MODE_700C) { recordAny = 0; } tnout = 0; } break; case SRX_MAYBE_UNSYNC: if (!sync) { syncTimer += dT; if (syncTimer >= UNSYNC_TIMER) { /* we really are out of sync */ /* finish up any open recording files */ if (sfRecFileFromRadio) sf_close(sfRecFileFromRadio); if (sfRecFileDecAudio) { sf_close(sfRecFileDecAudio); char finished[16] = {0x41, 0x43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; sendto(sockfd, finished, 16, 0, (struct sockaddr*)&addr_con, addrlen); } /* kick off a tx if triggered */ if (triggered) { float ber = (float)freedv_get_total_bit_errors(f2)/freedv_get_total_bits(f2); char tmpStr[MAX_CHAR]; sprintf(tmpStr, "SNR: %3.1f BER: %4.3f de %s\r", snr_sample, ber, callsign); strcpy(txtMsg, tmpStr); //fprintf(stderr, "TX txtMsg: %s\n", txtMsg); ptxtMsg = txtMsg; sfPlayFile = openPlayFile(txFileName, &sfFs); if (com_handle != COM_HANDLE_INVALID) { raiseRTS(); raiseDTR(); } if (*rpigpio) { sys_gpio(rpigpio_path, "1"); } if (my_rig) hamlib_ptt_on(); next_state = STX; } else { next_state = SRX_IDLE; triggered = 0; haveRecording = 0; } } } else next_state = SRX_SYNC; /* sync is back so false alarm */ break; case STX: if ((sfPlayFile == NULL) && (txFromNet == 0)) { if (com_handle != COM_HANDLE_INVALID) { lowerRTS(); lowerDTR(); } if (*rpigpio) { sys_gpio(rpigpio_path, "0"); } if (my_rig) hamlib_ptt_off(); next_state = SRX_IDLE; triggered = 0; haveRecording = 0; } break; } /* end switch statement for case statement */ logTimer += dT; if (logTimer >= LOG_TIMER) { logTimer = 0; if (verbose) { fprintf(stderr, "state: %-20s peak: %6d sync: %d SNR: %3.1f triggered: %d recordany: %d\n", state_str[state], peak, sync, snr_est, triggered, recordAny); } if (*statusPageFileName) { char timeStr[MAX_CHAR]; time_t ltime; /* calendar time */ ltime=time(NULL); /* get current cal time */ sprintf(timeStr, "%s",asctime( localtime(<ime) ) ); strtok(timeStr, "\n"); fstatus = fopen(statusPageFileName, "wt"); if (fstatus != NULL) { fprintf(fstatus, "<html>\n<head>\n<meta http-equiv=\"refresh\" content=\"2\">\n</head>\n<body>\n"); fprintf(fstatus, "%s: state: %s peak: %d sync: %d SNR: %3.1f triggered: %d recordany: %d txtMsg: %s\n", timeStr, state_str[state], peak, sync, snr_est, triggered, recordAny, txtMsg); fprintf(fstatus, "</body>\n</html>\n"); fclose(fstatus); } } /* toggle alive GPIO */ if (*rpigpioalive) { if (gpioAliveState) { gpioAliveState = 0; sys_gpio(rpigpioalive_path, "0"); } else { gpioAliveState = 1; sys_gpio(rpigpioalive_path, "1"); } } if ( !sync) { if ( (txFromNet == 1) || ((beaconTimer++ > 250) && ident_en) ) { beaconTimer = 0; triggered = 1; sprintf(txtMsg, "700E Test by %s\r", callsign); ptxtMsg = txtMsg; if (sfPlayFile == NULL) sfPlayFile = openPlayFile(txFileName, &sfFs); if (com_handle != COM_HANDLE_INVALID) { raiseRTS(); raiseDTR(); } if (*rpigpio) { sys_gpio(rpigpio_path, "1"); } if (my_rig) hamlib_ptt_on(); next_state = STX; } } } // end logTimer state = next_state; } /* end while loop */ /* Ctrl-C has been pressed lets shut down gracefully ------------------*/ /* lower PTT lines, shut down ports */ if (com_handle != COM_HANDLE_INVALID) { lowerRTS(); lowerDTR(); closeComPort(); } if (*rpigpio) { sys_gpio(rpigpio_path, "0"); sys_gpio("/sys/class/gpio/unexport", rpigpio); } if (*rpigpioalive) { sys_gpio(rpigpioalive_path, "0"); sys_gpio("/sys/class/gpio/unexport", rpigpioalive); } if (my_rig) { hamlib_ptt_off(); rig_close(my_rig); rig_cleanup(my_rig); } /* Shut down port audio */ err = Pa_StopStream(stream); if (err != paNoError) { fprintf(stderr, "Couldn't stop sound device\n"); exit(1); } Pa_CloseStream(stream); Pa_Terminate(); /* clean up states */ codec2_fifo_destroy(fifo); src_delete(rxsrc); src_delete(txsrc); src_delete(playsrc); freedv_close(f2); //fclose(ftmp); return 0; } /*--------------------------------------------------------------------------------------------------------*\ Comm port fuctions lifted from FreeDV \*--------------------------------------------------------------------------------------------------------*/ //---------------------------------------------------------------- // openComPort() opens the com port specified by the string // ie: "/dev/ttyUSB0" //---------------------------------------------------------------- int openComPort(const char *name) { if(com_handle != COM_HANDLE_INVALID) closeComPort(); #ifdef _WIN32 { COMMCONFIG CC; DWORD CCsize=sizeof(CC); COMMTIMEOUTS timeouts; DCB dcb; if(GetDefaultCommConfigA(name, &CC, &CCsize)) { CC.dcb.fOutxCtsFlow = FALSE; CC.dcb.fOutxDsrFlow = FALSE; CC.dcb.fDtrControl = DTR_CONTROL_DISABLE; CC.dcb.fDsrSensitivity = FALSE; CC.dcb.fRtsControl = RTS_CONTROL_DISABLE; SetDefaultCommConfigA(name, &CC, CCsize); } if((com_handle=CreateFileA(name ,GENERIC_READ|GENERIC_WRITE /* Access */ ,0 /* Share mode */ ,NULL /* Security attributes */ ,OPEN_EXISTING /* Create access */ ,FILE_ATTRIBUTE_NORMAL /* File attributes */ ,NULL /* Template */ ))==INVALID_HANDLE_VALUE) return false; if(GetCommTimeouts(com_handle, &timeouts)) { timeouts.ReadIntervalTimeout=MAXDWORD; timeouts.ReadTotalTimeoutMultiplier=0; timeouts.ReadTotalTimeoutConstant=0; // No-wait read timeout timeouts.WriteTotalTimeoutMultiplier=0; timeouts.WriteTotalTimeoutConstant=5000; // 5 seconds SetCommTimeouts(com_handle,&timeouts); } /* Force N-8-1 mode: */ if(GetCommState(com_handle, &dcb)==TRUE) { dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; dcb.DCBlength = sizeof(DCB); dcb.fBinary = TRUE; dcb.fOutxCtsFlow = FALSE; dcb.fOutxDsrFlow = FALSE; dcb.fDtrControl = DTR_CONTROL_DISABLE; dcb.fDsrSensitivity = FALSE; dcb.fTXContinueOnXoff= TRUE; dcb.fOutX = FALSE; dcb.fInX = FALSE; dcb.fRtsControl = RTS_CONTROL_DISABLE; dcb.fAbortOnError = FALSE; SetCommState(com_handle, &dcb); } } #else { struct termios t; if((com_handle=open(name, O_NONBLOCK|O_RDWR))==COM_HANDLE_INVALID) return -1; if(tcgetattr(com_handle, &t)==-1) { close(com_handle); com_handle = COM_HANDLE_INVALID; return -1; } t.c_iflag = ( IGNBRK /* ignore BREAK condition */ | IGNPAR /* ignore (discard) parity errors */ ); t.c_oflag = 0; /* No output processing */ t.c_cflag = ( CS8 /* 8 bits */ | CREAD /* enable receiver */ /* Fun snippet from the FreeBSD manpage: If CREAD is set, the receiver is enabled. Otherwise, no character is received. Not all hardware supports this bit. In fact, this flag is pretty silly and if it were not part of the termios specification it would be omitted. */ | CLOCAL /* ignore modem status lines */ ); t.c_lflag = 0; /* No local modes */ if(tcsetattr(com_handle, TCSANOW, &t)==-1) { close(com_handle); com_handle = COM_HANDLE_INVALID; return -1; } } #endif return 0; } void closeComPort(void) { #ifdef _WIN32 CloseHandle(com_handle); #else close(com_handle); #endif com_handle = COM_HANDLE_INVALID; } //---------------------------------------------------------------- // (raise|lower)(RTS|DTR)() // // Raises/lowers the specified signal //---------------------------------------------------------------- void raiseDTR(void) { if(com_handle == COM_HANDLE_INVALID) return; #ifdef _WIN32 EscapeCommFunction(com_handle, SETDTR); #else { // For C89 happiness int flags = TIOCM_DTR; ioctl(com_handle, TIOCMBIS, &flags); } #endif } void raiseRTS(void) { if(com_handle == COM_HANDLE_INVALID) return; #ifdef _WIN32 EscapeCommFunction(com_handle, SETRTS); #else { // For C89 happiness int flags = TIOCM_RTS; ioctl(com_handle, TIOCMBIS, &flags); } #endif } void lowerDTR(void) { if(com_handle == COM_HANDLE_INVALID) return; #ifdef _WIN32 EscapeCommFunction(com_handle, CLRDTR); #else { // For C89 happiness int flags = TIOCM_DTR; ioctl(com_handle, TIOCMBIC, &flags); } #endif } void lowerRTS(void) { if(com_handle == COM_HANDLE_INVALID) return; #ifdef _WIN32 EscapeCommFunction(com_handle, CLRRTS); #else { // For C89 happiness int flags = TIOCM_RTS; ioctl(com_handle, TIOCMBIC, &flags); } #endif } #define BUFSIZE 2048 void *udp_listener_thread(void* p) { struct sockaddr_in myaddr; /* our address */ struct sockaddr_in remaddr; /* remote address */ socklen_t addrlen = sizeof(remaddr); /* length of addresses */ int recvlen; /* # bytes received */ int fd; /* our socket */ char txt[BUFSIZE]; /* receive buffer */ short *txtp; txtp = (short*)&txt[0]; /* create a UDP socket */ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { fprintf(stderr, "cannot create socket\n"); return 0; } /* bind the socket to any valid IP address and a specific port */ memset((char *)&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_addr.s_addr = htonl(INADDR_ANY); myaddr.sin_port = htons(SERVICE_PORT); if (bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { fprintf(stderr, "bind failed"); return 0; } /* now loop, receiving data and printing what we received */ while (runListener) { fprintf(stderr, "udp listener waiting on port %d - send me a command!\n", SERVICE_PORT); recvlen = recvfrom(fd, txt, BUFSIZE, 0, (struct sockaddr *)&remaddr, &addrlen); fprintf(stderr, "received %d bytes\n", recvlen); if ((recvlen > 0) && (recvlen < 20)) { txt[recvlen] = 0; fprintf(stderr, "txt: %s\n", txt); if (strcmp(txt, "recordany") == 0) { recordAny = 1; fprintf(stderr, "Next signal to sync will be recorded regardless of txt msg\n"); } if (strcmp(txt, "trigger") == 0) { triggered = 1; fprintf(stderr, "Now triggered\n"); } } /* audio to send */ /* receive net audio, first short 0x4141, then length */ if (txtp[0] == 0x4141) { if (txtp[1] > 14) { codec2_fifo_write(nfifo, &txtp[2], (recvlen / 2) - 2); txFromNet = 1; } else { txFromNet = 0; } } } return NULL; } pthread_t start_udp_listener_thread(void) { pthread_t athread; runListener = 1; if (pthread_create(&athread, NULL, udp_listener_thread, NULL) != 0) fprintf(stderr, "Can't create UDP listener thread\n"); return athread; }
_______________________________________________ Freetel-codec2 mailing list Freetel-codec2@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/freetel-codec2