AAC ADTS Encoder (Push)

This article explains how you can use Transcoder.push to encode an AAC ADTS audio stream from raw audio frames.

The code snippets in this article are from the enc_aac_adts_push Python sample.

Source Audio

As audio input we use the equinox-48KHz.wav file from the AVBlocks Assets archive. After downloading and unzipping you will find equinox-48KHz.wav in the aud subdirectory.

Code

This code shows how you can encode raw uncompressed audio frames into an AAC ADTS stream. Two Transcoder objects are used, one to read the raw LPCM frames from a WAV file, and another to encode the raw frames to AAC ADTS stream. The encoding is done via the Transcoder.push method.

Initialize AVBlocks

The first step in any AVBlocks application is to initialize the library. This must be done before using any other AVBlocks functionality. Call Library.shutdown() at the end of the program to clean up resources. After the command line options are parsed, the program initializes the library, calls encode and shuts the library down.

Library.initialize()

# Set license information. Without this AVBlocks runs in Demo mode.
# Library.set_license("<license-string>")

result = encode(input_file, output_file)

Library.shutdown()

sys.exit(0 if result else 2)

Configure WAV Reader

The first Transcoder reads the WAV file and outputs raw 16 bit stereo LPCM audio at 48 KHz. We pull the raw audio frames from this Transcoder and push them to the encoder.

def create_wav_reader(input_file: str) -> Transcoder:
    """Create transcoder for reading WAV file and outputting raw PCM."""
    # Input socket - it will automatically detect stream info from the file
    wav_input_socket = MediaSocket()
    wav_input_socket.file = input_file

    # Output stream info
    pcm_asi = AudioStreamInfo()
    pcm_asi.stream_type = StreamType.LPCM
    pcm_asi.channels = 2
    pcm_asi.sample_rate = 48000
    pcm_asi.bits_per_sample = 16

    # Output pin
    pcm_pin = MediaPin()
    pcm_pin.stream_info = pcm_asi

    # Output socket - we need LPCM stream type
    pcm_output_socket = MediaSocket()
    pcm_output_socket.stream_type = StreamType.LPCM
    pcm_output_socket.pins.add(pcm_pin)

    # Create transcoder
    wav_reader = Transcoder()
    wav_reader.allow_demo_mode = True
    wav_reader.inputs.add(wav_input_socket)
    wav_reader.outputs.add(pcm_output_socket)

    return wav_reader

Configure AAC Encoder

The second Transcoder is the AAC encoder. Its input socket describes the raw LPCM audio that we push, and its output socket writes the AAC ADTS file. The input socket has no file because the audio frames are pushed to the Transcoder.

def create_input_socket() -> MediaSocket:
    """Create input socket for raw PCM data."""
    socket = MediaSocket()
    socket.stream_type = StreamType.LPCM

    pin = MediaPin()
    asi = AudioStreamInfo()
    asi.stream_type = StreamType.LPCM
    asi.channels = 2
    asi.sample_rate = 48000
    asi.bits_per_sample = 16

    pin.stream_info = asi
    socket.pins.add(pin)

    return socket


def create_output_socket(output_file: str) -> MediaSocket:
    """Create output socket for AAC ADTS file."""
    socket = MediaSocket()
    socket.stream_type = StreamType.AAC
    socket.stream_sub_type = StreamSubType.AAC_ADTS
    socket.file = output_file

    pin = MediaPin()
    asi = AudioStreamInfo()
    asi.stream_type = StreamType.AAC
    asi.stream_sub_type = StreamSubType.AAC_ADTS

    # You can change the sampling rate and the number of the channels
    # asi.channels = 1
    # asi.sample_rate = 44100

    pin.stream_info = asi
    socket.pins.add(pin)

    return socket

Open Transcoders

Both Transcoders must be opened before audio frames are pulled and pushed. If the encoder fails to open, the WAV reader is closed before returning.

# Create WAV reader transcoder
wav_reader = create_wav_reader(input_file)

if not wav_reader.open():
    print_error("WAV Reader open", wav_reader.error)
    return False

# Create encoder transcoder
encoder = Transcoder()
encoder.allow_demo_mode = True
encoder.inputs.add(create_input_socket())
encoder.outputs.add(create_output_socket(output_file))

if not encoder.open():
    print_error("Encoder open", encoder.error)
    wav_reader.close()
    return False

Call Transcoder.pull and Transcoder.push

The pull-push loop gets one raw audio frame at a time from the WAV reader with Transcoder.pull and gives it to the encoder with Transcoder.push. When the WAV reader reports end of stream (CodecError.EOS) the encoder is flushed with flush(), so that the encoder writes the audio frames that it still holds.

# Pull-push encoding loop
pcm_sample = MediaSample()
reader_eos = False

while not reader_eos:
    # Pull PCM sample from WAV reader
    res, _ = wav_reader.pull(pcm_sample)

    if res:
        # Push PCM sample to encoder
        if not encoder.push(0, pcm_sample):
            print_error("Encoder push", encoder.error)
            wav_reader.close()
            encoder.close()
            return False
        continue

    # No more PCM data from reader
    error = wav_reader.error
    if error.facility == ErrorFacility.Codec and error.code == CodecError.EOS:
        # Flush encoder
        encoder.flush()
        reader_eos = True
        continue

    # Reader error
    print_error("WAV Reader pull", error)
    wav_reader.close()
    encoder.close()
    return False

wav_reader.close()
encoder.close()

print("Encoding completed successfully.")

Complete Program

"""
Encode WAV file to AAC ADTS using push mode.

This sample encodes a WAV audio file to AAC ADTS format using the push mode.
It first decodes the WAV file to PCM samples using a "reader" transcoder,
then pushes those PCM samples to an "encoder" transcoder.
"""

import os
import sys
import click

from avblocks import (
    Library, Transcoder, MediaSocket, MediaPin,
    AudioStreamInfo, MediaSample, StreamType, StreamSubType,
    ErrorFacility, CodecError
)


def delete_file(filename: str):
    """Delete a file if it exists."""
    try:
        if os.path.exists(filename):
            os.remove(filename)
    except OSError:
        pass


def print_error(action: str, error) -> None:
    """Print error information."""
    if action:
        print(f"{action}: ", end="")

    if error.facility == ErrorFacility.Success:
        print("Success")
        return

    print(f"{error.message or ''}, facility:{error.facility} code:{error.code} hint:{error.hint or ''}")


def create_input_socket() -> MediaSocket:
    """Create input socket for raw PCM data."""
    socket = MediaSocket()
    socket.stream_type = StreamType.LPCM

    pin = MediaPin()
    asi = AudioStreamInfo()
    asi.stream_type = StreamType.LPCM
    asi.channels = 2
    asi.sample_rate = 48000
    asi.bits_per_sample = 16

    pin.stream_info = asi
    socket.pins.add(pin)

    return socket


def create_output_socket(output_file: str) -> MediaSocket:
    """Create output socket for AAC ADTS file."""
    socket = MediaSocket()
    socket.stream_type = StreamType.AAC
    socket.stream_sub_type = StreamSubType.AAC_ADTS
    socket.file = output_file

    pin = MediaPin()
    asi = AudioStreamInfo()
    asi.stream_type = StreamType.AAC
    asi.stream_sub_type = StreamSubType.AAC_ADTS

    # You can change the sampling rate and the number of the channels
    # asi.channels = 1
    # asi.sample_rate = 44100

    pin.stream_info = asi
    socket.pins.add(pin)

    return socket


def create_wav_reader(input_file: str) -> Transcoder:
    """Create transcoder for reading WAV file and outputting raw PCM."""
    # Input socket - it will automatically detect stream info from the file
    wav_input_socket = MediaSocket()
    wav_input_socket.file = input_file

    # Output stream info
    pcm_asi = AudioStreamInfo()
    pcm_asi.stream_type = StreamType.LPCM
    pcm_asi.channels = 2
    pcm_asi.sample_rate = 48000
    pcm_asi.bits_per_sample = 16

    # Output pin
    pcm_pin = MediaPin()
    pcm_pin.stream_info = pcm_asi

    # Output socket - we need LPCM stream type
    pcm_output_socket = MediaSocket()
    pcm_output_socket.stream_type = StreamType.LPCM
    pcm_output_socket.pins.add(pcm_pin)

    # Create transcoder
    wav_reader = Transcoder()
    wav_reader.allow_demo_mode = True
    wav_reader.inputs.add(wav_input_socket)
    wav_reader.outputs.add(pcm_output_socket)

    return wav_reader


def encode(input_file: str, output_file: str) -> bool:
    """Encode WAV file to AAC ADTS using push mode."""
    # Transcoder will fail if output exists (by design)
    delete_file(output_file)

    # Create output directory if it doesn't exist
    output_dir = os.path.dirname(output_file)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # Create WAV reader transcoder
    wav_reader = create_wav_reader(input_file)

    if not wav_reader.open():
        print_error("WAV Reader open", wav_reader.error)
        return False

    # Create encoder transcoder
    encoder = Transcoder()
    encoder.allow_demo_mode = True
    encoder.inputs.add(create_input_socket())
    encoder.outputs.add(create_output_socket(output_file))

    if not encoder.open():
        print_error("Encoder open", encoder.error)
        wav_reader.close()
        return False

    # Pull-push encoding loop
    pcm_sample = MediaSample()
    reader_eos = False

    while not reader_eos:
        # Pull PCM sample from WAV reader
        res, _ = wav_reader.pull(pcm_sample)

        if res:
            # Push PCM sample to encoder
            if not encoder.push(0, pcm_sample):
                print_error("Encoder push", encoder.error)
                wav_reader.close()
                encoder.close()
                return False
            continue

        # No more PCM data from reader
        error = wav_reader.error
        if error.facility == ErrorFacility.Codec and error.code == CodecError.EOS:
            # Flush encoder
            encoder.flush()
            reader_eos = True
            continue

        # Reader error
        print_error("WAV Reader pull", error)
        wav_reader.close()
        encoder.close()
        return False

    wav_reader.close()
    encoder.close()

    print("Encoding completed successfully.")
    return True


@click.command()
@click.option('-i', '--input', 'input_file',
              help='Input WAV file',
              type=click.Path(exists=True))
@click.option('-o', '--output', 'output_file',
              help='Output AAC ADTS file',
              type=click.Path())
def main(input_file: str, output_file: str):
    """Encode WAV file to AAC ADTS using push mode."""

    # Set default options if not provided
    if not input_file or not output_file:
        script_dir = os.path.dirname(os.path.abspath(__file__))

        if not input_file:
            input_file = os.path.join(script_dir, "../../assets/aud/Hydrate-Kenny_Beltrey.wav")
        if not output_file:
            output_file = os.path.join(script_dir, "../../output/enc_aac_adts_push/Hydrate-Kenny_Beltrey.aac")

        print("Using default options:")
        print(f"  --input {input_file}")
        print(f"  --output {output_file}")
        print()

    # Validate options
    print(f"--input: {input_file}")
    print(f"--output: {output_file}")

    Library.initialize()

    # Set license information. Without this AVBlocks runs in Demo mode.
    # Library.set_license("<license-string>")

    result = encode(input_file, output_file)

    Library.shutdown()

    sys.exit(0 if result else 2)


if __name__ == '__main__':
    main()

How to run

See the Python sample for details. The samples are installed as command-line tools when you install the package from the repository root with uv pip install --editable . (see Installation).

Command Line

enc-aac-adts-push --input <wav file> --output <aac file>

Examples

List options:

enc-aac-adts-push --help
Usage: enc-aac-adts-push [OPTIONS]

  Encode WAV file to AAC ADTS using push mode.

Options:
  -i, --input PATH   Input WAV file
  -o, --output PATH  Output AAC ADTS file
  --help             Show this message and exit.

Encode the input file ./assets/aud/equinox-48KHz.wav into output file ./output/enc_aac_adts_push/equinox-48KHz.adts.aac:

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

enc-aac-adts-push \
    --input ./assets/aud/equinox-48KHz.wav \
    --output ./output/enc_aac_adts_push/equinox-48KHz.adts.aac
# Windows
New-Item -ItemType Directory -Force -Path ./output/enc_aac_adts_push

enc-aac-adts-push `
    --input ./assets/aud/equinox-48KHz.wav `
    --output ./output/enc_aac_adts_push/equinox-48KHz.adts.aac