Render yuv420p Frames in OpenGL with GPU-accelerated conversion


Rendering YUV frames is pretty frequent when it comes to video streams. For instance, frames coming from a camera or frames resulting from the decoding of compressed streams may be in some YUV format, for instance yuv420p. It happened to me quite some times that I needed to convert those frames to some RGB format and also render them in an OpenGL scene in an efficient way.

GPU

The simplest technique is to convert (as efficiently as possible) the frames to RGB and then upload the RGB frames to OpenGL. Another option is to upload YUV frames directly to the GPU and implement the conversion in an OpenGL shader, which should be even more efficient, and should also result in a lower bandwidth during the transfer. I created a simple demo app in Qt/OpenGL that does exactly this: https://github.com/carlonluca/qtyuv.

Shader

The computation is entirely done in the shader:

uniform sampler2DRect s_texture_y;
uniform sampler2DRect s_texture_u;
uniform sampler2DRect s_texture_v;
uniform lowp float qt_Opacity;
varying highp vec2 vTexCoord;

void main()
{
    float Y = texture2DRect(s_texture_y, vTexCoord).r;
    float U = texture2DRect(s_texture_u, vec2(vTexCoord.x/2., vTexCoord.y/2.)).r - 0.5;
    float V = texture2DRect(s_texture_v, vec2(vTexCoord.x/2., vTexCoord.y/2.)).r - 0.5;
    vec3 color = vec3(Y, U, V);
    mat3 colorMatrix = mat3(
                1,   0,       1.402,
                1,  -0.344,  -0.714,
                1,   1.772,   0);
    gl_FragColor = vec4(color*colorMatrix, 1.)*qt_Opacity;
}

The three planes in the yuv420p frame are uploaded to the GPU and the shader multiplies the vector by a conversion matrix:

$$\begin{bmatrix}R \\ G \\ B \end{bmatrix}=\begin{bmatrix}1 & 0 & 1.402 \\ 1 & -0.344 & -0.714 \\ 1 & 1.772 & 0 \end{bmatrix} \cdot \begin{bmatrix}Y \\ U \\ V \end{bmatrix}$$

Demo

In https://github.com/carlonluca/qtyuv there is a demo written in C++ using Qt5. If you want to run it, first create some yuv420p stream file. If you want, you can create it quickly with ffmpeg by transcoding the small sample in the repo:

ffmpeg -i coffee_run.mkv -pix_fmt yuv420p -f rawvideo coffee_run.raw

The result is around 2GB, so I did not include it in the repo.

Then, you’ll have to build the demo (I tested with Qt 5.15.2):

mkdir build
cd build
cmake ..
make

The you can run it:

./Qt5Yuv ../coffee_run.raw 2048x858 24

If you pass -h you’ll see what the options mean:

./Qt5Yuv --help
Usage: ./Qt5Yuv [options] source size fps
Test helper

Options:
  -h, --help     Displays help on commandline options.
  --help-all     Displays help including Qt specific options.
  -v, --version  Displays version information.

Arguments:
  source         Raw yuv420p file
  size           Size with the format widthxheight
  fps            Framerate in frames per second

Have fun! 😉

Leave a Reply

Your email address will not be published. Required fields are marked *