EasyPlayerPro(Windows)開發系列之解決ffmpeg接口調用卡住的問題

在EasyPlayerPro的開發過程中,經測試發現ffmpeg的讀取網絡流以及網絡數據的接口都有較大概率出現阻塞的問題,ffmpeg也提供了設置阻塞回調或者設置超時等方式來跳出阻塞而不會導致接口永久卡住;而在某些時候,比如,網絡斷開時間過長的時候,這個時候阻塞回調將不在有用而且阻塞的接口也不再返回數據,出現”永久性”假死的情況,針對這些問題,本文將對其處理方式進行一一講解。

1.播放器結束時接口導致線程卡住
針對該問題,我們通常可以在ffmpeg的阻塞回調函數中設置退出標誌來解決,如下代碼所示:

    //播放器退出狀態標誌,解除阻塞
    if(pPlayer->player_status & PS_CLOSE)
    {
        return AVERROR_EOF;  
    }

2.播放器因爲接口卡住而出現斷線
這個問題也就是我們通常情況下所說的斷線重連的處理,斷線重來你分兩步走,第一步,判斷出現斷線的時機;第二布,斷線進行重連的處理;
第一步,通常認定讀取的網絡流數據丟失一定的時間爲斷線,阻塞回調函數處理如下:

    int64_t curTime = av_gettime();
    //5s超時退出  
    if ((curTime - pPlayer->cur_read_time) > pPlayer->reconnect_time * 1000 * 1000)//5秒一次的重連
    {
        pPlayer->error_flag = 1;
        char sErrorInfo[100] = { 0, };
        sprintf(sErrorInfo, "interrupt_cb() enter,流已斷開,正在嘗試重連......curtime=%lld\n", curTime);
        OutputDebugStringA(sErrorInfo);
        return AVERROR_STREAM_NOT_FOUND;//AVERROR_EOF;
    }

cur_read_time在初始讀取網絡流時去一個當前的時間戳,以及av_read_frame時每一幀進行時間戳的更新,如果過一定的時間仍未更新該值,我們則認定網絡已經斷開,置error_flag =1進行重連,重連過程如下代碼所示:

    while (!(player->player_status & PS_CLOSE))
    {
        usleep(1000*1000);
        if (player->error_flag>0)//must be sth error
        {
            if (player->player_status & PS_CLOSE)
                goto error_handler;

            player->b_ready = 0;
            player_pause(player);
            usleep(500*1000);

            if (player->player_status & PS_CLOSE)
                goto error_handler;

            int64_t media_duration = -1;
            int64_t media_seek_pos = -1;
            if (player->avformat_context)
                media_duration = (player->avformat_context->duration * 1000 / AV_TIME_BASE);    
            render_getparam(player->render, PARAM_MEDIA_POSITION, &media_seek_pos);
            if (media_seek_pos > 0)
                media_seek_pos += 500;

            if (player->acodec_context) avcodec_close(player->acodec_context);
            player->acodec_context = NULL;
            if (player->vcodec_context) avcodec_close(player->vcodec_context);
            player->vcodec_context = NULL;
            if (player->avformat_context)
            {
                avformat_close_input(&player->avformat_context);
                avformat_free_context(player->avformat_context);
            }
            player->avformat_context = NULL;

            //++ for avdevice
            char          *url = player->file_url;
            AVInputFormat *fmt = NULL;
            void          *win = player->render_hwnd;

            player->avformat_context = avformat_alloc_context();
            player->avformat_context->interrupt_callback.callback = interrupt_cb;
            player->avformat_context->interrupt_callback.opaque = player;

            // open input file
            AVDictionary *options = NULL;
            //++ for find trsp
            if ((strstr(url, "rtsp") == url) || (strstr(url, "RTSP") == url))
            {
                if (player->link_mode == STREAM_LINK_TCP)
                    av_dict_set(&options, "rtsp_transport", "tcp", 0);
                else
                    av_dict_set(&options, "rtsp_transport", "udp", 0);
            }
            //-- for find trsp
            player->cur_read_time = av_gettime();
            if (avformat_open_input(&player->avformat_context, url, fmt, &options) != 0) 
            {
                continue;
                //goto error_handler;
            }
            if (player->player_status & PS_CLOSE)
                goto error_handler;

            // find stream info
            if (avformat_find_stream_info(player->avformat_context, NULL) < 0) 
            {
                continue;
                //goto error_handler;
            }
            if (player->player_status & PS_CLOSE)
                goto error_handler;

            // set current audio & video stream
            player->astream_index = -1; reinit_stream(player, AVMEDIA_TYPE_AUDIO, 0);
            player->vstream_index = -1; reinit_stream(player, AVMEDIA_TYPE_VIDEO, 0);

            // for audio
            if (player->astream_index != -1)
            {
                arate = player->acodec_context->sample_rate;
                aformat = player->acodec_context->sample_fmt;
                alayout = player->acodec_context->channel_layout;
                //++ fix audio channel layout issue
                if (alayout == 0) {
                    alayout = av_get_default_channel_layout(player->acodec_context->channels);
                }
                //-- fix audio channel layout issue
            }

            // for video
            if (player->vstream_index != -1) {
                vrate = player->avformat_context->streams[player->vstream_index]->r_frame_rate;
                if (vrate.num / vrate.den >= 100) {
                    vrate.num = 25;
                    vrate.den = 1;
                }
                player->vcodec_context->pix_fmt = vformat;
                width = player->vcodec_context->width;
                height = player->vcodec_context->height;
            }

#if 0
            // open render
            player->render = render_open(ADEV_RENDER_TYPE_WAVEOUT, arate, (AVSampleFormat)aformat, alayout,
                player->render_mode/*VDEV_RENDER_TYPE_GDI*//*VDEV_RENDER_TYPE_D3D*/, win, vrate, vformat, width, height, player->speed);
            if (player->vstream_index == -1) {
                int effect = VISUAL_EFFECT_WAVEFORM;
                render_setparam(player->render, PARAM_VISUAL_EFFECT, &effect);
            }
#endif
            if (player->player_status & PS_CLOSE)
                goto error_handler;
            player->b_ready = 1;
        }
    }

3.avformat_open_input以及av_read_frame接口出現永久性阻塞的處理

經測試,ffmpeg提供的avformat_open_input以及av_read_frame接口有概率出現永久性阻塞,即回調函數停止工作,該函數永久性不在返回的問題,解決辦法就是線程調用(當然正常情況下也一般都是線程調用),然後在播放器停止或者已知爲卡住的情況下強制結束線程,需要注意的是強制結束線程可能導致內存等資源訪問衝突的問題,需要靈活處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章