Create a Command Line Tool using CMake on Windows

This topic describes the steps needed to configure a CMake project for an AVBlocks Plus Command Line Tool. AVBlocks Plus requires a C++20 compiler. Scripts are PowerShell.

The code snippets used in this article are from the simple_converter Windows sample. This sample takes H.264/AVC video and AAC audio in MP4 container as input and converts them to H.265/HEVC video and AAC audio in MP4 container.

Test that you have all tools installed

CMake:

cmake --version

ninja:

ninja --version

If you don’t have those tools follow the steps in the Setup C++ development environment on Windows post to configure a C++ development environment.

Create the project directory

mkdir ~/avblocks/cmake/simple-converter

Create the CMake project

Switch to the project directory:

cd ~/avblocks/cmake/simple-converter

Add src/main.cpp:

#include <iostream>

int main() {
  std::cout << "Hello AVBlocks!\n";
}

Add CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)

project(simple-converter)

add_executable(simple-converter src/main.cpp)

Add .gitignore:

.cache/
build/

Add build.ps1:

New-Item -Force -Path ./build/debug -ItemType Directory 
Push-Location ./build/debug
    cmake -G 'Ninja' -DCMAKE_BUILD_TYPE=debug ../..
    ninja
Pop-Location

Add configure.ps1:

$tempFile = [IO.Path]::GetTempFileName()

# get the path to Visual Studio 2022
$vs_install_dir = $(Get-VSSetupInstance | Select-VSSetupInstance -Version '[17.0,18.0]' | Select-Object -ExpandProperty InstallationPath)
$vs_common_tools = "${vs_install_dir}/Common7/Tools/"

# use the C++ compiler for the host architecture: x64 (amd64) or ARM64 (arm64)
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }

# run VsDevCmd.bat for the C++ compiler and save the environment to a file
cmd /c " `"$vs_common_tools/VsDevCmd.bat`" -arch=$arch -host_arch=$arch && set > `"$tempFile`""

# set the environment into PowerShell
Get-Content $tempFile | Foreach-Object {
    if($_ -match "^(.*?)=(.*)$") {
        Set-Content "env:\$($matches[1])" $matches[2]
    }
}

Remove-Item $tempFile

You should end up with the following directory structure:

simple-converter
├── .gitignore
├── CMakeLists.txt
├── build.ps1
├── configure.ps1
└── src
    └── main.cpp

Test the build

# source configure.ps1
. .\configure.ps1

# build
./build.ps1

Update for AVBlocks Plus

  1. Download the 64 bit version of AVBlocks Core (Windows) that matches your machine. Check $env:PROCESSOR_ARCHITECTURE: AMD64 means x64, ARM64 means ARM64. The file you need will have a name similar to the following, except for the version number which may be different:

    Architecture

    File

    x64 (AMD64)

    avblocks-v3.4.0-demo.1-windows.zip

    ARM64

    avblocks-v3.4.0-demo.1-windows-arm64.zip

    You can also download it with PowerShell from the project directory:

    $BaseUrl = "https://github.com/avblocks/avblocks-core/releases/download"
    $Version = "v3.4.0-demo.1"
    
    # "windows" for x64 (AMD64), "windows-arm64" for ARM64
    $Platform = "windows"
    
    Invoke-WebRequest `
        -Uri "${BaseUrl}/${Version}/avblocks-${Version}-${Platform}.zip" `
        -OutFile "avblocks-${Version}-${Platform}.zip"
    
  2. Extract the archive in a location of your choice, then copy the include and lib directories to the avblocks subdirectory of the CMake project directory. The CMake project directory is the directory that contains the CMakeLists.txt file. AVBlocks Plus also needs the header file avb++.h. Download it from the avblocks-plus repository and save it in the avblocks/include/primo/avblocks directory.

    If you downloaded the archive to the project directory with PowerShell, you can extract it directly into the avblocks subdirectory:

    Expand-Archive `
        -Path "avblocks-${Version}-${Platform}.zip" `
        -DestinationPath "./avblocks" -Force
    

    You should end up with a directory structure similar to the following:

    simple-converter
    ├── .gitignore
    ├── CMakeLists.txt
    ├── avblocks
    │   ├── include
    │   └── lib
    ├── build.ps1
    ├── configure.ps1
    └── src
        └── main.cpp
    
  3. Replace the contents of .gitignore with this code:

    .cache/
    build/
    avblocks/
    
  4. Replace the contents of CMakeLists.txt with this code:

    cmake_minimum_required(VERSION 3.16)
    
    project(simple_converter)
    set (target simple_converter)
    
    add_executable(${target})
    
    # common definitions
    target_compile_definitions(${target} PRIVATE UNICODE _UNICODE)
    
    # debug definitions
    target_compile_definitions(${target} PRIVATE  _DEBUG)
    
    # debug compile options
    target_compile_options(${target} PRIVATE /Zi /Od)
    
    # enable C++ 20 standard features
    target_compile_features(${target} PRIVATE cxx_std_20)
    
    # include dirs
    target_include_directories(${target} PRIVATE avblocks/include)
    
    # architecture: the AVBlocks libraries are in avblocks/lib/x64 or avblocks/lib/arm64
    if(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
        set(AVBLOCKS_ARCH arm64)
    else()
        set(AVBLOCKS_ARCH x64)
    endif()
    
    # libs
    target_link_directories(${target} PRIVATE avblocks/lib/${AVBLOCKS_ARCH})
    target_link_libraries(${target} AVBlocks64.dll)
    
    # add /MTd or /MT compiler option 
    set_property(TARGET ${target} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
    
    # sources
    file(GLOB source "src/*.cpp")
    target_sources(${target} PRIVATE ${source})
    
  5. Replace the contents of src/main.cpp with this code:

    #include <primo/avblocks/avb++.h>
    
    #include <cstdio>
    #include <iostream>
    
    using namespace primo::codecs;
    using namespace primo::avblocks::modern;
    
    int wmain()
    {
        TLibrary library;
    
        const wchar_t* inputFile  = L"Wildlife_h264_aac.mp4";
        const wchar_t* outputFile = L"Wildlife_h265_aac.mp4";
    
        // transcoder will fail if output exists (by design)
        _wremove(outputFile);
    
        try {
            // Probe the input file and create the input socket from the probe
            auto input = TMediaSocketW(TMediaInfoW().file(inputFile).open());
    
            // Demo mode is allowed when using the demo version of the library.
            TTranscoderW()
                .allowDemoMode(true)
                .addInput(input)
                // The output starts as a copy of the input
                .addOutput(
                    input.clone()
                        .file(outputFile)
                        .videoInfoRef(0, [](TVideoStreamInfo& video) {
                            // Change the output video stream to H.265/HEVC
                            // Input is H.264/AVC at 700 kbps
                            // With H.265/HEVC we can use lower bitrate, e.g. 500 kbps
                            video.streamType(StreamType::H265)
                                 .streamSubType(StreamSubType::HEVC_Annex_B)
                                 .bitrate(500'000);
                        })
                )
                .transcode();
    
            std::wcout << L"Output: " << outputFile << std::endl;
            return 0;
    
        } catch (const TAVBlocksException& ex) {
            std::wcerr << L"AVBlocks error: " << ex.what() << std::endl;
            return 1;
        }
    }
    
  6. Build the project

    . ./configure.ps1
    ./build.ps1
    

Run the application

  1. Download the Wildlife_h264_aac.mp4 sample file and save it in the project directory:

    Invoke-WebRequest `
    -Uri https://archive.org/download/WildlifeSampleVideo/Wildlife.mp4 `
    -OutFile Wildlife_h264_aac.mp4
    
  2. Copy the file AVBlocks64.dll from avblocks/lib/x64 (or avblocks/lib/arm64 on ARM64) to build/debug.

  3. Run the application:

    ./build/debug/simple-converter
    

    Wait for the Transcoder to finish - it will take a few seconds. The converted file Wildlife_h265_aac.mp4 will be in the project directory.

Troubleshooting

  • You may get The program can't start because AVBlocks64.dll is missing from your computer. Try reinstalling the program to fix this problem. or a similar message. To fix that, copy the file AVBlocks64.dll from avblocks/lib/x64 (or avblocks/lib/arm64 on ARM64) to build/debug.

  • TTranscoder::open may fail if there is already a file Wildlife_h265_aac.mp4 in the project directory. Delete Wildlife_h265_aac.mp4 to solve that.