signed

QiShunwang

“诚信为本、客户至上”

YUV播放器

2021/5/14 21:03:33   来源:

我们先回顾下YUV格式,可以通过下面的链接了解YUV的基础知识

https://blog.csdn.net/qq_15255121/article/details/115469018

 

我们先用ffmpeg的命令提取一段yuv数据。

提取YUV数据

ffmpeg -i input.mp4 -an -c:v rawvideo -pixel_format yuv420p out.yuv

· -c:v rawvideo 指定将视频转成原始数据

· -pixel_format yuv420p 指定转换格式为yuv420p

 

我们提取的时候 可以知道width=960 height=540

 

我们可以通过ffplay -s 960x540 out.yuv 来看下数据是否是正常的

 

接下来我们简单说下yuv420的数据格式。

yuv420

通过图片我们可以知道每行只有yu或者yv

每行的yuv比值是 4:2:0 或者 4:0:2

 

我们知道yuv4:4:4 是每行的yuv都是4:4:4

所以yuv420的数据量是yuv444的 二分之一

 

 

YUV存储格式

planar(平面)

I420 YYYYYYYYUUVV =>YUV420P

YV12 YYYYYYYYVVUU=>YUV420P (IOS)

 

packet(打包)

NV12 YYYYYYYYUVUV =>YUV420SP

NV21 YYYYYYYYVUVU => YUV420SP(ANDROID)

 

 

planner模式来说数据放在3个数组里面,一个放y,一个放u,一个放v

packet模式所有数据放在一个数组里面 一个数组里面既有y,又有u,还有v

 

 

我们提取的视频是yuv420p,可以知道是平面模式。

SDL_PIXELFORMAT_IYUV 就是SDL对应的YUV420P

 

 

我们要写yuv播放器,实际上就是从yuv文件当中读取yuv数据,然后给渲染器进行渲染。yuv420p在文件当中每帧的数据是按照先存y,再存u,再存v的格式进行存储的。

 

我们有两种实现方式

1、每次都取一帧数据,进行渲染

那么我们就可以通过 yuv数据的格式,计算出每帧数据的大小。yuv420的大小等于= 视频的宽度 * 视频的高度 * 1.5.算出每帧数据的大小。

我们也可以通过ffmpeg中的av_image_get_buffer_size函数计算每帧数据的大小。

我们假定帧率是25帧,那么我们就40ms从文件当中读取一帧数据进行播放。

2、一次读取指定大小的数据放在缓冲区,然后每次从缓冲区当中取出每帧数据大小的数据进行渲染。缓冲区取完了,再去文件进行读取。

这种方式要比第一种更优,因为避免了从硬盘上一直读取数据。

 

我们为了学习sdl,选用第一种方式。

 

我们用SDL创建一个线程,

线程

SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);

fn:执行的函数

name:线程名

data:执行函数参数的参数 (可以是结构体 基本类型 字符串)

 

SDL更新纹理

extern DECLSPEC int SDLCALL SDL_UpdateTexture(SDL_Texture * texture,

const SDL_Rect * rect,

const void *pixels, int pitch);

extern DECLSPEC int SDLCALL SDL_UpdateYUVTexture(SDL_Texture * texture,

const SDL_Rect * rect,

const Uint8 *Yplane, int Ypitch,

const Uint8 *Uplane, int Upitch,

const Uint8 *Vplane, int Vpitch);

#include <stdio.h>
#include <string.h>
#include <SDL2/SDL.h>
#include <libavutil/imgutils.h>

//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 main(int argc, char *argv[])
{

    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 = 960, w_height = 540;
    const int video_width = 960, video_height = 540;

    const char *path = "/Users/yuanxuzhen/tools/音视频/out.yuv";

    const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;

    //initialize 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;

    //create texture for render
    texture = SDL_CreateTexture(renderer,
                                pixformat,
                                SDL_TEXTUREACCESS_STREAMING,
                                video_width,
                                video_height);

    //创建AVFrame
    int ret = 0;

    if (ret < 0)
    {
        printf("av_frame_get_buffer fail error =  %s", av_err2str(ret));
        goto __FAIL;
    }
    //第九步 创建buffer用于存储文件读取出来的数据
    size_t buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, video_width, video_height, 32);

    uint8_t *buffer = malloc(buffer_size);

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

    //read block data

    timer_thread = SDL_CreateThread(refresh_video_timer,
                                    NULL,
                                    NULL);

    do
    {
        //Wait
        SDL_WaitEvent(&event);
        if (event.type == REFRESH_EVENT)
        {
            //not enought data to render
            int ret = fread(buffer, buffer_size, 1, video_fd);
            if (ret == 0)
            {
                thread_exit = 1;
                break;
            }

            SDL_UpdateTexture(texture, NULL, buffer, video_width);

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

            SDL_RenderClear(renderer);
            SDL_RenderCopy(renderer, texture, NULL, &rect);
            SDL_RenderPresent(renderer);
        }
        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);
    }

    if (buffer)
    {
        free(buffer);
    }

    SDL_Quit();

    return 0;
}