AAC ADTS Encoder (Push)¶
This article explains how you can use TTranscoder::push to encode an AAC (Advanced Audio Coding) ADTS (Audio Data Transport Stream) audio stream from raw audio frames.
The code snippets in this article are from the enc_aac_adts_push macOS sample.
Linux and Windows samples are also available:
Source Audio¶
For source we use the equinox-48KHz.wav file from the AVBlocks Assets repository. After downloading and unzipping you will find equinox-48KHz.wav in the aud subdirectory.
Code¶
This code takes a WAV file and encodes it to compressed AAC ADTS format using the push method. Two TTranscoder objects are used: one to read raw LPCM frames from the WAV file, and another to encode those frames to AAC ADTS.
Initialize AVBlocks¶
The first step in any AVBlocks application is to initialize the library. This must be done before using any other AVBlocks functionality. The TLibrary constructor initializes the library and loads the necessary codecs. The TLibrary destructor shuts the library down and releases any allocated memory, so the library is terminated when the TLibrary object goes out of scope at the end of main.
int main(int argc, char* argv[])
{
Options opt;
switch(prepareOptions(opt, argc, argv))
{
case Command: return 0;
case Error: return 1;
case Parsed: break;
}
TLibrary library;
return encode(opt) ? 0 : 1;
}
Configure Input Socket¶
The encoder input socket defines the format of the raw audio frames being pushed to the encoder. In this case, we’re configuring it for uncompressed LPCM audio with 2 channels at 48 kHz with 16-bit samples. The socket represents the input source, while the pin represents the specific audio stream within that source.
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
Configure Output Socket¶
The encoder output socket defines the format of the encoded AAC data. We configure it to output AAC with ADTS (Audio Data Transport Stream) format. The output file path is set on the socket.
TMediaSocket()
.file(opt.outputFile)
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
)
)
Configure WAV Reader Transcoder¶
The WAV reader transcoder reads the source WAV file and outputs raw LPCM frames. It creates an input socket from the WAV file and an output socket configured for LPCM format. The transcoder is opened to prepare for reading frames one by one using the pull method.
TTranscoder wavReader;
wavReader
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.inputFile)
)
.addOutput(
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
)
.open();
Encode with Push¶
This is the main encoding function. It opens both the WAV reader and encoder transcoders, then runs a push loop that pulls LPCM frames from the WAV reader and pushes them to the AAC encoder. When the WAV reader reaches EOS (End of Stream), pushEos is called on the encoder to signal the end of the stream.
The function checks error codes at each step to determine if encoding completed successfully or if an error occurred.
bool encode(const Options& opt)
{
deleteFile(opt.outputFile.c_str());
try {
// WAV reader: reads PCM from WAV file
TTranscoder wavReader;
wavReader
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.inputFile)
)
.addOutput(
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
)
.open();
// Encoder: receives pushed PCM, writes AAC ADTS to file
TTranscoder encoder;
encoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
)
.addOutput(
TMediaSocket()
.file(opt.outputFile)
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
)
)
)
.open();
// Push loop: pull PCM from wavReader, push to encoder
int32_t wavOutputIndex = 0;
TMediaSample sample;
for (;;)
{
if (wavReader.pull(wavOutputIndex, sample))
{
if (!encoder.push(0, sample))
{
printError("Encoder push", encoder.error());
wavReader.close();
encoder.close();
return false;
}
}
else
{
auto error = wavReader.error();
if (error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS)
{
encoder.pushEos(0);
break;
}
else
{
printError("WAV reader pull", error);
wavReader.close();
encoder.close();
return false;
}
}
}
wavReader.close();
encoder.close();
std::println("Output: {}", opt.outputFile);
return true;
} catch (const TAVBlocksException& ex) {
std::println(stderr, "AVBlocks error: {}", ex.what());
return false;
} catch (const std::exception& ex) {
std::println(stderr, "Error: {}", ex.what());
return false;
}
}
Complete C++ Code¶
Here’s the complete working example that demonstrates AAC ADTS encoding using AVBlocks. This code combines all the previous snippets into a functional program that can be compiled and run. The main function handles command-line argument parsing, initializes AVBlocks, performs the encoding operation using push, and properly shuts down the library before exiting.
#include <primo/avblocks/avb++.h>
#include <print>
#include "options.h"
#include "util.h"
using namespace primo::codecs;
using namespace primo::avblocks::modern;
bool encode(const Options& opt)
{
deleteFile(opt.outputFile.c_str());
try {
// WAV reader: reads PCM from WAV file
TTranscoder wavReader;
wavReader
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.inputFile)
)
.addOutput(
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
)
.open();
// Encoder: receives pushed PCM, writes AAC ADTS to file
TTranscoder encoder;
encoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.streamType(StreamType::LPCM)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::LPCM)
.channels(2)
.sampleRate(48000)
.bitsPerSample(16)
)
)
)
.addOutput(
TMediaSocket()
.file(opt.outputFile)
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::AAC)
.streamSubType(StreamSubType::AAC_ADTS)
)
)
)
.open();
// Push loop: pull PCM from wavReader, push to encoder
int32_t wavOutputIndex = 0;
TMediaSample sample;
for (;;)
{
if (wavReader.pull(wavOutputIndex, sample))
{
if (!encoder.push(0, sample))
{
printError("Encoder push", encoder.error());
wavReader.close();
encoder.close();
return false;
}
}
else
{
auto error = wavReader.error();
if (error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS)
{
encoder.pushEos(0);
break;
}
else
{
printError("WAV reader pull", error);
wavReader.close();
encoder.close();
return false;
}
}
}
wavReader.close();
encoder.close();
std::println("Output: {}", opt.outputFile);
return true;
} catch (const TAVBlocksException& ex) {
std::println(stderr, "AVBlocks error: {}", ex.what());
return false;
} catch (const std::exception& ex) {
std::println(stderr, "Error: {}", ex.what());
return false;
}
}
int main(int argc, char* argv[])
{
Options opt;
switch(prepareOptions(opt, argc, argv))
{
case Command: return 0;
case Error: return 1;
case Parsed: break;
}
TLibrary library;
return encode(opt) ? 0 : 1;
}
How to Run¶
See the build instructions for macOS and the enc_aac_adts_push example for details.
Command Line¶
enc_aac_adts_push --input <wav file> --output <aac file>
Examples¶
List options:
./bin/x64/enc_aac_adts_push --help
Usage: enc_aac_adts_push --input <wav file> --output <aac file>
-h, --help
-i, --input input WAV file
-o, --output output AAC file
Encode the input file ./assets/aud/equinox-48KHz.wav into output file ./output/enc_aac_adts_push/equinox-48KHz.adts.aac:
mkdir -p ./output/enc_aac_adts_push
./bin/x64/enc_aac_adts_push \
--input ./assets/aud/equinox-48KHz.wav \
--output ./output/enc_aac_adts_push/equinox-48KHz.adts.aac