diff options
Diffstat (limited to 'src/dabplus-enc.cpp')
-rw-r--r-- | src/dabplus-enc.cpp | 423 |
1 files changed, 300 insertions, 123 deletions
diff --git a/src/dabplus-enc.cpp b/src/dabplus-enc.cpp index 0f34d65..5c2f1cb 100644 --- a/src/dabplus-enc.cpp +++ b/src/dabplus-enc.cpp @@ -24,6 +24,7 @@ #include "VLCInput.h" #include "SampleQueue.h" #include "zmq.hpp" +#include "common.h" extern "C" { #include "encryption.h" @@ -41,21 +42,33 @@ extern "C" { #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> +#include <fcntl.h> #include "libAACenc/include/aacenc_lib.h" extern "C" { #include <fec.h> +#include "libtoolame-dab/toolame.h" } + + +// Enumerate which encoder we can use +enum class encoder_selection_t { + fdk_dabplus, + toolame_dab +}; + 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" - "JACK, ALSA or a file source\n" - "and encode to a ZeroMQ output for ODR-DabMux.\n" + "dabplus-enc %s is an audio encoder for both DAB and DAB+.\n" + "The DAB+ HE-AACv2 encoder is based on a Thirt-Party Modified\n" + "Version of the Fraunhofer FDK AAC Codec Library for Android,\n" + "and the DAB encoder is using the tooLAME MPEG\n" + "encoder sources. The encoder can read from JACK, ALSA or\n" + "a file source and encode to a ZeroMQ output for ODR-DabMux.\n" "(Experimental!)It can also use libvlc as an input.\n" "\n" "The -D option enables experimental sound card clock drift compensation.\n" @@ -74,7 +87,7 @@ void usage(const char* name) { "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", + "%s [INPUT SELECTION] [OPTION...]\n", #if defined(GITVERSION) GITVERSION #else @@ -83,7 +96,11 @@ void usage(const char* name) { , name); fprintf(stderr, " For the alsa input:\n" +#if HAVE_ALSA " -d, --device=alsa_device Set ALSA input device (default: \"default\").\n" +#else + " The Alsa input was disabled at compile time\n" +#endif " For the file input:\n" " -i, --input=FILENAME Input filename (default: stdin).\n" " -f, --format={ wav, raw } Set input file format (default: wav).\n" @@ -113,9 +130,16 @@ void usage(const char* name) { " -D, --drift-comp Enable ALSA/VLC sound card drift compensation.\n" " 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" + " DAB specific options\n" + " -a, --dab Encode in DAB and not in DAB+.\n" + " --dabmode=MODE Channel mode: s/d/j/m\n" + " (default: j if stereo, m if mono).\n" + " --dabpsy=PSY Psychoacoustic model 0/1/2/3\n" + " (default: 1).\n" + " DAB+ specific options\n" + " -A, --no-afterburner Disable AAC encoder quality increaser.\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" @@ -242,9 +266,11 @@ int prepare_aac_encoder( int main(int argc, char *argv[]) { - int subchannel_index = 8; //64kbps subchannel + int bitrate = 0; // 0 is default int ch=0; + encoder_selection_t selected_encoder = encoder_selection_t::fdk_dabplus; + // For the ALSA input const char *alsa_device = NULL; @@ -275,6 +301,9 @@ int main(int argc, char *argv[]) AACENC_InfoStruct info = { 0 }; int aot = AOT_NONE; + char dab_channel_mode = '\0'; + int dab_psy_model = 1; + /* Keep track of peaks */ int peak_left = 0; int peak_right = 0; @@ -302,6 +331,8 @@ int main(int argc, char *argv[]) const struct option longopts[] = { {"bitrate", required_argument, 0, 'b'}, {"channels", required_argument, 0, 'c'}, + {"dabmode", required_argument, 0, 4 }, + {"dabpsy", required_argument, 0, 5 }, {"device", required_argument, 0, 'd'}, {"format", required_argument, 0, 'f'}, {"input", required_argument, 0, 'i'}, @@ -318,7 +349,7 @@ int main(int argc, char *argv[]) {"vlc-opt", required_argument, 0, 'L'}, {"write-icy-text", required_argument, 0, 'w'}, {"aaclc", no_argument, 0, 0 }, - {"afterburner", no_argument, 0, 'a'}, + {"dab", no_argument, 0, 'a'}, {"drift-comp", no_argument, 0, 'D'}, {"fifo-silence", no_argument, 0, 3 }, {"help", no_argument, 0, 'h'}, @@ -362,16 +393,20 @@ int main(int argc, char *argv[]) aot = AOT_DABPLUS_PS; break; case 3: // FIFO SILENCE - inFifoSilence = true; + case 4: // DAB channel mode + dab_channel_mode = optarg[0]; + break; + case 5: // DAB psy model + dab_psy_model = atoi(optarg); break; case 'a': - fprintf(stderr, "Warning, -a option does not exist anymore!\n"); + selected_encoder = encoder_selection_t::toolame_dab; break; case 'A': afterburner = false; break; case 'b': - subchannel_index = atoi(optarg) / 8; + bitrate = atoi(optarg); break; case 'c': channels = atoi(optarg); @@ -471,15 +506,33 @@ int main(int argc, char *argv[]) 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 (selected_encoder == encoder_selection_t::fdk_dabplus) { + if (bitrate == 0) { + bitrate = 64; + } + + int subchannel_index = bitrate / 8; + + 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; + } } + else if (selected_encoder == encoder_selection_t::toolame_dab) { + if (bitrate == 0) { + bitrate = 192; + } - if ( ! (sample_rate == 32000 || sample_rate == 48000)) { - fprintf(stderr, "Invalid sample rate. Possible values are: 32000, 48000.\n"); - return 1; + if ( ! (sample_rate == 24000 || sample_rate == 48000)) { + fprintf(stderr, "Invalid sample rate. Possible values are: 24000, 48000.\n"); + return 1; + } } if (padlen < 0) { @@ -561,12 +614,71 @@ int main(int argc, char *argv[]) } + std::vector<uint8_t> input_buf; + HANDLE_AACENCODER encoder; - if (prepare_aac_encoder(&encoder, subchannel_index, channels, - sample_rate, afterburner, &aot) != 0) { - fprintf(stderr, "Encoder preparation failed\n"); - return 1; + if (selected_encoder == encoder_selection_t::fdk_dabplus) { + int subchannel_index = bitrate / 8; + if (prepare_aac_encoder(&encoder, subchannel_index, channels, + sample_rate, afterburner, &aot) != 0) { + fprintf(stderr, "Encoder preparation failed\n"); + return 1; + } + + 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); + + input_buf.resize(input_size); + } + else if (selected_encoder == encoder_selection_t::toolame_dab) { + int err = toolame_init(); + + if (err == 0) { + err = toolame_set_samplerate(sample_rate); + } + + if (err == 0) { + err = toolame_set_bitrate(bitrate); + } + + if (err == 0) { + err = toolame_set_psy_model(dab_psy_model); + } + + if (dab_channel_mode == '\0') { + if (channels == 2) { + dab_channel_mode = 'j'; // Default to joint-stereo + } + else if (channels == 1) { + dab_channel_mode = 'm'; // Default to mono + } + else { + fprintf(stderr, "Unsupported channels number %d\n", channels); + return 1; + } + } + + if (err == 0) { + err = toolame_set_channel_mode(dab_channel_mode); + } + + if (err) { + fprintf(stderr, "libtoolame-dab init failed: %d\n", err); + return err; + } + + // TODO int toolame_set_pad(int pad_len); + + input_buf.resize(2 * 1152 * BYTES_PER_SAMPLE); } /* We assume that we need to call the encoder @@ -574,24 +686,13 @@ int main(int argc, char *argv[]) * 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; - + int enc_calls_per_output = 1; // Valid for libtoolame-dab - if (aacEncInfo(encoder, &info) != AACENC_OK) { - fprintf(stderr, "Unable to get the encoder info\n"); - return 1; + if (selected_encoder == encoder_selection_t::fdk_dabplus) { + enc_calls_per_output = (aot == AOT_DABPLUS_AAC_LC) ? sample_rate / 8000 : sample_rate / 16000; } - // 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 = 8*input_size + NUM_SAMPLES_PER_CALL; + int max_size = 8*input_buf.size() + NUM_SAMPLES_PER_CALL; SampleQueue<uint8_t> queue(BYTES_PER_SAMPLE, channels, max_size); @@ -608,8 +709,10 @@ int main(int argc, char *argv[]) } // We'll use one of the tree possible inputs +#if HAVE_ALSA AlsaInputThreaded alsa_in_threaded(alsa_device, channels, sample_rate, queue); AlsaInputDirect alsa_in_direct(alsa_device, channels, sample_rate); +#endif FileInput file_in(infile, raw_input, sample_rate); #if HAVE_JACK JackInput jack_in(jack_name, channels, sample_rate, queue); @@ -652,6 +755,7 @@ int main(int argc, char *argv[]) } } #endif +#if HAVE_ALSA else if (drift_compensation) { if (alsa_in_threaded.prepare() != 0) { fprintf(stderr, "Alsa with drift compensation: preparation failed\n"); @@ -667,19 +771,37 @@ int main(int argc, char *argv[]) return 1; } } +#else + else { + fprintf(stderr, "No input defined\n"); + return 1; + } +#endif - 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]; + int outbuf_size; + std::vector<uint8_t> zmqframebuf; + std::vector<uint8_t> outbuf; + if (selected_encoder == encoder_selection_t::fdk_dabplus) { + outbuf_size = bitrate/8*120; + outbuf.resize(24*120); + zmqframebuf.resize(ZMQ_HEADER_SIZE + 24*120); - unsigned char pad_buf[padlen + 1]; + if(outbuf_size % 5 != 0) { + fprintf(stderr, "Warning: (outbuf_size mod 5) = %d\n", outbuf_size % 5); + } + } + else if (selected_encoder == encoder_selection_t::toolame_dab) { + outbuf_size = 4092; + outbuf.resize(outbuf_size); + zmqframebuf.resize(ZMQ_HEADER_SIZE + outbuf_size); + fprintf(stderr, "Setting outbuf size to %zu\n", outbuf.size()); - if(outbuf_size % 5 != 0) { - fprintf(stderr, "(outbuf_size mod 5) = %d\n", outbuf_size % 5); } + zmq_frame_header_t *zmq_frame_header = (zmq_frame_header_t*)&zmqframebuf[0]; + + unsigned char pad_buf[padlen + 1]; + fprintf(stderr, "Starting encoding\n"); int retval = 0; @@ -688,17 +810,9 @@ int main(int argc, char *argv[]) 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; - + ssize_t read_bytes = 0; + do { 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) { @@ -757,25 +871,24 @@ int main(int argc, char *argv[]) } // -------------- Read Data - memset(outbuf, 0x00, outbuf_size); - memset(input_buf, 0x00, input_size); + memset(&outbuf[0], 0x00, outbuf_size); + memset(&input_buf[0], 0x00, input_buf.size()); - ssize_t read; if (infile) { - read = file_in.read(input_buf, input_size); - if (read < 0) { + read_bytes = file_in.read(&input_buf[0], input_buf.size()); + if (read_bytes < 0) { break; } - else if (read != input_size) { + else if (read_bytes != input_buf.size()) { if (inFifoSilence && file_in.eof()) { - memset(input_buf, 0, input_size); - read = input_size; - usleep((long)input_size * 1000000 / + memset(&input_buf[0], 0, input_buf.size()); + read_bytes = input_buf.size(); + usleep((long)input_buf.size() * 1000000 / (BYTES_PER_SAMPLE * channels * sample_rate)); } else { fprintf(stderr, "Short file read !\n"); - break; + read_bytes = 0; } } } @@ -793,9 +906,9 @@ int main(int argc, char *argv[]) } size_t overruns; - read = queue.pop(input_buf, input_size, &overruns); // returns bytes + read_bytes = queue.pop(&input_buf[0], input_buf.size(), &overruns); // returns bytes - if (read != input_size) { + if (read_bytes != input_buf.size()) { status |= STATUS_UNDERRUN; } @@ -806,12 +919,12 @@ int main(int argc, char *argv[]) else { vlc_in = &vlc_in_direct; - read = vlc_in_direct.read(input_buf, input_size); - if (read < 0) { + read_bytes = vlc_in_direct.read(&input_buf[0], input_buf.size()); + if (read_bytes < 0) { fprintf(stderr, "Detected fault in VLC input!\n"); break; } - else if (read != input_size) { + else if (read_bytes != input_buf.size()) { fprintf(stderr, "Short VLC read !\n"); break; } @@ -823,16 +936,18 @@ int main(int argc, char *argv[]) } #endif else if (drift_compensation || jack_name) { +#if HAVE_ALSA if (drift_compensation && alsa_in_threaded.fault_detected()) { fprintf(stderr, "Detected fault in alsa input!\n"); retval = 5; break; } +#endif size_t overruns; - read = queue.pop(input_buf, input_size, &overruns); // returns bytes + read_bytes = queue.pop(&input_buf[0], input_buf.size(), &overruns); // returns bytes - if (read != input_size) { + if (read_bytes != input_buf.size()) { status |= STATUS_UNDERRUN; } @@ -841,16 +956,18 @@ int main(int argc, char *argv[]) } } else { - read = alsa_in_direct.read(input_buf, input_size); - if (read < 0) { +#if HAVE_ALSA + read_bytes = alsa_in_direct.read(&input_buf[0], input_buf.size()); + if (read_bytes < 0) { break; } - else if (read != input_size) { + else if (read_bytes != input_buf.size()) { fprintf(stderr, "Short alsa read !\n"); } +#endif } - for (int i = 0; i < read; i+=4) { + for (int i = 0; i < read_bytes; 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); @@ -876,50 +993,94 @@ int main(int argc, char *argv[]) measured_silence_ms = 0; } - // -------------- AAC Encoding - - int calculated_padlen = ret > 0 ? pad_buf[padlen] : 0; - - - in_ptr[0] = input_buf; - in_ptr[1] = pad_buf + (padlen - calculated_padlen); // offset due to unused PAD bytes - 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"); + int numOutBytes = 0; + if (read_bytes and + selected_encoder == encoder_selection_t::fdk_dabplus) { + AACENC_InArgs in_args = { 0 }; + AACENC_OutArgs out_args = { 0 }; + // -------------- AAC Encoding + // + int in_identifier[] = {IN_AUDIO_DATA, IN_ANCILLRY_DATA}; + int out_identifier = OUT_BITSTREAM_DATA; + const int calculated_padlen = ret > 0 ? pad_buf[padlen] : 0; + const int subchannel_index = bitrate / 8; + + void *in_ptr[2], *out_ptr; + int in_size[2], in_elem_size[2]; + int out_size, out_elem_size; + + in_ptr[0] = &input_buf[0]; + in_ptr[1] = pad_buf + (padlen - calculated_padlen); // offset due to unused PAD bytes + in_size[0] = read_bytes; + in_size[1] = calculated_padlen; + in_elem_size[0] = BYTES_PER_SAMPLE; + in_elem_size[1] = sizeof(uint8_t); + in_args.numInSamples = input_buf.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[0]; + out_size = outbuf.size(); + 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; } - fprintf(stderr, "Encoding failed (%d)\n", err); - retval = 3; - break; + calls++; + + numOutBytes = out_args.numOutBytes; + } + else if (selected_encoder == encoder_selection_t::toolame_dab) { + const int calculated_padlen = ret > 0 ? pad_buf[padlen] : 0; + uint8_t *xpad_data = pad_buf + (padlen - calculated_padlen); // offset due to unused PAD bytes + + short input_buffers[2][1152]; + + if (channels == 1) { + memcpy(input_buffers[0], &input_buf[0], 1152 * BYTES_PER_SAMPLE); + } + else if (channels == 2) { + for (int i = 0; i < 1152; i++) { + int16_t l = input_buf[4*i] | (input_buf[4*i+1] << 8); + int16_t r = input_buf[4*i+2] | (input_buf[4*i+3] << 8); + + input_buffers[0][i] = l; + input_buffers[1][i] = r; + } + } + else { + fprintf(stderr, "INTERNAL ERROR! invalid number of channels\n"); + } + + if (read_bytes) { + numOutBytes = toolame_encode_frame(input_buffers, xpad_data, &outbuf[0], outbuf.size()); + } + else { + numOutBytes = toolame_finish(&outbuf[0], outbuf.size()); + } } - calls++; /* Check if the encoder has generated output data */ - if (out_args.numOutBytes != 0) - { + if (numOutBytes != 0 and + selected_encoder == encoder_selection_t::fdk_dabplus) { + // Our timing code depends on this if (calls != enc_calls_per_output) { fprintf(stderr, "INTERNAL ERROR! calls=%d" @@ -932,6 +1093,7 @@ int main(int argc, char *argv[]) int row, col; unsigned char buf_to_rs_enc[110]; unsigned char rs_enc[10]; + const int subchannel_index = bitrate / 8; for(row=0; row < subchannel_index; row++) { for(col=0;col < 110; col++) { buf_to_rs_enc[col] = outbuf[subchannel_index * col + row]; @@ -945,24 +1107,33 @@ int main(int argc, char *argv[]) } } + numOutBytes = outbuf_size; + } + + if (numOutBytes != 0) { if (out_fh) { - fwrite(outbuf, 1, outbuf_size, out_fh); + fwrite(&outbuf[0], 1, numOutBytes, out_fh); } else { // ------------ ZeroMQ transmit try { zmq_frame_header->version = 1; - zmq_frame_header->encoder = ZMQ_ENCODER_FDK; - zmq_frame_header->datasize = outbuf_size; + if (selected_encoder == encoder_selection_t::fdk_dabplus) { + zmq_frame_header->encoder = ZMQ_ENCODER_FDK; + } + else if (selected_encoder == encoder_selection_t::toolame_dab) { + zmq_frame_header->encoder = ZMQ_ENCODER_TOOLAME; + } + zmq_frame_header->datasize = numOutBytes; zmq_frame_header->audiolevel_left = peak_left; zmq_frame_header->audiolevel_right = peak_right; - assert(ZMQ_FRAME_SIZE(zmq_frame_header) <= NUMOF(zmqframebuf)); + assert(ZMQ_FRAME_SIZE(zmq_frame_header) <= zmqframebuf.size()); memcpy(ZMQ_FRAME_DATA(zmq_frame_header), - outbuf, outbuf_size); + &outbuf[0], numOutBytes); - zmq_sock.send(zmqframebuf, ZMQ_FRAME_SIZE(zmq_frame_header), + zmq_sock.send(&zmqframebuf[0], ZMQ_FRAME_SIZE(zmq_frame_header), ZMQ_DONTWAIT); } catch (zmq::error_t& e) { @@ -977,7 +1148,10 @@ int main(int argc, char *argv[]) break; } } + } + if (numOutBytes != 0) + { if (show_level) { if (channels == 1) { fprintf(stderr, "\rIn: [%-6s] %1s %1s %1s", @@ -1013,7 +1187,8 @@ int main(int argc, char *argv[]) } fflush(stdout); - } + } while (read_bytes > 0); + fprintf(stderr, "\n"); if (out_fh) { @@ -1023,7 +1198,9 @@ int main(int argc, char *argv[]) zmq_sock.close(); free_rs_char(rs_handler); - aacEncClose(&encoder); + if (selected_encoder == encoder_selection_t::fdk_dabplus) { + aacEncClose(&encoder); + } return retval; } |