AAC ADTS Encoder (Push)¶
This article explains how you can use Transcoder::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 Transcoder 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 Library::initialize() method sets up the internal state and loads necessary codecs. Always remember to call Library::shutdown() at the end of your program to properly clean up resources and release any allocated memory.
int main(int argc, char *argv[])
{
Options opt;
switch (prepareOptions(opt, argc, argv))
{
case Command:
return 0;
case Error:
return 1;
case Parsed:
break;
}
Library::initialize();
bool result = encode(opt);
Library::shutdown();
return result ? 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.
primo::ref<MediaSocket> createInputSocket()
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::LPCM);
auto pin = primo::make_ref(Library::createMediaPin());
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::LPCM);
asi->setChannels(2);
asi->setSampleRate(48000);
asi->setBitsPerSample(16);
pin->setStreamInfo(asi.get());
socket->pins()->add(pin.get());
return socket;
}
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 before creating the encoder transcoder.
primo::ref<MediaSocket> createOutputSocket(const std::string& outputFile)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::AAC);
socket->setStreamSubType(StreamSubType::AAC_ADTS);
socket->setFile(primo::ustring(outputFile));
auto pin = primo::make_ref(Library::createMediaPin());
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::AAC);
asi->setStreamSubType(StreamSubType::AAC_ADTS);
// You can change the sampling rate and the number of the channels
// asi->setChannels(1);
// asi->setSampleRate(44100);
pin->setStreamInfo(asi.get());
socket->pins()->add(pin.get());
return socket;
}
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.
primo::ref<Transcoder> createWavReader(const std::string& inputFile)
{
// input socket
// it will automatically detect stream info from the file
auto wavInputSocket = primo::make_ref(Library::createMediaSocket());
wavInputSocket->setFile(primo::ustring(inputFile));
// output stream info
auto pcmAsi = primo::make_ref(Library::createAudioStreamInfo());
pcmAsi->setStreamType(StreamType::LPCM);
pcmAsi->setChannels(2);
pcmAsi->setSampleRate(48000);
pcmAsi->setBitsPerSample(16);
// output pin
auto pcmPin = primo::make_ref(Library::createMediaPin());
pcmPin->setStreamInfo(pcmAsi.get());
// output socket. we need LPCM stream type
auto pcmOutputSocket = primo::make_ref(Library::createMediaSocket());
pcmOutputSocket->setStreamType(StreamType::LPCM);
pcmOutputSocket->pins()->add(pcmPin.get());
// create transcoder
auto wavReader = primo::make_ref(Library::createTranscoder());
wavReader->setAllowDemoMode(true);
wavReader->inputs()->add(wavInputSocket.get());
wavReader->outputs()->add(pcmOutputSocket.get());
return wavReader;
}
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), a null sample is pushed to 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(Options &opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
// Create WAV reader transcoder
auto wavReader = createWavReader(opt.inputFile);
if (!wavReader->open())
{
printError("WAV Reader open", wavReader->error());
return false;
}
// Create encoder transcoder
auto encoder = primo::make_ref(Library::createTranscoder());
encoder->setAllowDemoMode(true);
encoder->inputs()->add(createInputSocket().get());
encoder->outputs()->add(createOutputSocket(opt.outputFile).get());
if (!encoder->open())
{
printError("Encoder open", encoder->error());
wavReader->close();
return false;
}
// Push encoding loop
int32_t wavOutputIndex = 0;
auto pcmSample = primo::make_ref(Library::createMediaSample());
bool wavEos = false;
while (!wavEos)
{
// Get PCM sample from WAV reader
if (wavReader->pull(wavOutputIndex, pcmSample.get()))
{
// Push PCM sample to encoder
if (!encoder->push(0, pcmSample.get()))
{
printError("Encoder push", encoder->error());
wavReader->close();
encoder->close();
return false;
}
}
else
{
// No more PCM data from WAV reader
const primo::error::ErrorInfo *error = wavReader->error();
if (error->facility() == primo::error::ErrorFacility::Codec &&
error->code() == primo::codecs::CodecError::EOS)
{
// Push null to signal EOS to encoder
encoder->push(0, nullptr);
wavEos = true;
}
else
{
printError("WAV Reader pull", error);
wavReader->close();
encoder->close();
return false;
}
}
}
wavReader->close();
encoder->close();
return true;
}
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 <primo/platform/reference++.h>
#include <primo/platform/error_facility.h>
#include <primo/platform/ustring.h>
#include "util.h"
#include "options.h"
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
using namespace primo::codecs;
using namespace primo::avblocks;
using namespace std;
primo::ref<MediaSocket> createInputSocket()
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::LPCM);
auto pin = primo::make_ref(Library::createMediaPin());
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::LPCM);
asi->setChannels(2);
asi->setSampleRate(48000);
asi->setBitsPerSample(16);
pin->setStreamInfo(asi.get());
socket->pins()->add(pin.get());
return socket;
}
primo::ref<MediaSocket> createOutputSocket(const std::string& outputFile)
{
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::AAC);
socket->setStreamSubType(StreamSubType::AAC_ADTS);
socket->setFile(primo::ustring(outputFile));
auto pin = primo::make_ref(Library::createMediaPin());
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::AAC);
asi->setStreamSubType(StreamSubType::AAC_ADTS);
// You can change the sampling rate and the number of the channels
// asi->setChannels(1);
// asi->setSampleRate(44100);
pin->setStreamInfo(asi.get());
socket->pins()->add(pin.get());
return socket;
}
primo::ref<Transcoder> createWavReader(const std::string& inputFile)
{
// input socket
// it will automatically detect stream info from the file
auto wavInputSocket = primo::make_ref(Library::createMediaSocket());
wavInputSocket->setFile(primo::ustring(inputFile));
// output stream info
auto pcmAsi = primo::make_ref(Library::createAudioStreamInfo());
pcmAsi->setStreamType(StreamType::LPCM);
pcmAsi->setChannels(2);
pcmAsi->setSampleRate(48000);
pcmAsi->setBitsPerSample(16);
// output pin
auto pcmPin = primo::make_ref(Library::createMediaPin());
pcmPin->setStreamInfo(pcmAsi.get());
// output socket. we need LPCM stream type
auto pcmOutputSocket = primo::make_ref(Library::createMediaSocket());
pcmOutputSocket->setStreamType(StreamType::LPCM);
pcmOutputSocket->pins()->add(pcmPin.get());
// create transcoder
auto wavReader = primo::make_ref(Library::createTranscoder());
wavReader->setAllowDemoMode(true);
wavReader->inputs()->add(wavInputSocket.get());
wavReader->outputs()->add(pcmOutputSocket.get());
return wavReader;
}
bool encode(Options &opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
// Create WAV reader transcoder
auto wavReader = createWavReader(opt.inputFile);
if (!wavReader->open())
{
printError("WAV Reader open", wavReader->error());
return false;
}
// Create encoder transcoder
auto encoder = primo::make_ref(Library::createTranscoder());
encoder->setAllowDemoMode(true);
encoder->inputs()->add(createInputSocket().get());
encoder->outputs()->add(createOutputSocket(opt.outputFile).get());
if (!encoder->open())
{
printError("Encoder open", encoder->error());
wavReader->close();
return false;
}
// Push encoding loop
int32_t wavOutputIndex = 0;
auto pcmSample = primo::make_ref(Library::createMediaSample());
bool wavEos = false;
while (!wavEos)
{
// Get PCM sample from WAV reader
if (wavReader->pull(wavOutputIndex, pcmSample.get()))
{
// Push PCM sample to encoder
if (!encoder->push(0, pcmSample.get()))
{
printError("Encoder push", encoder->error());
wavReader->close();
encoder->close();
return false;
}
}
else
{
// No more PCM data from WAV reader
const primo::error::ErrorInfo *error = wavReader->error();
if (error->facility() == primo::error::ErrorFacility::Codec &&
error->code() == primo::codecs::CodecError::EOS)
{
// Push null to signal EOS to encoder
encoder->push(0, nullptr);
wavEos = true;
}
else
{
printError("WAV Reader pull", error);
wavReader->close();
encoder->close();
return false;
}
}
}
wavReader->close();
encoder->close();
return true;
}
int main(int argc, char *argv[])
{
Options opt;
switch (prepareOptions(opt, argc, argv))
{
case Command:
return 0;
case Error:
return 1;
case Parsed:
break;
}
Library::initialize();
bool result = encode(opt);
Library::shutdown();
return result ? 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