1. 图像格式转换

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_textureif (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_BGRA32case 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 texturecase 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 texturedefault: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;
}

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

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)种情况。

1.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是可以直接显示的,不必进行图像转换。

1.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;
}

1.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。

1.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[]);

1.5 图像显示

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

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

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

ffplay源码分析:图像格式转换相关推荐

  1. FFplay源码分析-音视频同步1

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 FFplay 源码分析系列以一条简单的命令开始,ffplay -i a.mp4.a.mp4下载链接:百度网盘,提取 ...

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

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

  3. FFplay源码分析-avformat_open_input

    本系列 以 ffmpeg4.4 源码为准,主要讲解 ffplay 的 RTMP 协议解析,播放.本文使用的命令如下: ffplay -i rtmp://192.168.0.122/live/lives ...

  4. ffplay源码分析---播放控制

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

  5. ffplay源码分析:代码框架

    1. 代码框架 本节简单梳理ffplay.c代码框架.一些关键问题及细节问题在后续章节探讨. 1.1 流程图 1.2 主线程 主线程主要实现三项功能:视频播放(音视频同步).字幕播放.SDL消息处理. ...

  6. ffplay源码分析

    音视频同步基础概念 由于音频和视频的输出不在同一个线程,而且,也不一定会同时解出同一个pts的音频帧和视频帧.更有甚者,编码或封装的时候可能pts还是不连续的,或有个别错误的.因此,在进行音频和视频的 ...

  7. 音视频技术之ffplay源码分析-音视频同步

    音视频同步的目的是为了使播放的声音和显示的画面保持一致.视频按帧播放,图像显示设备每次显示一帧画面,视频播放速度由帧率确定,帧率指示每秒显示多少帧:音频按采样点播放,声音播放设备每次播放一个采样点,声 ...

  8. FFplay源码分析-日志

    本文的环境是局域网RTMP直播,SRS部署在另一台电脑,播放是另一台电脑.ffplay 播放命令如下: .\ffplay -fflags nobuffer -x 800 -f flv -i rtmp: ...

  9. ffplay源码分析:音视频同步

    1. 音视频同步 音视频同步的目的是为了使播放的声音和显示的画面保持一致.视频按帧播放,图像显示设备每次显示一帧画面,视频播放速度由帧率确定,帧率指示每秒显示多少帧:音频按采样点播放,声音播放设备每次 ...

最新文章

  1. 十四、Java练习:一个猜数游戏
  2. nodejs+grunt配置记
  3. 关于Linux路由表的route命令
  4. centos7 和centos 6的一些区别
  5. 《操作系统真象还原》——0.23 操作系统是如何识别文件系统的
  6. Qt工作笔记-SIGNAL之textChanged
  7. CSS选择器速记笔记
  8. Gstreamer调用pulseaudio播放流程(十三)
  9. Wpf之元素绑定元素属性
  10. 【Python实例第35讲】高斯过程分类:Iris数据集
  11. 计算机制作乘法表格,excel表格乘法怎么用,excel表格怎么算乘法
  12. python第三项开始每一项都等于前两项的积_Python二十九个常见的脚本汇总!
  13. OLED原理,时序和操作
  14. 摘录3:没有趋势,没有背驰。
  15. 22.裸板--I2C协议
  16. 杭州电子科技大学acm--2022
  17. 【数学建模】常用微分方程模型 + 详细手写公式推导 + Matlab代码实现
  18. 找出最接近的数据-MATLAB
  19. Navigating to current location (/) is not allowed
  20. java项目疑难解答_Tivoli Kernel Services 疑难解答

热门文章

  1. 【tensorflow】图像加减乘除
  2. 访问Oracle em https https://localhost:1158/em 报访问网页提示此网站的安全证书有问题解决方法
  3. 系统分析师-资料总结-上
  4. 2.4GWiFi与5GWiFi的区别与市场分析-模组厂家
  5. x20手机科学计算机,vivo X20全面屏智能手机
  6. 2022年高教社杯建模国赛论文写作指导
  7. 这个Excel技巧,你一定要知道!旋风图该如何制作?
  8. 我们都是大数据时代的海狸
  9. 研究生小结,以及一些送给师弟师妹们的话~
  10. 双系统没有无线网问题