MP3 Encoder (Push)

This article explains how you can use TTranscoder::push to encode an MP3 audio stream from raw audio frames.

The code snippets in this article are from the enc_mp3_push 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 MP3 stream. Two TTranscoder objects are used, one to read the raw LPCM frames from a file, and another to encode the raw frames to MP3 stream. The encoding is done via the TTranscoder::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. The TLibrary constructor initializes the library and loads the necessary codecs. The TLibrary destructor shuts the library down and releases any allocated memory, so the library is terminated when the TLibrary object goes out of scope at the end of main.

int main(int argc, char* argv[])
{
    Options opt;
    switch(prepareOptions(opt, argc, argv))
    {
        case Command: return 0;
        case Error: return 1;
        case Parsed: break;
    }

    TLibrary library;
    return encode(opt) ? 0 : 1;
}

Configure Input Socket

The input socket defines the format of the raw LPCM audio frames that will be fed to the MP3 encoder. This socket describes uncompressed 16-bit stereo audio at 48 kHz sample rate. The socket represents the input source, while the pin represents the specific audio stream within that source. The TAudioStreamInfo object specifies the audio format parameters that the encoder expects to receive.

TMediaSocket()
    .streamType(StreamType::LPCM)
    .addPin(
        TMediaPin()
            .streamInfo(TAudioStreamInfo()
                .streamType(StreamType::LPCM)
                .channels(2)
                .sampleRate(48000)
                .bitsPerSample(16)
            )
    )

Configure Output Socket

The output socket defines where and how the encoded MP3 data will be written. This code sets up the MP3 encoding parameters including the output file location, audio format, and encoding options. You can customize the bitrate, sample rate, channels, and stereo mode. The socket represents the output destination, while the pin represents the specific MP3 stream within that destination.

TMediaSocket()
    .file(opt.outputFile)
    .streamType(StreamType::MPEG_Audio)
    .streamSubType(StreamSubType::MPEG_Audio_Layer3)
    .addPin(
        TMediaPin()
            .streamInfo(TAudioStreamInfo()
                .streamType(StreamType::MPEG_Audio)
                .streamSubType(StreamSubType::MPEG_Audio_Layer3)
            )
    )

Configure WAV Reader

This code creates a transcoder that reads WAV files and outputs raw LPCM audio frames. In a real-world scenario, you might have a different audio source like an audio capture device or streaming input. The WAV reader transcoder converts the compressed or container-wrapped audio in the WAV file into raw PCM samples that can be fed to the MP3 encoder. The allowDemoMode(true) call allows the transcoder to work without a valid license for testing purposes.

TTranscoder wavReader;
wavReader
    .allowDemoMode(true)
    .addInput(
        TMediaSocket()
            .file(opt.inputFile)
    )
    .addOutput(
        TMediaSocket()
            .streamType(StreamType::LPCM)
            .addPin(
                TMediaPin()
                    .streamInfo(TAudioStreamInfo()
                        .streamType(StreamType::LPCM)
                        .channels(2)
                        .sampleRate(48000)
                        .bitsPerSample(16)
                    )
            )
    )
    .open();

Configure Transcoder and Encode

This is the main encoding function that coordinates the entire MP3 encoding process. It creates both the WAV reader (to provide raw audio frames) and the MP3 encoder (to compress those frames). The function demonstrates the push-based encoding workflow where raw PCM samples are pulled from the WAV reader and pushed to the MP3 encoder. The encoding loop continues until all audio data has been processed, with proper error handling and end-of-stream detection.

bool encode(const Options& opt)
{
    deleteFile(opt.outputFile.c_str());

    try {
        // WAV reader: reads PCM from WAV file
        TTranscoder wavReader;
        wavReader
            .allowDemoMode(true)
            .addInput(
                TMediaSocket()
                    .file(opt.inputFile)
            )
            .addOutput(
                TMediaSocket()
                    .streamType(StreamType::LPCM)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::LPCM)
                                .channels(2)
                                .sampleRate(48000)
                                .bitsPerSample(16)
                            )
                    )
            )
            .open();

        // Encoder: receives pushed PCM, writes MP3 to file
        TTranscoder encoder;
        encoder
            .allowDemoMode(true)
            .addInput(
                TMediaSocket()
                    .streamType(StreamType::LPCM)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::LPCM)
                                .channels(2)
                                .sampleRate(48000)
                                .bitsPerSample(16)
                            )
                    )
            )
            .addOutput(
                TMediaSocket()
                    .file(opt.outputFile)
                    .streamType(StreamType::MPEG_Audio)
                    .streamSubType(StreamSubType::MPEG_Audio_Layer3)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::MPEG_Audio)
                                .streamSubType(StreamSubType::MPEG_Audio_Layer3)
                            )
                    )
            )
            .open();

        // Push loop: pull PCM from wavReader, push to encoder
        int32_t wavOutputIndex = 0;
        TMediaSample sample;

        for (;;)
        {
            if (wavReader.pull(wavOutputIndex, sample))
            {
                if (!encoder.push(0, sample))
                {
                    printError("Encoder push", encoder.error());
                    wavReader.close();
                    encoder.close();
                    return false;
                }
            }
            else
            {
                auto error = wavReader.error();
                if (error.facility() == primo::error::ErrorFacility::Codec &&
                    error.code()     == primo::codecs::CodecError::EOS)
                {
                    encoder.pushEos(0);
                    break;
                }
                else
                {
                    printError("WAV reader pull", error);
                    wavReader.close();
                    encoder.close();
                    return false;
                }
            }
        }

        wavReader.close();
        encoder.close();

        std::println("Output: {}", opt.outputFile);
        return true;

    } catch (const TAVBlocksException& ex) {
        std::println(stderr, "AVBlocks error: {}", ex.what());
        return false;
    } catch (const std::exception& ex) {
        std::println(stderr, "Error: {}", ex.what());
        return false;
    }
}

Complete C++ Code

Here’s the complete working example that demonstrates MP3 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, and properly shuts down the library before exiting.

#include <primo/avblocks/avb++.h>

#include <print>

#include "options.h"
#include "util.h"

using namespace primo::codecs;
using namespace primo::avblocks::modern;

bool encode(const Options& opt)
{
    deleteFile(opt.outputFile.c_str());

    try {
        // WAV reader: reads PCM from WAV file
        TTranscoder wavReader;
        wavReader
            .allowDemoMode(true)
            .addInput(
                TMediaSocket()
                    .file(opt.inputFile)
            )
            .addOutput(
                TMediaSocket()
                    .streamType(StreamType::LPCM)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::LPCM)
                                .channels(2)
                                .sampleRate(48000)
                                .bitsPerSample(16)
                            )
                    )
            )
            .open();

        // Encoder: receives pushed PCM, writes MP3 to file
        TTranscoder encoder;
        encoder
            .allowDemoMode(true)
            .addInput(
                TMediaSocket()
                    .streamType(StreamType::LPCM)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::LPCM)
                                .channels(2)
                                .sampleRate(48000)
                                .bitsPerSample(16)
                            )
                    )
            )
            .addOutput(
                TMediaSocket()
                    .file(opt.outputFile)
                    .streamType(StreamType::MPEG_Audio)
                    .streamSubType(StreamSubType::MPEG_Audio_Layer3)
                    .addPin(
                        TMediaPin()
                            .streamInfo(TAudioStreamInfo()
                                .streamType(StreamType::MPEG_Audio)
                                .streamSubType(StreamSubType::MPEG_Audio_Layer3)
                            )
                    )
            )
            .open();

        // Push loop: pull PCM from wavReader, push to encoder
        int32_t wavOutputIndex = 0;
        TMediaSample sample;

        for (;;)
        {
            if (wavReader.pull(wavOutputIndex, sample))
            {
                if (!encoder.push(0, sample))
                {
                    printError("Encoder push", encoder.error());
                    wavReader.close();
                    encoder.close();
                    return false;
                }
            }
            else
            {
                auto error = wavReader.error();
                if (error.facility() == primo::error::ErrorFacility::Codec &&
                    error.code()     == primo::codecs::CodecError::EOS)
                {
                    encoder.pushEos(0);
                    break;
                }
                else
                {
                    printError("WAV reader pull", error);
                    wavReader.close();
                    encoder.close();
                    return false;
                }
            }
        }

        wavReader.close();
        encoder.close();

        std::println("Output: {}", opt.outputFile);
        return true;

    } catch (const TAVBlocksException& ex) {
        std::println(stderr, "AVBlocks error: {}", ex.what());
        return false;
    } catch (const std::exception& ex) {
        std::println(stderr, "Error: {}", ex.what());
        return false;
    }
}

int main(int argc, char* argv[])
{
    Options opt;
    switch(prepareOptions(opt, argc, argv))
    {
        case Command: return 0;
        case Error: return 1;
        case Parsed: break;
    }

    TLibrary library;
    return encode(opt) ? 0 : 1;
}

How to Run

See the build instructions for macOS and the enc_mp3_push example for details.

Command Line

./enc_mp3_push --input <wav file> --output <mp3 file>

Examples

List options:

./bin/x64/enc_mp3_push --help
Usage: enc_mp3_push --input <wav file> --output <mp3 file>
  -h,    --help
  -i,    --input    input WAV file
  -o,    --output   output MP3 file

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

mkdir -p ./output/enc_mp3_push

./bin/x64/enc_mp3_push \
    --input ./assets/aud/equinox-48KHz.wav \
    --output ./output/enc_mp3_push/equinox-48KHz.mp3