HEVC / H.265 Annex B Decoder (Run)

This article explains how you can use Transcoder.run to decode a HEVC / H.265 Annex B elementary stream to a raw YUV video file.

The code snippets in this article are from the dec_hevc_file Python sample.

Source Video

For source we use a HEVC-encoded video file. After downloading and unzipping the AVBlocks Assets archive, you can find HEVC test files in the vid subdirectory.

Code

This code takes a compressed HEVC elementary stream file and decodes it to raw YUV video format.

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 Library.initialize() method sets up the internal state and loads necessary codecs. Always remember to call Library.shutdown() at the end of your program to properly clean up resources and release any allocated memory.

Library.initialize()

# Set license information. Without this AVBlocks runs in Demo mode.
# Library.set_license("<license-string>")

result = decode(input_file, output_file)

Library.shutdown()

sys.exit(0 if result else 2)

Configure Input Socket

The input socket defines the source HEVC elementary stream. We use MediaInfo to automatically detect the video parameters from the input file. This is more convenient than manually specifying parameters, as MediaInfo reads the stream headers and extracts all necessary information about the encoded video, including resolution, frame rate, and codec settings.

# Open input file with MediaInfo
media_info = MediaInfo()
media_info.inputs[0].file = input_file

if not media_info.open():
    print_error("MediaInfo.open()", media_info.error)
    return False

in_socket = MediaSocket.from_media_info(media_info)

Configure Output Socket

The output socket defines the raw YUV video format for the decoded output. We specify the output file path, set the stream type to UncompressedVideo, and configure the video pin with ColorFormat.YUV420 for standard 4:2:0 YUV planar format.

def create_output_socket(output_file: str) -> MediaSocket:
    """Create output socket for YUV file."""
    socket = MediaSocket()
    socket.file = output_file
    socket.stream_type = StreamType.UncompressedVideo

    pin = MediaPin()
    socket.pins.add(pin)

    vsi = VideoStreamInfo()
    pin.stream_info = vsi

    vsi.stream_type = StreamType.UncompressedVideo
    vsi.color_format = ColorFormat.YUV420

    return socket

Configure and Run Transcoder

This section shows how to set up the transcoder with the input and output sockets, then run the decoding operation. The allow_demo_mode = True property allows the transcoder to work even without a valid license (useful for testing, but not recommended for production). We add our input and output sockets to the transcoder, open it to prepare for processing, run the actual transcoding operation, and finally close it to clean up.

def decode(input_file: str, output_file: str) -> bool:
    """Decode H.265/HEVC file to YUV."""
    # Transcoder will fail if output exists (by design)
    delete_file(output_file)

    # Create output directory if it doesn't exist
    output_dir = os.path.dirname(output_file)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # Open input file with MediaInfo
    media_info = MediaInfo()
    media_info.inputs[0].file = input_file

    if not media_info.open():
        print_error("MediaInfo.open()", media_info.error)
        return False

    in_socket = MediaSocket.from_media_info(media_info)
    out_socket = create_output_socket(output_file)

    # Create transcoder
    with Transcoder() as transcoder:
        transcoder.allow_demo_mode = True
        transcoder.inputs.add(in_socket)
        transcoder.outputs.add(out_socket)

        res = transcoder.open()
        print_error("Transcoder open", transcoder.error)
        if not res:
            return False

        res = transcoder.run()
        print_error("Transcoder run", transcoder.error)
        if not res:
            return False

        transcoder.close()
        print_error("Transcoder close", transcoder.error)

    return True

Complete Code

Here’s the complete working example that demonstrates HEVC/H.265 decoding using AVBlocks for Python. This code combines all the previous snippets into a functional program that can be run.

"""
Decode H.265/HEVC file to YUV.

This sample decodes an H.265/HEVC video file to raw YUV format using AVBlocks.
"""

import os
import sys
import click

from avblocks import (
    Library, Transcoder, MediaInfo, MediaSocket, MediaPin,
    VideoStreamInfo, StreamType, ColorFormat, ErrorFacility
)


def delete_file(filename: str):
    """Delete a file if it exists."""
    try:
        if os.path.exists(filename):
            os.remove(filename)
    except OSError:
        pass


def print_error(action: str, error) -> None:
    """Print error information."""
    if action:
        print(f"{action}: ", end="")

    if error.facility == ErrorFacility.Success:
        print("Success")
        return

    print(f"{error.message or ''}, facility:{error.facility} code:{error.code} hint:{error.hint or ''}")


def create_output_socket(output_file: str) -> MediaSocket:
    """Create output socket for YUV file."""
    socket = MediaSocket()
    socket.file = output_file
    socket.stream_type = StreamType.UncompressedVideo

    pin = MediaPin()
    socket.pins.add(pin)

    vsi = VideoStreamInfo()
    pin.stream_info = vsi

    vsi.stream_type = StreamType.UncompressedVideo
    vsi.color_format = ColorFormat.YUV420

    return socket


def decode(input_file: str, output_file: str) -> bool:
    """Decode H.265/HEVC file to YUV."""
    # Transcoder will fail if output exists (by design)
    delete_file(output_file)

    # Create output directory if it doesn't exist
    output_dir = os.path.dirname(output_file)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # Open input file with MediaInfo
    media_info = MediaInfo()
    media_info.inputs[0].file = input_file

    if not media_info.open():
        print_error("MediaInfo.open()", media_info.error)
        return False

    in_socket = MediaSocket.from_media_info(media_info)
    out_socket = create_output_socket(output_file)

    # Create transcoder
    with Transcoder() as transcoder:
        transcoder.allow_demo_mode = True
        transcoder.inputs.add(in_socket)
        transcoder.outputs.add(out_socket)

        res = transcoder.open()
        print_error("Transcoder open", transcoder.error)
        if not res:
            return False

        res = transcoder.run()
        print_error("Transcoder run", transcoder.error)
        if not res:
            return False

        transcoder.close()
        print_error("Transcoder close", transcoder.error)

    return True


@click.command()
@click.option('-i', '--input', 'input_file',
              help='Input HEVC / H.265 file',
              type=click.Path(exists=True))
@click.option('-o', '--output', 'output_file',
              help='Output YUV file',
              type=click.Path())
def main(input_file: str, output_file: str):
    """Decode H.265/HEVC file to YUV."""

    # Set default options if not provided
    if not input_file or not output_file:
        script_dir = os.path.dirname(os.path.abspath(__file__))

        if not input_file:
            input_file = os.path.join(script_dir, "../../assets/vid/foreman_qcif.h265")
        if not output_file:
            output_file = os.path.join(script_dir, "../../output/dec_hevc_file/foreman_qcif.yuv")

        print("Using default options:")
        print(f"  --input {input_file}")
        print(f"  --output {output_file}")
        print()

    # Validate options
    print(f"--input: {input_file}")
    print(f"--output: {output_file}")

    Library.initialize()

    # Set license information. Without this AVBlocks runs in Demo mode.
    # Library.set_license("<license-string>")

    result = decode(input_file, output_file)

    Library.shutdown()

    sys.exit(0 if result else 2)


if __name__ == '__main__':
    main()

How to Run

See the Python sample for details. The samples are installed as command-line tools when you install the package from the repository root with uv pip install --editable . (see Installation).

Command Line

dec-hevc-file --input <file.h265> --output <file.yuv>

Examples

List options:

dec-hevc-file --help
Usage: dec-hevc-file [OPTIONS]

  Decode H.265/HEVC file to YUV.

Options:
  -i, --input PATH   Input HEVC / H.265 file
  -o, --output PATH  Output YUV file
  --help             Show this message and exit.

Decode the input file ./assets/vid/foreman_qcif.h265 into output file ./output/dec_hevc_file/foreman_qcif.yuv:

# Linux and macOS
mkdir -p ./output/dec_hevc_file

dec-hevc-file \
    --input ./assets/vid/foreman_qcif.h265 \
    --output ./output/dec_hevc_file/foreman_qcif.yuv
# Windows
New-Item -ItemType Directory -Force -Path ./output/dec_hevc_file

dec-hevc-file `
    --input ./assets/vid/foreman_qcif.h265 `
    --output ./output/dec_hevc_file/foreman_qcif.yuv