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


    本文为作者原创,转载请注明出处: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
    ");
                    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.
    ");
                    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.
    ", 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简易播放器的实现-视频播放

  • 相关阅读:
    hibernate_0100_HelloWorld
    MYSQL子查询的五种形式
    JSF是什么?它与Struts是什么关系?
    nop指令的作用
    htmlparser实现从网页上抓取数据(收集)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts tags are only usable when the
    FCKeditor 在JSP上的完全安装
    Java遍历文件夹的2种方法
    充电电池和充电时间说明
    吃知了有什么好处
  • 原文地址:https://www.cnblogs.com/leisure_chn/p/10311376.html
Copyright © 2020-2023  润新知