MP3 Encoder (Pull)¶
This article explains how to use TTranscoder::pull to encode a WAV file to an MP3 audio stream.
The code snippets in this article are from the enc_mp3_pull 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 MP3 using the pull method. The encoded samples are pulled from the transcoder and written to the output file by the application.
Initialize AVBlocks¶
Initialize the AVBlocks library before creating the transcoder, and shut it down after encoding is complete.
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 Output Socket¶
The output socket describes the MP3 stream. The stream info defines the codec.
TMediaSocket()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
)
)
Configure Transcoder and Pull Encoded Samples¶
The sample creates the input socket from the WAV file name only, because AVBlocks detects the audio format automatically. It also creates the output socket, then creates a transcoder, enables demo mode, opens it, pulls encoded samples, writes each sample buffer to the output file, and checks for end-of-stream. The output socket has no file set because the application writes the pulled samples itself.
bool encode(const Options& opt)
{
deleteFile(opt.outputFile.c_str());
std::ofstream outfile(opt.outputFile, std::ios_base::binary);
if (!outfile.is_open())
{
std::println(stderr, "Could not open file: {}", opt.outputFile);
return false;
}
try {
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.inputFile)
)
.addOutput(
// No file on the output socket — we pull samples manually
TMediaSocket()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
)
)
)
.open();
int32_t outputIndex = 0;
TMediaSample sample;
while (transcoder.pull(outputIndex, sample))
{
auto buf = sample.buffer();
outfile.write(reinterpret_cast<const char*>(buf.data()), buf.dataSize());
}
const auto error = transcoder.error();
bool success = (error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS);
if (!success)
printError("Transcoder pull", error);
transcoder.close();
if (success)
std::println("Output: {}", opt.outputFile);
return success;
} 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 from enc_mp3_pull.cpp:
#include <primo/avblocks/avb++.h>
#include <print>
#include <fstream>
#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());
std::ofstream outfile(opt.outputFile, std::ios_base::binary);
if (!outfile.is_open())
{
std::println(stderr, "Could not open file: {}", opt.outputFile);
return false;
}
try {
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(
TMediaSocket()
.file(opt.inputFile)
)
.addOutput(
// No file on the output socket — we pull samples manually
TMediaSocket()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
.addPin(
TMediaPin()
.streamInfo(TAudioStreamInfo()
.streamType(StreamType::MPEG_Audio)
.streamSubType(StreamSubType::MPEG_Audio_Layer3)
)
)
)
.open();
int32_t outputIndex = 0;
TMediaSample sample;
while (transcoder.pull(outputIndex, sample))
{
auto buf = sample.buffer();
outfile.write(reinterpret_cast<const char*>(buf.data()), buf.dataSize());
}
const auto error = transcoder.error();
bool success = (error.facility() == primo::error::ErrorFacility::Codec &&
error.code() == primo::codecs::CodecError::EOS);
if (!success)
printError("Transcoder pull", error);
transcoder.close();
if (success)
std::println("Output: {}", opt.outputFile);
return success;
} 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_pull example for details.
Command Line¶
./enc_mp3_pull --input <wav file> --output <mp3 file>
Examples¶
List options:
./bin/x64/enc_mp3_pull --help
Usage: enc_mp3_pull --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_pull
./bin/x64/enc_mp3_pull \
--input ./assets/aud/equinox-48KHz.wav \
--output ./output/enc_mp3_pull/equinox-48KHz.mp3