/* ------------------------------------------------------------------ * Copyright (C) 2011 Martin Storsjo * Copyright (C) 2013,2014 Matthias P. Braendli * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * See the License for the specific language governing permissions * and limitations under the License. * ------------------------------------------------------------------- */ #include "config.h" #include "AlsaInput.h" #include "FileInput.h" #include "JackInput.h" #include "SampleQueue.h" #include "zmq.hpp" extern "C" { #include "encryption.h" #include "utils.h" #include "wavreader.h" } #include #include #include #include #include #include #include #include #include #include "libAACenc/include/aacenc_lib.h" extern "C" { #include } using namespace std; void usage(const char* name) { fprintf(stderr, "dabplus-enc %s is a HE-AACv2 encoder for DAB+\n" "based on fdk-aac-dabplus that can read from" #if HAVE_JACK " JACK, ALSA or a file source\n" #else " a ALSA or file source\n" #endif "and encode to a ZeroMQ output for ODR-DabMux.\n" "\n" "The -D option enables experimental sound card clock drift compensation.\n" "A consumer sound card has a clock that is always a bit imprecise, and\n" "would drift off after some time. ODR-DabMux cannot handle such drift\n" "because it would have to throw away or insert a full DAB+ superframe,\n" "which would create audible artifacts. This drift compensation can\n" "make sure that the encoding rate is correct by inserting or deleting\n" "audio samples.\n" "\n" "When this option is enabled, you will see U and O printed in the\n" "console. These correspond to audio underruns and overruns caused\n" "by sound card clock drift. When sparse, they should not create audible\n" "artifacts.\n" "\n" "This encoder includes PAD (DLS and MOT Slideshow) support by\n" "http://rd.csp.it to be used with mot-encoder\n" "\nUsage:\n" "%s (-i file|-d alsa_device) [OPTION...]\n", #if defined(GITVERSION) GITVERSION #else PACKAGE_VERSION #endif , name); fprintf(stderr, " For the alsa input:\n" " -d, --device=alsa_device Set ALSA input device (default: \"default\").\n" " -D, --drift-comp Enable ALSA sound card drift compensation.\n" " For the file input:\n" " -i, --input=FILENAME Input filename (default: stdin).\n" " -f, --format={ wav, raw } Set input file format (default: wav).\n" " --fifo-silence Input file is fifo and encoder generates silence when fifo is empty. Ignore EOF.\n" #if HAVE_JACK " For the JACK input:\n" " -j, --jack=name Enable JACK input, and define our name\n" #endif " Encoder parameters:\n" " -b, --bitrate={ 8, 16, ..., 192 } Output bitrate in kbps. Must be a multiple of 8.\n" " -A, --no-afterburner Disable AAC encoder quality increaser.\n" " -c, --channels={ 1, 2 } Nb of input channels (default: 2).\n" " -r, --rate={ 32000, 48000 } Input sample rate (default: 48000).\n" " --aaclc Force the usage of AAC-LC (no SBR, no PS)\n" " --sbr Force the usage of SBR\n" " --ps Force the usage of PS\n" " Output and pad parameters:\n" " -o, --output=URI Output zmq uri. (e.g. 'tcp://localhost:9000')\n" " -or- Output file uri. (e.g. 'file.dab')\n" " -or- a single dash '-' to denote stdout\n" " -k, --secret-key=FILE Enable ZMQ encryption with the given secret key.\n" " -p, --pad=BYTES Set PAD size in bytes.\n" " -P, --pad-fifo=FILENAME Set PAD data input fifo name" " (default: /tmp/pad.fifo).\n" " -l, --level Show peak audio level indication.\n" " -s, --silence=TIMEOUT Abort encoding after TIMEOUT seconds of silence.\n" "\n" "Only the tcp:// zeromq transport has been tested until now,\n" " but epgm:// and pgm:// are also accepted\n" ); } int prepare_aac_encoder( HANDLE_AACENCODER *encoder, int subchannel_index, int channels, int sample_rate, int afterburner, int *aot) { HANDLE_AACENCODER handle = *encoder; CHANNEL_MODE mode; switch (channels) { case 1: mode = MODE_1; break; case 2: mode = MODE_2; break; default: fprintf(stderr, "Unsupported channels number %d\n", channels); return 1; } if (aacEncOpen(&handle, 0x01|0x02|0x04, channels) != AACENC_OK) { fprintf(stderr, "Unable to open encoder\n"); return 1; } *encoder = handle; if (*aot == AOT_NONE) { if(channels == 2 && subchannel_index <= 6) { *aot = AOT_DABPLUS_PS; } else if((channels == 1 && subchannel_index <= 8) || subchannel_index <= 10) { *aot = AOT_DABPLUS_SBR; } else { *aot = AOT_DABPLUS_AAC_LC; } } fprintf(stderr, "Using %d subchannels. AAC type: %s%s%s. channels=%d, sample_rate=%d\n", subchannel_index, *aot == AOT_DABPLUS_PS ? "HE-AAC v2" : "", *aot == AOT_DABPLUS_SBR ? "HE-AAC" : "", *aot == AOT_DABPLUS_AAC_LC ? "AAC-LC" : "", channels, sample_rate); if (aacEncoder_SetParam(handle, AACENC_AOT, *aot) != AACENC_OK) { fprintf(stderr, "Unable to set the AOT\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_SAMPLERATE, sample_rate) != AACENC_OK) { fprintf(stderr, "Unable to set the sample rate\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_CHANNELMODE, mode) != AACENC_OK) { fprintf(stderr, "Unable to set the channel mode\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_CHANNELORDER, 1) != AACENC_OK) { fprintf(stderr, "Unable to set the wav channel order\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_GRANULE_LENGTH, 960) != AACENC_OK) { fprintf(stderr, "Unable to set the granule length\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_TRANSMUX, TT_DABPLUS) != AACENC_OK) { fprintf(stderr, "Unable to set the RAW transmux\n"); return 1; } /*if (aacEncoder_SetParam(handle, AACENC_BITRATEMODE, AACENC_BR_MODE_SFR) * != AACENC_OK) { fprintf(stderr, "Unable to set the bitrate mode\n"); return 1; }*/ fprintf(stderr, "AAC bitrate set to: %d\n", subchannel_index*8000); if (aacEncoder_SetParam(handle, AACENC_BITRATE, subchannel_index*8000) != AACENC_OK) { fprintf(stderr, "Unable to set the bitrate\n"); return 1; } if (aacEncoder_SetParam(handle, AACENC_AFTERBURNER, afterburner) != AACENC_OK) { fprintf(stderr, "Unable to set the afterburner mode\n"); return 1; } if (!afterburner) { fprintf(stderr, "Warning: Afterburned disabled!\n"); } if (aacEncEncode(handle, NULL, NULL, NULL, NULL) != AACENC_OK) { fprintf(stderr, "Unable to initialize the encoder\n"); return 1; } return 0; } #define no_argument 0 #define required_argument 1 #define optional_argument 2 #define STATUS_PAD_INSERTED 0x1 #define STATUS_OVERRUN 0x2 #define STATUS_UNDERRUN 0x4 int main(int argc, char *argv[]) { int subchannel_index = 8; //64kbps subchannel int ch=0; // For the ALSA input const char *alsa_device = NULL; // For the file input const char *infile = NULL; int raw_input = 0; // For the file output FILE *out_fh = NULL; const char *jack_name = NULL; const char *outuri = NULL; int sample_rate=48000, channels=2; const int bytes_per_sample = 2; void *rs_handler = NULL; bool afterburner = true; bool inFifoSilence = false; bool drift_compensation = false; AACENC_InfoStruct info = { 0 }; int aot = AOT_NONE; /* Keep track of peaks */ int peak_left = 0; int peak_right = 0; /* On silence, die after the silence_timeout expires */ bool die_on_silence = false; int silence_timeout = 0; int measured_silence_ms = 0; /* For MOT Slideshow and DLS insertion */ const char* pad_fifo = "/tmp/pad.fifo"; int pad_fd; unsigned char pad_buf[128]; int padlen = 0; /* Encoder status, see the above STATUS macros */ int status = 0; /* Whether to show the 'sox'-like measurement */ int show_level = 0; /* Data for ZMQ CURVE authentication */ char* keyfile = NULL; char secretkey[CURVE_KEYLEN+1]; const struct option longopts[] = { {"bitrate", required_argument, 0, 'b'}, {"channels", required_argument, 0, 'c'}, {"device", required_argument, 0, 'd'}, {"format", required_argument, 0, 'f'}, {"input", required_argument, 0, 'i'}, #if HAVE_JACK {"jack", required_argument, 0, 'j'}, #endif {"output", required_argument, 0, 'o'}, {"pad", required_argument, 0, 'p'}, {"pad-fifo", required_argument, 0, 'P'}, {"rate", required_argument, 0, 'r'}, {"silence", required_argument, 0, 's'}, {"secret-key", required_argument, 0, 'k'}, {"no-afterburner", no_argument, 0, 'A'}, {"afterburner", no_argument, 0, 'a'}, {"drift-comp", no_argument, 0, 'D'}, {"help", no_argument, 0, 'h'}, {"level", no_argument, 0, 'l'}, {"aaclc", no_argument, 0, 0 }, {"sbr", no_argument, 0, 1 }, {"ps", no_argument, 0, 2 }, {"fifo-silence", no_argument, 0, 3 }, {0, 0, 0, 0}, }; fprintf(stderr, "Welcome to %s %s, compiled at %s, %s", PACKAGE_NAME, #if defined(GITVERSION) GITVERSION, #else PACKAGE_VERSION, #endif __DATE__, __TIME__); fprintf(stderr, "\n"); fprintf(stderr, " http://opendigitalradio.org\n\n"); if (argc < 2) { usage(argv[0]); return 1; } int index; while(ch != -1) { ch = getopt_long(argc, argv, "aAhDlb:c:f:i:j:k:o:r:d:p:P:s:", longopts, &index); switch (ch) { case 0: // AAC-LC aot = AOT_DABPLUS_AAC_LC; break; case 1: // SBR aot = AOT_DABPLUS_SBR; break; case 2: // PS aot = AOT_DABPLUS_PS; break; case 3: // FIFO SILENCE inFifoSilence = true; break; case 'a': fprintf(stderr, "Warning, -a option does not exist anymore!\n"); break; case 'A': afterburner = false; break; case 'b': subchannel_index = atoi(optarg) / 8; break; case 'c': channels = atoi(optarg); break; case 'd': alsa_device = optarg; break; case 'D': drift_compensation = true; break; case 'f': if(strcmp(optarg, "raw")==0) { raw_input = 1; } else if(strcmp(optarg, "wav")!=0) usage(argv[0]); break; case 'i': infile = optarg; break; #if HAVE_JACK case 'j': jack_name = optarg; break; #endif case 'k': keyfile = optarg; break; case 'l': show_level = 1; break; case 'o': outuri = optarg; break; case 'p': padlen = atoi(optarg); break; case 'P': pad_fifo = optarg; break; case 'r': sample_rate = atoi(optarg); break; case 's': silence_timeout = atoi(optarg); if (silence_timeout > 0 && silence_timeout < 3600*24*30) { die_on_silence = true; } else { fprintf(stderr, "Invalid silence timeout (%d) given!\n"); return 1; } break; case '?': case 'h': usage(argv[0]); return 1; } } if (alsa_device && infile && jack_name) { fprintf(stderr, "You must define only one possible input, not several!\n"); return 1; } if (subchannel_index < 1 || subchannel_index > 24) { fprintf(stderr, "Bad subchannel index: %d, must be between 1 and 24. Try other bitrate.\n", subchannel_index); return 1; } if ( ! (sample_rate == 32000 || sample_rate == 48000)) { fprintf(stderr, "Invalid sample rate. Possible values are: 32000, 48000.\n"); return 1; } zmq::context_t zmq_ctx; zmq::socket_t zmq_sock(zmq_ctx, ZMQ_PUB); if (outuri) { if (strcmp(outuri, "-") == 0) { out_fh = stdout; } else if ((strncmp(outuri, "tcp://", 6) == 0) || (strncmp(outuri, "pgm://", 6) == 0) || (strncmp(outuri, "epgm://", 7) == 0)) { if (keyfile) { fprintf(stderr, "Enabling encryption\n"); int rc = readkey(keyfile, secretkey); if (rc) { fprintf(stderr, "Error reading secret key\n"); return 2; } const int yes = 1; zmq_sock.setsockopt(ZMQ_CURVE_SERVER, &yes, sizeof(yes)); zmq_sock.setsockopt(ZMQ_CURVE_SECRETKEY, secretkey, CURVE_KEYLEN); } zmq_sock.connect(outuri); } else { // We assume it's a file name out_fh = fopen(outuri, "wb"); if (!out_fh) { fprintf(stderr, "Can't open output file!\n"); return 1; } } } else { fprintf(stderr, "Output URI not defined\n"); return 1; } if (padlen != 0) { int flags; if (mkfifo(pad_fifo, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) != 0) { if (errno != EEXIST) { fprintf(stderr, "Can't create pad file: %d!\n", errno); return 1; } } pad_fd = open(pad_fifo, O_RDONLY | O_NONBLOCK); if (pad_fd == -1) { fprintf(stderr, "Can't open pad file!\n"); return 1; } flags = fcntl(pad_fd, F_GETFL, 0); if (fcntl(pad_fd, F_SETFL, flags | O_NONBLOCK)) { fprintf(stderr, "Can't set non-blocking mode in pad file!\n"); return 1; } } HANDLE_AACENCODER encoder; if (prepare_aac_encoder(&encoder, subchannel_index, channels, sample_rate, afterburner, &aot) != 0) { fprintf(stderr, "Encoder preparation failed\n"); return 1; } /* We assume that we need to call the encoder * enc_calls_per_output before it gives us one encoded audio * frame. This information is used when the alsa drift compensation * is active */ const int enc_calls_per_output = (aot == AOT_DABPLUS_AAC_LC) ? sample_rate / 8000 : sample_rate / 16000; if (aacEncInfo(encoder, &info) != AACENC_OK) { fprintf(stderr, "Unable to get the encoder info\n"); return 1; } // Each DAB+ frame will need input_size audio bytes const int input_size = channels * bytes_per_sample * info.frameLength; fprintf(stderr, "DAB+ Encoding: framelen=%d (%dB)\n", info.frameLength, input_size); uint8_t input_buf[input_size]; int max_size = 2*input_size + NUM_SAMPLES_PER_CALL; SampleQueue queue(BYTES_PER_SAMPLE, channels, max_size); /* symsize=8, gfpoly=0x11d, fcr=0, prim=1, nroots=10, pad=135 */ rs_handler = init_rs_char(8, 0x11d, 0, 1, 10, 135); if (rs_handler == NULL) { perror("init_rs_char failed"); return 1; } /* No input defined ? default to alsa "default" */ if (!alsa_device) { alsa_device = "default"; } // We'll use one of the tree possible inputs AlsaInputThreaded alsa_in_threaded(alsa_device, channels, sample_rate, queue); AlsaInputDirect alsa_in_direct(alsa_device, channels, sample_rate); FileInput file_in(infile, raw_input, sample_rate); #if HAVE_JACK JackInput jack_in(jack_name, channels, sample_rate, queue); #endif if (infile) { if (file_in.prepare() != 0) { fprintf(stderr, "File input preparation failed\n"); return 1; } } #if HAVE_JACK else if (jack_name) { if (jack_in.prepare() != 0) { fprintf(stderr, "JACK preparation failed\n"); return 1; } } #endif else if (drift_compensation) { if (alsa_in_threaded.prepare() != 0) { fprintf(stderr, "Alsa preparation failed\n"); return 1; } fprintf(stderr, "Start ALSA capture thread\n"); alsa_in_threaded.start(); } else { if (alsa_in_direct.prepare() != 0) { fprintf(stderr, "Alsa preparation failed\n"); return 1; } } int outbuf_size = subchannel_index*120; uint8_t zmqframebuf[ZMQ_HEADER_SIZE + 24*120]; zmq_frame_header_t *zmq_frame_header = (zmq_frame_header_t*)zmqframebuf; uint8_t outbuf[24*120]; if(outbuf_size % 5 != 0) { fprintf(stderr, "(outbuf_size mod 5) = %d\n", outbuf_size % 5); } fprintf(stderr, "Starting encoding\n"); int retval = 0; int send_error_count = 0; struct timespec tp_next; clock_gettime(CLOCK_MONOTONIC, &tp_next); int calls = 0; // for checking while (1) { int in_identifier[] = {IN_AUDIO_DATA, IN_ANCILLRY_DATA}; int out_identifier = OUT_BITSTREAM_DATA; AACENC_BufDesc in_buf = { 0 }, out_buf = { 0 }; AACENC_InArgs in_args = { 0 }; AACENC_OutArgs out_args = { 0 }; void *in_ptr[2], *out_ptr; int in_size[2], in_elem_size[2]; int out_size, out_elem_size; // -------------- wait the right amount of time if (drift_compensation || jack_name) { struct timespec tp_now; clock_gettime(CLOCK_MONOTONIC, &tp_now); unsigned long time_now = (1000000000ul * tp_now.tv_sec) + tp_now.tv_nsec; unsigned long time_next = (1000000000ul * tp_next.tv_sec) + tp_next.tv_nsec; const unsigned long dabplus_superframe_nsec = 120000000ul; const unsigned long wait_time = dabplus_superframe_nsec / enc_calls_per_output; unsigned long waiting = wait_time - (time_now - time_next); if ((time_now - time_next) < wait_time) { //printf("Sleep %zuus\n", waiting / 1000); usleep(waiting / 1000); } // Move our time_counter into the future, for // the next frame. tp_next.tv_nsec += wait_time; if (tp_next.tv_nsec > 1000000000L) { tp_next.tv_nsec -= 1000000000L; tp_next.tv_sec += 1; } } // --------------- Read data from the PAD fifo int ret; if (padlen != 0) { ret = read(pad_fd, pad_buf, padlen); } else { ret = 0; } if(ret < 0 && errno == EAGAIN) { // If this condition passes, there is no data to be read in_buf.numBufs = 1; // Samples; } else if(ret >= 0) { // Otherwise, you're good to go and buffer should contain "count" bytes. in_buf.numBufs = 2; // Samples + Data; if (ret > 0) status |= STATUS_PAD_INSERTED; } else { // Some other error occurred during read. fprintf(stderr, "Unable to read from PAD!\n"); break; } // -------------- Read Data memset(outbuf, 0x00, outbuf_size); memset(input_buf, 0x00, input_size); ssize_t read; if (infile) { read = file_in.read(input_buf, input_size); if (read < 0) { break; } else if (read != input_size) { if (inFifoSilence && file_in.eof()) { memset(input_buf, 0, input_size); read = input_size; usleep((long)input_size * 1000000 / (bytes_per_sample * channels * sample_rate)); } else { fprintf(stderr, "Short file read !\n"); break; } } } else if (drift_compensation || jack_name) { if (drift_compensation && alsa_in_threaded.fault_detected()) { fprintf(stderr, "Detected fault in alsa input!\n"); retval = 5; break; } size_t overruns; read = queue.pop(input_buf, input_size, &overruns); // returns bytes if (read != input_size) { status |= STATUS_UNDERRUN; } if (overruns) { status |= STATUS_OVERRUN; } } else { read = alsa_in_direct.read(input_buf, input_size); if (read < 0) { break; } else if (read != input_size) { fprintf(stderr, "Short alsa read !\n"); } } for (int i = 0; i < read; i+=4) { int16_t l = input_buf[i] | (input_buf[i+1] << 8); int16_t r = input_buf[i+2] | (input_buf[i+3] << 8); peak_left = MAX(peak_left, l); peak_right = MAX(peak_right, r); } /* Silence detection */ if (die_on_silence && MAX(peak_left, peak_right) == 0) { const unsigned int dabplus_superframe_msec = 120ul; const unsigned int frame_time_msec = dabplus_superframe_msec / enc_calls_per_output; measured_silence_ms += frame_time_msec; if (measured_silence_ms > 1000*silence_timeout) { fprintf(stderr, "Silence detected for %d seconds, aborting.\n", silence_timeout); retval = 2; break; } } else { measured_silence_ms = 0; } // -------------- AAC Encoding int calculated_padlen = ret > 0 ? padlen : 0; in_ptr[0] = input_buf; in_ptr[1] = pad_buf; in_size[0] = read; in_size[1] = calculated_padlen; in_elem_size[0] = BYTES_PER_SAMPLE; in_elem_size[1] = sizeof(uint8_t); in_args.numInSamples = input_size/BYTES_PER_SAMPLE; in_args.numAncBytes = calculated_padlen; in_buf.bufs = (void**)&in_ptr; in_buf.bufferIdentifiers = in_identifier; in_buf.bufSizes = in_size; in_buf.bufElSizes = in_elem_size; out_ptr = outbuf; out_size = sizeof(outbuf); out_elem_size = 1; out_buf.numBufs = 1; out_buf.bufs = &out_ptr; out_buf.bufferIdentifiers = &out_identifier; out_buf.bufSizes = &out_size; out_buf.bufElSizes = &out_elem_size; AACENC_ERROR err; if ((err = aacEncEncode(encoder, &in_buf, &out_buf, &in_args, &out_args)) != AACENC_OK) { if (err == AACENC_ENCODE_EOF) { fprintf(stderr, "encoder error: EOF reached\n"); break; } fprintf(stderr, "Encoding failed (%d)\n", err); retval = 3; break; } calls++; /* Check if the encoder has generated output data */ if (out_args.numOutBytes != 0) { // Our timing code depends on this if (calls != enc_calls_per_output) { fprintf(stderr, "INTERNAL ERROR! calls=%d" ", expected %d\n", calls, enc_calls_per_output); } calls = 0; // ----------- RS encoding int row, col; unsigned char buf_to_rs_enc[110]; unsigned char rs_enc[10]; for(row=0; row < subchannel_index; row++) { for(col=0;col < 110; col++) { buf_to_rs_enc[col] = outbuf[subchannel_index * col + row]; } encode_rs_char(rs_handler, buf_to_rs_enc, rs_enc); for(col=110; col<120; col++) { outbuf[subchannel_index * col + row] = rs_enc[col-110]; assert(subchannel_index * col + row < outbuf_size); } } if (out_fh) { fwrite(outbuf, 1, outbuf_size, out_fh); } else { // ------------ ZeroMQ transmit try { zmq_frame_header->version = 1; zmq_frame_header->encoder = ZMQ_ENCODER_FDK; zmq_frame_header->datasize = outbuf_size; zmq_frame_header->audiolevel_left = peak_left; zmq_frame_header->audiolevel_right = peak_right; assert(ZMQ_FRAME_SIZE(zmq_frame_header) <= NUMOF(zmqframebuf)); memcpy(ZMQ_FRAME_DATA(zmq_frame_header), outbuf, outbuf_size); zmq_sock.send(zmqframebuf, ZMQ_FRAME_SIZE(zmq_frame_header), ZMQ_DONTWAIT); } catch (zmq::error_t& e) { fprintf(stderr, "ZeroMQ send error !\n"); send_error_count ++; } if (send_error_count > 10) { fprintf(stderr, "ZeroMQ send failed ten times, aborting!\n"); retval = 4; break; } } if (out_args.numOutBytes + row*10 == outbuf_size) { if (show_level) { fprintf(stderr, "\rIn: [%6s|%-6s] %1s %1s %1s", level(0, &peak_left), level(1, &peak_right), status & STATUS_PAD_INSERTED ? "P" : " ", status & STATUS_UNDERRUN ? "U" : " ", status & STATUS_OVERRUN ? "O" : " "); } else { if (status & STATUS_OVERRUN) { fprintf(stderr, "O"); } if (status & STATUS_UNDERRUN) { fprintf(stderr, "U"); } } } status = 0; } fflush(stdout); } fprintf(stderr, "\n"); if (out_fh) { fclose(out_fh); } zmq_sock.close(); free_rs_char(rs_handler); aacEncClose(&encoder); return retval; }