ijkplayer 视频解码线程

在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()干了两件事:

  1. 调用SDL_CreateThreadEx("amediacodec_input_thread")又创建了一个新的线程,干嘛用的待会说
  2. 调用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将解码后的视频帧取出

到这里视频解码线程就做完了,软解类似

这部分还有致谢同事东升同学和江月同学给的指导,哈哈

你可能感兴趣的:(ijkplayer 视频解码线程)