# Intel Media FFMPEG Transcode Container This project hosts a container demonstrating the use of ffmpeg using GPU offload for transcode operations. # Usage examples ## Build the container Build the container: ```bash docker build . -t intel-media-ffmpeg ``` or you can pull the container from Harbor: ```bash docker pull amr-registry.caas.intel.com/vtt-osgc/solutions/intel-media-ffmpeg docker tag amr-registry.caas.intel.com/vtt-osgc/solutions/intel-media-ffmpeg intel-media-ffmpeg ``` ## Download test content You will need some test content to run the following tests. You can download a test stream here: ```bash [ ! -e media ] && mkdir media wget -q -O media/AUD_MW_E.264 https://fate-suite.libav.org/h264-conformance/AUD_MW_E.264 ``` ## Run a set of tests in sequence The following will decode, encode, and transcode the sample content: ```bash HOST_MEDIA=$(pwd)/media [ ! -e "${HOST_MEDIA}/AUD_MW_E.264" ] && { mkdir -p ${HOST_MEDIA} wget -q -O ${HOST_MEDIA}/AUD_MW_E.264 https://fate-suite.libav.org/h264-conformance/AUD_MW_E.264 } tests=( "decode AUD_MW_E.264 AUD_MW.yuv" "encode AUD_MW.yuv AUD_MW_E.h264" "transcode AUD_MW_E.264 AUD_MW_E.hevc" "1N_transcode AUD_MW_E.264 AUD_1N" ) for test in "${tests[@]}"; do test=($test) docker run --rm \ --volume ${HOST_MEDIA}:/media -it \ --device=/dev/dri -e QSV_DEVICE=${QSV_DEVICE:-/dev/dri/D128} \ intel-media-ffmpeg ${test[*]} || { echo "Error: Test failed: ${test[*]}" break } done ``` The above will download the file AUD_MW_E.264 and then perform various actions on it. If successful, looking in $(HOST_MEDIA} should show the following files (all with data in them.) ```bash $ ls -1tr "${HOST_MEDIA}" AUD_MW_E.264 AUD_MW.yuv AUD_MW_E.h264 AUD_MW_E.hevc AUD_1N-5M.mp4 AUD_1N-4M60FPS.h264 ``` ## Launch a shell in the container The examples below are all assumed to be running in the container's environment: ```bash docker run \ --rm \ --device=/dev/dri \ -e QSV_DEVICE=${QSV_DEVICE:-/dev/dri/renderD128} \ -it \ intel-media-ffmpeg \ shell ``` ## Decode AVC (H.264) video decode and save as YUV 420P raw file: ```bash IN_FILE=AUD_WM_E.264 OUT_FILE=AUD_MW.yuv HOST_MEDIA=$(pwd)/media docker run \ --rm \ --device /dev/dri \ --volume ${HOST_MEDIA}:/media \ -it \ intel-media-ffmpeg \ ffmpeg \ -hwaccel qsv \ -qsv_device ${QSV_DEVICE:-/dev/dri/renderD128} \ -c:v h264_qsv \ -i /media/"${IN_FILE}" \ -vf hwdownload,format=nv12 -pix_fmt yuv420p \ -y \ /media/"${OUT_FILE}" ``` ## Encode Encode a 10 frames of 720p raw input as H264 with 5Mbps using VBR mode: ```bash IN_FILE=AUD_MW.yuv OUT_FILE=AUD_MW_E.h264 HOST_MEDIA=$(pwd)/media docker run \ --rm \ --device /dev/dri \ --volume ${HOST_MEDIA}:/media \ -it \ intel-media-ffmpeg \ ffmpeg \ -loglevel debug \ -init_hw_device vaapi=va:${QSV_DEVICE:-/dev/dri/renderD128} \ -init_hw_device qsv=hw@va \ -filter_hw_device hw \ -f rawvideo \ -pix_fmt yuv420p \ -s:v 176x144 \ -i /media/"${IN_FILE}" \ -vf hwupload=extra_hw_frames=64,format=qsv \ -c:v h264_qsv \ -b:v 5M \ -frames:v 10 \ -y \ /media/"${OUT_FILE}" ``` ## Transcode ### AVC (H.264) => HEVC (H.265) with 5Mbps using VBR ```bash IN_FILE=AUD_MW_E.264 OUT_FILE=AUD_MW_E.hevc HOST_MEDIA=$(pwd)/media docker run \ --rm \ --device=/dev/dri \ --volume ${HOST_MEDIA}:/media \ -it \ intel-media-ffmpeg \ ffmpeg \ -loglevel debug \ -hwaccel qsv \ -qsv_device ${QSV_DEVICE:-/dev/dri/renderD128} \ -c:v h264_qsv \ -i /media/"${IN_FILE}" \ -c:v hevc_qsv \ -b:v 5M \ -y \ /media/"${OUT_FILE}" ``` ### 1:N transcoding ```bash IN_FILE=AUD_MW_E.264 OUT_FILE=AUD_1N_ HOST_MEDIA=$(pwd)/media docker run \ --rm \ --device=/dev/dri \ --volume ${HOST_MEDIA}:/media \ -it \ intel-media-ffmpeg \ ffmpeg \ -hwaccel qsv \ -qsv_device ${QSV_DEVICE} \ -c:v h264_qsv \ -i /media/"${IN_FILE}" \ -filter_complex "split=2[s1][s2]; \ [s1]scale_qsv=1280:720[o1]; \ [s2]vpp_qsv=framerate=60[o2]" \ -map [o1] -c:v h264_qsv -b:v 5M /media/"${OUT_FILE}-5M.mp4" \ -map [o2] -c:v h264_qsv -b:v 4M /media/"${OUT_FILE}-4M60FPS.h264" ``` ## Developing The Dockerfile itself is constructed from re-usable snippets, located in the templates/ directory, and can be regenerated by running: ```bash scripts/build-dockerfile ``` The above script uses environment substitution to stamp version information within the created Dockerfile. The files which declare the environment variables are in **SOLUTION** and **MANIFEST**. After joining the template/* pieces together, the file **Dockerfile.solution** is then added to the Dockerfile with environment substitution. ## SOLUTION Solution specific definitions: CONTAINER_IMAGE is used as the container tag name OS_DISTRO is used as the base OS distribution. Possible values: ubuntu OS_RELEASE is used as the OS version. Possible values: disco, eoan ## MANIFEST The version of MANIFEST is created by the set of Agama packages from the Agama repository and name-mangling them to be a VERSION declaration: For example: libgl1-mesa-glx_19.0.1-agama-109_amd64.deb is changed to: LIBGL1_MESA_GLX_VERSION=19.0.1-agama-109 The script you can use to recreate the OS_RELEASE defined in SOLUTION is: NOTE: This only works on the Ubuntu releases. ```bash AGAMA_VERSION=169 . SOLUTION echo "AGAMA_VERSION=${AGAMA_VERSION}" > MANIFEST wget -q -O - \ https://osgc.jf.intel.com/packages/agama/ubuntu/dists/${OS_RELEASE}/main/binary-amd64/Packages.bz2 | bunzip2 | sed -nE 's/^(Package|Version): (.*)/\2/p' | paste -s -d' \n' | while read package version rest; do package=$(echo $package | sed -E -e s#-#_#g -e 's#(.*)#\U\1#g')_VERSION echo $package=$version done | grep ${AGAMA_VERSION}\$ >> MANIFEST ``` This allows the Dockerfile templates to then version pin Agama packages: ```Dockerfile RUN apt-get install -y libgl1-mesa-glx=$LIBGL1_MESA_GLX_VERSION ``` The `scripts/build-dockerfile` loads MANIFEST, which defines LIBGL1_MESA_GLX_VERSION. That is then subsituted for the version in the above Dockerfile snippet when being placed into the main Dockerfile. # Tagging If the build succeeds, we want to be able to tag the git project as well as corresponding Docker images with the appropriate Agama tag: ```bash . MANIFEST ; git tag -f agama-${AGAMA_VERSION} ``` # Appendix A: Multicard Most of the filters and drivers for ffmpeg will default to connecting to /dev/dri/renderD128. If you have multiple cards, the card you want to connect to might be exposed on a different render interface. You can configure which interface is used by setting the QSV_DEVICE environment variable prior to running intel-docker (or by passing -e QSV_DEVICE to docker if you run it manually.) You can find out the correct path for your Intel Graphics card by running: ``` ls -l /dev/dri/by-path/pci-*$(lspci | grep Intel.*Graphics | cut -d " " -f1)* ``` If the interface is on /dev/dri/renderD129, set QSV_DEVICE as follows: ``` export QSV_DEVICE=/dev/dri/renderD129 ```