ijkplayer播放器剖析(四)音频解码与音频输出机制分析
ijkplayer播放器剖析系列文章:
ijkplayer播放器剖析(一)从应用层分析至Jni层的流程分析
ijkplayer播放器剖析(二)消息机制分析
ijkplayer播放器剖析(三)音频解码与音频输出机制分析
一、引言:
在上一篇博客中,将音频的解码和输出放在了一起分析,文章显得又长又冗杂,考虑到视频渲染及同步也是一个重点分析点,所以这篇博客仅分析视频解码相关的内容。因为ijkplayer和FFmpeg在音频和视频的处理上有很多共用代码,并且在上一篇博客中讲解的足够详细,所以对于视频解码的分析就直接以重点代码来分析了。
二、MediaCodec解码通路分析:
先来看下视频解码相关的通路,ijkplayer有一个option叫“async-init-decoder”,可以通过上层apk设置到底层中。这个option的含义我不是特别清楚,一般情况下,是没有设置的。所以,在ijkplayer的地方读取该值时为0,即ffp->async_init_decoder。
下面看一下ijkplayer创建vdec的地方:
stream_component_open@ijkmedia\ijkplayer\ff_ffplay.c:case AVMEDIA_TYPE_VIDEO:is->video_stream = stream_index;is->video_st = ic->streams[stream_index];/* 通常默认为0,走下面的else */if (ffp->async_init_decoder) {...} else {/* 初始化 */decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);/* 打开vdec */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;is->queue_attachments_req = 1;/* 这里是关于帧率设置的判断,略过 */...break;
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
如果上面没有主动设置option下来的话,代码中就会走到else中去,首先是decoder_init
,最重要的操作是将packet的queue绑定到解码器中。接下来看下
ffpipeline_open_video_decoder
:
IJKFF_Pipenode* ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{return pipeline->func_open_video_decoder(pipeline, ffp);
}
pipeline的创建如下:
ijkmp_android_create@ijkmedia\ijkplayer\android\ijkplayer_android.c:IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))
{...mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);if (!mp->ffplayer->pipeline)goto fail;...
}
找到函数指针的指向:
pipeline->func_open_video_decoder = func_open_video_decoder;
func_open_video_decoder@ijkmedia\ijkplayer\android\ijkplayer_android.c:static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;IJKFF_Pipenode *node = NULL;/* 走Mediacodec的硬解码 */if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2)node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);/* 走FFmpeg的软解 */if (!node) {node = ffpipenode_create_video_decoder_from_ffplay(ffp);}return node;
}
可以看到,代码中可以使用mediacodec
或者FFmpeg
进行视频解码,当然,选择mediacodec解码是需要if中的判断条件来让上层进行设置的:
@ijkmedia\ijkplayer\ff_ffplay_options.h:// Android only options{ "mediacodec", "MediaCodec: enable H264 (deprecated by 'mediacodec-avc')",OPTION_OFFSET(mediacodec_avc), OPTION_INT(0, 0, 1) },{ "mediacodec-all-videos", "MediaCodec: enable all videos",OPTION_OFFSET(mediacodec_all_videos), OPTION_INT(0, 0, 1) },{ "mediacodec-avc", "MediaCodec: enable H264",OPTION_OFFSET(mediacodec_avc), OPTION_INT(0, 0, 1) },{ "mediacodec-hevc", "MediaCodec: enable HEVC", OPTION_OFFSET(mediacodec_hevc), OPTION_INT(0, 0, 1) },
我们只研究硬解码ffpipenode_create_video_decoder_from_android_mediacodec
:
IJKFF_Pipenode *ffpipenode_create_video_decoder_from_android_mediacodec(FFPlayer *ffp, IJKFF_Pipeline *pipeline, SDL_Vout *vout)
{...IJKFF_Pipenode *node = ffpipenode_alloc(sizeof(IJKFF_Pipenode_Opaque));if (!node)return node; ...node->func_destroy = func_destroy;/* 上层没有设置这个option将走else分支 */if (ffp->mediacodec_sync) {node->func_run_sync = func_run_sync_loop;} else {node->func_run_sync = func_run_sync;}node->func_flush = func_flush;opaque->pipeline = pipeline;opaque->ffp = ffp;opaque->decoder = &is->viddec;opaque->weak_vout = vout; ...
}
把重要的函数指针指向都确认好了之后,接下来我们就要去看vdec的解码线程了。看下decoder_start
的入参,即解码线程video_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;
}
之所以前面花了大篇幅去分析node_vdec
,就是为了确认ffpipenode_run_sync
:
int ffpipenode_run_sync(IJKFF_Pipenode *node)
{return node->func_run_sync(node);
}
node->func_run_sync
指向的是func_run_sync
:
static int func_run_sync(IJKFF_Pipenode *node)
{.../* 找到mediacodec的解码器之后不会进入这个if */if (!opaque->acodec) {return ffp_video_thread(ffp);}...frame = av_frame_alloc();if (!frame)goto fail;/* 1.创建填充数据的线程 */opaque->enqueue_thread = SDL_CreateThreadEx(&opaque->_enqueue_thread, enqueue_thread_func, node, "amediacodec_input_thread");if (!opaque->enqueue_thread) {ALOGE("%s: SDL_CreateThreadEx failed\n", __func__);ret = -1;goto fail;}while (!q->abort_request) {...got_frame = 0;/* 2.获取outputbuffer */ret = drain_output_buffer(env, node, timeUs, &dequeue_count, frame, &got_frame);...if (got_frame) {/* 3.将output picture入队列 */ret = ffp_queue_picture(ffp, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);if (ret) {if (frame->opaque)SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);}av_frame_unref(frame); }}
}
三、往MediaCodec中填充数据:
如果熟悉Android MediaCodec的操作流程,就能够看出来上面这个函数浓缩了整个操作。首先,ijkplayer专门创建了一个线程enqueue_thread_func往mediacodec中填充数据:
static int enqueue_thread_func(void *arg)
{....while (!q->abort_request && !opaque->abort) {ret = feed_input_buffer(env, node, AMC_INPUT_TIMEOUT_US, &dequeue_count);if (ret != 0) {goto fail;}}...
}
如果buffer队列没有停止接收数据的话,那么就会一直调用feed_input_buffer函数:
static int feed_input_buffer(JNIEnv *env, IJKFF_Pipenode *node, int64_t timeUs, int *enqueue_count)
{.../* 从mediacodec出列一个inputbuff的index */input_buffer_index = SDL_AMediaCodec_dequeueInputBuffer(opaque->acodec, timeUs);.../* 将packet中的待解码数据写入到mediacodec中 */copy_size = SDL_AMediaCodec_writeInputData(opaque->acodec, input_buffer_index, d->pkt_temp.data, d->pkt_temp.size);.../* 数据写完之后将inbuffer入列等待解码 */amc_ret = SDL_AMediaCodec_queueInputBuffer(opaque->acodec, input_buffer_index, 0, copy_size, time_stamp, queue_flags);
}
有兴趣的同学可以去看JNI如何反射到java层的,这个函数的主要作用就是从mediacodec中出列可用的inputbuffer,然后将packet中的源数据写入到mediacodec,再入列即可。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
四、从MediaCodec中取出数据:
接下来看一下ijkplayer是如何处理mediacodec解码完后的数据的。
回到func_run_sync,先看while循环中的drain_output_buffer:
static int drain_output_buffer(JNIEnv *env, IJKFF_Pipenode *node, int64_t timeUs, int *dequeue_count, AVFrame *frame, int *got_frame)
{...int ret = drain_output_buffer_l(env, node, timeUs, dequeue_count, frame, got_frame);...
}
static int drain_output_buffer_l(JNIEnv *env, IJKFF_Pipenode *node, int64_t timeUs, int *dequeue_count, AVFrame *frame, int *got_frame)
{.../* 从mediacodec的buffer队列中出列可用的buffer index */output_buffer_index = SDL_AMediaCodecFake_dequeueOutputBuffer(opaque->acodec, &bufferInfo, timeUs);if (output_buffer_index == AMEDIACODEC__INFO_OUTPUT_BUFFERS_CHANGED) {ALOGI("AMEDIACODEC__INFO_OUTPUT_BUFFERS_CHANGED\n");// continue;}...else if (output_buffer_index >= 0){...if (opaque->n_buf_out){...}/* 进入else分支进行数据的copy */else{ret = amc_fill_frame(node, frame, got_frame, output_buffer_index, SDL_AMediaCodec_getSerial(opaque->acodec), &bufferInfo);}}
}
这个函数很长,从表面看也仅仅是通过调用mediacodec拿到可以使用的outputbuffer的可用index
,还需要找到buffer才能去进行copy操作。看一下amc_fill_frame
函数中:
static int amc_fill_frame(IJKFF_Pipenode *node,AVFrame *frame,int *got_frame,int output_buffer_index,int acodec_serial,SDL_AMediaCodecBufferInfo *buffer_info)
{IJKFF_Pipenode_Opaque *opaque = node->opaque;FFPlayer *ffp = opaque->ffp;VideoState *is = ffp->is;/* 搞了一个代理 */frame->opaque = SDL_VoutAndroid_obtainBufferProxy(opaque->weak_vout, acodec_serial, output_buffer_index, buffer_info);if (!frame->opaque)goto fail;frame->width = opaque->frame_width;frame->height = opaque->frame_height;frame->format = IJK_AV_PIX_FMT__ANDROID_MEDIACODEC;frame->sample_aspect_ratio = opaque->codecpar->sample_aspect_ratio;frame->pts = av_rescale_q(buffer_info->presentationTimeUs, AV_TIME_BASE_Q, is->video_st->time_base);if (frame->pts < 0)frame->pts = AV_NOPTS_VALUE;// ALOGE("%s: %f", __func__, (float)frame->pts);*got_frame = 1;return 0;
fail:*got_frame = 0;return -1;
}
这个地方需要注意,ijkplayer搞了一个代理来进行数据操作,但是往下一直追踪代码并没有发现有拷贝的地方,仅仅是将buffer_index
进行了一个赋值:
proxy->buffer_index = buffer_index;
那么说明,拷贝操作将在后面进行。
继续回到外层的func_run_sync
函数,看一下ffp_queue_picture
:
int ffp_queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
{return queue_picture(ffp, src_frame, pts, duration, pos, serial);
}
queue_picture
这个函数就在ff_ffplay.c,这个函数ijkplayer也写的很复杂,只抓重点如下:
static int queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
{.../* 出队列一帧可写的frame等待填充 */if (!(vp = frame_queue_peek_writable(&is->pictq)))return -1;.../* 进行数据拷贝:src_frame->vp->bmp */if (SDL_VoutFillFrameYUVOverlay(vp->bmp, src_frame) < 0) {av_log(NULL, AV_LOG_FATAL, "Cannot initialize the conversion context\n");exit(1);}.../* 将picture推入队列 */frame_queue_push(&is->pictq);...
}
将mediacodec解码出来的视频帧入列之后,接下来就是进行同步和渲染的事情了。
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
ijkplayer播放器剖析(四)音频解码与音频输出机制分析相关推荐
- ijkplayer播放器剖析(一)让ijkplayer播起来
一.引言: ijkplayer是一款对FFmpeg封装非常好的第三方开源播放器,遗憾的是,ijkplayer2.0似乎不开源,并且1.0版本更新也基本停止了,很多公司都会采用ijkplayer作为其播 ...
- ijkplayer播放器剖析(三)音频解码与音频输出机制分析
一.引言: 在前面的博客中,我们对ijkplayer整个jni的流程及消息机制都详细的分析了一遍,分析流程机制有助于我们对整个架构有一个大致的了解,便于后续对音视频解码与输出渲染的分析,消息机制的分析 ...
- ijkplayer播放器剖析(六)视频同步与渲染机制分析
一.引言: 在前面的博客中,将音频解码播放及视频解码都分析了,这篇博客将单独针对视频同步及渲染来分析,看下ijkplayer是如何做的.本博客分析的同步方式为以音频为主,视频去同步音频. 二.同步前提 ...
- ijkplayer播放器剖析(五)视频解码线程分析
一.引言: 在上一篇博客中,将音频的解码和输出放在了一起分析,文章显得又长又冗杂,考虑到视频渲染及同步也是一个重点分析点,所以这篇博客仅分析视频解码相关的内容.因为ijkplayer和FFmpeg在音 ...
- 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇
https://www.cnblogs.com/harlanc/p/9693983.html 目录 OpenSL ES & AudioTrack 源码分析 创建播放器音频输出对象 配置并创建音 ...
- ExoPlayer播放器剖析(六)ExoPlayer同步机制分析
关联博客 ExoPlayer播放器剖析(一)进入ExoPlayer的世界 ExoPlayer播放器剖析(二)编写exoplayer的demo ExoPlayer播放器剖析(三)流程分析-从build到 ...
- ExoPlayer播放器剖析(五)ExoPlayer对AudioTrack的操作
关联博客 ExoPlayer播放器剖析(一)进入ExoPlayer的世界 ExoPlayer播放器剖析(二)编写exoplayer的demo ExoPlayer播放器剖析(三)流程分析-从build到 ...
- 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇...
一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...
- IjkPlayer播放器秒开优化以及常用Option设置
IjkPlayer播放器秒开优化以及常用Option设置 96 GexYY 关注 1.5 2018.04.19 13:28* 字数 592 阅读 10797评论 9喜欢 32 ijkplayer和ff ...
最新文章
- 图解Redis事务机制
- arcgis和matlab,ArcGIS和MATLAB应用并不困难
- 29. Leetcode 19. 删除链表的倒数第 N 个结点 (链表-双指针)
- GNU make manual 翻译( 一百零九)
- 《大数据原理:复杂信息的准备、共享和分析》一一2.5 在标识符中嵌入信息:不推荐...
- 厉害了,我的清华大学,各系横幅让网友看花眼,尤其是第3条
- undolog 是binlog_mysql日志redo log、undo log、binlog以及作用看这篇就可以啦
- java:Map借口及其子类HashMap五,identityHashMap子类
- JavaScript 01
- 自然语言处理(NLP)与自然语言理解(NLU)的区别
- 数学建模5 代码论文降重 Excel表处理数据
- 将子节点中含子节点的json数据转换成ztree适合的json数据格式
- vue 关于飞行地图展示的功能
- 2021年苹果ASO商店优化技巧
- android+widget日历开发,安卓日历小部件源码(AppWidgetProvider)
- 《京东话费充值系统架构演进实践》读后感
- 《计算机网络参考模型》
- 解锁网易云音乐小工具_什么?网易云音乐又变灰了
- WebStorm破解补丁激活
- 正则表达式 - Python 正则表达式 学习笔记 最全整理