音视频开发二十一:YUV视频播放器

逻辑流程

ffmpegg功能强大,封装了很多API供我么调用,但是视频帧的渲染及音频帧的播放,ffmpeg就无能为力了,因此需要借助类似sdl库等其他第三方组件来完成。

逻辑流程图如下:

代码实现

通过线程完成一个YUV播放器,每隔40ms更新窗口显示画面

#include <stdio.h>
#include <string.h>

#include <SDL.h>

#define BLOCK_SIZE 4096000

//event message
#define REFRESH_EVENT  (SDL_USEREVENT + 1)
#define QUIT_EVENT  (SDL_USEREVENT + 2)

int thread_exit = 0;

// 用线程控制每一帧的播放时间,每隔多少时间触发一个事件通知
int refresh_video_timer(void* udata) {
    
    

    thread_exit = 0;

    while (!thread_exit) {
    
    
        SDL_Event event;
        event.type = REFRESH_EVENT; // 更新事件
        SDL_PushEvent(&event);
        SDL_Delay(40);
    }

    thread_exit = 0;

    //push quit event
    SDL_Event event;
    event.type = QUIT_EVENT;
    SDL_PushEvent(&event);

    return 0;
}

int yuv_player()
{
    
    

    FILE* video_fd = NULL;

    SDL_Event event;
    SDL_Rect rect;

    Uint32 pixformat = 0;

    SDL_Window* win = NULL;
    SDL_Renderer* renderer = NULL;
    SDL_Texture* texture = NULL;

    SDL_Thread* timer_thread = NULL;

    int w_width = 640, w_height = 480;
    const int video_width = 608, video_height = 368;

    Uint8* video_pos = NULL;
    Uint8* video_end = NULL;

    unsigned int remain_len = 0;
    unsigned int video_buff_len = 0;
    unsigned int blank_space_len = 0;
    Uint8* video_buf = NULL;

    //const char* path = "f:\\test_data\\crop_jiuzhe_summer.yuv";
    const char* path = "f:\\test_data\\out.yuv";

    // 一个yuv图片的长度 
    /*
        这个公式用于计算YUV格式视频每帧数据的长度(字节数)。
        YUV是一种基于颜色空间的视频格式,其中每个像素点不是一个RGB变量,而是由一个Luma(亮度)和两个色度(Chroma)组成的,通常用YUV 4:2:0的格式存储。在YUV 4:2:0格式中,Y分量的采样率是1,而U和V分量的采样率是1/2,即每四个Y像素只有一个U和V像素。因此,每个像素点需要1.5个字节的存储空间。

        根据这个公式,假设视频的宽度是video_width,高度是video_height,每个像素点需要1.5个字节的存储空间,那么一个YUV帧的大小可以表示为:

        yuv_frame_len = video_width * video_height * 1.5

        这个公式中12/8用于将1.5个字节转化为12位,然后再将结果转化为字节。
    *
    */
    const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;
    unsigned int temp_yuv_frame_len = yuv_frame_len;
    if (yuv_frame_len & 0xF) {
    
    
        temp_yuv_frame_len = (yuv_frame_len & 0xFFF0) + 0x10;
    }

    //1.初始化SDL
    if (SDL_Init(SDL_INIT_VIDEO)) {
    
    
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    //creat window from SDL
    win = SDL_CreateWindow("YUV Player",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        w_width, w_height,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (!win) {
    
    
        fprintf(stderr, "Failed to create window, %s\n", SDL_GetError());
        goto __FAIL;
    }

    // 创建渲染器
    renderer = SDL_CreateRenderer(win, -1, 0);

    //IYUV: Y + U + V  (3 planes)
    //YV12: Y + V + U  (3 planes)
    pixformat = SDL_PIXELFORMAT_IYUV;
    

    
    //根据渲染器创建纹理
    texture = SDL_CreateTexture(renderer,
        pixformat,
        SDL_TEXTUREACCESS_STREAMING,
        video_width,
        video_height);

    video_buf = (Uint8*)malloc(temp_yuv_frame_len);

    //打开yuv文件
    video_fd = fopen(path, "r");
    if (!video_fd) {
    
    
        fprintf(stderr, "Failed to open yuv file\n");
        goto __FAIL;
    }

    //读取yuv数据保存到video_buf中
    if ((video_buff_len = fread(video_buf, 1, BLOCK_SIZE, video_fd)) <= 0) {
    
    
        fprintf(stderr, "Failed to read data from yuv file!\n");
        goto __FAIL;
    }

    //set video positon
    video_pos = video_buf;
   

    timer_thread = SDL_CreateThread(refresh_video_timer,
        NULL,
        NULL);

    do {
    
    
        //等待事件
        SDL_WaitEvent(&event);
        if (event.type == REFRESH_EVENT) {
    
    
           
            // 更新纹理
            SDL_UpdateTexture(texture, NULL, video_pos, video_width);

            //FIX: If window is resize
            rect.x = 0;
            rect.y = 0;
            rect.w = w_width;
            rect.h = w_height;

            // 显示到窗口
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            SDL_RenderPresent(renderer);

            if ((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0) {
    
    
                fprintf(stderr, "eof, exit thread!");
                thread_exit = 1;
                continue;// to wait event for exiting
            }

        }
        else if (event.type == SDL_WINDOWEVENT) {
    
    
            //If Resize
            SDL_GetWindowSize(win, &w_width, &w_height);
        }
        else if (event.type == SDL_QUIT) {
    
    
            thread_exit = 1;
        }
        else if (event.type == QUIT_EVENT) {
    
    
            break;
        }
    } while (1);

__FAIL:

    //close file
    if (video_fd) {
    
    
        fclose(video_fd);
    }

    SDL_Quit();

    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_38056514/article/details/130190891