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