Android音视频系列

ijkplayer 开源项目分析(四)解决video size识

2020-03-01  本文已影响0人  码上就说

ijkplayer 开源项目分析(一)编译
ijkplayer 开源项目分析(二)播放流程概要
ijkplayer 开源项目分析(三)msg_queue消息机制剖析
ijkplayer 开源项目分析(四)解决video size识别问题

大家如果关注过我,应该看过我之前写过一篇文章《视频的宽高应该怎么看?》这篇文章中介绍了三个概念;

  • SAR —— Sample Aspect Ratio 采样横纵比。表示横向的像素点数和纵向的像素点数的比值。
  • DAR —— Display Aspect Ratio 显示横纵比。最终显示的图像在长度单位上的横纵比。
  • PAR —— Pixel Aspect Ratio 像素横纵比。表示每个像素的宽度与长度的比值。可以认为每个像素不是正方形的。

我们当然知道DAR才是视频真是的显示比例,但是坑爹的是基本上没有播放器直接告诉我们DAR,不为什么原因,因为DAR无法直接获取,要通过一定的计算得到;而且还需要一定复杂度的计算;
PAR x SAR = DAR
也就是说PAR和SAR相乘得到DAR,但是我们使用MediaPlayer或者其他的播放器,没有直接给我们提供PAR和SAR,这个怎么办?
好在ijkplayer基于ffmpeg,ffmpeg是底层解码、编码、filter、postproc等一条龙服务,音视频上没有什么东西是它办不到的,那这个肯定也可以办到;

首先看一下ijkplayer关于video size的标准接口:
IMediaPlayer.java 中的OnVideoSizeChangedListener接口

    interface OnVideoSizeChangedListener {
        void onVideoSizeChanged(IMediaPlayer mp, int width, int height,
                                int sar_num, int sar_den);
    }

里面传入5个参数,width 和 height自然是视频的宽和高,sar_num和sar_den是横向采样和纵向采样的数值,sar_num / sar_den 就是SAR,很显然这个对我们来说没有用;

这儿介绍一个例子:
https://hls.aoxtv.com/v3.szjal.cn/20200114/dtOHlPFE/index.m3u8


这个视频使用video_width和 video_height 来规定播放surface的宽高是这样的:
有没有看起来怪怪的,视频整体上被压缩了;
如果以DAR来规定播放surface的宽高,应该是下面这样子的;

这样看起来是正常的了。

还是老问题,怎么弄?
我们首先搞清楚两点:

  • onVideoSizeChanged 里面的width和height在onPrepared回调的时候肯定有数据了;
  • DAR数据想获取应该在读取视频帧的时候获取;

追踪ijkplayer源码,废话不多说,ijkplayer请求数据在ff_ffplay.c中的stream_open函数开始;

    is->initialized_decoder = 0;
    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
    if (!is->read_tid) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
        goto fail;
  

这儿创建线程执行 read_thread 方法;

/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg)

这个函数很重要,请求网络数据和读取本地数据都在这个函数中操作;

  • 1.分离视频文件的轨道,音频轨道,视频轨道,字幕轨道;
  • 2.针对视频轨道,解析轨道信息中的帧数据,解析函数是stream_component_open函数;
  • 3.读取视频轨道frame信息,执行av_read_frame函数可以实现;
  • 4.解包音频信息和视频信息,packet_queue_put 放入队列中,后续会处理这些队列;

这里只是简单说下流程,后续我会详细分析具体的步骤;

    if (is->video_st && is->video_st->codecpar) {
        AVCodecParameters *codecpar = is->video_st->codecpar;
        int codec_num = codecpar->sample_aspect_ratio.num;
        int codec_den = codecpar->sample_aspect_ratio.den;
        int width = codecpar->width;
        int height = codecpar->height;
        ffp_notify_msg3(ffp, FFP_MSG_VIDEO_SIZE_CHANGED, width, height);
        ffp_notify_msg3(ffp, FFP_MSG_SAR_CHANGED, codec_num, codec_den);
    }
    ffp->prepared = true;
    ffp_notify_msg1(ffp, FFP_MSG_PREPARED);

解析出视频的前几个帧的数据信息,能够解析出视频的video_width和video_height 之后,就会回调FFP_MSG_VIDEO_SIZE_CHANGED;
看到下面紧接着调用了FFP_MSG_PREPARED回调;
FFP_MSG_SAR_CHANGED这个就是SAR信息的回调,那DAR数据怎么计算得到?
1080 * 736 / (276 * 1287) = DAR_num / DAR_den
利用rational.c中的av_reduce 方法,可以计算出DAR_num和DAR_den;

    if (is->video_st && is->video_st->codecpar) {
        AVCodecParameters *codecpar = is->video_st->codecpar;
        int codec_num = codecpar->sample_aspect_ratio.num;
        int codec_den = codecpar->sample_aspect_ratio.den;
        int width = codecpar->width;
        int height = codecpar->height;
        int dar_num;
        int dar_den;
        ffp_notify_msg3(ffp, FFP_MSG_VIDEO_SIZE_CHANGED, width, height);
        ffp_notify_msg3(ffp, FFP_MSG_SAR_CHANGED, codec_num, codec_den);
        av_reduce(&dar_num, &dar_den, width * (int64_t)codec_num, height * (int64_t)codec_den, 1024 * 1024);
        av_log(NULL, AV_LOG_ERROR, "litianpeng dar_width=%d, dar_height=%d", dar_num, dar_den);
        ffp_notify_msg3(ffp, FFP_MSG_VIDEO_DAR_CHANGED, dar_num, dar_den);
    }

实际上就是找到 1080 * 736 和 276 * 1287 最大公约数,然后分别除以最大公约数,得到了DAR_num=320,DAR_den=143;
将这个回调到IjkMediaPlayer上;
具体的代码见:https://github.com/JeffMony/ijkplayer中的jeffmony_branch中的https://github.com/JeffMony/ijkplayer/commit/4e014eb4ea8165032a940f21cf3c4cad52d7d415

小结

音视频的知识点很多,道阻且长,勇往直前。

上一篇下一篇

猜你喜欢

热点阅读