Create a Command Line Tool in Xcode on macOS

This topic describes the steps needed to configure an Objective-C Command Line Tool in Xcode.

The code snippets used in this article are from the simple_converter macOS sample.

Create the Xcode project

  1. Open Xcode, select Create New Project, pick macOS as a platform, pick Command Line Tool for Application

  2. Set Product Name to simple-converter, select Objective-C for language.

  3. Rename main.m to main.mm.

  4. 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 Xcode 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"
    
  5. Extract the ZIP archive in a location of your choice, then copy the include and lib directories to the avblocks subdirectory of the Xcode project directory. The Xcode project directory is the directory that contains the simple-converter.xcodeproj project 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 directory that contains main.mm.

    If you downloaded the archive to the Xcode 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
    ├── avblocks
    │   ├── include
    │   └── lib
    ├── simple-converter
    │   └── main.mm
    └── simple-converter.xcodeproj
    
  6. In Xcode, select the simple-converter project in Xcode, and then the ‘Build Settings’ tab:

    • Under Apple Clang - Language - C++, set the C++ Language Dialect to C++20[-std=c++20]

    • Under Search Paths | Header Search Paths, add the $(PROJECT_DIR)/avblocks/include directory to the list

    • Under linking - General | Runpath Search Paths, add @executable_path to the list

    • Set the Build Products Path to $(PROJECT_DIR)/build

  7. In Xcode, select the ‘simple-converter’ target, and then the ‘Build Phases’ tab:

    • Expand the ‘Link Binary with Libraries’ section

    • Add the libAVBlocks.dylib from the $(PROJECT_DIR)/avblocks/lib/arm64 directory on Apple Silicon, or $(PROJECT_DIR)/avblocks/lib/x64 on Intel.

  8. Replace the contents of main.mm 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;
        }
    }
    
  9. Restart Xcode!!! Otherwise it will not pick the new Build Products Path

  10. Build the project ( ⌘B )

  11. Copy the file libAVBlocks.dylib from avblocks/lib/arm64 (Apple Silicon) or avblocks/lib/x64 (Intel) to build/Debug.

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 in Xcode. Wait for the Transcoder to finish - it will take a few minutes. The converted file Wildlife_h265_aac.mp4 will be in the build/Debug directory.

Troubleshooting

  • You may get dyld: Library not loaded: @executable_path/libAVBlocks.dylib or a similar message. To fix that, copy the file libAVBlocks.dylib from avblocks/lib to build/Debug.

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