MP3 Encoder (Pull)¶
This article explains how to use Transcoder::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;
}
Library::initialize();
bool result = encode(opt);
Library::shutdown();
return result ? 0 : 1;
}
Configure Output Socket¶
The output socket describes the MP3 stream. The stream info defines the codec, and the optional comments show how to change the bitrate, sampling rate, channels, and stereo mode.
primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
// create stream info to describe the output audio stream
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::MPEG_Audio);
asi->setStreamSubType(StreamSubType::MPEG_Audio_Layer3);
// The default bitrate is 128000. You can set it to 192000, 256000, etc.
// asi->setBitrate(192000);
// Optionally set the sampling rate and the number of the channels, e.g. 44.1 Khz, Mono
// asi->setSampleRate(44100);
// asi->setChannels(1);
// create a pin using the stream info
auto pin = primo::make_ref(Library::createMediaPin());
pin->setStreamInfo(asi.get());
// the pin allows you to specify additional parameters for the encoder
// for example, change the stereo mode, e.g. Joint Stereo
// pin->params()->addInt(Param::Encoder::Audio::MPEG1::StereoMode, StereoMode::Joint);
// finally create a socket for the output container format which is MP3 in this case
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::MPEG_Audio);
socket->setStreamSubType(StreamSubType::MPEG_Audio_Layer3);
socket->pins()->add(pin.get());
return socket;
}
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(Options& opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
std::ofstream outfile(opt.outputFile, ios_base::binary);
if (!outfile.is_open())
{
cout << "Could not open file " << opt.outputFile << endl;
return false;
}
// create input socket
primo::ref<MediaSocket> inSocket (Library::createMediaSocket());
inSocket->setFile(primo::ustring(opt.inputFile));
// create output socket
auto outSocket = createOutputSocket(opt);
// create transcoder
auto transcoder = primo::make_ref(Library::createTranscoder());
transcoder->setAllowDemoMode(true);
transcoder->inputs()->add(inSocket.get());
transcoder->outputs()->add(outSocket.get());
if (!transcoder->open())
{
printError("Transcoder::open", transcoder->error());
return false;
}
// encode by pulling encoded samples
int32_t outputIndex = 0;
primo::ref<MediaSample> sample(Library::createMediaSample());
while (transcoder->pull(outputIndex, sample.get()))
{
outfile.write((const char *)sample->buffer()->data(), sample->buffer()->dataSize());
}
const primo::error::ErrorInfo *error = transcoder->error();
printError("Transcoder::pull", error);
bool success = false;
if ((error->facility() == primo::error::ErrorFacility::Codec) &&
(error->code() == primo::codecs::CodecError::EOS))
{
// ok
success = true;
}
transcoder->close();
return success;
}
Complete C++ Code¶
Here’s the complete working example from enc_mp3_pull.cpp:
#include <primo/avblocks/avb.h>
#include <primo/platform/reference++.h>
#include <primo/platform/error_facility.h>
#include <primo/platform/ustring.h>
#include "util.h"
#include "options.h"
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
using namespace primo::codecs;
using namespace primo::avblocks;
using namespace std;
primo::ref<MediaSocket> createOutputSocket(Options& opt)
{
// create stream info to describe the output audio stream
auto asi = primo::make_ref(Library::createAudioStreamInfo());
asi->setStreamType(StreamType::MPEG_Audio);
asi->setStreamSubType(StreamSubType::MPEG_Audio_Layer3);
// The default bitrate is 128000. You can set it to 192000, 256000, etc.
// asi->setBitrate(192000);
// Optionally set the sampling rate and the number of the channels, e.g. 44.1 Khz, Mono
// asi->setSampleRate(44100);
// asi->setChannels(1);
// create a pin using the stream info
auto pin = primo::make_ref(Library::createMediaPin());
pin->setStreamInfo(asi.get());
// the pin allows you to specify additional parameters for the encoder
// for example, change the stereo mode, e.g. Joint Stereo
// pin->params()->addInt(Param::Encoder::Audio::MPEG1::StereoMode, StereoMode::Joint);
// finally create a socket for the output container format which is MP3 in this case
auto socket = primo::make_ref(Library::createMediaSocket());
socket->setStreamType(StreamType::MPEG_Audio);
socket->setStreamSubType(StreamSubType::MPEG_Audio_Layer3);
socket->pins()->add(pin.get());
return socket;
}
bool encode(Options& opt)
{
// transcoder will fail if output exists (by design)
deleteFile(primo::ustring(opt.outputFile));
std::ofstream outfile(opt.outputFile, ios_base::binary);
if (!outfile.is_open())
{
cout << "Could not open file " << opt.outputFile << endl;
return false;
}
// create input socket
primo::ref<MediaSocket> inSocket (Library::createMediaSocket());
inSocket->setFile(primo::ustring(opt.inputFile));
// create output socket
auto outSocket = createOutputSocket(opt);
// create transcoder
auto transcoder = primo::make_ref(Library::createTranscoder());
transcoder->setAllowDemoMode(true);
transcoder->inputs()->add(inSocket.get());
transcoder->outputs()->add(outSocket.get());
if (!transcoder->open())
{
printError("Transcoder::open", transcoder->error());
return false;
}
// encode by pulling encoded samples
int32_t outputIndex = 0;
primo::ref<MediaSample> sample(Library::createMediaSample());
while (transcoder->pull(outputIndex, sample.get()))
{
outfile.write((const char *)sample->buffer()->data(), sample->buffer()->dataSize());
}
const primo::error::ErrorInfo *error = transcoder->error();
printError("Transcoder::pull", error);
bool success = false;
if ((error->facility() == primo::error::ErrorFacility::Codec) &&
(error->code() == primo::codecs::CodecError::EOS))
{
// ok
success = true;
}
transcoder->close();
return success;
}
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 = encode(opt);
Library::shutdown();
return result ? 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