本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10311376.html

ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg提供的解码器和SDL库进行视频播放。本文基于FFmpeg工程4.1版本进行分析,其中ffplay源码清单如下:

https://github.com/FFmpeg/FFmpeg/blob/n4.1/fftools/ffplay.c

在尝试分析源码前,可先阅读如下参考文章作为铺垫:

[1]. 雷霄骅,视音频编解码技术零基础学习方法

[2]. 视频编解码基础概念

[3]. 色彩空间与像素格式

[4]. 音频参数解析

[5]. FFmpeg基础概念

“ffplay源码分析”系列文章如下:

[1]. ffplay源码分析1-概述

[2]. ffplay源码分析2-数据结构

[3]. ffplay源码分析3-代码框架

[4]. ffplay源码分析4-音视频同步

[5]. ffplay源码分析5-图像格式转换

[6]. ffplay源码分析6-音频重采样

[7]. ffplay源码分析7-播放控制

5. 图像格式转换

FFmpeg解码得到的视频帧的格式未必能被SDL支持,在这种情况下,需要进行图像格式转换,即将视频帧图像格式转换为SDL支持的图像格式,否则是无法正常显示的。

图像格式转换是在视频播放线程(主线程中)中的upload_texture()函数中实现的。调用链如下:

main() -- >
event_loop -->
refresh_loop_wait_event() -->
video_refresh() -->
video_display() -->
video_image_display() -->
upload_texture()

upload_texture()

upload_texture()源码如下:

static int upload_texture(SDL_Texture **tex, AVFrame *frame, struct SwsContext **img_convert_ctx) {
int ret = 0;
Uint32 sdl_pix_fmt;
SDL_BlendMode sdl_blendmode;
// 根据frame中的图像格式(FFmpeg像素格式),获取对应的SDL像素格式
get_sdl_pix_fmt_and_blendmode(frame->format, &sdl_pix_fmt, &sdl_blendmode);
// 参数tex实际是&is->vid_texture,此处根据得到的SDL像素格式,为&is->vid_texture
if (realloc_texture(tex, sdl_pix_fmt == SDL_PIXELFORMAT_UNKNOWN ? SDL_PIXELFORMAT_ARGB8888 : sdl_pix_fmt, frame->width, frame->height, sdl_blendmode, 0) < 0)
return -1;
switch (sdl_pix_fmt) {
// frame格式是SDL不支持的格式,则需要进行图像格式转换,转换为目标格式AV_PIX_FMT_BGRA,对应SDL_PIXELFORMAT_BGRA32
case SDL_PIXELFORMAT_UNKNOWN:
/* This should only happen if we are not using avfilter... */
*img_convert_ctx = sws_getCachedContext(*img_convert_ctx,
frame->width, frame->height, frame->format, frame->width, frame->height,
AV_PIX_FMT_BGRA, sws_flags, NULL, NULL, NULL);
if (*img_convert_ctx != NULL) {
uint8_t *pixels[4];
int pitch[4];
if (!SDL_LockTexture(*tex, NULL, (void **)pixels, pitch)) {
sws_scale(*img_convert_ctx, (const uint8_t * const *)frame->data, frame->linesize,
0, frame->height, pixels, pitch);
SDL_UnlockTexture(*tex);
}
} else {
av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");
ret = -1;
}
break;
// frame格式对应SDL_PIXELFORMAT_IYUV,不用进行图像格式转换,调用SDL_UpdateYUVTexture()更新SDL texture
case SDL_PIXELFORMAT_IYUV:
if (frame->linesize[0] > 0 && frame->linesize[1] > 0 && frame->linesize[2] > 0) {
ret = SDL_UpdateYUVTexture(*tex, NULL, frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
} else if (frame->linesize[0] < 0 && frame->linesize[1] < 0 && frame->linesize[2] < 0) {
ret = SDL_UpdateYUVTexture(*tex, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0],
frame->data[1] + frame->linesize[1] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[1],
frame->data[2] + frame->linesize[2] * (AV_CEIL_RSHIFT(frame->height, 1) - 1), -frame->linesize[2]);
} else {
av_log(NULL, AV_LOG_ERROR, "Mixed negative and positive linesizes are not supported.\n");
return -1;
}
break;
// frame格式对应其他SDL像素格式,不用进行图像格式转换,调用SDL_UpdateTexture()更新SDL texture
default:
if (frame->linesize[0] < 0) {
ret = SDL_UpdateTexture(*tex, NULL, frame->data[0] + frame->linesize[0] * (frame->height - 1), -frame->linesize[0]);
} else {
ret = SDL_UpdateTexture(*tex, NULL, frame->data[0], frame->linesize[0]);
}
break;
}
return ret;
}

frame中的像素格式是FFmpeg中定义的像素格式,FFmpeg中定义的很多像素格式和SDL中定义的很多像素格式其实是同一种格式,只名称不同而已。

根据frame中的像素格式与SDL支持的像素格式的匹配情况,upload_texture()处理三种类型,对应switch语句的三个分支:

  1. 如果frame图像格式对应SDL_PIXELFORMAT_IYUV格式,不进行图像格式转换,使用SDL_UpdateYUVTexture()将图像数据更新到&is->vid_texture
  2. 如果frame图像格式对应其他被SDL支持的格式(诸如AV_PIX_FMT_RGB32),也不进行图像格式转换,使用SDL_UpdateTexture()将图像数据更新到&is->vid_texture
  3. 如果frame图像格式不被SDL支持(即对应SDL_PIXELFORMAT_UNKNOWN),则需要进行图像格式转换
  4. 2)两种类型不进行图像格式转换。我们考虑第3)种情况。

5.1 根据映射表获取frame对应SDL中的像素格式

get_sdl_pix_fmt_and_blendmode()

这个函数的作用,获取输入参数format(FFmpeg像素格式)在SDL中的像素格式,取到的SDL像素格式存在输出参数sdl_pix_fmt

static void get_sdl_pix_fmt_and_blendmode(int format, Uint32 *sdl_pix_fmt, SDL_BlendMode *sdl_blendmode)
{
int i;
*sdl_blendmode = SDL_BLENDMODE_NONE;
*sdl_pix_fmt = SDL_PIXELFORMAT_UNKNOWN;
if (format == AV_PIX_FMT_RGB32 ||
format == AV_PIX_FMT_RGB32_1 ||
format == AV_PIX_FMT_BGR32 ||
format == AV_PIX_FMT_BGR32_1)
*sdl_blendmode = SDL_BLENDMODE_BLEND;
for (i = 0; i < FF_ARRAY_ELEMS(sdl_texture_format_map) - 1; i++) {
if (format == sdl_texture_format_map[i].format) {
*sdl_pix_fmt = sdl_texture_format_map[i].texture_fmt;
return;
}
}
}

在ffplay.c中定义了一个表sdl_texture_format_map[],其中定义了FFmpeg中一些像素格式与SDL像素格式的映射关系,如下:

static const struct TextureFormatEntry {
enum AVPixelFormat format;
int texture_fmt;
} sdl_texture_format_map[] = {
{ AV_PIX_FMT_RGB8, SDL_PIXELFORMAT_RGB332 },
{ AV_PIX_FMT_RGB444, SDL_PIXELFORMAT_RGB444 },
{ AV_PIX_FMT_RGB555, SDL_PIXELFORMAT_RGB555 },
{ AV_PIX_FMT_BGR555, SDL_PIXELFORMAT_BGR555 },
{ AV_PIX_FMT_RGB565, SDL_PIXELFORMAT_RGB565 },
{ AV_PIX_FMT_BGR565, SDL_PIXELFORMAT_BGR565 },
{ AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24 },
{ AV_PIX_FMT_BGR24, SDL_PIXELFORMAT_BGR24 },
{ AV_PIX_FMT_0RGB32, SDL_PIXELFORMAT_RGB888 },
{ AV_PIX_FMT_0BGR32, SDL_PIXELFORMAT_BGR888 },
{ AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888 },
{ AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888 },
{ AV_PIX_FMT_RGB32, SDL_PIXELFORMAT_ARGB8888 },
{ AV_PIX_FMT_RGB32_1, SDL_PIXELFORMAT_RGBA8888 },
{ AV_PIX_FMT_BGR32, SDL_PIXELFORMAT_ABGR8888 },
{ AV_PIX_FMT_BGR32_1, SDL_PIXELFORMAT_BGRA8888 },
{ AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV },
{ AV_PIX_FMT_YUYV422, SDL_PIXELFORMAT_YUY2 },
{ AV_PIX_FMT_UYVY422, SDL_PIXELFORMAT_UYVY },
{ AV_PIX_FMT_NONE, SDL_PIXELFORMAT_UNKNOWN },
};

可以看到,除了最后一项,其他格式的图像送给SDL是可以直接显示的,不必进行图像转换。

关于这些像素格式的含义,可参考“色彩空间与像素格式

5.2 重新分配vid_texture

realloc_texture()

根据新得到的SDL像素格式,为&is->vid_texture重新分配空间,如下所示,先SDL_DestroyTexture()销毁,再SDL_CreateTexture()创建

static int realloc_texture(SDL_Texture **texture, Uint32 new_format, int new_width, int new_height, SDL_BlendMode blendmode, int init_texture)
{
Uint32 format;
int access, w, h;
if (!*texture || SDL_QueryTexture(*texture, &format, &access, &w, &h) < 0 || new_width != w || new_height != h || new_format != format) {
void *pixels;
int pitch;
if (*texture)
SDL_DestroyTexture(*texture);
if (!(*texture = SDL_CreateTexture(renderer, new_format, SDL_TEXTUREACCESS_STREAMING, new_width, new_height)))
return -1;
if (SDL_SetTextureBlendMode(*texture, blendmode) < 0)
return -1;
if (init_texture) {
if (SDL_LockTexture(*texture, NULL, &pixels, &pitch) < 0)
return -1;
memset(pixels, 0, pitch * new_height);
SDL_UnlockTexture(*texture);
}
av_log(NULL, AV_LOG_VERBOSE, "Created %dx%d texture with %s.\n", new_width, new_height, SDL_GetPixelFormatName(new_format));
}
return 0;
}

5.3 复用或新分配一个SwsContext

sws_getCachedContext()

*img_convert_ctx = sws_getCachedContext(*img_convert_ctx,
frame->width, frame->height, frame->format, frame->width, frame->height,
AV_PIX_FMT_BGRA, sws_flags, NULL, NULL, NULL);

检查输入参数,第一个输入参数*img_convert_ctx对应形参struct SwsContext *context

如果context是NULL,调用sws_getContext()重新获取一个context。

如果context不是NULL,检查其他项输入参数是否和context中存储的各参数一样,若不一样,则先释放context再按照新的输入参数重新分配一个context。若一样,直接使用现有的context。

5.4 图像格式转换

if (*img_convert_ctx != NULL) {
uint8_t *pixels[4];
int pitch[4];
if (!SDL_LockTexture(*tex, NULL, (void **)pixels, pitch)) {
sws_scale(*img_convert_ctx, (const uint8_t * const *)frame->data, frame->linesize,
0, frame->height, pixels, pitch);
SDL_UnlockTexture(*tex);
}
}

上述代码有三个步骤:

  1. SDL_LockTexture()锁定texture中的一个rect(此处是锁定整个texture),锁定区具有只写属性,用于更新图像数据。pixels指向锁定区。
  2. sws_scale()进行图像格式转换,转换后的数据写入pixels指定的区域。pixels包含4个指针,指向一组图像plane。
  3. SDL_UnlockTexture()将锁定的区域解锁,将改变的数据更新到视频缓冲区中。

    上述三步完成后,texture中已包含经过格式转换后新的图像数据。

    补充一下细节,sws_scale()函数原型如下:
/**
* Scale the image slice in srcSlice and put the resulting scaled
* slice in the image in dst. A slice is a sequence of consecutive
* rows in an image.
*
* Slices have to be provided in sequential order, either in
* top-bottom or bottom-top order. If slices are provided in
* non-sequential order the behavior of the function is undefined.
*
* @param c the scaling context previously created with
* sws_getContext()
* @param srcSlice the array containing the pointers to the planes of
* the source slice
* @param srcStride the array containing the strides for each plane of
* the source image
* @param srcSliceY the position in the source image of the slice to
* process, that is the number (counted starting from
* zero) in the image of the first row of the slice
* @param srcSliceH the height of the source slice, that is the number
* of rows in the slice
* @param dst the array containing the pointers to the planes of
* the destination image
* @param dstStride the array containing the strides for each plane of
* the destination image
* @return the height of the output slice
*/
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);

5.5 图像显示

texture对应一帧待显示的图像数据,得到texture后,执行如下步骤即可显示:

SDL_RenderClear();                  // 使用特定颜色清空当前渲染目标
SDL_RenderCopy(); // 使用部分图像数据(texture)更新当前渲染目标
SDL_RenderPresent(sdl_renderer); // 执行渲染,更新屏幕显示

图像显示的流程细节可参考如下文章:

FFmpeg简易播放器的实现-视频播放

ffplay源码分析5-图像格式转换的更多相关文章

  1. ffplay源码分析1-概述

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10301215.html ffplay是一个很简单的播放器,但是初次接触仍会感到概念和细节 ...

  2. ffplay源码分析6-音频重采样

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10312713.html ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg ...

  3. ffplay源码分析7-播放控制

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10316225.html ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg ...

  4. ffplay源码分析4-音视频同步

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10307089.html ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg ...

  5. ffplay源码分析3-代码框架

    ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg提供的解码器和SDL库进行视频播放.本文基于FFmpeg工程4.1版本进行分析,其中ffplay源码清单如下: https://gith ...

  6. ffplay源码分析2-数据结构

    ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg提供的解码器和SDL库进行视频播放.本文基于FFmpeg工程4.1版本进行分析,其中ffplay源码清单如下: https://gith ...

  7. SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

    目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.c ...

  8. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  9. SpringMVC关于json、xml自动转换的原理研究[附带源码分析](使用JAXB转换XML)

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

随机推荐

  1. ajax之Content-Type决定form-data方式提交还是request-payloud等

    1.post请求的Content-Type为application/x-www-form-urlencoded,参数是在请求体中,即上面请求中的Form Data. 在servlet中,可以通过req ...

  2. 【NIFI】 Apache NiFI 与 SQL 操作

    本里需要基础知识:[NIFI] Apache NiFI 安装及简单的使用 查询SQL 1.拖入一个 Processor:ExecuteSQLRecord(执行sql记录) 2.配置,SETTINGS的 ...

  3. python学习 day12 (3月18日)----(装饰器内置函数)

    读时间函数: # import time # def func(): # start_time = time.time() # 代码运行之前的时间 # print('这是一个func函数') # ti ...

  4. 初识kbmmw 中的ORM

    在kbmmw 5.02.1 中,加入了ORM 的功能(这里可能和其他语言的定义不完全一样),我们就简单的认为 它就是一个类与数据库的转换吧.今天就先介绍一下如何通过kbmmw 的ORM 功能,实现类与 ...

  5. php 制作验证码不显示的问题

    php制作验证码的代码,这里就不多说了,网上有很多的,这里说一些可能遇到的问题. 1. 首先是检查自己的php.ini文件,是否支持gd库. 2.保证代码没有出问题. 3.检查字体文件路径是否正确. ...

  6. springboot 增加过滤器方法

    在访问服务器时,我们需要控制用户是否允许权限,这个时候可以使用过滤器. 在springboot 配置过滤器的方法如下: 编写过滤器代码: package com.neo.filter; import ...

  7. spring cloud 组件图

    spring cloud 提供了一套微服务的框架. 上图就是微服务一些常用的组件. 1.EureKa 实现服务的注册和发现. 2.Ribbon 实现服务的调用(客户端实现负载均衡) 3.Feign 实 ...

  8. ibatis注意要点

    一.ibatis的关键字like查询 select * from t_student where s_name '%张%'; 这种like语句在ibatis中怎么写,他们现在的项目是用ibatis作为 ...

  9. idea使用git提交代码到远程,这里是没有冲突的演示

    首先在项目鼠标右键,找到Git,然后在Git选项里找到Add,点击: 添加到暂存区后,再次找到Git,找到Commit Directory,点击: 然后弹出这个界面: 选中你自己修改的记录,一些不必要 ...

  10. 以太网MAC地址规范

    原文地址:http://blog.csdn.net/skyflying2012/article/details/40322563 之前一段时间在做网卡驱动的工作,现在产品量产,利用ifconfig e ...