美文网首页ijkplayer
ijkplayer 视频解码线程

ijkplayer 视频解码线程

作者: ce0b74704937 | 来源:发表于2018-06-06 20:38 被阅读0次

    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 视频解码线程

        本文链接:https://www.haomeiwen.com/subject/ztsisftx.html