/* * High Speed modem to transfer data in a 2,7kHz SSB channel * ========================================================= * Author: DJ0ABR * made for: AMSAT-DL * * (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. * */ /* * this is a console program * it can be compiled under Linux: make * and under Windows: Visual-Studio * * !!! compile x86 (32bit) version !!! all supplied dlls are 32 bit !!! * ==================================================================== * * 3rd party libraries: * 1) BASS Audio from https://www.un4seen.com/ copy bass.h and bass.lib into source directory Windows: copy bass.dll into executable directory Linux: copy libbass.so into shared-lib folder, usually /usr/local/lib ! NOTE: for PC-Linux and ARM-Linux you need different libraries ! 2) liquid-DSP Linux Install Script: this installs it from source sudo apt install git autoconf libsndfile-dev libasound-dev git clone git://github.com/jgaeddert/liquid-dsp.git cd liquid-dsp ./bootstrap.sh ./configure make -j 8 sudo make install sudo ldconfig a working copy of the source code is in ../3rdParty/liquid-dsp to use this source simply remove the "git clone" line from above script it installs libliquid.so into /usr/local/lib (Ubuntu) and liquid.h into /usr/local/include/liquid/ Windows: ready libraries are in ../3rdParty/liquid-dsp-windows copy liquid.h and liquid.lib into source directory copy liquid.dll into executable directory */ #include "hsmodem.h" void toGR_sendData(uint8_t* data, int type, int status); void bc_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock); void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock); void startModem(); // threads will exit if set to 0 int keeprunning = 1; // UDP I/O int BC_sock_AppToModem = -1; int DATA_sock_AppToModem = -1; //int DATA_sock_from_GR = -1; int DATA_sock_FFT_from_GR = -1; int DATA_sock_I_Q_from_GR = -1; int UdpBCport_AppToModem = 40131; int UdpDataPort_AppToModem = 40132; int UdpDataPort_ModemToApp = 40133; // op mode depending values // default mode if not set by the app int speedmode = 2; int bitsPerSymbol = 2; // QPSK=2, 8PSK=3 int constellationSize = 4; // QPSK=4, 8PSK=8 char localIP[] = { "127.0.0.1" }; char ownfilename[] = { "hsmodem" }; char appIP[20] = { 0 }; int fixappIP = 0; int restart_modems = 0; int trigger_resetmodem = 0; int caprate = 44100; int txinterpolfactor = 20; int rxPreInterpolfactor = 5; int linespeed = 4410; int captureDeviceNo = -1; int playbackDeviceNo = -1; int MicDeviceNo = -1; int LSDeviceNo = -1; int initialPBvol = -1; int initialCAPvol = -1; int announcement = 0; int VoiceAudioMode = VOICEMODE_OFF; int codec = 1; // 0=opus, 1=codec2 int init_audio_result = 0; int init_voice_result = 0; int main(int argc, char* argv[]) { int opt = 0; char* modemip = NULL; #ifdef _LINUX_ while ((opt = getopt(argc, argv, "m:")) != -1) { switch (opt) { case 'm': // specify IP of application: hsmodem -m 192.168.0.1 modemip = optarg; memset(appIP, 0, 20); int len = strlen(modemip); if (len < 16) { memcpy(appIP, modemip, len); fixappIP = 1; printf("Application IP set to: %s\n", modemip); } else { printf("invalid Application IP: %s\n", modemip); exit(0); } break; } } if (isRunning(ownfilename) == 1) exit(0); install_signal_handler(); #endif #ifdef _WIN32_ if (argc != 1 && argc != 3) { printf("invalid argument\n"); exit(0); } if (argc == 3) { memset(appIP, 0, 20); int len = strlen(argv[2]); if (len < 16) { memcpy(appIP, argv[2], len); fixappIP = 1; printf("Application IP set to: %s\n", argv[2]); } else { printf("invalid Application IP: %s\n", modemip); exit(0); } } #endif init_packer(); initFEC(); init_fft(); init_voiceproc(); /*int ar = init_audio(playbackDeviceNo, captureDeviceNo); if (ar == -1) { keeprunning = 0; exit(0); }*/ // start udp RX to listen for broadcast search message from Application UdpRxInit(&BC_sock_AppToModem, UdpBCport_AppToModem, &bc_rxdata, &keeprunning); // start udp RX for data from application UdpRxInit(&DATA_sock_AppToModem, UdpDataPort_AppToModem, &appdata_rxdata, &keeprunning); // start udp RX to listen for data from GR Receiver //UdpRxInit(&DATA_sock_from_GR, UdpDataPort_fromGR, &GRdata_rxdata, &keeprunning); printf("QO100modem initialised and running\n"); while (keeprunning) { if (restart_modems == 1) { startModem(); restart_modems = 0; } //doArraySend(); if (VoiceAudioMode == VOICEMODE_INTERNALLOOP) { // loop voice mic to LS float f; if (cap_read_fifo_voice(&f)) { if(softwareCAPvolume_voice >= 0) f *= softwareCAPvolume_voice; pb_write_fifo_voice(f); } } if (VoiceAudioMode == VOICEMODE_CODECLOOP || VoiceAudioMode == VOICEMODE_DV_FULLDUPLEX) { // send mic to codec float f; if (cap_read_fifo_voice(&f)) { if (softwareCAPvolume_voice >= 0) f *= softwareCAPvolume_voice; encode(f); } } // demodulate incoming audio data stream static int old_tm = 0; int tm = getus(); if (tm >= (old_tm + 1000000)) { // read Audio device list every 1s readAudioDevices(); old_tm = tm; } int dret = demodulator(); if (dret == 0) { // no new data in fifo // not important how long to sleep, 10ms is fine sleep_ms(10); } } printf("stopped: %d\n", keeprunning); #ifdef _LINUX_ close(BC_sock_AppToModem); #endif #ifdef _WIN32_ closesocket(BC_sock_AppToModem); #endif return 0; } typedef struct { int audio; int tx; int rx; int bpsym; int linespeed; int codecrate; } SPEEDRATE; // AudioRate, TX-Resampler, RX-Resampler/4, bit/symbol, Codec-Rate SPEEDRATE sr[8] = { // QPSK modes {48000, 32, 8, 2, 3000, 2400}, {48000, 24, 6, 2, 4000, 3200}, {44100, 20, 5, 2, 4410, 3600}, {48000, 20, 5, 2, 4800, 4000}, // 8PSK modes {44100, 24, 6, 3, 5500, 4400}, {48000, 24, 6, 3, 6000, 4800}, {44100, 20, 5, 3, 6600, 5200}, {48000, 20, 5, 3, 7200, 6000} }; void startModem() { bitsPerSymbol = sr[speedmode].bpsym; constellationSize = (1 << bitsPerSymbol); // QPSK=4, 8PSK=8 caprate = sr[speedmode].audio; txinterpolfactor = sr[speedmode].tx; rxPreInterpolfactor = sr[speedmode].rx; linespeed = sr[speedmode].linespeed; opusbitrate = sr[speedmode].codecrate; // int TX audio and modulator close_dsp(); init_audio_result = init_audio(playbackDeviceNo, captureDeviceNo); setPBvolume(initialPBvol); setCAPvolume(initialCAPvol); init_dsp(); } void setAudioDevices(int pb, int cap, int pbvol, int capvol, int announce, int pbls, int pbmic) { //printf("%d %d\n", pb, cap); if (pb != playbackDeviceNo || cap != captureDeviceNo) { restart_modems = 1; playbackDeviceNo = pb; captureDeviceNo = cap; initialPBvol = pbvol; initialCAPvol = capvol; initialLSvol = pbls; initialMICvol = pbmic; } announcement = announce; } // called from UDP RX thread for Broadcast-search from App void bc_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { if (len > 0 && pdata[0] == 0x3c) { setAudioDevices(pdata[1], pdata[2], pdata[3], pdata[4], pdata[5], pdata[6], pdata[7]); char rxip[20]; strcpy(rxip, inet_ntoa(rxsock->sin_addr)); if (fixappIP == 0) { if (strcmp(appIP, rxip)) { printf("new app IP: %s, restarting modems\n", rxip); restart_modems = 1; } strcpy(appIP, rxip); //printf("app (%s) is searching modem. Sending modem IP to the app\n",appIP); // App searches for the modem IP, mirror the received messages // so the app gets an UDP message with this local IP int alen; uint8_t* txdata = getAudioDevicelist(&alen); sendUDP(appIP, UdpDataPort_ModemToApp, txdata, alen); } else { // appIP is fixed, answer only to this IP if (!strcmp(appIP, rxip)) { //printf("app (%s) is searching modem. Sending modem IP to the app\n",appIP); restart_modems = 1; // App searches for the modem IP, mirror the received messages // so the app gets an UDP message with this local IP int alen; uint8_t* txdata = getAudioDevicelist(&alen); sendUDP(appIP, UdpDataPort_ModemToApp, txdata, alen); } } } } // called by UDP RX thread for data from App void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { uint8_t type = pdata[0]; uint8_t minfo = pdata[1]; // type values: see oscardata config.cs: frame types if (type == 16) { // Byte 1 contains the resampler ratio for TX and RX modem if (pdata[1] >= 8) { printf("wrong speedmode %d, ignoring\n", pdata[1]); return; } speedmode = pdata[1]; printf("set speedmode to %d\n", speedmode); restart_modems = 1; transmissions = 1000; // announcement at next TX return; } if (type == 17) { // auto send file // TODO // for testing only: // simulate sending a text file with 1kB length /*int testlen = 100000; uint8_t arr[100000]; char c = 'A'; for (int i = 0; i < testlen; i++) { arr[i] = c; if (++c > 'Z') c = 'A'; } arraySend(arr, testlen, 3, (char*)"testfile.txt");*/ return; } if (type == 18) { // auto send folder // TODO } if (type == 19) { // shut down this modem PC #ifdef _LINUX_ int r = system("sudo shutdown now"); exit(r); #endif } if (type == 20) { // reset liquid RX modem resetModem(); return; } if (type == 21) { // set playback volume (in % 0..100) setVolume(0,minfo); return; } if (type == 22) { // set capture volume (in % 0..100) setVolume(1,minfo); return; } if (type == 23) { // set playback volume (in % 0..100) setVolume_voice(0, minfo); return; } if (type == 24) { // set capture volume (in % 0..100) setVolume_voice(1, minfo); return; } if (type == 25) { //printf("%d %d %d %d %d\n", pdata[0], pdata[1], pdata[2], pdata[3], pdata[4]); LSDeviceNo = pdata[1]; MicDeviceNo = pdata[2]; VoiceAudioMode = pdata[3]; codec = pdata[4]; // init voice audio init_voice_result = init_audio_voice(LSDeviceNo, MicDeviceNo); init_voiceproc(); return; } if (type == 26) { // GUI requests termination of this hsmodem printf("shut down hsmodem\n"); closeAllandTerminate(); } if (len != (PAYLOADLEN + 2)) { printf("data from app: wrong length:%d (should be %d)\n", len - 2, PAYLOADLEN); return; } //if (getSending() == 1) return; // already sending (Array sending) if (minfo == 0 || minfo == 3) { // this is the first frame of a larger file sendAnnouncement(); // send it multiple times, like a preamble, to give the // receiver some time for synchronisation // caprate: samples/s. This are symbols: caprate/txinterpolfactor // and bits: symbols * bitsPerSymbol // and bytes/second: bits/8 = (caprate/txinterpolfactor) * bitsPerSymbol / 8 // one frame has 258 bytes, so we need for 6s: 6* ((caprate/txinterpolfactor) * bitsPerSymbol / 8) /258 + 1 frames int numframespreamble = 6 * ((caprate / txinterpolfactor) * bitsPerSymbol / 8) / 258 + 1; for (int i = 0; i < numframespreamble; i++) toGR_sendData(pdata + 2, type, minfo); } else if ((len - 2) < PAYLOADLEN) { // if not enough data for a full payload add Zeros uint8_t payload[PAYLOADLEN]; memset(payload, 0, PAYLOADLEN); memcpy(payload, pdata + 2, len - 2); toGR_sendData(payload, type, minfo); } else { toGR_sendData(pdata + 2, type, minfo); } } void toGR_sendData(uint8_t* data, int type, int status) { int len = 0; uint8_t* txdata = Pack(data, type, status, &len); //showbytestring((char *)"BERtx: ", txdata, len); if (txdata != NULL) sendToModulator(txdata, len); } // called by liquid demodulator for received data void GRdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { static int fnd = 0; // raw symbols uint8_t* pl = unpack_data(pdata, len); if (pl != NULL) { if (VoiceAudioMode != VOICEMODE_DV_FULLDUPLEX) { // complete frame received // send payload to app uint8_t txpl[PAYLOADLEN + 10 + 1]; memcpy(txpl + 1, pl, PAYLOADLEN + 10); txpl[0] = 1; // type 1: payload data follows sendUDP(appIP, UdpDataPort_ModemToApp, txpl, PAYLOADLEN + 10 + 1); } else { // send to Codec decoder if (*(pl + 3) != 0) // minfo=0 ... just a filler, ignore toCodecDecoder(pl + 10, PAYLOADLEN); } fnd = 0; trigger_resetmodem = 0; rx_in_sync = 1; } else { // no frame found // if longer ws seconds nothing found, reset liquid RX modem // comes here with symbol rate, i.e. 4000 S/s int ws = 5; int wt = sr[speedmode].audio / sr[speedmode].tx; if (++fnd >= (wt * ws) || trigger_resetmodem) { fnd = 0; trigger_resetmodem = 0; rx_in_sync = 0; printf("no signal detected %d, reset RX modem\n", wt); resetModem(); } else if (fnd >= wt) { rx_in_sync = 0; } } }