HEVC / H.265 Access Unit Decoder¶
This article explains how you can use {transcoder-push-net} to decode HEVC / H.265 access units to raw YUV video frames.
The code snippets in this article are from the dec_hevc_au .NET sample.
Source Video¶
As video input we use the foreman_qcif.h265.au directory from the AVBlocks Assets archive. After downloading and unzipping you will find foreman_qcif.h265.au in the vid subdirectory. The directory contains HEVC access units named au_####.h265.
Code¶
This code reads a sequence of HEVC access-unit files, pushes each access unit into a Transcoder, 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. Call Library.Shutdown() before the program exits to clean up resources.
static int Main(string[] args)
{
var opt = new Options();
if (!opt.Prepare(args))
return opt.Error ? (int)ExitCodes.OptionsError : (int)ExitCodes.Success;
Library.Initialize();
// Set license information. To run AVBlocks in demo mode, comment the next line out
// Library.SetLicense("<license-string>");
bool decodeResult = DecodeAUs(opt);
Library.Shutdown();
return decodeResult ? (int)ExitCodes.Success : (int)ExitCodes.DecodingError;
}
Create Output Socket¶
The output socket is configured for uncompressed video. The sample uses the color format from the command line when provided. The output file path is specified on the command line.
static MediaSocket CreateOutputSocket(Options opt)
{
MediaSocket socket = new MediaSocket();
socket.File = opt.OutputFile;
socket.StreamType = StreamType.UncompressedVideo;
MediaPin pin = new MediaPin();
socket.Pins.Add(pin);
VideoStreamInfo vsi = new VideoStreamInfo();
pin.StreamInfo = vsi;
vsi.StreamType = StreamType.UncompressedVideo;
vsi.ColorFormat = opt.ColorId;
vsi.ScanType = ScanType.Progressive;
return socket;
}
Configure Transcoder¶
The sample uses MediaInfo on the first access-unit file to detect the input stream properties. It then creates an input socket from that media information, clears the file and stream from the socket, creates the output socket, and opens the transcoder.
static bool ConfigureTranscoder(Transcoder transcoder, string auFile, Options opt)
{
using (var mediaInfo = new MediaInfo())
{
mediaInfo.Inputs[0].File = auFile;
if (!mediaInfo.Open())
{
PrintError("MediaInfo open", mediaInfo.Error);
return false;
}
// create input socket from media info
MediaSocket inSocket = MediaSocket.FromMediaInfo(mediaInfo);
inSocket.File = null;
inSocket.Stream = null;
// create output socket
MediaSocket outSocket = CreateOutputSocket(opt);
transcoder.Inputs.Add(inSocket);
transcoder.Outputs.Add(outSocket);
}
return true;
}
Push Access Units¶
The main transcode loop reads files named au_####.h265 from the input directory. Each file is loaded into a MediaBuffer, attached to a MediaSample, and pushed into the transcoder. After all access units are pushed, the sample flushes the transcoder to write delayed decoded frames.
static bool DecodeAUs(Options opt)
{
// delete output file if exists
DeleteFile(opt.OutputFile);
// create output directory
string outputDir = Path.GetDirectoryName(opt.OutputFile);
if (!string.IsNullOrEmpty(outputDir))
Directory.CreateDirectory(outputDir);
string firstAuFile = BuildAuPath(opt, 0);
if (!File.Exists(firstAuFile))
{
Console.WriteLine("First AU file not found: " + firstAuFile);
return false;
}
using (var transcoder = new Transcoder())
{
transcoder.AllowDemoMode = true;
// configure transcoder using first AU file
if (!ConfigureTranscoder(transcoder, firstAuFile, opt))
return false;
if (!transcoder.Open())
{
PrintError("Transcoder open", transcoder.Error);
return false;
}
// process all AU files
for (int i = 0; ; i++)
{
string auFile = BuildAuPath(opt, i);
if (!File.Exists(auFile))
break;
var sample = new MediaSample();
sample.Buffer = new MediaBuffer(File.ReadAllBytes(auFile));
if (!transcoder.Push(0, sample))
{
PrintError("Transcoder push", transcoder.Error);
return false;
}
}
if (!transcoder.Flush())
{
PrintError("Transcoder flush", transcoder.Error);
return false;
}
transcoder.Close();
}
Console.WriteLine("Output file: " + opt.OutputFile);
return true;
}
Complete Code¶
Here’s the complete working example that demonstrates HEVC / H.265 access-unit decoding using AVBlocks for .NET.
using System;
using System.IO;
using PrimoSoftware.AVBlocks;
namespace CliSample
{
class Program
{
static int Main(string[] args)
{
var opt = new Options();
if (!opt.Prepare(args))
return opt.Error ? (int)ExitCodes.OptionsError : (int)ExitCodes.Success;
Library.Initialize();
// Set license information. To run AVBlocks in demo mode, comment the next line out
// Library.SetLicense("<license-string>");
bool decodeResult = DecodeAUs(opt);
Library.Shutdown();
return decodeResult ? (int)ExitCodes.Success : (int)ExitCodes.DecodingError;
}
static bool DecodeAUs(Options opt)
{
// delete output file if exists
DeleteFile(opt.OutputFile);
// create output directory
string outputDir = Path.GetDirectoryName(opt.OutputFile);
if (!string.IsNullOrEmpty(outputDir))
Directory.CreateDirectory(outputDir);
string firstAuFile = BuildAuPath(opt, 0);
if (!File.Exists(firstAuFile))
{
Console.WriteLine("First AU file not found: " + firstAuFile);
return false;
}
using (var transcoder = new Transcoder())
{
transcoder.AllowDemoMode = true;
// configure transcoder using first AU file
if (!ConfigureTranscoder(transcoder, firstAuFile, opt))
return false;
if (!transcoder.Open())
{
PrintError("Transcoder open", transcoder.Error);
return false;
}
// process all AU files
for (int i = 0; ; i++)
{
string auFile = BuildAuPath(opt, i);
if (!File.Exists(auFile))
break;
var sample = new MediaSample();
sample.Buffer = new MediaBuffer(File.ReadAllBytes(auFile));
if (!transcoder.Push(0, sample))
{
PrintError("Transcoder push", transcoder.Error);
return false;
}
}
if (!transcoder.Flush())
{
PrintError("Transcoder flush", transcoder.Error);
return false;
}
transcoder.Close();
}
Console.WriteLine("Output file: " + opt.OutputFile);
return true;
}
static bool ConfigureTranscoder(Transcoder transcoder, string auFile, Options opt)
{
using (var mediaInfo = new MediaInfo())
{
mediaInfo.Inputs[0].File = auFile;
if (!mediaInfo.Open())
{
PrintError("MediaInfo open", mediaInfo.Error);
return false;
}
// create input socket from media info
MediaSocket inSocket = MediaSocket.FromMediaInfo(mediaInfo);
inSocket.File = null;
inSocket.Stream = null;
// create output socket
MediaSocket outSocket = CreateOutputSocket(opt);
transcoder.Inputs.Add(inSocket);
transcoder.Outputs.Add(outSocket);
}
return true;
}
static string BuildAuPath(Options opt, int index)
{
string pattern = "au_{0:0000}.h265";
string path = Path.Combine(opt.InputDir, string.Format(pattern, index));
return path;
}
static MediaSocket CreateOutputSocket(Options opt)
{
MediaSocket socket = new MediaSocket();
socket.File = opt.OutputFile;
socket.StreamType = StreamType.UncompressedVideo;
MediaPin pin = new MediaPin();
socket.Pins.Add(pin);
VideoStreamInfo vsi = new VideoStreamInfo();
pin.StreamInfo = vsi;
vsi.StreamType = StreamType.UncompressedVideo;
vsi.ColorFormat = opt.ColorId;
vsi.ScanType = ScanType.Progressive;
return socket;
}
static void DeleteFile(string filename)
{
try
{
if (File.Exists(filename))
File.Delete(filename);
}
catch { }
}
static void PrintError(string action, ErrorInfo e)
{
if (action != null)
{
Console.Write("{0}: ", action);
}
if (ErrorFacility.Success == e.Facility)
{
Console.WriteLine("Success");
return;
}
else
{
Console.WriteLine("{0}, facility:{1} code:{2} hint:{3}", e.Message ?? "", e.Facility, e.Code, e.Hint ?? "");
}
}
enum ExitCodes : int
{
Success = 0,
OptionsError = 1,
DecodingError = 2,
}
}
}
How to Run¶
See the build instructions for macOS and the dec_hevc_au .NET sample for details.
Command Line¶
dec_hevc_au --input <directory> --output <file.yuv> [--color <COLOR>]
Examples¶
List options:
./bin/net10.0/dec_hevc_au --help
List supported color formats:
./bin/net10.0/dec_hevc_au --colors
Decode the H.265 Access Units from ./assets/vid/foreman_qcif.h265.au/ into output file ./output/dec_hevc_au/foreman_qcif.yuv:
# Linux and macOS
mkdir -p ./output/dec_hevc_au
./bin/net10.0/dec_hevc_au \
--input ./assets/vid/foreman_qcif.h265.au \
--output ./output/dec_hevc_au/foreman_qcif.yuv \
--color yuv420
# Windows
mkdir -Force -Path ./output/dec_hevc_au
./bin/net10.0/dec_hevc_au `
--input ./assets/vid/foreman_qcif.h265.au `
--output ./output/dec_hevc_au/foreman_qcif.yuv `
--color yuv420