ijkplayer 开源项目分析(四)解决video size识
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
小结
音视频的知识点很多,道阻且长,勇往直前。