AAC ADTS Decoder (Pull)¶
This article explains how you can use Transcoder::pull to decode an AAC (Advanced Audio Coding) ADTS (Audio Data Transport Stream) elementary stream.
The code snippets in this article are from the dec_aac_adts_pull macOS sample.
Linux and Windows samples are also available:
Source Audio¶
For source we use the Hydrate-Kenny_Beltrey.adts.aac file from the AVBlocks Assets repository. After downloading and unzipping you will find Hydrate-Kenny_Beltrey.adts.aac in the aud subdirectory.
Code¶
This code takes an AAC ADTS stream and decodes it to uncompressed LPCM samples.
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 = decode(opt);
Library::shutdown();
return result ? 0 : 1;
}
Configure Decoder Output Socket¶
The decoder output socket defines the format of the decoded audio data. In this case, we’re configuring it to output uncompressed LPCM audio with 2 channels at 48 kHz with 16-bit samples. The socket represents the output destination, while the pin represents the specific audio stream within that destination. The AudioStreamInfo object specifies the audio format parameters.
primo::ref<MediaSocket> createDecoderOutputSocket()
{
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 WAV Writer Socket¶
The WAV writer transcoder takes the decoded PCM samples and writes them to a WAV file. It creates an input socket matching the decoder output format and an output socket configured for WAV container format. The output file path is set on the WAV socket before creating the transcoder.
primo::ref<Transcoder> createWavWriter(const std::string &outputFile)
{
// input stream info, pin and socket
auto infmt = primo::make_ref(Library::createAudioStreamInfo());
infmt->setStreamType(StreamType::LPCM);
infmt->setChannels(2);
infmt->setSampleRate(48000);
infmt->setBitsPerSample(16);
auto inPin = primo::make_ref(Library::createMediaPin());
inPin->setStreamInfo(infmt.get());
auto inSocket = primo::make_ref(Library::createMediaSocket());
inSocket->setStreamType(StreamType::LPCM);
inSocket->pins()->add(inPin.get());
// output stream info, pin and socket
auto outfmt = primo::make_ref(infmt->clone());
auto outPin = primo::make_ref(Library::createMediaPin());
outPin->setStreamInfo(outfmt.get());
auto outSocket = primo::make_ref(Library::createMediaSocket());
outSocket->setStreamType(StreamType::WAVE);
outSocket->pins()->add(outPin.get());
// set output file
outSocket->setFile(primo::ustring(outputFile));
// create transcoder
auto wavWriter = primo::make_ref(Library::createTranscoder());
wavWriter->setAllowDemoMode(true);
wavWriter->inputs()->add(inSocket.get());
wavWriter->outputs()->add(outSocket.get());
return wavWriter;
}
Decode with Pull¶
This is the main decoding function that uses the pull method to retrieve decoded PCM samples from the AAC decoder and pushes them to a WAV writer transcoder. The pull loop continues until the decoder returns EOS (End of Stream). When EOS is reached, a null sample is pushed to the WAV writer to signal the end of the stream.
The function checks the error code to determine if the decoding completed successfully or if an actual error occurred.
bool decode(Options &opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
// Create decoder transcoder
auto decoder = primo::make_ref(Library::createTranscoder());
decoder->setAllowDemoMode(true);
auto inputSocket = primo::make_ref(Library::createMediaSocket());
inputSocket->setFile(primo::ustring(opt.inputFile));
decoder->inputs()->add(inputSocket.get());
decoder->outputs()->add(createDecoderOutputSocket().get());
if (!decoder->open())
{
printError("Decoder open", decoder->error());
return false;
}
// Create WAV writer transcoder
auto wavWriter = createWavWriter(opt.outputFile);
if (!wavWriter->open())
{
printError("WAV Writer open", wavWriter->error());
decoder->close();
return false;
}
// Pull-push decoding loop
int32_t decoderOutputIndex = 0;
auto pcmSample = primo::make_ref(Library::createMediaSample());
bool decoderEos = false;
while (!decoderEos)
{
// Pull PCM sample from decoder
if (decoder->pull(decoderOutputIndex, pcmSample.get()))
{
// Push PCM sample to WAV writer
if (!wavWriter->push(0, pcmSample.get()))
{
printError("WAV Writer push", wavWriter->error());
decoder->close();
wavWriter->close();
return false;
}
continue;
}
// No more PCM data from decoder
const primo::error::ErrorInfo *error = decoder->error();
if (error->facility() == primo::error::ErrorFacility::Codec &&
error->code() == primo::codecs::CodecError::EOS)
{
// Push null to signal EOS to WAV writer
wavWriter->push(0, nullptr);
decoderEos = true;
continue;
}
printError("Decoder pull", error);
decoder->close();
wavWriter->close();
return false;
}
decoder->close();
wavWriter->close();
return true;
}
Complete C++ Code¶
Here’s the complete working example that demonstrates AAC ADTS decoding 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 decoding operation using pull, and properly shuts down the library before exiting.
#include <primo/avblocks/avb.h>
#include <primo/platform/reference++.h>
#include <primo/platform/ustring.h>
#include "util.h"
#include "options.h"
using namespace primo::codecs;
using namespace primo::avblocks;
using namespace std;
primo::ref<MediaSocket> createDecoderOutputSocket()
{
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<Transcoder> createWavWriter(const std::string &outputFile)
{
// input stream info, pin and socket
auto infmt = primo::make_ref(Library::createAudioStreamInfo());
infmt->setStreamType(StreamType::LPCM);
infmt->setChannels(2);
infmt->setSampleRate(48000);
infmt->setBitsPerSample(16);
auto inPin = primo::make_ref(Library::createMediaPin());
inPin->setStreamInfo(infmt.get());
auto inSocket = primo::make_ref(Library::createMediaSocket());
inSocket->setStreamType(StreamType::LPCM);
inSocket->pins()->add(inPin.get());
// output stream info, pin and socket
auto outfmt = primo::make_ref(infmt->clone());
auto outPin = primo::make_ref(Library::createMediaPin());
outPin->setStreamInfo(outfmt.get());
auto outSocket = primo::make_ref(Library::createMediaSocket());
outSocket->setStreamType(StreamType::WAVE);
outSocket->pins()->add(outPin.get());
// set output file
outSocket->setFile(primo::ustring(outputFile));
// create transcoder
auto wavWriter = primo::make_ref(Library::createTranscoder());
wavWriter->setAllowDemoMode(true);
wavWriter->inputs()->add(inSocket.get());
wavWriter->outputs()->add(outSocket.get());
return wavWriter;
}
bool decode(Options &opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
// Create decoder transcoder
auto decoder = primo::make_ref(Library::createTranscoder());
decoder->setAllowDemoMode(true);
auto inputSocket = primo::make_ref(Library::createMediaSocket());
inputSocket->setFile(primo::ustring(opt.inputFile));
decoder->inputs()->add(inputSocket.get());
decoder->outputs()->add(createDecoderOutputSocket().get());
if (!decoder->open())
{
printError("Decoder open", decoder->error());
return false;
}
// Create WAV writer transcoder
auto wavWriter = createWavWriter(opt.outputFile);
if (!wavWriter->open())
{
printError("WAV Writer open", wavWriter->error());
decoder->close();
return false;
}
// Pull-push decoding loop
int32_t decoderOutputIndex = 0;
auto pcmSample = primo::make_ref(Library::createMediaSample());
bool decoderEos = false;
while (!decoderEos)
{
// Pull PCM sample from decoder
if (decoder->pull(decoderOutputIndex, pcmSample.get()))
{
// Push PCM sample to WAV writer
if (!wavWriter->push(0, pcmSample.get()))
{
printError("WAV Writer push", wavWriter->error());
decoder->close();
wavWriter->close();
return false;
}
continue;
}
// No more PCM data from decoder
const primo::error::ErrorInfo *error = decoder->error();
if (error->facility() == primo::error::ErrorFacility::Codec &&
error->code() == primo::codecs::CodecError::EOS)
{
// Push null to signal EOS to WAV writer
wavWriter->push(0, nullptr);
decoderEos = true;
continue;
}
printError("Decoder pull", error);
decoder->close();
wavWriter->close();
return false;
}
decoder->close();
wavWriter->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 = decode(opt);
Library::shutdown();
return result ? 0 : 1;
}
How to Run¶
See the build instructions for macOS and the dec_aac_adts_pull example for details.
Command Line¶
./dec_aac_adts_pull --input <aac file> --output <wav file>
Examples¶
List options:
./bin/x64/dec_aac_adts_pull --help
dec_aac_adts_pull --input <aac file> --output <wav file>
-h, --help
-i, --input input AAC file
-o, --output output WAV file
The following example decodes input file Hydrate-Kenny_Beltrey.adts.aac into output file Hydrate-Kenny_Beltrey.wav:
mkdir -p ./output/dec_aac_adts_pull
./bin/x64/dec_aac_adts_pull \
--input ./assets/aud/Hydrate-Kenny_Beltrey.adts.aac \
--output ./output/dec_aac_adts_pull/Hydrate-Kenny_Beltrey.wav
Copy the Hydrate-Kenny_Beltrey.aac file from the assets archive to x64/Debug under the project’s directory.
Run the application in Visual Studio.