美文网首页
ijkplayer 开源项目分析(七)ijkplayer自动旋转

ijkplayer 开源项目分析(七)ijkplayer自动旋转

作者: 码上就说 | 来源:发表于2020-05-29 10:21 被阅读0次

ijkplayer 开源项目分析(一)编译
ijkplayer 开源项目分析(二)播放流程概要
ijkplayer 开源项目分析(三)msg_queue消息机制剖析
ijkplayer 开源项目分析(四)解决video size识别问题
ijkplayer 开源项目分析(五)ffmpeg升级到4.X版本
ijkplayer 开源项目分析(六)视频解析核心流程
ijkplayer 开源项目分析(七)ijkplayer自动旋转功能
ijkplayer 开源项目分析(八)avformat_open_input剖析
ijkplayer 开源项目分析(九)核心option解析
ijkplayer 开源项目分析(十)提升直播画面质量
ijkplayer 开源项目分析(十一)filter过滤器介绍
ijkplayer 开源项目分析(十二)filter改变声音音量

我们在使用ijkplayer播放视频的时候,有时候会出现部分视频旋转角度不对,例如下面的图:
正常播放的情况是这样子的:

很显然,使用ijkplayer播放的时候,视频旋转角度没有调整好。

这个视频的基本信息如下:


可以看出来视频的旋转角度是90度,是逆时针90度。

两个问题:

  • 1.如何识别视频的旋转角度?
  • 2.如何自动旋转视频?

如何识别视频的旋转角度

视频的旋转角度是视频的基本信息,就和宽、高、时长等信息一样,都是基本的metadata信息。
IjkMediaPlayer有一个onInfo的回调——MEDIA_INFO_VIDEO_ROTATION_CHANGED

    int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;

这儿显然是回调告知视频的旋转角度的,根据对代码的分析,我们知道这个回调的起点有两处。
一个是ff_ffplay.c中的ffplay_video_thread函数,调用的地方如下:

#if CONFIG_AVFILTER
    AVFilterGraph *graph = avfilter_graph_alloc();
    AVFilterContext *filt_out = NULL, *filt_in = NULL;
    int last_w = 0;
    int last_h = 0;
    enum AVPixelFormat last_format = -2;
    int last_serial = -1;
    int last_vfilter_idx = 0;
    if (!graph) {
        av_frame_free(&frame);
        return AVERROR(ENOMEM);
    }

#else
    ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, ffp_get_video_rotate_degrees(ffp));
#endif

从代码中看,CONFIG_AVFILTER打开的时候,就不会回调FFP_MSG_VIDEO_ROTATION_CHANGED了,为什么这样做?
另一个调用的地方是ffpipenode_android_mediacodec_vdec.c中的recreate_format_l函数, 如下:

    rotate_degrees = ffp_get_video_rotate_degrees(ffp);
    if (ffp->mediacodec_auto_rotate &&
        rotate_degrees != 0 &&
        SDL_Android_GetApiLevel() >= IJK_API_21_LOLLIPOP) {
        ALOGI("amc: rotate in decoder: %d\n", rotate_degrees);
        opaque->frame_rotate_degrees = rotate_degrees;
        SDL_AMediaFormat_setInt32(opaque->input_aformat, "rotation-degrees", rotate_degrees);
        ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, 0);
    } else {
        ALOGI("amc: rotate notify: %d\n", rotate_degrees);
        ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, rotate_degrees);
    }

fp->mediacodec_auto_rotate &&
rotate_degrees != 0 &&
SDL_Android_GetApiLevel() >= IJK_API_21_LOLLIPOP,这么一大堆条件成立的情况下,回调的旋转角度就是0了, 为什么?

发现获取旋转角度的函数是ffp_get_video_rotate_degrees,这个函数的源码如下:

int ffp_get_video_rotate_degrees(FFPlayer *ffp)
{
    VideoState *is = ffp->is;
    if (!is)
        return 0;

    int theta  = abs((int)((int64_t)round(fabs(get_rotation(is->video_st))) % 360));
    switch (theta) {
        case 0:
        case 90:
        case 180:
        case 270:
            break;
        case 360:
            theta = 0;
            break;
        default:
            ALOGW("Unknown rotate degress: %d\n", theta);
            theta = 0;
            break;
    }

    return theta;
}

从is->video_st中得到,is-video_st是AVStream。

在分析代码的时候,上面提出了两个问题?

    1. CONFIG_AVFILTER宏和视频旋转有什么关系?
    1. ffp->mediacodec_auto_rotate和视频旋转有什么关系?

这两个问题很有意思,因为解释了上面这两个问题,我们就能做到根据视频的选选角度自动旋转视频了。

如何自动旋转视频

1.CONFIG_AVFILTER配置1

CONFIG_AVFILTER打开的时候,就不会回调旋转角度的函数,不回调,本身说明此时视频的旋转角度为0,说明视频已经被自动旋转过了。大胆猜测一下,简答验证一下。
首先修改./config/module.sh中的配置,注释掉:

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-filters"

然后打开./ijkmedia/ijkplayer/config.h

#ifndef FFPLAY__CONFIG_H
#define FFPLAY__CONFIG_H

#include "libffmpeg/config.h"

// FIXME: merge filter related code and enable it
// remove these lines to enable avfilter
#ifdef CONFIG_AVFILTER
#undef CONFIG_AVFILTER
#endif
#define CONFIG_AVFILTER 0

#ifdef FFP_MERGE
#undef FFP_MERGE
#endif

#ifdef FFP_SUB
#undef FFP_SUB
#endif

#ifndef FFMPEG_LOG_TAG
#define FFMPEG_LOG_TAG "IJKFFMPEG"
#endif

#endif//FFPLAY__CONFIG_H

将CONFIG_AVFILTER改为1,然后重新编译一下,这时候再尝试一下,可以自动旋转视频了。这时候再去源码中找答案。发现ff_ffplay.c中configure_video_filters函数中有下面关键的几行代码:

#define INSERT_FILT(name, arg) do {                                          \
    AVFilterContext *filt_ctx;                                               \
                                                                             \
    ret = avfilter_graph_create_filter(&filt_ctx,                            \
                                       avfilter_get_by_name(name),           \
                                       "ffplay_" name, arg, NULL, graph);    \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    ret = avfilter_link(filt_ctx, 0, last_filter, 0);                        \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    last_filter = filt_ctx;                                                  \
} while (0)

    if (ffp->autorotate) {
        double theta  = get_rotation(is->video_st);

        if (fabs(theta - 90) < 1.0) {
            INSERT_FILT("transpose", "clock");
        } else if (fabs(theta - 180) < 1.0) {
            INSERT_FILT("hflip", NULL);
            INSERT_FILT("vflip", NULL);
        } else if (fabs(theta - 270) < 1.0) {
            INSERT_FILT("transpose", "cclock");
        } else if (fabs(theta) > 1.0) {
            char rotate_buf[64];
            snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta);
            INSERT_FILT("rotate", rotate_buf);
        }
    }

这个ffp->autorotate初始值就是1,这儿看一下transpose/hflip/vflip/rotate的含义。

  • 如果视频逆时针90旋转,直接transpose=clock就行
  • 如果视频逆时针180旋转,先hflip水平翻转,再vflip竖直翻转
  • 如果视频逆时针270旋转,直接transpose=cclock就行
  • 如果是其他的角度,则直接使用rotate方法直接旋转。

transpose=clock表示顺时针90度旋转,transpose=cclock表示逆时针90度旋转。

打开ffp->mediacodec_auto_rotate

这个mediacodec_auto_rotate是ijkplayer 的option的一种。我们在ijkplayer中IjkMediaPlayer初始化的时候,可以设置一些option参数,打开或者关闭一些开关,设置播放器的基本参数。option的参数很多种,可以单独写一篇文章介绍了,这儿不作展开。
mediacodec_auto_rotate参数默认情况下是关掉的,如果你要打开,可以在初始化设置option参数:

mIjkMediaPlayerImpl.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
mIjkMediaPlayerImpl.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec_auto_rotate", 1);

mediacodec_auto_rotate使用硬解码的自动旋转功能,所以肯定要打开mediacodec开关的。

    rotate_degrees = ffp_get_video_rotate_degrees(ffp);
    if (ffp->mediacodec_auto_rotate &&
        rotate_degrees != 0 &&
        SDL_Android_GetApiLevel() >= IJK_API_21_LOLLIPOP) {
        ALOGI("amc: rotate in decoder: %d\n", rotate_degrees);
        opaque->frame_rotate_degrees = rotate_degrees;
        SDL_AMediaFormat_setInt32(opaque->input_aformat, "rotation-degrees", rotate_degrees);
        ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, 0);
    } else {
        ALOGI("amc: rotate notify: %d\n", rotate_degrees);
        ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, rotate_degrees);
    }

代码中条件有讲得很清楚:

  • (1) 打开mediacodec_auto_rotate开关
  • (2) 旋转角度不为0
  • (3) 当前的api-level不能小于21

最终调用的SDL_AMediaFormat_setInt32(opaque->input_aformat, "rotation-degrees", rotate_degrees); 设置到SDL底层了,主要的工作还是MediaCodec在解码视频帧的时候,需要将角度旋转以下,这样帧图片就摆正了。

这也就解释了为什么MediaPlayer会自动帮忙旋转视频,因为MediaPlayer默认使用的是MediaCodec解码。

小结

  • 1.如何识别一个视频的旋转角度:
    表面上可以通过onInfo回调中的MEDIA_INFO_VIDEO_ROTATION_CHANGED选项来获取旋转角度,但是ffmpeg内部是通过获取视频metadata的所有信息的,视频旋转角度知识metadata中的其中一个元素。
  • 2.ijkplayer如何完成视频自动旋转的:
    有两种办法,一种是通过加上avfilter通过filter来实现对视频帧的转换;另一种是通过MediaCodec来自动旋转。

相关文章

网友评论

      本文标题:ijkplayer 开源项目分析(七)ijkplayer自动旋转

      本文链接:https://www.haomeiwen.com/subject/dxgzahtx.html