ijkplayer在视频解码上支持软解和硬解两种方式,可在起播前配置优先使用的解码方式,播放过程中不可切换。iOS平台上硬解使用VideoToolbox,ijkplayer中的音频解码只支持软解,暂不支持硬解。
上一篇中提到了开启解码器stream_component_open方法,本篇将重点学习它的实现:
static int stream_component_open(FFPlayer *ffp, int stream_index)
{
......
// 解码器初始化
avctx = avcodec_alloc_context3(NULL);
if (!avctx)
return AVERROR(ENOMEM);
//用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中
ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
if (ret < 0)
goto fail;
//ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不一样,ffmpeg提供函数在各个time_base中进行切换。搞清楚各个time_base的来源,对于阅读ffmpeg的代码很重要
av_codec_set_pkt_timebase(avctx, ic->streams[stream_index]->time_base);
// 获取解码器
codec = avcodec_find_decoder(avctx->codec_id);
switch (avctx->codec_type) {
......
case AVMEDIA_TYPE_AUDIO:
......
if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)
goto out;
case AVMEDIA_TYPE_VIDEO:
if (ffp->async_init_decoder) {
......
} else {
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
//打开ffmpeg的解码器(硬解 or 软解),创建IJKFF_Pipenode。
ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);
if (!ffp->node_vdec)
goto fail;
}
//开始解码
if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0)
goto out;
......
}
func_open_video_decoder
static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
IJKFF_Pipenode* node = NULL;
IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
//如果配置了videotoolbox,会优先去尝试打开硬件解码器
if (ffp->videotoolbox) {
// 硬解
node = ffpipenode_create_video_decoder_from_ios_videotoolbox(ffp);
if (!node)
ALOGE("vtb fail!!! switch to ffmpeg decode!!!! \n");
}
if (node == NULL) {
// 软解
node = ffpipenode_create_video_decoder_from_ffplay(ffp);
ffp->stat.vdec_type = FFP_PROPV_DECODER_AVCODEC;
opaque->is_videotoolbox_open = false;
} else {
ffp->stat.vdec_type = FFP_PROPV_DECODER_VIDEOTOOLBOX;
opaque->is_videotoolbox_open = true;
}
ffp_notify_msg2(ffp, FFP_MSG_VIDEO_DECODER_OPEN, opaque->is_videotoolbox_open);
return node;
}
videotoolbox需要在起播前通过如下方法配置:
ijkmp_set_option_int(_mediaPlayer, IJKMP_OPT_CATEGORY_PLAYER, "videotoolbox", 1);
video的解码线程为video_thread,audio的解码线程为audio_thread
不管视频解码还是音频解码,其基本流程都是从解码前的数据缓冲区中取出一帧数据进行解码,完成后放入相应的解码后的数据缓冲区。
static int video_thread(void *arg)
{
FFPlayer *ffp = (FFPlayer *)arg;
int ret = 0;
if (ffp->node_vdec) {
ret = ffpipenode_run_sync(ffp->node_vdec);
}
return ret;
}
以视频软解为例,最终,线程会调用到ffplay_video_thread。
ffplay_video_thread
static int ffplay_video_thread(void *arg)
{
FFPlayer *ffp = arg;
......
for (;;) {
// 解码前的video queue中取出一帧数据,送入decoder进行解码,得到解码后的数据
ret = get_video_frame(ffp, frame);
......
// 解码后的数据送入pictq(这里不仅仅是做了入队处理,还把AVFrame转化成了一个自定义类型SDL_VoutOverlay)
ret = queue_picture(ffp, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);
}
return 0;
}
参考:https://blog.csdn.net/xipiaoyouzi/article/details/74280170