zoukankan      html  css  js  c++  java
  • 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简易播放器的实现-视频播放

  • 相关阅读:
    EV录屏
    Oracle 游标详解 【转载至CSDN「鱼丸丶粗面」】
    ORACLE查询表数据占用存储空间大小(清理表碎片)
    开源项目
    Shiro 简介(认证、授权、加密、会话管理、与 Web 集成、缓存等)
    OSCHINA 公布 2019 年度最受欢迎中国开源软件
    腾讯开发者手册
    hutool JAVA 工具类
    小程序登录
    微信小程序button组件样式
  • 原文地址:https://www.cnblogs.com/leisure_chn/p/10311376.html
Copyright © 2011-2022 走看看