Qt基於SDL庫簡單實現YUV影片播放

NOPASSWD發表於2024-07-24

示例使用的是Qt5.10和minGW_32位,用C語言實現。
將用於測試的YUV影片和SDL2.dll放到對應的build目錄下,將SDL庫放到專案目錄下


.pro檔案:

點選檢視程式碼
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += main.c

win32{
INCLUDEPATH += $$PWD/SDL2/include
LIBS += $$PWD/SDL2/lib/x86/SDL2.lib
}
main.c檔案:
點選檢視程式碼
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#define REFRESH_EVENT (SDL_USEREVENT+1)
#define QUIT_EVENT (SDL_USEREVENT+2)
#define YUV_WIDTH 320
#define YUV_HEIGHT 240
#define YUV_FORMAT SDL_PIXELFORMAT_IYUV
int s_thread_exit = 0;
int refresh_video_timer(void *data){
    while(!s_thread_exit){
        SDL_Event event;
        event.type = REFRESH_EVENT;
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }
//執行緒不退出,就持續將事件(更新事件)傳送到事件佇列當中,每個事件對應一幀,40ms延遲能實現25fps的影片播放
    s_thread_exit = 0;
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);
    return 0;
}
#undef main
int main(int argc,char* argv[])
{
    if(SDL_Init(SDL_INIT_VIDEO)){
        fprintf(stderr,"Could not initialize SDL:%s\n",SDL_GetError());
        return -1;
    }
    SDL_Event event;
    SDL_Rect rect;
    SDL_Window *window=NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;
    SDL_Thread *timer_thread = NULL;
    uint32_t pixformat = YUV_FORMAT;
    int video_width = YUV_WIDTH;
    int video_height = YUV_HEIGHT;
    int win_width = YUV_WIDTH;
    int win_height = YUV_HEIGHT;
    FILE *video_fd = NULL;
    const char *yuv_path = "yuv420p_320x240.yuv";
    size_t video_buff_len = 0;
    uint8_t *video_buf = NULL;
    uint32_t y_frame_len = video_width*video_height;
    uint32_t u_frame_len = video_width*video_height/4;
    uint32_t v_frame_len = video_width*video_height/4;
    uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;
    window = SDL_CreateWindow("Simplest YUV Player",SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED,video_width,video_height,
                              SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if(!window){
        fprintf(stderr,"SDL:could not create window,err:%s\n",SDL_GetError());
        goto _FAIL;
    }
    renderer = SDL_CreateRenderer(window,-1,0);
    texture = SDL_CreateTexture(renderer,pixformat,SDL_TEXTUREACCESS_STREAMING,
                                video_width,video_height);
    video_buf = (uint8_t*)malloc(yuv_frame_len);
    if(!video_buf){
        fprintf(stderr,"Failed to alloce yuv frame space!\n");
        goto _FAIL;
    }
    video_fd = fopen(yuv_path,"rb");
    if(!video_fd){
        printf(stderr,"Failed to open yuv file\n");
        goto _FAIL;
    }
    timer_thread = SDL_CreateThread(refresh_video_timer,NULL,NULL);
    while (1) {
        SDL_WaitEvent(&event);
        if(event.type== REFRESH_EVENT){
            video_buff_len = fread(video_buf,1,yuv_frame_len,video_fd);
            if(video_buff_len<=0){
                fprintf(stderr,"Failed to read data from yuv file!\n");
                goto _FAIL;
            }
            SDL_UpdateTexture(texture,NULL,video_buf,video_width);
            rect.x = 0;
            rect.y = 0;
            float w_ratio=win_width*1.0/video_width;
            float h_ratio=win_height*1.0/video_height;
            rect.w=video_width*w_ratio;
            rect.h=video_height*h_ratio;
//            rect.w = video_width * 0.5;
//            rect.h = video_height * 0.5;
            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer,texture,NULL,&rect);
            SDL_RenderPresent(renderer);
        }
        else if(event.type==SDL_WINDOWEVENT){
            SDL_GetWindowSize(window,&win_width,&win_height);
            printf("SDL_WINDOWEVENT win_width:%d,win_height:%d",win_width,win_height);
            printf("video_width:%d,video_height:%d",video_width,video_height);
            printf("rect.w:%d,rect.h:%d\n",rect.w,rect.h);
        }
        else if(event.type==SDL_QUIT){
            s_thread_exit =1;
        }
        else if(event.type==QUIT_EVENT){
            break;
        }
    }
    _FAIL:
    s_thread_exit =1;
    if(timer_thread)
        SDL_WaitThread(timer_thread,NULL);
    if(video_buf)
        free(video_buf);
    if(video_fd)
        free(video_fd);
    if(texture)
        SDL_DestroyTexture(texture);
    if(renderer)
        SDL_DestroyRenderer(renderer);
    SDL_Quit();
    return 0;
}

使用qq自帶的截圖工具檢視影片解析度和視窗大小(開啟qq掛著,Alt+A),

如果大小和命令列輸出的不一致,可能是程式碼錯誤或者顯示設定不是100%。

rect.w=video_width*w_ratio;
rect.h=video_height*h_ratio;

rect的大小隨視窗window的大小改變

這張圖是

rect.w = video_width * 0.5;
rect.h = video_height * 0.5;

的執行結果。
可見影片的解析度並不隨視窗window的縮放而改變,黑色區域大小就是windows的大小,影片可視範圍就是rect的大小。

相關文章