IJKPlayer ffplay流程詳解

ffplay流程圖.jpg

上圖摘自 雷霄驊 雷神的博客 是FFmpeg中 ffplay.c的結構流程圖

ijkplayer的ff_ffplay.c是建立在FFmpeg的ffplay.c的基礎之上的。

其核心:

read_thread線程負責接收服務器的數據 
video_thread線程負責video decode  
audio_thread負責audio decode
video_refresh_thread線程負責video 的渲染

audio的播放暫時還沒有細細的研究 audio_open是關鍵函數 SDL_OpenAudio():SDL中打開音頻設備的函數。注意它是根據SDL_AudioSpec參數打開音頻設備。SDL_AudioSpec中的callback字段指定了音頻播放的回調函數sdl_audio_callback()。當音頻設備需要更多數據的時候,會調用該回調函數。因此該函數是會被反復調用的。

ffp_prepare_async_l 函數開啟read_thread和video_refresh_thread線程。 
read_thread開啟audio_thread和video_thread解碼線程

ffp_prepare_async_l函數主要調用 ffpipeline_open_audio_output函數和stream_open函數。

ffpipeline_open_audio_output函數調用SDL_AoutIos_CreateForAudioUnit函數

SDL_Aout *SDL_AoutIos_CreateForAudioUnit()
{
    SDL_Aout *aout = SDL_Aout_CreateInternal(sizeof(SDL_Aout_Opaque));
    if (!aout)
        return NULL;

    // SDL_Aout_Opaque *opaque = aout->opaque;

    aout->free_l = aout_free_l;
    aout->open_audio  = aout_open_audio;
    aout->pause_audio = aout_pause_audio;
    aout->flush_audio = aout_flush_audio;
    aout->close_audio = aout_close_audio;

    aout->func_set_playback_rate = aout_set_playback_rate;

    return aout;
}
  

SDL_AoutIos_CreateForAudioUnit函數 實現了函數指針的賦值,用于ffplay回調控制audio output的方法。

stream_open函數進行初始化frame隊列 packet隊列還有音視頻同步相關的clock時鐘并且同時開啟了read_thread線程和video_refresh_thread線程。

video_refresh_thread:這個線程負責 video 的渲染。
read_thread:

這個線程先進行FFmpeg初始化操作,然后獲取視頻流信息

avformat_open_input():打開媒體。

avformat_find_stream_info():獲得媒體信息。這個函數有時候會影響首畫的時間,看這個函數的源碼發現:這個函數會一直分析視頻流的信息,當一直獲取不到信息的時候就會一直在這個函數中,一直到它最大的限制,才會出來,會有好幾秒的時間。如果網絡卡的話,時間會更長。但是我們可以自己給他添加限制,到達我們自己的限制條件的時候就會出來,不會一直在這個函數里面。

ic->probesize這個控制分析視頻流的大小,當讀的視頻流大小達到這個size大小的時候,即使沒有獲取到信息 也會出來

ic->max_analyze_duration這個事控制視頻流信息的大小,同理 當沒有獲取到信息,讀取的視頻流的時長達到這個duration的時候 也會出來。

av_dump_format():輸出媒體信息到控制臺。

stream_component_open():分別打開視頻/音頻/字幕解碼線程。(在下面詳細講解該函數)

av_read_frame():獲取一幀壓縮編碼數據(即一個AVPacket)。

packet_queue_put():根據壓縮編碼數據類型的不同(視頻/音頻/字幕),放到不同的PacketQueue中。
stream_component_open該函數:

1.如果是video:

ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);在這個函數中選擇硬解碼還是軟解碼,在iOS中實現如下

static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
    IJKFF_Pipenode* node = NULL;
    IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
    if (ffp->videotoolbox) {
        node = ffpipenode_create_video_decoder_from_ios_videotoolbox(ffp);
    }
    if (node == NULL) {
        ALOGE("vtb fail!!! switch to ffmpeg decode!!!! \n");
        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;
}

在android中實現如下:

static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{
    IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;
    IJKFF_Pipenode        *node = NULL;

    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);
    if (!node) {
        node = ffpipenode_create_video_decoder_from_ffplay(ffp);
    }

    return node;
}

選擇完解碼器,然后開始解碼

decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")創建一個解碼線程
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;
}

不管是iOS還是android中:

ffpipenode_create_video_decoder_from_ffplay
ffpipenode_create_video_decoder_from_android_mediacodec
ffpipenode_create_video_decoder_from_ios_videotoolbox
這三個方法都是返回node,并node這個結構體中的函數指針賦值。我們解碼的時候 調用的就是node->func_run_sync該方法,使用選擇的解碼器去解碼video。

軟解碼 該方法如下:

static int func_run_sync(IJKFF_Pipenode *node)
{
    IJKFF_Pipenode_Opaque *opaque = node->opaque;

    return ffp_video_thread(opaque->ffp);
}

使用videotoolbox 該方法如下:

static int func_run_sync(IJKFF_Pipenode *node)

{

    IJKFF_Pipenode_Opaque *opaque = node->opaque;

    int ret = videotoolbox_video_thread(opaque);

    if (opaque->context) {

        dealloc_videotoolbox(opaque->context);

        free(opaque->context);

        opaque->context = NULL;

    }
    return ret;
} 

使用mediacodec,該方法如下:

static int func_run_sync(IJKFF_Pipenode *node)
{
    JNIEnv                *env      = NULL;
    IJKFF_Pipenode_Opaque *opaque   = node->opaque;
    FFPlayer              *ffp      = opaque->ffp;
    VideoState            *is       = ffp->is;
    Decoder               *d        = &is->viddec;
    PacketQueue           *q        = d->queue;
    int                    ret      = 0;
    int                    dequeue_count = 0;
    AVFrame               *frame    = NULL;
    int                    got_frame = 0;
    AVRational             tb         = is->video_st->time_base;
    AVRational             frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
    double                 duration;
    double                 pts;
    // int64_t                decode_time = 0;
    if (!opaque->acodec) {
        return ffp_video_thread(ffp);
    }

    if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
        ALOGE("%s: SetupThreadEnv failed\n", __func__);
        return -1;
    }

    frame = av_frame_alloc();
    if (!frame)
        goto fail;

    if (opaque->frame_rotate_degrees == 90 || opaque->frame_rotate_degrees == 270) {
        opaque->frame_width  = opaque->avctx->height;
        opaque->frame_height = opaque->avctx->width;
    } else {
        opaque->frame_width  = opaque->avctx->width;
        opaque->frame_height = opaque->avctx->height;
    }

    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) {
        /*spb add
        if(ffp_frame_queue_nb_remaining(&is->pictq) == 2)
            ffp_frame_queue_next(&is->pictq);
        if (is->videoq.nb_packets == 0)
        {
           av_log(NULL, AV_LOG_INFO, "spb_log videoq is empty\n");
        }
         end*/
        Frame *vp = ffp_frame_queue_peek_writable(&is->pictq);
        int64_t timeUs = opaque->acodec_first_dequeue_output_request ? 0 : AMC_OUTPUT_TIMEOUT_US;
        got_frame = 0;
        // int64_t start_drain = av_gettime()/1000;
        ret = drain_output_buffer(env, node, timeUs, &dequeue_count, frame, &vp->dest_frame, &got_frame);
        // int64_t end_drain = av_gettime()/1000;
        if (opaque->acodec_first_dequeue_output_request) {
            SDL_LockMutex(opaque->acodec_first_dequeue_output_mutex);
            opaque->acodec_first_dequeue_output_request = false;
            SDL_CondSignal(opaque->acodec_first_dequeue_output_cond);
            SDL_UnlockMutex(opaque->acodec_first_dequeue_output_mutex);
        }
        if (ret != 0) {
            ret = -1;
            if (got_frame && frame->opaque)
                SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);
            goto fail;
        }
        if (got_frame) {
            // av_log(NULL, AV_LOG_INFO, "spb_log drain_output_buffer success need time:  %lld,    time : %lld\n", end_drain - start_drain, end_drain - decode_time);
            // decode_time = end_drain;
            duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
            pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
            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);
        }
    }

fail:
    av_frame_free(&frame);
    SDL_AMediaCodecFake_abort(opaque->acodec);
    if (opaque->n_buf_out) {
        free(opaque->amc_buf_out);
        opaque->n_buf_out = 0;
        opaque->amc_buf_out = NULL;
        opaque->off_buf_out = 0;
        opaque->last_queued_pts = AV_NOPTS_VALUE;
    }
    if (opaque->acodec) {
        SDL_VoutAndroid_invalidateAllBuffers(opaque->weak_vout);
        SDL_LockMutex(opaque->acodec_mutex);
        SDL_AMediaCodec_stop(opaque->acodec);
        SDL_UnlockMutex(opaque->acodec_mutex);
    }
    SDL_WaitThread(opaque->enqueue_thread, NULL);
    SDL_AMediaCodec_decreaseReferenceP(&opaque->acodec);
    ALOGI("MediaCodec: %s: exit: %d", __func__, ret);
    return ret;
#if 0
fallback_to_ffplay:
    ALOGW("fallback to ffplay decoder\n");
    return ffp_video_thread(opaque->ffp);
#endif
}
這個方法代碼較多,但是我們能看到 其核心就做了兩件事
第一件事 opaque->enqueue_thread = SDL_CreateThreadEx(&opaque->_enqueue_thread, enqueue_thread_func, node, "amediacodec_input_thread"); 創建一個新的線程,在packet queue中不停的取數據 放入硬解碼芯片。
第二件事 在下面的while循環中 調用drain_output_buffer這個函數 獲取解碼后的數據信息。

2.如果是audio:

先執行ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt),audio_open方法。
并沒有詳細的研究audio的每一步,audio_open也是在根據獲取到的流媒體信息進行audio 解碼和播放的初始化操作。
其中 wanted_spec.callback = sdl_audio_callback 是有關audio播放的一個函數指針的賦值。

然后

(ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec") 開啟線程解碼

audio解碼使用軟解,audio數據量小,算法相對簡單沒有video復雜,解碼對CPU資源占用極小。

ijk銷毀函數:iOS調用shutdown,android調用release。
這兩個函數本質都是要調用ffplay的 ffp_stop_l和ffp_wait_stop_l函數,
ijk的釋放都是在ffp_wait_stop_l函數中,這個函數主要通過調用stream_close函數來釋放掉ijk。
stream_close會銷毀在ijk初始化時候創建的 隊列 流 解碼器 線程鎖和線程條件變量。

未完待續。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容