AAC ADTS Encoder (Pull)

This article explains how you can use Transcoder.Pull to encode a WAV file to AAC (Advanced Audio Coding) ADTS (Audio Data Transport Stream) elementary stream.

The code snippets in this article are from the enc_aac_adts_pull sample.

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 pull method, which allows you to retrieve encoded samples one at a time.

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.

static int Main(string[] args)
{
    var opt = new Options();

    if (!opt.Prepare(args))
        return opt.Error ? (int)ExitCodes.OptionsError : (int)ExitCodes.Success;

    Library.Initialize();

    // Set license information. Without this AVBlocks runs in Demo mode.
    // Library.SetLicense("<license-string>");

    bool encodeResult = Encode(opt);

    Library.Shutdown();

    return encodeResult ? (int)ExitCodes.Success : (int)ExitCodes.EncodingError;
}

Configure Output Socket

The output socket defines the format of the encoded audio data. Unlike the run method, when using pull we don’t specify an output file in the socket. Instead, we configure only the stream format and parameters. The socket represents the output format configuration, while the pin represents the specific audio stream. The AudioStreamInfo object specifies the audio format parameters - you can customize the number of channels or sampling rate by uncommenting the relevant lines.

static MediaSocket CreateOutputSocket()
{
    MediaSocket socket = new MediaSocket();
    socket.StreamType = StreamType.Aac;
    socket.StreamSubType = StreamSubType.AacAdts;

    MediaPin pin = new MediaPin();
    socket.Pins.Add(pin);

    AudioStreamInfo asi = new AudioStreamInfo();
    pin.StreamInfo = asi;

    asi.StreamType = StreamType.Aac;
    asi.StreamSubType = StreamSubType.AacAdts;

    // You can change the sampling rate and the number of the channels
    // asi.Channels = 1;
    // asi.SampleRate = 44100;

    return socket;
}

Configure Transcoder and Encode with Pull

This is the main encoding function that uses the pull method to retrieve encoded samples. The pull method allows you to control when and how encoded data is retrieved, giving you more flexibility in processing. The transcoder reads from the input WAV file and encodes it to AAC ADTS format, but instead of writing directly to a file, it provides the encoded samples through the Pull() method.

The AllowDemoMode = true setting allows the transcoder to work even without a valid license (useful for testing, but not recommended for production). After opening the transcoder, we continuously pull encoded samples and write them to the output file. The pull loop continues until Pull() returns false, which happens when all input has been processed.

The function checks the error code to determine if the encoding completed successfully (EOS - End of Stream) or if an actual error occurred.

static bool Encode(Options opt)
{
    // transcoder will fail if output exists (by design)
    DeleteFile(opt.OutputFile);

    // Create output directory if needed
    string outputDir = Path.GetDirectoryName(opt.OutputFile);
    if (!string.IsNullOrEmpty(outputDir))
        Directory.CreateDirectory(outputDir);

    using (FileStream outfile = File.Create(opt.OutputFile))
    {
        // create input socket
        MediaSocket inSocket = new MediaSocket();
        inSocket.File = opt.InputFile;

        // create output socket
        MediaSocket outSocket = CreateOutputSocket();

        // create Transcoder
        using (Transcoder transcoder = new Transcoder())
        {
            transcoder.AllowDemoMode = true;
            transcoder.Inputs.Add(inSocket);
            transcoder.Outputs.Add(outSocket);

            if (!transcoder.Open())
            {
                PrintError("Transcoder open", transcoder.Error);
                return false;
            }

            // encode by pulling encoded samples
            int outputIndex = 0;
            MediaSample sample = new MediaSample();

            while (transcoder.Pull(out outputIndex, sample))
            {
                outfile.Write(sample.Buffer.Start, sample.Buffer.DataOffset, sample.Buffer.DataSize);
            }

            ErrorInfo error = transcoder.Error;
            PrintError("Transcoder pull", error);

            bool success = false;
            if (error.Facility == ErrorFacility.Codec &&
                error.Code == (int)CodecError.EOS)
            {
                // ok
                success = true;
            }

            transcoder.Close();

            return success;
        }
    }
}

Complete C# Code

Here’s the complete working example that demonstrates AAC ADTS encoding using the pull method. This code combines all the previous snippets into a functional program that can be compiled and run. The Main method handles command-line argument parsing, initializes AVBlocks, performs the encoding operation using pull, and properly shuts down the library before exiting.

using System;
using System.IO;
using PrimoSoftware.AVBlocks;

namespace CliSample
{
    class Program
    {
        static int Main(string[] args)
        {
            var opt = new Options();

            if (!opt.Prepare(args))
                return opt.Error ? (int)ExitCodes.OptionsError : (int)ExitCodes.Success;

            Library.Initialize();

            // Set license information. Without this AVBlocks runs in Demo mode.
            // Library.SetLicense("<license-string>");

            bool encodeResult = Encode(opt);

            Library.Shutdown();

            return encodeResult ? (int)ExitCodes.Success : (int)ExitCodes.EncodingError;
        }

        static MediaSocket CreateOutputSocket()
        {
            MediaSocket socket = new MediaSocket();
            socket.StreamType = StreamType.Aac;
            socket.StreamSubType = StreamSubType.AacAdts;

            MediaPin pin = new MediaPin();
            socket.Pins.Add(pin);

            AudioStreamInfo asi = new AudioStreamInfo();
            pin.StreamInfo = asi;

            asi.StreamType = StreamType.Aac;
            asi.StreamSubType = StreamSubType.AacAdts;

            // You can change the sampling rate and the number of the channels
            // asi.Channels = 1;
            // asi.SampleRate = 44100;

            return socket;
        }

        static bool Encode(Options opt)
        {
            // transcoder will fail if output exists (by design)
            DeleteFile(opt.OutputFile);

            // Create output directory if needed
            string outputDir = Path.GetDirectoryName(opt.OutputFile);
            if (!string.IsNullOrEmpty(outputDir))
                Directory.CreateDirectory(outputDir);

            using (FileStream outfile = File.Create(opt.OutputFile))
            {
                // create input socket
                MediaSocket inSocket = new MediaSocket();
                inSocket.File = opt.InputFile;

                // create output socket
                MediaSocket outSocket = CreateOutputSocket();

                // create Transcoder
                using (Transcoder transcoder = new Transcoder())
                {
                    transcoder.AllowDemoMode = true;
                    transcoder.Inputs.Add(inSocket);
                    transcoder.Outputs.Add(outSocket);

                    if (!transcoder.Open())
                    {
                        PrintError("Transcoder open", transcoder.Error);
                        return false;
                    }

                    // encode by pulling encoded samples
                    int outputIndex = 0;
                    MediaSample sample = new MediaSample();

                    while (transcoder.Pull(out outputIndex, sample))
                    {
                        outfile.Write(sample.Buffer.Start, sample.Buffer.DataOffset, sample.Buffer.DataSize);
                    }

                    ErrorInfo error = transcoder.Error;
                    PrintError("Transcoder pull", error);

                    bool success = false;
                    if (error.Facility == ErrorFacility.Codec &&
                        error.Code == (int)CodecError.EOS)
                    {
                        // ok
                        success = true;
                    }

                    transcoder.Close();

                    return success;
                }
            }
        }

        static void DeleteFile(string filename)
        {
            try
            {
                if (File.Exists(filename))
                    File.Delete(filename);
            }
            catch { }
        }

        static void PrintError(string action, ErrorInfo e)
        {
            if (action != null)
            {
                Console.Write("{0}: ", action);
            }

            if (ErrorFacility.Success == e.Facility)
            {
                Console.WriteLine("Success");
                return;
            }
            else
            {
                Console.WriteLine("{0}, facility:{1} code:{2} hint:{3}", e.Message ?? "", e.Facility, e.Code, e.Hint ?? "");
            }
        }

        enum ExitCodes : int
        {
            Success = 0,
            OptionsError = 1,
            EncodingError = 2,
        }
    }
}

How to Run

See the build instructions for Windows, macOS, and Linux, and the enc_aac_adts_pull sample for details.

Command Line

enc_aac_adts_pull --input <wav file> --output <aac file>

Examples

List options:

./bin/net10.0/enc_aac_adts_pull --help
enc_aac_adts_pull 1.0.0.0
Copyright (C) 2023 Primo Software

  -i, --input     input WAV file

  -o, --output    output AAC file

  --help          Display this help screen.

  --version       Display version information.

The following example encodes input file ./assets/aud/equinox-48KHz.wav into output file equinox-48KHz.adts.aac:

# Linux and macOS
mkdir -p ./output/enc_aac_adts_pull

./bin/net10.0/enc_aac_adts_pull \
    --input ./assets/aud/equinox-48KHz.wav \
    --output ./output/enc_aac_adts_pull/equinox-48KHz.adts.aac
# Windows
mkdir -Force -Path ./output/enc_aac_adts_pull

./bin/net10.0/enc_aac_adts_pull `
    --input ./assets/aud/equinox-48KHz.wav `
    --output ./output/enc_aac_adts_pull/equinox-48KHz.adts.aac