SDL2:第四個程序(Mac):顯示YUV數據

在上一個程序SDL2:第三個程序(Mac):顯示任意圖片我們利用sdl2_image庫已經可以做到顯示任意格式的圖片了。

可我這種最終要利用SDL2做視頻工具的男人,怎麼會滿足於小小的圖片呢。

因爲FFmpeg解碼出來的視頻幀數據是以YUV數據的格式展現,所以我必須先弄清楚YUV格式,在SDL2中是怎麼顯示的。

爲此,我專門用FFmpeg命令,將小王子的圖片從jpg格式,轉成了I420的YUV4:2:0的格式,以便測試。下載鏈接如下:little_prince_yv12_960x540.yuv

不知道爲啥,下載居然要積分,大家可以自己利用ffmpeg轉一個就成。

ffmpeg -pix_fmt yuv420p -s 960x540 -i little_prince.jpg little_prince_yuv420p_960x540.yuv
  • -pix_fmt:表示要用什麼格式轉換,yuv420p是參數,你也可以通過ffmpeg -pix_fmts查看其它支持的類型。
  • -s:表示一幀的尺寸,這個尺寸將是你生成yuv數據的寬和高,需要牢牢記住,因爲轉換成yuv數據後,數據將不會存儲任何無關信息,包括尺寸。
  • -i:很簡單,就是需要轉換的文件,i表示input。參數不僅可以是jpg格式的,也可以是bmp等任何其它常見類型。
  • little_prince_yuv420p_960x540.yuv:我的輸出文件名,在文件名中,記錄了yuv的格式,和尺寸,這些信息在顯示過程中比較重要。

下面來看看代碼:

代碼

代碼是在SDL2:第三個程序(Mac):顯示任意圖片基礎上擴展而來,cmake部分,毫無變化:

cmake_minimum_required(VERSION 3.10)
project(PlaySDL)

set(CMAKE_CXX_STANDARD 11)
set(MY_LIBRARY_DIR /usr/local/Cellar)
set(SDL_DIR ${MY_LIBRARY_DIR}/sdl2/2.0.9_1/)
set(SDL_IMAGE_DIR ${MY_LIBRARY_DIR}/sdl2_image/2.0.4/)

include_directories(${SDL_DIR}/include/SDL2/
        ${SDL_IMAGE_DIR}/include/)

link_libraries(${SDL_DIR}/lib/
        ${SDL_IMAGE_DIR}/lib/)

add_executable(PlaySDL main.cpp)

target_link_libraries(PlaySDL SDL2 SDL2_image)

其中有部分關於sdl2_image的庫,在本次的程序中使用不到的可以直接刪掉。

下面是代碼部分:

#include <iostream>

#include <SDL.h>
#include <SDL2/SDL_image.h>

using namespace  std;

const int WIDTH = 960, HEIGHT = 540;
int main() {

    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        cout << "SDL could not initialized with error: " << SDL_GetError() << endl;
        return -1;
    }
    SDL_Window *window = SDL_CreateWindow("Hello SDL world!", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                          WIDTH, HEIGHT, SDL_WINDOW_ALLOW_HIGHDPI);
    if (NULL == window) {
        cout << "SDL could not create window with error: " << SDL_GetError() << endl;
        return -1;
    }

    if (!(IMG_Init(IMG_INIT_JPG) & IMG_INIT_JPG)) {
        cout << "SDL_image could not init with error: " << IMG_GetError() << endl;
        return -1;
    }

    FILE* pFile = fopen("little_prince_i420_960x540.yuv", "rb");
    if (pFile == NULL) {
        cerr << "little_prince_i420_960x540.yuv open failed" << endl;
    }
    unsigned char *m_yuv_data;
    int frameSize = HEIGHT * WIDTH * 3 / 2; // 單幀數據的bit數
    m_yuv_data = (unsigned char*)malloc(frameSize * sizeof(unsigned char));
    fread(m_yuv_data, 1, frameSize, pFile);
    fclose(pFile);

    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); // 創建渲染器
    // 創建紋理
    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
    if (texture != NULL) {
        SDL_Event windowEvent;
        while (true) {
            if (SDL_PollEvent(&windowEvent)) {
                if (SDL_QUIT == windowEvent.type) {
                    cout << "SDL quit!!" << endl;
                    break;
                }
            }
            SDL_UpdateTexture(texture, NULL, m_yuv_data, WIDTH); // 更新紋理
            SDL_RenderClear(renderer); // 清楚渲染器
            SDL_RenderCopy(renderer, texture, NULL, NULL); // 拷貝渲染器到紋理。
            SDL_RenderPresent(renderer); // 渲染
        }

        SDL_DestroyWindow(window);
        SDL_Quit();
        return 0;
    }
}

先來看一下顯示效果:
在這裏插入圖片描述

YUV數據提取

這裏需要講一下關於YUV數據填充的部分,即:

FILE* pFile = fopen("little_prince_i420_960x540.yuv", "rb");
unsigned char *m_yuv_data;
int frameSize = HEIGHT * WIDTH * 3 / 2; // 單幀數據的bit數
m_yuv_data = (unsigned char*)malloc(frameSize * sizeof(unsigned char));
fread(m_yuv_data, 1, frameSize, pFile);
fclose(pFile);

fopen就不用說了,通過fopen獲得我們前面通過ffmpeg製作好的yuv文件little_prince_yv12_960x540.yuv的句柄。核心問題是,如何從這個句柄中,提取出我們需要的Y、U、V分量。

關鍵在於YUV數據的格式和size。格式決定了YUV分量在文件中的排列方式,size決定了總的像素值。我們已經知道圖片是I420格式。也就是yuv420p,它是三平面存儲,每個像素內存計算方式是:

亮度[(4) + U(1) + V(1)]/4(像素) = 3/2bit

所有I420單幀圖片要佔用的內存爲:int frameSize = HEIGHT * WIDTH * 3 / 2

接着就是分配一個無符號的內存空間,來存儲從文件中讀取的yuv數據。

本次程序的流程圖如下:

在這裏插入圖片描述

代碼流程圖中,黃色部分函數調用,是從第一個程序copy過來的,它幾乎是所有SDL程序的必要流程了,要豐富SDL程序,都是在黃色框架之間擴充功能和代碼,相關函數解釋可以查看:

SDL2常用函數&結構分析:SDL_Init

SDL2常用函數&結構分析:SDL_Window&SDL_CreateWindow
SDL_Window

SDL2常用函數&結構分析:SDL_Event&SDL_PollEvent

綠色部分,是本次代碼新增,用於顯示YUV的極簡邏輯,從文件提取yuv數據在本文中已經說到,其它沒有提到的相關函數解釋可以查看:

SDL2常用函數&結構分析:SDL_Renderer&SDL_CreateRenderer&SDL_RenderCopy&SDL_RenderPresent

SDL2常用函數&結構分析:SDL_Texture&SDL_CreateTexture&SDL_UpdateTexture

github地址:

代碼github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章