首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ffplay源码分析5-图像格式转换

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

作者头像
叶余
发布于 2019-04-02 07:46:09
发布于 2019-04-02 07:46:09
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

5. 图像格式转换

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
main() -- >
event_loop -->
refresh_loop_wait_event() -->
video_refresh() -->
video_display() -->
video_image_display() -->
upload_texture()

upload_texture()

upload_texture()源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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),则需要进行图像格式转换

1) 2)两种类型不进行图像格式转换。我们考虑第3)种情况。

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

get_sdl_pix_fmt_and_blendmode()

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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像素格式的映射关系,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()创建

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
*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 图像格式转换

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()函数原型如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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后,执行如下步骤即可显示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SDL_RenderClear();                  // 使用特定颜色清空当前渲染目标
SDL_RenderCopy();                   // 使用部分图像数据(texture)更新当前渲染目标
SDL_RenderPresent(sdl_renderer);    // 执行渲染,更新屏幕显示

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
开发播放器知识点滴
这几天重新看了一下播放器的源码,对里边的一些知识又有了一些重新的认识。由于都是一些琐碎的知识点,不好放在其它的文章里,所以只能写这样一篇文章将它们记录下来。
音视频_李超
2020/04/02
9920
开发播放器知识点滴
FFmpeg简易播放器的实现-最简版
下图引用自“雷霄骅,视音频编解码技术零基础学习方法”,因原图太小,看不太清楚,故重新制作了一张图片。
叶余
2019/04/02
1.6K0
FFmpeg简易播放器的实现-最简版
FFmpeg简易播放器的实现-视频播放
下图引用自“雷霄骅,视音频编解码技术零基础学习方法”,因原图太小,看不太清楚,故重新制作了一张图片。
叶余
2019/04/02
4.7K0
FFmpeg简易播放器的实现-视频播放
FFmpeg菜鸡互啄#第5篇#视频帧格式转换
关键步骤 int rgbsize = avpicture_get_size(PIX_FMT_RGB24, pVCodecCtx->width, pVCodecCtx->height);//算出该格式和分辨率下一帧图像的数据大小 //uint8_t* rgbBuffer = (uint8_t *)av_malloc(rgbsize * sizeof(uint8_t));//分配保存图像的内存 //avpicture_fill((AVPicture *)&rgbFrame, rgbBuf
_gongluck
2018/03/08
2.2K0
使用FFMPEG的sws_scale函数实现各种原始颜色格式互转(YUV\RGB\)
一、环境介绍 FFMPEG版本: 4.2.2 测试系统:ubuntu18.04 二、示例代码 /* YUYV转QImage格式 */ QImage YUYV422_TO_QImage(uint8_t *yuyv422,int image_width,int image_height) { uint8_t *out_buffer= nullptr; AVFrame *Input_pFrame= nullptr; AVFrame *Output_pFrame = nullptr;
DS小龙哥
2022/01/17
1.8K0
ffplay播放器原理剖析
*****************************************************************************
全栈程序员站长
2022/09/07
8560
【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 )
① FFMPEG 初始化 : 参考博客 【Android FFMPEG 开发】FFMPEG 初始化 ( 网络初始化 | 打开音视频 | 查找音视频流 )
韩曙亮
2023/03/27
9430
FFMPEG 实现 YUV,RGB各种图像原始数据之间的转换(swscale)
下面来看一个视频解码的简单例子,这个程序完成了对”北京移动开发者大会茶歇视频2.flv”(其实就是优酷上的一个普通视频)的解码工作,并将解码后的数据保存为原始数据文件(例如YUV420,YUV422,RGB24等等)。其中略去了很多的代码。
全栈程序员站长
2022/09/13
3.1K0
如何使用libswscale库将YUV420P格式的图像序列转换为RGB24格式输出?
  将视频中的图像帧按照一定比例缩放或指定宽高进行放大和缩小是视频编辑中最为常见的操作之一,这里我们将1920x1080的yuv图像序列转换成640x480的rgb图像序列,并输出到文件。视频图像转换的核心为一个SwsContext结构,其中保存了输入图像和输出图像的宽高以及像素格式等多种参数。我们通过调用sws_getContext()函数就可以十分方便地创建并获取SwsContext结构的实例。下面给出初始化的代码:
故乡的樱花开了
2023/10/22
6780
FFmpeg菜鸡互啄#第6篇#音频帧格式转换(重采样)
关键步骤 struct SwrContext* swr_covert_ctx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(pACodecCtx->channels), AV_SAMPLE_FMT_S16, pACodecCtx->sample_rate, av_get_default_channel_layout(pACodecCtx->channels), pACodecCtx->sample_fmt, pACodecCtx->
_gongluck
2018/03/08
3.7K0
ffmpeg libswscale实现YUV转RGB
这种复杂的方法可以配置一些sws_getContext()配置不了的参数。比如说设置图像的YUV像素的取值范围是JPEG标准(Y、U、V取值范围都是0-255)还是MPEG标准(Y取值范围是16-235,U、V的取值范围是16-240)
曾大稳
2018/09/11
3.9K0
ffmpeg中avframe的YUV格式数据到OpenCV中Mat的BGR格式转换
ffmpeg实现音视频编解码是非常常用的工具,视频解码出来的raw数据是yuv格式,用来进行后续的图像处理一般是RGB格式的。所以需要从yuv到rgb或者bgr的转换,ffmpeg提供了相应的转换API函数: 下面代码中dec_ctx是解码器上下文,AV_PIX_FMT_BGR24是要转换成的图像数据格式,通过avpicture_get_size()函数获取图像的数据占用空间大小,并使用av_malloc()分配一个outBuff。将outbuff挂到video_frameBGR结构体上,并设置好格式转换
一棹烟波
2018/01/12
3K0
Ubuntu 14.04LTS FFmpeg H264解码实战
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next
dejunzhang
2018/10/04
1.9K0
FFmpeg 播放 RTSP/Webcam 流
本文将介绍 FFmpeg 如何播放 RTSP/Webcam/File 流。流程如下:
GoCoding
2021/09/09
1.6K0
ffplay.c 源码分析- 视频部分
ffplay.c 中线程模型简单命令。主要是有如下几个线程: 1. 渲染的线程-主线程 简单的理解,来说就是main方法运行所在的线程。 实际上是SDL_CreateWindow 调用所在的线程。以Android为例(笔者比较熟悉),创建的是OpenGL的Surface。也就是EGLContext所在的线程了。
deep_sadness
2018/12/14
2.4K0
ffplay.c 源码分析- 视频部分
音视频开发之旅(34) - 基于FFmpeg实现简单的视频解码器
FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张图把解码流程图,如下
音视频开发之旅
2021/02/28
1.5K0
FFmpeg_3.2.4+SDL_2.0.5学习(1)音视频解码帧及显示/播放数据
int OpenAVFile(const char* szFileName) { char errbuf[256] = { 0 }; int iRes = 0; int vindex = -1; AVFormatContext* pFmtCtx = NULL; AVCodecContext* vCodecCtx = NULL; AVCodec* vCodec = NULL; AVPacket* pkt = NULL; AVFrame* pfe = NULL; AVFrame* YUV =
_gongluck
2018/03/08
1.5K0
QT应用编程: 基于FFMPEG设计的精简版视频播放器
win32下使用FFMPEG 4.2.2库下载地址:https://download.csdn.net/download/xiaolong1126626497/12321684
DS小龙哥
2022/01/07
8710
QT应用编程: 基于FFMPEG设计的精简版视频播放器
灵办AI探索之旅:颠覆传统的代码开发工具
灵办AI是一个先进的人工智能工具,专注于提高软件开发和项目管理的效率。其核心功能包括代码生成、优化、评估和自动化修复,旨在帮助开发者和团队提升开发速度和代码质量。 体验地址:https://ilingban.com/browser_extension/?from=jj
洁洁
2024/08/19
2100
灵办AI探索之旅:颠覆传统的代码开发工具
利用ffmpeg将H264解码为RGB
由于公司买到了一个不提供解码器的设备,我不得已还要做解码的工作。在网上找了一圈,H264解码比较方便的也就是ffmpeg一系列的函数库了,原本设备中也是用这套函数库解码,但厂家不给提供,没办法,只得自己搞了。
全栈程序员站长
2022/09/10
8930
推荐阅读
相关推荐
开发播放器知识点滴
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验