Create a Command Line Tool using CMake on macOS

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.

The code snippets used in this article are from the simple_converter macOS 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

C++ Compiler (clang):

clang --version

CMake:

cmake --version

ninja:

ninja --version

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

Create the project directory

mkdir -p ~/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.16)

project(simple-converter)

add_executable(simple-converter src/main.cpp)

Add build.sh:

#!/usr/bin/env bash

mkdir -p ./build/debug
pushd ./build/debug
    cmake -G 'Ninja' -DCMAKE_BUILD_TYPE=Debug  ../.. && \
    ninja
    ret=$?
popd  

Add .gitignore:

.DS_Store
.cache/
build/

You should end up with the following directory structure:

tree -a -L 2 simple-converter

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

Test the build

chmod +x build.sh

./build.sh

Update for AVBlocks Plus

  1. Download the 64 bit version of AVBlocks Core (macOS) that matches your Mac. Run uname -m to check: x86_64 means Intel, arm64 means Apple Silicon. The file you need will have a name similar to the following, except for the version number which may be different:

    Architecture

    File

    Intel (x86_64)

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

    Apple Silicon (arm64)

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

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

    AVBLOCKS_BASE_URL="https://github.com/avblocks/avblocks-core/releases/download"
    AVBLOCKS_VERSION="v3.4.0-demo.1"
    
    # "darwin" for Intel (x86_64), "darwin-arm64" for Apple Silicon (arm64)
    AVBLOCKS_PLATFORM="darwin"
    
    curl -L \
      -o "avblocks-${AVBLOCKS_VERSION}-${AVBLOCKS_PLATFORM}.zip" \
      "${AVBLOCKS_BASE_URL}/${AVBLOCKS_VERSION}/avblocks-${AVBLOCKS_VERSION}-${AVBLOCKS_PLATFORM}.zip"
    
  2. Extract the ZIP 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. The AVBlocks Plus samples use std::println, which shim23.h provides for C++20 builds. Download shim23.h and save it in a shim directory next to the src directory.

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

    mkdir -p ./avblocks
    unzip -q "avblocks-${AVBLOCKS_VERSION}-${AVBLOCKS_PLATFORM}.zip" -d ./avblocks
    

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

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

    .DS_Store
    .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})
    
    # 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()
    
    # definitions for Debug
    target_compile_definitions(${target} PUBLIC  _DEBUG)
    
    # compile options for Debug
    target_compile_options(${target} PRIVATE -std=c++20 -stdlib=libc++)
    if(AVBLOCKS_ARCH STREQUAL "x64")
        target_compile_options(${target} PRIVATE -m64)
    endif()
    target_compile_options(${target} PRIVATE -fPIC)
    target_compile_options(${target} PRIVATE -g)
    
    # includes
    target_include_directories(${target} PRIVATE 
        avblocks/include
    )
    
    # libs
    target_link_directories(${target} PRIVATE 
        ${PROJECT_SOURCE_DIR}/avblocks/lib/${AVBLOCKS_ARCH}
    )
    target_link_libraries(${target} 
        libAVBlocks.dylib 
        "-framework CoreFoundation" 
        "-framework AppKit"
    )
    
    # sources
    file(GLOB source "src/*.cpp" "src/*.mm")
    target_sources(${target} PRIVATE ${source})
    
  5. Replace the contents of src/main.cpp with this code:

    #include <primo/avblocks/avb++.h>
    
    #include <cstdio>
    #include <print>
    
    #include "../shim/shim23.h"
    
    using namespace primo::codecs;
    using namespace primo::avblocks::modern;
    
    int main()
    {
        TLibrary library;
    
        const char* inputFile  = "Wildlife_h264_aac.mp4";
        const char* outputFile = "Wildlife_h265_aac.mp4";
    
        // transcoder will fail if output exists (by design)
        std::remove(outputFile);
    
        try {
            // Probe the input file and create the input socket from the probe
            auto input = TMediaSocket(TMediaInfo().file(inputFile).open());
    
            // Demo mode is allowed when using the demo version of the library.
            TTranscoder()
                .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::println("Output: {}", outputFile);
            return 0;
    
        } catch (const TAVBlocksException& ex) {
            std::println(stderr, "AVBlocks error: {}", ex.what());
            return 1;
        }
    }
    
  6. Build the project

    ./build.sh
    

Run the application

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

    curl \
    -L -o Wildlife_h264_aac.mp4 \
    https://archive.org/download/WildlifeSampleVideo/Wildlife.mp4
    
  2. Run the application:

    ./build/debug/simple-converter
    

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

Troubleshooting

  • 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.