在ijkplayer 读线程中说过,ijkplayer主要创建了三个线程,一个是音频输出线程,一个是音频解码线程,一个是视频解码线程,它们都是在ff_ffplay.c/stream_component_open()完成的
跟一下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;
}
可以看出视频解码线程,最终调用函数ffpipenode_run_sync(),该函数指针的赋值在ff_ffpipeline.c/func_open_video_decoder()中完成(而func_open_video_decoder函数的赋值同func_open_audio_output函数都在ffpipeline_create_from_android赋值),在ff_ffpipeline.c/func_open_video_decoder()中会判断使用硬件解码(硬解)还是软件解码(软解)
软解代码较好跟,所以下面重点说一下硬解的流程,如果是硬解ff_ffpipeline.c/func_open_video_decoder()会调用ffpipenode_create_video_decoder_from_android_mediacodec(),该函数里面会给ffpipenode_run_sync函数指针赋值,说明在硬解情况下,video_thread()中调用ffpipenode_run_sync()最终是调用了ffpipenode_android_mediacodec_vdec.c/ffpipenode_run_sync(),其主要代码如下
static int func_run_sync(IJKFF_Pipenode *node)
{
...
opaque->enqueue_thread = SDL_CreateThreadEx(&opaque->_enqueue_thread, enqueue_thread_func, node, "amediacodec_input_thread");
...
while (!q->abort_request) {
...
ret = drain_output_buffer(env, node, timeUs, &dequeue_count, frame, &got_frame);
...
}
...
}
ffpipenode_android_mediacodec_vdec.c/ffpipenode_run_sync()干了两件事:
- 调用SDL_CreateThreadEx("amediacodec_input_thread")又创建了一个新的线程,干嘛用的待会说
- 调用drain_output_buffer函数
1. 调用SDL_CreateThreadEx(..., enqueue_thread_func, ..., "amediacodec_input_thread")
该线程命名叫amediacodec_input_thread,可以猜测它应该是将待解码视频帧推入硬解解码器的,该线程里调用了feed_input_buffer()函数,该函数主要代码如下
static int feed_input_buffer(JNIEnv *env, IJKFF_Pipenode *node, int64_t timeUs, int *enqueue_count)
{
...
if (d->pkt_temp.data) {
...
queue_flags = 0;
input_buffer_index = SDL_AMediaCodec_dequeueInputBuffer(opaque->acodec, timeUs);
if (input_buffer_index < 0) {
...
} else {
...
copy_size = SDL_AMediaCodec_writeInputData(opaque->acodec, input_buffer_index, d->pkt_temp.data, d->pkt_temp.size);
...
}
...
// ALOGE("queueInputBuffer, %lld\n", time_stamp);
amc_ret = SDL_AMediaCodec_queueInputBuffer(opaque->acodec, input_buffer_index, 0, copy_size, time_stamp, queue_flags);
...
}
...
}
feed_input_buffer中先是调用SDL_AMediaCodec_dequeueInputBuffer()获取输入buffer的地址,然后调用SDL_AMediaCodec_writeInputData()将待解码视频帧输入buffer里,最后调用SDL_AMediaCodec_queueInputBuffer()将buffer的数据送给解码器解码
可以看出新创建出的线程实际就是将数据塞入硬件解码器中
2. 调用drain_output_buffer函数
drain_output_buffer函数调用ffpipenode_android_mediacodec_vdec.c/drain_output_buffer_l()函数
static int drain_output_buffer_l(JNIEnv *env, IJKFF_Pipenode *node, int64_t timeUs, int *dequeue_count, AVFrame *frame, int *got_frame)
{
...
output_buffer_index = SDL_AMediaCodecFake_dequeueOutputBuffer(opaque->acodec, &bufferInfo, timeUs);
...
ret = amc_fill_frame(...)
...
}
ffpipenode_android_mediacodec_vdec.c/drain_output_buffer_l()先调用SDL_AMediaCodecFake_dequeueOutputBuffer()获取output buffer中解码好的数据地址,然后调用amc_fill_frame将解码后的视频帧取出
到这里视频解码线程就做完了,软解类似
这部分还有致谢同事东升同学和江月同学给的指导,哈哈