{"id":13446,"date":"2023-05-03T09:41:48","date_gmt":"2023-05-03T07:41:48","guid":{"rendered":"https:\/\/www.linphone.org\/?post_type=actualites&#038;p=13446"},"modified":"2024-12-02T12:38:58","modified_gmt":"2024-12-02T11:38:58","slug":"optimised-video-stream-rendering-with-opengl","status":"publish","type":"actualites","link":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/","title":{"rendered":"Optimised video stream rendering with OpenGL"},"content":{"rendered":"<p>This article offers a technical insight into how the video of a VoIP call is rendered on a screen. While this may appear to be a rather basic operation at first glance, it is not, as it requires very CPU-intensive computations to convert the pixel format. By offloading these computations onto the graphics card, we can save a lot of CPU bandwidth, which can then be allocated to the video encoder, thus enabling better overall quality.\u00a0Our software developer Thibault, who used to work on this project with the Liblinphone team and who is now part of the Flexisip team, has written the following explanation for you.<\/p>\n<p><strong><em>A developer&#8217;s story time<\/em><\/strong><\/p>\n<p><span style=\"color: #ff5e00;\"><u><strong>OpenGL, Colourspaces, and Linphone<\/strong><\/u><\/span><\/p>\n<p><span style=\"color: #ff5e00;\"><strong>What is OpenGL? What is Y&#8217;CbCr? And why does Linphone need any of this?<\/strong><\/span><\/p>\n<img decoding=\"async\" src=\"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png\" alt=\"\" \/>\n<p>In this article, we will focus on the very last part of this pipeline:<strong>\u00a0the video renderer.<\/strong><\/p>\n<p>The renderer takes its input from the video\u00a0decoder\u00a0(H.264, VP8, etc.) and paints its output to a graphical buffer, which is the rectangle you see on your screen (the one with the smiling face of your friend).<\/p>\n<p>This is where we encounter Y&#8217;CbCr and colourspaces. For technical reasons, the output of a video decoder is a frame in a Y&#8217;CbCr colourspace.<br \/>\nMost programmers and digital artists are used to representing images in the RGB colourspace, which is a grid of pixels with three channels: the red, green, and blue channels (and sometimes an alpha channel for transparency).<br \/>\nA Y&#8217;CbCr data frame is almost the same, except it encodes colour with three different channels: luma, chroma blue, and chroma red.<\/p>\n<p>At this point, and for details on how this colourspace works, I encourage you to read the summary and the Rationale section of\u00a0the\u00a0<span style=\"color: #ff5e00;\"><a style=\"color: #ff5e00;\" href=\"https:\/\/en.wikipedia.org\/wiki\/YCbCr\"><strong><u><em>Wikipedia article on Y&#8217;CbCr<\/em><\/u><\/strong><\/a><\/span>.<\/p>\n<p>All that is then left for the renderer to do is to translate the dataframe to RGB colourspace, scale it to match the resolution of the output buffer, and draw the result onto that buffer.<br \/>\nHowever, this is a massively parallel process, as it needs to be done for every pixel. If you have a background in computer graphics, you know what I&#8217;m getting at: this is a job for your graphics card!<\/p>\n<p>To be able to talk to the graphics card,\u00a0<strong>you need OpenGL<\/strong>.<br \/>\nOpenGL (Open Graphics Library) is a cross-platform library and standard that allows a programmer to control a GPU (graphics processing unit) to carry out tasks such as 2D and 3D rendering.<br \/>\nIt is well-known in the video game industry, along with DirectX and Metal (equivalent libraries that are not cross-platform), and is now slowly becoming obsolete due to the new Vulkan standard.<\/p>\n<p>So there you have it:\u00a0<strong>Linphone uses OpenGL to perform the Y&#8217;CbCr\u00a0\u2192\u00a0RGB translation for video rendering.<\/strong><\/p>\n<p class=\"rtecenter\"><strong><img decoding=\"async\" src=\"https:\/\/linphone.org\/sites\/default\/files\/crea_nl_etroit_1200_x_175_px-4.png\" alt=\"\" \/><\/strong><\/p>\n<p>The context of my mission around the video renderer was to create evolutions in the existing code to make it compliant with the OpenGL version 4.1 specification. There was existing code that I needed to either evolve or re-write, but\u00a0in any case I supposed I had a rather good understanding of what it was doing&#8230;<\/p>\n<p>I found that the existing code was doing the Y&#8217;CbCr to RGB conversion in more or less this way:<\/p>\n<p class=\"rtecenter\"><img decoding=\"async\" src=\"https:\/\/linphone.org\/sites\/default\/files\/2_2.png\" alt=\"\" \/><\/p>\n<p>This is basically a 3&#215;3 matrix, which is applied to each Y&#8217;CbCr pixel to obtain an RGB pixel. A 720P resolution image contains 921,600 pixels, which gives rise to a lot of computations, and that&#8217;s where OpenGL helps.<\/p>\n<p>In version 2.0 (2004), OpenGL introduced the concept of\u00a0<span style=\"color: #ff5e00;\"><u><em><strong><a style=\"color: #ff5e00;\" href=\"https:\/\/en.wikipedia.org\/wiki\/Shader\">shaders<\/a><\/strong><\/em><\/u>\u00a0<\/span>through the\u00a0<span style=\"color: #ff5e00;\"><a style=\"color: #ff5e00;\" href=\"https:\/\/en.wikipedia.org\/wiki\/OpenGL_Shading_Language\"><u><em><strong>OpenGL Shading Language<\/strong><\/em><\/u><\/a><\/span>, a C-like language for expressing computations to be performed by the GPU. Although initially created to compute the light and colour levels when rendering a 3D object, shaders have evolved to perform a variety of specialised functions. Here, a\u00a0diverted\u00a0but clever usage of shaders is applied: there is no real 3D scene, and we simply draw a flat texture on the screen. The texture, instead of being given as an RGB buffer as expected, is a Y&#8217;CbCr image, and the shader program is executed on the graphics card&#8217;s GPU to transform this Y&#8217;CbCr texture into a RGB one. Simple, isn&#8217;t it?<\/p>\n<p>But, where does the transformation matrix above come from?<\/p>\n<p>I searched for &#8220;yuv2rgb&#8221; on the web, as well as for some of the magic numbers used, like &#8220;1.164&#8221;. Although I was able to find many examples of code (mostly C) that used similar constants to &#8220;convert YUV to RGB&#8221; (whatever that meant), none explained how they were obtained.<\/p>\n<p>Refusing to give up, I finally landed on the YUV Wikipedia article, which\u2014past the numbers and formulas that didn&#8217;t seem to match the magic numbers in my shader\u2014led me to the\u00a0<span style=\"color: #ff5e00;\"><a style=\"color: #ff5e00;\" href=\"https:\/\/en.wikipedia.org\/wiki\/YCbCr\"><strong><u><em>Y&#8217;CbCr article<\/em><\/u><\/strong><\/a><\/span>.<\/p>\n<p>There, everything finally started to make sense. I had finally found where my magic numbers were coming from: they are the result of simplifying (in the mathematical sense) the inverse conversion matrix from the ITU-R BT.601 standard, mixed with partial range shifting and scaling!\u00a0Notably, the 1.164 factor mentioned earlier is a rounded version of the 255\/219 scale factor that can be found in the &#8220;<span style=\"color: #ff5e00;\"><strong><em><u><a style=\"color: #ff5e00;\" href=\"https:\/\/en.wikipedia.org\/wiki\/YCbCr#ITU-R_BT.601_conversion\">ITU-R BT.601 conversion<\/a><\/u><\/em><\/strong><\/span>&#8221; section of that article.<\/p>\n<p>I know, I know\u2014I don&#8217;t understand half of this either\u2014but the point is that my numbers went from\u00a0magic\u00a0to\u00a0scientific.<br \/>\nI don&#8217;t need to understand the physics of phosphor light emission, nor how Kr, Kg, and Kb were chosen. I just need to know that that&#8217;s\u00a0how\u00a0they were chosen.<\/p>\n<p><span style=\"color: #ff5e00;\"><u><strong>Example code with documentation<\/strong><\/u><\/span><\/p>\n<p>So after all my research and effort, this is the final code that we use today in the OpenGL 4.1 context:<\/p>\n<p class=\"rtecenter\"><img decoding=\"async\" src=\"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-12.png\" alt=\"\" \/><\/p>\n<p><span style=\"color: #ff5e00;\"><a style=\"color: #ff5e00;\" href=\"https:\/\/gitlab.linphone.org\/BC\/public\/mediastreamer2\/-\/blob\/master\/src\/YCbCr_to_RGB.frag\">https:\/\/gitlab.linphone.org\/BC\/public\/mediastreamer2\/-\/blob\/master\/src\/Y&#8230;<\/a><\/span><\/p>\n<p>I tried to design it to make it easy to follow along when the Wikipedia article is opened next to it, and used explicit naming.<\/p>\n<p><span style=\"color: #ff5e00;\"><u><strong>Conclusion<\/strong><\/u><\/span><\/p>\n<p>Offloading specific processing tasks to the GPU is often a good idea to save CPU time and energy. This colour space conversion, in an unoptimised form (plain C code without SIMD assembly instructions), would consume roughly as much as the whole video encoding process running on the main CPU. This shows you how important this optimisation is in the video stream processing pipeline!<\/p>\n<p>The new\u00a0<a href=\"https:\/\/en.wikipedia.org\/wiki\/Vulkan\"><strong><em><u><span style=\"color: #ff5e00;\">Vulkan<\/span><\/u><\/em><\/strong><\/a>\u00a0API, an open standard for 3D graphics, has announced GPU-accelerated video codec APIs (H.264, H.265, AV1). This looks extremely promising for further optimisation of Linphone&#8217;s video processing pipeline by offloading tasks to the GPU.<\/p>\n<p>For any further information, do not hesitate to contact our team!<\/p>\n<p class=\"rtecenter\">\u00a0<a href=\"https:\/\/linphone.typeform.com\/to\/RRDfNq?typeform-source=linphone.org\"><img decoding=\"async\" class=\"aligncenter\" src=\"https:\/\/linphone.org\/sites\/default\/files\/2_0.png\" alt=\"\" width=\"133\" height=\"50\" \/><\/a><\/p>\n","protected":false},"author":10,"featured_media":0,"parent":0,"template":"","meta":{"_acf_changed":false,"inline_featured_image":false},"actualites-category":[42],"class_list":["post-13446","actualites","type-actualites","status-publish","hentry","actualites-category-tech"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Optimised video stream rendering with OpenGL - Linphone<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Optimised video stream rendering with OpenGL - Linphone\" \/>\n<meta property=\"og:description\" content=\"This article offers a technical insight into how the video of a VoIP call is rendered...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/\" \/>\n<meta property=\"og:site_name\" content=\"Linphone\" \/>\n<meta property=\"article:modified_time\" content=\"2024-12-02T11:38:58+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/\",\"url\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/\",\"name\":\"Optimised video stream rendering with OpenGL - Linphone\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/linphone.org\\\/sites\\\/default\\\/files\\\/post_news_520_x_330_px_1920_x_1080_px-13.png\",\"datePublished\":\"2023-05-03T07:41:48+00:00\",\"dateModified\":\"2024-12-02T11:38:58+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/#primaryimage\",\"url\":\"https:\\\/\\\/linphone.org\\\/sites\\\/default\\\/files\\\/post_news_520_x_330_px_1920_x_1080_px-13.png\",\"contentUrl\":\"https:\\\/\\\/linphone.org\\\/sites\\\/default\\\/files\\\/post_news_520_x_330_px_1920_x_1080_px-13.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/news\\\/optimised-video-stream-rendering-with-opengl\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"News\",\"item\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/actualites-archives\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Optimised video stream rendering with OpenGL\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/\",\"name\":\"Linphone\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.linphone.org\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Optimised video stream rendering with OpenGL - Linphone","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/","og_locale":"en_US","og_type":"article","og_title":"Optimised video stream rendering with OpenGL - Linphone","og_description":"This article offers a technical insight into how the video of a VoIP call is rendered...","og_url":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/","og_site_name":"Linphone","article_modified_time":"2024-12-02T11:38:58+00:00","og_image":[{"url":"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/","url":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/","name":"Optimised video stream rendering with OpenGL - Linphone","isPartOf":{"@id":"https:\/\/www.linphone.org\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/#primaryimage"},"image":{"@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/#primaryimage"},"thumbnailUrl":"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png","datePublished":"2023-05-03T07:41:48+00:00","dateModified":"2024-12-02T11:38:58+00:00","breadcrumb":{"@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/#primaryimage","url":"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png","contentUrl":"https:\/\/linphone.org\/sites\/default\/files\/post_news_520_x_330_px_1920_x_1080_px-13.png"},{"@type":"BreadcrumbList","@id":"https:\/\/www.linphone.org\/en\/news\/optimised-video-stream-rendering-with-opengl\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.linphone.org\/en\/"},{"@type":"ListItem","position":2,"name":"News","item":"https:\/\/www.linphone.org\/en\/actualites-archives\/"},{"@type":"ListItem","position":3,"name":"Optimised video stream rendering with OpenGL"}]},{"@type":"WebSite","@id":"https:\/\/www.linphone.org\/en\/#website","url":"https:\/\/www.linphone.org\/en\/","name":"Linphone","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.linphone.org\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"_links":{"self":[{"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/actualites\/13446","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/actualites"}],"about":[{"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/types\/actualites"}],"author":[{"embeddable":true,"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/users\/10"}],"wp:attachment":[{"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/media?parent=13446"}],"wp:term":[{"taxonomy":"actualites-category","embeddable":true,"href":"https:\/\/www.linphone.org\/en\/wp-json\/wp\/v2\/actualites-category?post=13446"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}