目录
1 概述
2 AVpicture的介绍
2 sdl的集成
3 sdl2的集成
1 概述
在原生的ffmpeg提供的播放器的例子ffplay中,audio和video的输出是依靠sdl或者sdl2来完成的,具体代码如下(来自ffplay.c):
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, int64_t pos)
{
VideoPicture *vp;
int dst_pix_fmt;
vp = &is->pictq[is->pictq_windex];
/** alloc or resize hardware picture buffer */
if (!vp->bmp ||
vp->width != is->video_st->codec->width ||
vp->height != is->video_st->codec->height) {
/** if the frame is not skipped, then display it */
if (vp->bmp) {
AVPicture pict;
/** get a pointer on the bitmap */
SDL_LockYUVOverlay (vp->bmp);
dst_pix_fmt = PIX_FMT_YUV420P;
memset(&pict,0,sizeof(AVPicture));
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_flags = av_get_int(sws_opts, "sws_flags", NULL);
is->img_convert_ctx = sws_getCachedContext(is->img_convert_ctx,
vp->width, vp->height, vp->pix_fmt, vp->width, vp->height,
dst_pix_fmt, sws_flags, NULL, NULL, NULL);
if (is->img_convert_ctx == NULL) {
fprintf(stderr, "Cannot initialize the conversion context\n");
exit(1);
}
sws_scale(is->img_convert_ctx, src_frame->data, src_frame->linesize,
0, vp->height, pict.data, pict.linesize);
/** update the bitmap content */
SDL_UnlockYUVOverlay(vp->bmp);
vp->pts = pts;
vp->pos = pos;
return 0;
}
这里只摘取了部分重要的代码,其中vp->bmp为SDL_Overlay,也就是输出缓冲区,读者可以认为将yuv数据拷贝到此缓冲区
就可以显示了,对于ffplay来讲是通过sws_scale来完成的
但很多用户希望直接通过将src_frame中对应的数据直接拷贝到SDL_Overlay来达到目的,
其中原因是多方面的,对于dtplayer来说就是在decoder的时候已经通过调用sws_scale转换好了,此处再转换一次本就属于浪费
更重要的是:若将sws_scale放在sdl输出中,那么就必须强制的依赖ffmpeg,这也是无法接受的。
下面就介绍dtplayer中是如何直接操作src_frame来实现video帧的显示
2 AVpicture的介绍
想要正确的显示YUV数据,需要对AVPicture数据结构有比较透彻的了解
特别是其data【4】和linesize【4】这两个成员数组
对于audio来讲,只用了data[0]和linesize【0】,比较简单,这里只介绍video部分
AVPicture里面有data[4]和linesize[4]其中data是一个指向指针的指针(二级、二维指针),也就是指向视频数据缓冲区的首地址
在实际的数据排列中,yuv是连续存放的,data数组中的成员可以认为是指向的是数据中的位移,具体如下图
data -->xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
^ ^ ^
| | |
data[0] data[1] data[2]
比如说,当pix_fmt=PIX_FMT_YUV420P时,data中的数据是按照YUV的格式存储的
也就是:
data -->YYYYYYYYYYYYYYUUUUUUUUUUUUUVVVVVVVVVVVV
^ ^ ^
| | |
data[0] data[1] data[2]
linesize是指对应于每一行的大小,为什么需要这个变量,是因为在YUV格式和RGB格式时,每行的大小不一定等于图像的宽度。
linesize = width + padding size(16+16) for YUV
linesize = width*pixel_size for RGB
padding is needed during Motion Estimation and Motion Compensation for Optimizing MV serach and P/B frame reconstruction
for RGB only one channel is available
so RGB24 : data[0] = packet rgbrgbrgbrgb......
linesize[0] = width*3
data[1],data[2],data[3],linesize[1],linesize[2],linesize[2] have no any means for RGB
测试如下:(原始的320×182视频)
如果pix_fmt=PIX_FMT_RGBA32
linesize 的只分别为:1280 0 0 0
如果pix_fmt=PIX_FMT_RGB24
linesize 的只分别为:960 0 0 0
如果pix_fmt=PIX_FMT_YUV420P
linesize 的只分别为:352 176 176 0
【注】以上内容参考:
http://blog.csdn.net/liaozc/article/details/6110474
http://bbs.chinavideo.org/viewthread.php?tid=119&extra=page%3D1%26filter%3Ddigest&page=1
2 sdl的集成
对于sdl version1.0来说
这里对于如何在dtplayer框架中添加vo不做介绍,感兴趣的自己读代码好了,本身逻辑也非常简单
直接贴出render的代码
static int vo_sdl_render (vo_wrapper_t *wrapper, AVPicture_t * pict){ dt_lock (&vo_mutex); SDL_Rect rect; SDL_LockYUVOverlay (overlay); memcpy (overlay->pixels[0], pict->data[0], dw * dh); memcpy (overlay->pixels[1], pict->data[2], dw * dh / 4); memcpy (overlay->pixels[2], pict->data[1], dw * dh / 4); SDL_UnlockYUVOverlay (overlay); rect.x = dx; rect.y = dy; rect.w = dw; rect.h = dh; SDL_DisplayYUVOverlay (overlay, &rect); dt_unlock (&vo_mutex); return 0;}
可以看出对于AVpicture_t是封装的ffmpeg的AVPicture的结构,可以认为是没有区别的
操作就是将AVPicture的YUV缓冲区分别拷贝到 overlay的YUV缓冲区中,
需要注意的是这里拷贝的长度并非是AVpicture中的linesize【0】 linesize【1】 linesize【2】
这里的原因解释下:之所以不能能直接使用linesize数组,是因为linesize【0】linesize【1】 linesize【2】
代表的并不是YUV数据的长度,而是每行多少像素个数+padding(参考上面第2小节),也就是说
linesize[0] = dw + 32
linesize[1] = dw/2
linesize[1] = dw/2
但data中保存的却是正确的yuv数据,因此我们只需要正确的设置要拷贝的数据量,就可以正确的显示视频了
3 sdl2的集成
sdl2与sdl1的接口差别很大,看下sdl2的代码
static int vo_sdl2_render (vo_wrapper_t *wrapper, AVPicture_t * pict)
{
int ret = 0;
sdl2_ctx_t *ctx = (sdl2_ctx_t *)wrapper->handle;
if(!ctx->sdl_inited)
{
ret = sdl2_pre_init(ctx);
ctx->sdl_inited = !ret;
}
ctx->mux_vo.lock();
SDL_Rect dst;
dst.x = ctx->dx;
dst.y = ctx->dy;
dst.w = ctx->dw;
dst.h = ctx->dh;
if(ctx->ren == nullptr)
ctx->ren = SDL_CreateRenderer(ctx->win,-1,0);
if(ctx->tex == nullptr)
ctx->tex = SDL_CreateTexture(ctx->ren,SDL_PIXELFORMAT_YV12,SDL_TEXTUREACCESS_STREAMING,ctx->dw,ctx->dh);
//ctx->tex = SDL_CreateTexture(ctx->ren,SDL_PIXELFORMAT_YV12,SDL_TEXTUREACCESS_STATIC,ctx->dw,ctx->dh);
SDL_UpdateYUVTexture(ctx->tex,NULL, pict->data[0], pict->linesize[0], pict->data[1], pict->linesize[1], pict->data[2], pict->linesize[2]);
//SDL_UpdateTexture(ctx->tex, &dst, pict->data[0], pict->linesize[0]);
SDL_RenderClear(ctx->ren);
SDL_RenderCopy(ctx->ren,ctx->tex,&dst,&dst);
SDL_RenderPresent(ctx->ren);
ctx->mux_vo.unlock();
return 0;
}
sdl2的代码可以直接使用linesize数据的大小,是由于SDL_UpdateYUVTexture或者SDL_UpdateTexture本身直接需要的就是AVPicture中linesize的参数,看下接口说明
int SDL_UpdateYUVTexture(SDL_Texture* texture, const SDL_Rect* rect, const Uint8 * Yplane, int Ypitch, const Uint8* Uplane, int Upitch, const Uint8* Vplane, int Vpitch)
Function Parameters
texture | the texture to update |
rect | a pointer to the rectangle of pixels to update, or NULL to update the entire texture |
Yplane | the raw pixel data for the Y plane |
Ypitch | the number of bytes between rows of pixel data for the Y plane |
Uplane | the raw pixel data for the U plane |
Upitch | the number of bytes between rows of pixel data for the U plane |
Vplane | the raw pixel data for the V plane |
Vpitch | the number of bytes between rows of pixel data for the V plane |
由于参数匹配,所以直接使用就可以了。
这里在使用SDL2的时候还有个地方需要注意:sdl2 是通过SDL_Window SDL_Renderer SDL_Texture三者协同作用来工作的
SDL_Window ==窗口
SDL_Renderer ==画布
SDL_Texture ==内容
这里窗口的创建必须在主线程中,至少要在显示输出的线程中创建,否则通过SDL_RenderPresent显示video的时候是不会刷新的
可以参考之前dtplayer的实现,由于后面将sdl2提前到player层面作为界面,因此可reset到之前版本,具体步骤如下:
git clone https://github.com/peterfuture/dtplayer.git
git reset bcda0659a25f43f9f3e8eec9bb25d1b6c532281c
直接参考dtvideo/video_out/vo_sdl2.c就可以了
github:https://github.com/avplayer/dtplayer # C++
github:https://github.com/peterfuture/dtplayer # C
bug report: peter_future@outlook.com
blog: http://blog.csdn.net/dtplayer
bbs: http://avboost.com/
wiki: http://wiki.avplayer.org/Dtplayer
由于后面随着开发的进行文章会进行细节的更新,因此为了保证读者随时读到最新的内容,文章禁止转载,多谢大家支持!