Note: This is a more technical post than usual, and about 5 months late.
The decoding in the OBE C-100 decoder was optimised to make use of instructions in modern CPUs and this blog post explains how we did it.
HD-SDI video uses 10-bit pixels but computers operate in bytes (8-bits). However, 10-bit professional video doesn’t fit nicely into bytes. Instead, 10-bit video on a computer is stored in memory like this:
The X represents an unused bit – note how in total 12 out of 32 of the bits are unused (that’s 37.5%). It’s very wasteful if the data needs to be transferred to a piece of hardware like a Blackmagic SDI card.
Virtually all professional SDI cards use the ‘v210’ format that was first introduced by Apple in the 90s [1] and v210 improves the efficiency of 10-bit storage by packing the 10-bit video samples as follows:
(adapted from [1])
Now only 2 out of the 32-bits are unused, a major improvement. Using the old v210 encoder in FFmpeg, each pixel is loaded from memory, shifted to the correct position and “inserted” using the OR operation. When doing this on 1920×1080 material, this involves about 250 million of these operations every second. More CPU time is spent packing the pixels for display than actually decompressing them from the encoded video!
Clearly, we’ve got to do something about this – Thanks to the magic of SIMD instructions (in this case SSSE3 and AVX) we can instead process 12 pixels in one go [2]:
- Load luma pixels from memory
- Make sure they are within the v210 range
- Shift each pixel (if necessary) to appropriate position
- Shuffle pixels to rearrange them to v210 order
- Repeat 1-4 for chroma
- OR the luma and chroma registers together
- Store in memory
This can be (unscientifically) benchmarked with the command:
ffmpeg -pix_fmt yuv422p10 -s 1920x1080 -f rawvideo -i /dev/zero -f rawvideo -vcodec v210 -y /dev/null
Before: 168fps
After: 480fps
A 3x speed boost.
But, a lot of content that the decoder receives is 8-bit which has this packing format:
In existing software decoders, this needs to be converted to the 10-bit samples in the first picture and then packed into v210, a two step process. But, we can now just do this in a single step.
ffmpeg -pix_fmt yuv422p -s 1920x1080 -f rawvideo -i /dev/zero -f rawvideo -vcodec v210 -y /dev/null
Before: 95fps
After: 620fps
What more could be done:
- Allow the decoder to decode straight to v210 using FFmpeg’s draw_horiz_band capability.
- Try using AVX2 on newer Haswell CPUs – should provide a small speed increase but with an increased complexity.
- Use multiple CPU cores on the conversion – this isn’t really useful for OBE but people creating v210 files may find it useful (especially UHD content).
Thanks must go to those who helped review this code.
(This is from Apple’s venerable Letters from the Ice Floe)
[2] http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavcodec/x86/v210enc.asm