aboutsummaryrefslogtreecommitdiff
/*---------------------------------------------------------------------------*\

  FILE........: freedv_data_raw_tx.c
  AUTHOR......: David Rowe
  DATE CREATED: May 2020

  Demonstrates transmitting frames of raw data bytes (instead of
  compressed speech) using the FreeDV API.

\*---------------------------------------------------------------------------*/

/*
  Copyright (C) 2020 David Rowe

  All rights reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License version 2.1, as
  published by the Free Software Foundation.  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 Lesser General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "freedv_api.h"
#include "fsk.h"
#include "ldpc_codes.h"
#include "ofdm_internal.h"

size_t send_preamble(struct freedv *freedv, FILE *fout, int use_complex,
                     size_t n_mod_out);
size_t send_modulated_data(struct freedv *freedv, FILE *fout, int use_complex,
                           size_t n_mod_out, uint8_t bytes_in[]);
size_t send_postamble(struct freedv *freedv, FILE *fout, int use_complex,
                      size_t n_mod_out);
size_t send_silence(FILE *fout, size_t shorts_per_sample, size_t samples_delay);
void comp_to_short(short mod_out_short[], COMP mod_out_comp[], int n_mod_out);

int main(int argc, char *argv[]) {
  FILE *fin, *fout;
  char codename[80] = "H_256_512_4";
  struct freedv_advanced adv = {0, 2, 100, 8000, 1000, 200, codename};
  struct freedv *freedv;
  int mode;
  int use_clip, use_txbpf, testframes, Ntestframes = 0;
  int use_complex = 0;
  float amp = FSK_SCALE;
  size_t shorts_per_sample = 1;
  int Nbursts = 1, sequence_numbers = 0;
  int inter_burst_delay_ms = 0;
  int postdelay_ms = 0;
  uint8_t source_byte = 0;

  use_clip = -1;
  use_txbpf = -1;
  testframes = 0;
  int framesperburst = 1;
  int quiet = 0;

  int o = 0;
  int opt_idx = 0;
  while (o != -1) {
    static struct option long_opts[] = {
        {"testframes", required_argument, 0, 't'},
        {"help", no_argument, 0, 'h'},
        {"txbpf", required_argument, 0, 'b'},
        {"clip", required_argument, 0, 'l'},
        {"Fs", required_argument, 0, 'f'},
        {"Rs", required_argument, 0, 'r'},
        {"tone1", required_argument, 0, '1'},
        {"shift", required_argument, 0, 's'},
        {"bursts", required_argument, 0, 'e'},
        {"framesperburst", required_argument, 0, 'g'},
        {"delay", required_argument, 0, 'j'},
        {"postdelay", required_argument, 0, 'k'},
        {"seq", no_argument, 0, 'd'},
        {"source", required_argument, 0, 'i'},
        {"amp", required_argument, 0, 'a'},
        {"quiet", no_argument, 0, 'q'},
        {"complexout", no_argument, 0, 'c'},
        {"code", required_argument, 0, 'o'},
        {"listcodes", no_argument, 0, 'x'},
        {0, 0, 0, 0}};

    o = getopt_long(argc, argv, "a:cdt:hb:l:e:f:g:r:1:s:m:qi:o:x", long_opts,
                    &opt_idx);

    switch (o) {
      case 'a':
        amp = atof(optarg) / 2.0;
        break;
      case 'b':
        use_txbpf = atoi(optarg);
        break;
      case 'c':
        use_complex = 1;
        shorts_per_sample = 2;
        break;
      case 'd':
        sequence_numbers = 1;
        break;
      case 'i':
        source_byte = strtol(optarg, NULL, 0);
        fprintf(stderr, "source byte: 0x%02x\n", source_byte);
        break;
      case 'e':
        Nbursts = atoi(optarg);
        break;
      case 'g':
        framesperburst = atoi(optarg);
        break;
      case 'j':
        inter_burst_delay_ms = atoi(optarg);
        break;
      case 'k':
        postdelay_ms = atoi(optarg);
        break;
      case 't':
        testframes = 1;
        Ntestframes = atoi(optarg);
        break;
      case 'l':
        use_clip = atoi(optarg);
        break;
      case 'm':
        adv.M = atoi(optarg);
        break;
      case 'q':
        quiet = 1;
        break;
      case 'f':
        adv.Fs = atoi(optarg);
        break;
      case 'r':
        adv.Rs = atoi(optarg);
        break;
      case '1':
        adv.first_tone = atoi(optarg);
        break;
      case 's':
        adv.tone_spacing = atoi(optarg);
        break;
      case 'o':
        if (ldpc_codes_find(optarg) == -1) {
          fprintf(stderr, "%s not found, try --listcodes\n", optarg);
          exit(1);
        }
        strcpy(codename, optarg);
        break;
      case 'x':
        ldpc_codes_list();
        exit(0);
        break;
      case 'h':
      case '?':
        goto helpmsg;
        break;
    }
  }
  int dx = optind;

  if (argc < 4) {
  helpmsg:
    fprintf(
        stderr,
        "\nusage: %s [options] FSK_LDPC|DATAC0|... InputBinaryDataFile "
        "OutputModemRawFile\n"
        "\n"
        "  --testframes      T         send a total of T test frames (T should "
        "equal B*N)\n"
        "  --bursts          B         send B bursts of N testframes (default "
        "1)\n"
        "  --framesperburst  N         burst mode, N frames per burst (default "
        "1)\n"
        "  --delay           ms        testframe inter-burst delay in ms\n"
        "  --postdelay       ms        additional delay at end of run in ms\n"
        "  -c                          complex signed 16 bit output format "
        "(default real)\n"
        "  --clip            0|1       clipping for reduced PAPR\n"
        "  --txbpf           0|1       bandpass filter\n"
        "  --seq                       send packet sequence numbers (breaks "
        "testframe BER counting)\n"
        "  --source          Byte      insert a (non-zero) source address att "
        "byte[0]\n"
        "  --complexout                complex sample output (default real)\n"
        "  --quiet\n"
        "\n"
        "For FSK_LDPC only:\n\n"
        "  -a           amp      maximum amplitude of FSK signal\n"
        "  -m           2|4      number of FSK tones\n"
        "  --Fs         FreqHz   sample rate (default 8000)\n"
        "  --Rs         FreqHz   symbol rate (default 100)\n"
        "  --tone1      FreqHz   freq of first tone (default 1000)\n"
        "  --shift      FreqHz   shift between tones (default 200)\n\n"
        "  --code       CodeName LDPC code (defaults (512,256)\n"
        "  --listcodes           list available LDPC codes\n\n",
        argv[0]);
    fprintf(stderr,
            "example: $ %s --testframes 6 --bursts 3 --framesperburst 2 datac0 "
            "/dev/zero samples.s16\n",
            argv[0]);
    fprintf(
        stderr,
        "example: $ %s  -c --testframes 10 FSK_LDPC /dev/zero samples.iq16\n\n",
        argv[0]);
    exit(1);
  }

  if ((argc - dx) < 3) {
    fprintf(stderr, "too few arguments.\n");
    goto helpmsg;
  }

  mode = -1;
  if (!strcmp(argv[dx], "FSK_LDPC") || !strcmp(argv[dx], "fsk_ldpc"))
    mode = FREEDV_MODE_FSK_LDPC;
  if (!strcmp(argv[dx], "DATAC0") || !strcmp(argv[dx], "datac0"))
    mode = FREEDV_MODE_DATAC0;
  if (!strcmp(argv[dx], "DATAC1") || !strcmp(argv[dx], "datac1"))
    mode = FREEDV_MODE_DATAC1;
  if (!strcmp(argv[dx], "DATAC3") || !strcmp(argv[dx], "datac3"))
    mode = FREEDV_MODE_DATAC3;
  if (!strcmp(argv[dx], "DATAC4") || !strcmp(argv[dx], "datac4"))
    mode = FREEDV_MODE_DATAC4;
  if (!strcmp(argv[dx], "DATAC13") || !strcmp(argv[dx], "datac13"))
    mode = FREEDV_MODE_DATAC13;
  if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14"))
    mode = FREEDV_MODE_DATAC14;
  if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom"))
    mode = FREEDV_MODE_DATA_CUSTOM;
  if (mode == -1) {
    fprintf(stderr, "Error: in mode: %s", argv[dx]);
    exit(1);
  }

  if (strcmp(argv[dx + 1], "-") == 0)
    fin = stdin;
  else if ((fin = fopen(argv[dx + 1], "rb")) == NULL) {
    fprintf(stderr, "Error opening input file of bytes: %s: %s.\n",
            argv[dx + 1], strerror(errno));
    exit(1);
  }

  if (strcmp(argv[dx + 2], "-") == 0)
    fout = stdout;
  else if ((fout = fopen(argv[dx + 2], "wb")) == NULL) {
    fprintf(stderr, "Error opening output modem sample file: %s: %s.\n",
            argv[dx + 2], strerror(errno));
    exit(1);
  }

  if (mode == FREEDV_MODE_FSK_LDPC)
    freedv = freedv_open_advanced(mode, &adv);
  else if (mode == FREEDV_MODE_DATA_CUSTOM) {
    // demonstrate custom OFDM raw data modes
    struct OFDM_CONFIG ofdm_config;
    ofdm_init_mode("datac14", &ofdm_config);
    // modify datac14 to have 3 carriers instead of 4, which means
    // we have to tweak Np, and the number of unique word bits
    ofdm_config.nc = 3;
    ofdm_config.np = 6;
    ofdm_config.nuwbits = 48;
    ofdm_config.bad_uw_errors = 18;
    uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1,
                    0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0};
    memcpy(ofdm_config.tx_uw, uw, sizeof(uw));
    memcpy(&ofdm_config.tx_uw[ofdm_config.nuwbits - sizeof(uw)], uw,
           sizeof(uw));
    /* set up a trivial Tx band pass filter as a demo */
    static float tx_bpf[] = {1.0, 1.0, 1.0};
    ofdm_config.tx_bpf_proto = tx_bpf;
    ofdm_config.tx_bpf_proto_n = 3;
    adv.config = (void *)&ofdm_config;
    freedv = freedv_open_advanced(mode, &adv);
    freedv_ofdm_print_info(freedv);
  } else {
    freedv = freedv_open(mode);
  }

  assert(freedv != NULL);

  /* these are optional ------------------ */
  if (use_clip != -1) freedv_set_clip(freedv, use_clip);
  if (use_txbpf != -1) freedv_set_tx_bpf(freedv, use_txbpf);
  freedv_set_tx_amp(freedv, amp);

  /* Data modes have a multiple of 8 payload bits/frame */
  int bytes_per_modem_frame = freedv_get_bits_per_modem_frame(freedv) / 8;
  int payload_bytes_per_modem_frame = bytes_per_modem_frame;
  payload_bytes_per_modem_frame -= 2; /* 16 bits used for the CRC */
  if (!quiet)
    fprintf(stderr, "payload bytes_per_modem_frame: %d ",
            payload_bytes_per_modem_frame);
  assert((freedv_get_bits_per_modem_frame(freedv) % 8) == 0);
  int n_mod_out = freedv_get_n_tx_modem_samples(freedv);
  uint8_t bytes_in[bytes_per_modem_frame];

  if (mode == FREEDV_MODE_FSK_LDPC) {
    if (!quiet)
      fprintf(stderr,
              "Frequency: Fs: %4.1f Hz Rs: %5.0f Hz Tone1: %5.0f Hz Shift: "
              "%5.0f Hz M: %d \n",
              (float)adv.Fs, (float)adv.Rs, (float)adv.first_tone,
              (float)adv.tone_spacing, adv.M);

    if (adv.tone_spacing < adv.Rs) {
      fprintf(stderr, "Need shift: %d > Rs: %d\n", adv.tone_spacing, adv.Rs);
      exit(1);
    }
  }

  /* a few sanity checks */
  if (testframes) {
    if (Ntestframes != framesperburst * Nbursts) {
      if (!quiet)
        fprintf(stderr,
                "Adjusting testframes to equal framesperburst*bursts\n");
      Ntestframes = framesperburst * Nbursts;
    }
  } else {
    if (framesperburst != 1) {
      fprintf(stderr,
              "Only one frame per burst currently supported in stdin mode\n");
      exit(1);
    }
  }

  int frames = 0;
  size_t on_samples = 0;
  size_t off_samples = 0;

  /* initial silence */

  int samples_delay = 0;
  if (inter_burst_delay_ms) {
    /* user defined inter-burst delay */
    samples_delay = FREEDV_FS_8000 * inter_burst_delay_ms / 1000;
  } else {
    /* just enough silence at the end of burst to allow demod to complete
     * processing */
    samples_delay = 2 * freedv_get_n_nom_modem_samples(freedv);
  }
  off_samples += send_silence(fout, shorts_per_sample, samples_delay);

  /* --------- Test Frame Mode
   * --------------------------------------------------*/

  if (testframes) {
    /* generate a fixed test frame */
    uint8_t testframe_bytes[bytes_per_modem_frame];
    memset(testframe_bytes, 0, bytes_per_modem_frame);
    int bits_per_frame = freedv_get_bits_per_modem_frame(freedv);
    uint8_t testframe_bits[bits_per_frame];
    ofdm_generate_payload_data_bits(testframe_bits, bits_per_frame);
    freedv_pack(testframe_bytes, testframe_bits, bits_per_frame);
    if (!quiet) fprintf(stderr, "\n");

    for (int b = 0; b < Nbursts; b++) {
      on_samples += send_preamble(freedv, fout, use_complex, n_mod_out);

      for (int fpb = 0; fpb < framesperburst; fpb++) {
        memcpy(bytes_in, testframe_bytes, bytes_per_modem_frame);
        if (source_byte) bytes_in[0] = source_byte;
        if (sequence_numbers) bytes_in[1] = (frames + 1) & 0xff;

        /* The raw data modes requires a CRC in the last two bytes */
        uint16_t crc16 =
            freedv_gen_crc16(bytes_in, payload_bytes_per_modem_frame);
        bytes_in[bytes_per_modem_frame - 2] = crc16 >> 8;
        bytes_in[bytes_per_modem_frame - 1] = crc16 & 0xff;

        on_samples +=
            send_modulated_data(freedv, fout, use_complex, n_mod_out, bytes_in);

        /* if using pipes we don't want the usual buffering to occur */
        if (fout == stdout) fflush(stdout);

        frames++;
      }

      on_samples += send_postamble(freedv, fout, use_complex, n_mod_out);

      int samples_delay = 0;
      if (inter_burst_delay_ms) {
        /* user defined inter-burst delay */
        samples_delay = FREEDV_FS_8000 * inter_burst_delay_ms / 1000;
      } else {
        /* just enough silence at the end of burst to allow demod to complete
         * processing */
        samples_delay = 2 * freedv_get_n_nom_modem_samples(freedv);
      }
      off_samples += send_silence(fout, shorts_per_sample, samples_delay);
    }

  } else {
    /* --------- modulate data from stdin mode
     * --------------------------------------------------*/

    while (fread(bytes_in, sizeof(uint8_t), payload_bytes_per_modem_frame,
                 fin) == payload_bytes_per_modem_frame) {
      on_samples += send_preamble(freedv, fout, use_complex, n_mod_out);

      if (source_byte) bytes_in[0] = source_byte;
      if (sequence_numbers) bytes_in[1] = (frames + 1) & 0xff;

      /* The raw data modes requires a CRC in the last two bytes */
      uint16_t crc16 =
          freedv_gen_crc16(bytes_in, payload_bytes_per_modem_frame);
      bytes_in[bytes_per_modem_frame - 2] = crc16 >> 8;
      bytes_in[bytes_per_modem_frame - 1] = crc16 & 0xff;

      on_samples +=
          send_modulated_data(freedv, fout, use_complex, n_mod_out, bytes_in);

      /* if using pipes we don't want the usual buffering to occur */
      if (fout == stdout) fflush(stdout);

      on_samples += send_postamble(freedv, fout, use_complex, n_mod_out);

      int samples_delay = 0;
      if (inter_burst_delay_ms) {
        /* user defined inter-burst delay */
        samples_delay = FREEDV_FS_8000 * inter_burst_delay_ms / 1000;
      } else {
        /* just enough silence at the end of burst to allow demod to complete
         * processing */
        samples_delay = 2 * freedv_get_n_nom_modem_samples(freedv);
      }
      off_samples += send_silence(fout, shorts_per_sample, samples_delay);
      frames++;
    }
  }

  /* optional silence at the end of run */
  if (postdelay_ms) {
    int samples_delay = FREEDV_FS_8000 * postdelay_ms / 1000;
    if (!quiet)
      fprintf(stderr, "postdelay: %d %d\n", postdelay_ms, samples_delay);
    off_samples += send_silence(fout, shorts_per_sample, samples_delay);
  }

  /* SNR offset to use in channel simulator to account for on/off time of burst
   * signal */
  float mark_space_ratio = (float)on_samples / (on_samples + off_samples);
  float mark_space_SNR_offset = 10 * log10(mark_space_ratio);
  if (!quiet)
    fprintf(stderr, "mark:space: %3.2f SNR offset: %5.2f\n", mark_space_ratio,
            mark_space_SNR_offset);

  freedv_close(freedv);
  fclose(fin);
  fclose(fout);

  return 0;
}

size_t send_preamble(struct freedv *freedv, FILE *fout, int use_complex,
                     size_t n_mod_out) {
  short mod_out_short[2 * n_mod_out];
  int shorts_per_sample = 1;
  int n_preamble = 0;

  if (use_complex == 0) {
    n_preamble = freedv_rawdatapreambletx(freedv, mod_out_short);
  } else {
    COMP mod_out_comp[n_mod_out];
    n_preamble = freedv_rawdatapreamblecomptx(freedv, mod_out_comp);
    comp_to_short(mod_out_short, mod_out_comp, n_preamble);
    shorts_per_sample = 2;
  }
  assert(n_preamble == freedv_get_n_tx_preamble_modem_samples(freedv));
  assert(n_preamble <= n_mod_out);
  fwrite(mod_out_short, sizeof(short), shorts_per_sample * n_preamble, fout);
  return n_preamble;
}

size_t send_modulated_data(struct freedv *freedv, FILE *fout, int use_complex,
                           size_t n_mod_out, uint8_t bytes_in[]) {
  short mod_out_short[2 * n_mod_out];
  int shorts_per_sample = 1;

  if (use_complex == 0) {
    freedv_rawdatatx(freedv, mod_out_short, bytes_in);
  } else {
    COMP mod_out_comp[n_mod_out];
    freedv_rawdatacomptx(freedv, mod_out_comp, bytes_in);
    comp_to_short(mod_out_short, mod_out_comp, n_mod_out);
    shorts_per_sample = 2;
  }
  fwrite(mod_out_short, sizeof(short), shorts_per_sample * n_mod_out, fout);
  return n_mod_out;
}

size_t send_postamble(struct freedv *freedv, FILE *fout, int use_complex,
                      size_t n_mod_out) {
  short mod_out_short[2 * n_mod_out];
  int shorts_per_sample = 1;
  int n_postamble = 0;

  if (use_complex == 0) {
    n_postamble = freedv_rawdatapostambletx(freedv, mod_out_short);
  } else {
    COMP mod_out_comp[n_mod_out];
    n_postamble = freedv_rawdatapostamblecomptx(freedv, mod_out_comp);
    comp_to_short(mod_out_short, mod_out_comp, n_postamble);
    shorts_per_sample = 2;
  }
  assert(n_postamble == freedv_get_n_tx_postamble_modem_samples(freedv));
  assert(n_postamble <= n_mod_out);
  fwrite(mod_out_short, sizeof(short), shorts_per_sample * n_postamble, fout);
  return n_postamble;
}

size_t send_silence(FILE *fout, size_t shorts_per_sample,
                    size_t samples_delay) {
  size_t n = shorts_per_sample * samples_delay;
  short sil_short[n];
  for (int i = 0; i < n; i++) sil_short[i] = 0;
  fwrite(sil_short, sizeof(short), n, fout);
  return samples_delay;
}

void comp_to_short(short mod_out_short[], COMP mod_out_comp[], int n_mod_out) {
  for (int i = 0; i < n_mod_out; i++) {
    mod_out_short[2 * i] = (short)(mod_out_comp[i].real);
    mod_out_short[2 * i + 1] = (short)(mod_out_comp[i].imag);
  }
}