AVC / H.264 Access Unit Decoder¶
This article explains how you can use TTranscoder::push to decode AVC / H.264 access units to raw YUV video frames.
The code snippets in this article are from the dec_avc_au macOS sample.
Linux and Windows samples are also available:
Source Video¶
As video input we use the foreman_qcif.h264.au directory from the AVBlocks Assets archive. After downloading and unzipping you will find foreman_qcif.h264.au in the vid subdirectory. The directory contains AVC / H.264 access units named au_####.h264.
Code¶
This code reads a sequence of AVC / H.264 access-unit files, pushes each access unit into a TTranscoder, and writes decoded raw YUV video frames to a file.
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 object shuts the library down and cleans up resources when it 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 decode(opt) ? 0 : 1;
}
Configure Output Socket¶
The output socket is configured for uncompressed video. The sample uses the color format, frame size, and frame rate from the command line when provided. If the output file is not specified, it creates a default decoded_<width>x<height>.yuv file under output/dec_avc_au.
// Build output video stream info
TVideoStreamInfo outVsi;
outVsi
.streamType(StreamType::UncompressedVideo)
.colorFormat(opt.output_color.Id == ColorFormat::Unknown
? ColorFormat::YUV420
: opt.output_color.Id)
.frameWidth(yuv_width)
.frameHeight(yuv_height)
.scanType(ScanType::Progressive);
if (opt.fps > 0)
outVsi.frameRate(opt.fps);
static string buildOutputPath(Options& opt, int yuv_width, int yuv_height)
{
if (!opt.output_file.empty())
return opt.output_file;
fs::path dir(getExeDir() + "/../../output/dec_avc_au");
fs::create_directories(dir);
ostringstream s;
s << dir.c_str() << "/decoded_" << yuv_width << "x" << yuv_height << ".yuv";
return s.str();
}
Configure Transcoder¶
The sample uses TMediaInfo on the first access-unit file to detect the input stream properties. It then creates an input socket from that media information, creates the output socket, and opens the transcoder.
// Use MediaInfo to detect stream parameters from the first AU file
ostringstream s;
s << opt.input_dir << "/au_" << setw(4) << setfill('0') << 0 << ".h264";
TMediaInfo mediaInfo;
mediaInfo.inputs(0).file(s.str());
mediaInfo.open();
// Create push-mode transcoder
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(TMediaSocket(mediaInfo))
.addOutput(
TMediaSocket()
.file(outputFile)
.streamType(StreamType::UncompressedVideo)
.addPin(TMediaPin().streamInfo(outVsi))
)
.open();
Push Access Units¶
The main transcode loop reads files named au_####.h264 from the input directory. Each file is loaded into a TMediaBuffer, attached to a TMediaSample, and pushed into the transcoder. After all access units are pushed, the sample flushes the transcoder to write delayed decoded frames.
// Push AU files one by one
for (int i = 0; ; ++i)
{
ostringstream auPath;
auPath << opt.input_dir << "/au_" << setw(4) << setfill('0') << i << ".h264";
vector<uint8_t> auData = readFileBytes(auPath.str().c_str());
if (auData.empty())
break;
TMediaSample sample;
sample.buffer(TMediaBuffer().attach(auData.data(), auData.size()));
if (!transcoder.push(0, sample))
{
printError("Transcoder push", transcoder.error());
transcoder.close();
return false;
}
}
if (!transcoder.flush())
{
printError("Transcoder flush", transcoder.error());
transcoder.close();
return false;
}
transcoder.close();
Complete C++ Code¶
Here’s the complete working example that demonstrates AVC / H.264 access-unit decoding using AVBlocks. This code combines all the previous snippets into a functional program that can be compiled and run.
#include <primo/avblocks/avb++.h>
#include <print>
#include <iomanip>
#include <sstream>
#include <filesystem>
#include "options.h"
#include "util.h"
using namespace primo::codecs;
using namespace primo::avblocks::modern;
using namespace std;
namespace fs = std::filesystem;
static string buildOutputPath(Options& opt, int yuv_width, int yuv_height)
{
if (!opt.output_file.empty())
return opt.output_file;
fs::path dir(getExeDir() + "/../../output/dec_avc_au");
fs::create_directories(dir);
ostringstream s;
s << dir.c_str() << "/decoded_" << yuv_width << "x" << yuv_height << ".yuv";
return s.str();
}
bool decode(Options& opt)
{
try {
// Use MediaInfo to detect stream parameters from the first AU file
ostringstream s;
s << opt.input_dir << "/au_" << setw(4) << setfill('0') << 0 << ".h264";
TMediaInfo mediaInfo;
mediaInfo.inputs(0).file(s.str());
mediaInfo.open();
// Resolve output frame dimensions from detected stream info, or override from options
auto vsi = mediaInfo.outputs(0).pins(0).videoStreamInfo();
int yuv_width = opt.frame_size.width > 0 ? opt.frame_size.width : vsi.frameWidth();
int yuv_height = opt.frame_size.height > 0 ? opt.frame_size.height : vsi.frameHeight();
string outputFile = buildOutputPath(opt, yuv_width, yuv_height);
deleteFile(outputFile.c_str());
// Build output video stream info
TVideoStreamInfo outVsi;
outVsi
.streamType(StreamType::UncompressedVideo)
.colorFormat(opt.output_color.Id == ColorFormat::Unknown
? ColorFormat::YUV420
: opt.output_color.Id)
.frameWidth(yuv_width)
.frameHeight(yuv_height)
.scanType(ScanType::Progressive);
if (opt.fps > 0)
outVsi.frameRate(opt.fps);
// Create push-mode transcoder
TTranscoder transcoder;
transcoder
.allowDemoMode(true)
.addInput(TMediaSocket(mediaInfo))
.addOutput(
TMediaSocket()
.file(outputFile)
.streamType(StreamType::UncompressedVideo)
.addPin(TMediaPin().streamInfo(outVsi))
)
.open();
// Push AU files one by one
for (int i = 0; ; ++i)
{
ostringstream auPath;
auPath << opt.input_dir << "/au_" << setw(4) << setfill('0') << i << ".h264";
vector<uint8_t> auData = readFileBytes(auPath.str().c_str());
if (auData.empty())
break;
TMediaSample sample;
sample.buffer(TMediaBuffer().attach(auData.data(), auData.size()));
if (!transcoder.push(0, sample))
{
printError("Transcoder push", transcoder.error());
transcoder.close();
return false;
}
}
if (!transcoder.flush())
{
printError("Transcoder flush", transcoder.error());
transcoder.close();
return false;
}
transcoder.close();
println("Output: {}", outputFile);
return true;
} catch (const TAVBlocksException& ex) {
println(stderr, "AVBlocks error: {}", ex.what());
return false;
} catch (const exception& ex) {
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 decode(opt) ? 0 : 1;
}
How to Run¶
See the build instructions for macOS and the dec_avc_au example for details.
Command Line¶
./dec_avc_au --input <directory> [--output <file>] [--frame <width>x<height>] [--rate <fps>] [--color <COLOR>] [--colors]
Examples¶
List options:
./bin/x64/dec_avc_au --help
Usage: dec_avc_au --input <directory> [--output <file>] [--frame <width>x<height>] [--rate <fps>] [--color <COLOR>] [--colors]
-h, --help
-i, --input input directory (contains sequence of compressed file)
-o, --output output YUV file
-r, --rate frame rate
-c, --color output color format. Use --colors to list all supported color
formats
--colors list COLOR formats
The following command reads H.264 access-unit files under the assets/vid/foreman_qcif.h264.au/ directory and decodes them to raw video frames. The decoded video is written to output/dec_avc_au/decoded_176x144.yuv:
mkdir -p ./output/dec_avc_au
./bin/x64/dec_avc_au \
--input ./assets/vid/foreman_qcif.h264.au \
--output ./output/dec_avc_au/decoded_176x144.yuv \
--color yuv420