AVC / H.264 Annex B Parser
Contents
AVC / H.264 Annex B Parser#
How to use Transcoder.Pull to parse an AVC / H.264 Annex B elementary stream.
Video Input#
As video input we use the foreman_qcif.h264
file from the AVBlocks Assets archive. After downloading and unzipping you will find foreman_qcif.h264
in the vid
subdirectory.
Code#
This code takes an H.264 stream encoded according to ISO/IEC 14496 - 10 Annex B, and prints the header of each NAL (Network Abstraction Layer) unit.
Initialize AVBlocks#
static void ParseH264Stream()
{
Library.Initialize();
ParseH264Stream("foreman_qcif.h264");
Library.Shutdown();
}
Configure Transcoder#
static void ParseH264Stream(string inputFile)
{
// Create an input socket from file
var inSocket = new MediaSocket() {
File = inputFile
};
// Create an output socket with one video pin
var outSocket = new MediaSocket();
outSocket.Pins.Add(new MediaPin() {
StreamInfo = new VideoStreamInfo()
});
// Create Transcoder
using (var transcoder = new Transcoder())
{
transcoder.Inputs.Add(inSocket);
transcoder.Outputs.Add(outSocket);
if (transcoder.Open())
{
ParseH264Stream(transcoder);
transcoder.Close();
}
}
}
Call Transcoder.Pull#
static void ParseH264Stream(Transcoder transcoder)
{
int fromInput = 0;
var accessUnit = new MediaSample();
while (transcoder.Pull(out fromInput, accessUnit))
{
// Each call to Transcoder.Pull returns one Access Unit.
// The Access Unit may contain one or more NAL units.
ParseAccessUnit(accessUnit.Buffer);
}
}
Complete Program#
using System;
using System.Linq;
using PrimoSoftware.AVBlocks;
namespace H264Parser
{
class Program
{
// Network Abstraction Layer Unit Definitions
enum NALUType : byte
{
UNSPEC = 0, // Unspecified
SLICE = 1, // Coded slice of a non-IDR picture
DPA = 2, // Coded slice data partition A
DPB = 3, // Coded slice data partition B
DPC = 4, // Coded slice data partition C
IDR = 5, // Coded slice of an IDR picture
SEI = 6, // Supplemental enhancement information
SPS = 7, // Sequence parameter set
PPS = 8, // Picture parameter set
AUD = 9, // Access unit delimiter
EOSEQ = 10, // End of sequence
EOSTREAM = 11, // End of stream
FILL = 12 // Filler data
};
enum NALUPriority : byte
{
DISPOSABLE = 0,
LOW = 1,
HIGH = 2,
HIGHEST = 3,
};
struct NALUHeader
{
public byte Data {
private get;
set;
}
public byte ForbiddenBit {
get {
// 1 bit: (10000000)b = (80)h
return (byte)((Data & 0x80) >> 7);
}
}
public NALUPriority NalUnitRefIDC
{
get {
// 2 bits: (01100000)b = (60)h
return (NALUPriority)((Data & 0x60) >> 5);
}
}
public NALUType NalUnitType
{
get {
// 5 bits: (00011111)b = (1F)h
return (NALUType)(Data & 0x1F);
}
}
};
static int naluIndex = 0;
static void PrintNALUHeader(MediaBuffer buffer)
{
var header = new NALUHeader() {
Data = buffer.Bytes[buffer.DataOffset]
};
Console.WriteLine("NALU Index : {0}", naluIndex);
Console.WriteLine("NALU Type : {0}", header.NalUnitType);
Console.WriteLine("NALU Reference IDC : {0}", header.NalUnitRefIDC);
Console.WriteLine();
naluIndex++;
}
//
// The pseudo logic from ISO/IEC 14496 - 10 Annex B:
//
// byte_stream_nal_unit(NumBytesInNALunit) {
// while (next_bits(24) != 0x000001 &&
// next_bits(32) != 0x00000001)
// leading_zero_8bits /* equal to 0x00 */
//
// if (next_bits(24) != 0x000001)
// zero_byte /* equal to 0x00 */
//
// if (more_data_in_byte_stream()) {
// start_code_prefix_one_3bytes /* equal to 0x000001 */
// nal_unit(NumBytesInNALunit)
// }
//
// while (more_data_in_byte_stream() &&
// next_bits(24) != 0x000001 &&
// next_bits(32) != 0x00000001)
// trailing_zero_8bits /* equal to 0x00 */
// }
//
static void ParseAccessUnit(MediaBuffer buffer)
{
// This parsing code assumes that MediaBuffer contains
// a single Access Unit of one or more complete NAL Units
while (buffer.DataSize > 1)
{
int dataOffset = buffer.DataOffset;
int dataSize = buffer.DataSize;
byte[] data = buffer.Bytes.Skip(dataOffset).Take(4).ToArray();
// is this a NALU with a 3 byte start code prefix
if (dataSize >= 3 &&
0x00 == data[0] &&
0x00 == data[1] &&
0x01 == data[2])
{
// advance in the buffer
buffer.SetData(dataOffset + 3, dataSize - 3);
PrintNALUHeader(buffer);
}
// OR is this a NALU with a 4 byte start code prefix
else if (dataSize >= 4 &&
0x00 == data[0] &&
0x00 == data[1] &&
0x00 == data[2] &&
0x01 == data[3])
{
// advance in the buffer
buffer.SetData(dataOffset + 4, dataSize - 4);
PrintNALUHeader(buffer);
}
else
{
// advance in the buffer
buffer.SetData(dataOffset + 1, dataSize - 1);
}
// NOTE: Some NALUs may have a trailing zero byte. The `while`
// condition `buffer.DataSize > 1` will effectively
// skip the trailing zero byte.
}
}
static void ParseH264Stream(Transcoder transcoder)
{
int fromInput = 0;
var accessUnit = new MediaSample();
while (transcoder.Pull(out fromInput, accessUnit))
{
// Each call to Transcoder.Pull returns one Access Unit.
// The Access Unit may contain one or more NAL units.
ParseAccessUnit(accessUnit.Buffer);
}
}
static void ParseH264Stream(string inputFile)
{
// Create an input socket from file
var inSocket = new MediaSocket() {
File = inputFile
};
// Create an output socket with one video pin
var outSocket = new MediaSocket();
outSocket.Pins.Add(new MediaPin() {
StreamInfo = new VideoStreamInfo()
});
// Create Transcoder
using (var transcoder = new Transcoder())
{
transcoder.Inputs.Add(inSocket);
transcoder.Outputs.Add(outSocket);
if (transcoder.Open())
{
ParseH264Stream(transcoder);
transcoder.Close();
}
}
}
static void ParseH264Stream()
{
Library.Initialize();
ParseH264Stream("foreman_qcif.h264");
Library.Shutdown();
}
static void Main(string[] args)
{
ParseH264Stream();
}
}
}
How to run#
Follow the steps to create a C# console application in Visual Studio but in Program.cs
use the code from this article.
Copy the foreman_qcif.h264
file from the assets archive to bin/x64/Debug/net6.0
under the project’s directory.
Run the application in Visual Studio.