我们先回顾下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;
}