04-vlc完整的播放mp4文件流程

一、总体流程
vlc核心框架则使用c进行实现,一些具体的功能由插件实现。播放一个视频文件,流程大致如下:
1、用户打开文件播放操作,通过qt线程传递给input线程或者列表线程
2、列表线程做出一系列动作,如果成功则将通知qt线程,界面需要做出状态改变,并创建input线程
3、input线程从demux模块中获取数据,将数据给到decode解码线程
4、解码线程解码后,将数据给到输出线程(只有视频有输出线程,音频等待合适时间播放)
5、输出线程处理后,在正确的时间将数据显示
下面通过播放一个mp4文件,将整体流程过一遍
二、ui线程——打开本地mp4文件
通过播放一个mp4本地文件,来分析qt线程与input线程之间的通信
1、选择一个本地mp4文件后,点击播放按钮,播放按钮对应的响应函数如下:

void OpenDialog::play()
{
    enqueue( false );
}

void OpenDialog::enqueue( bool b_enqueue )
{
    ...
    for( int i = 0; i < itemsMRL.count(); i++ )
    {
        bool b_start = !i && !b_enqueue;
        /* Take options from the UI, not from what we stored */
        QStringList optionsList = getOptions().split( " :" );
        /* Switch between enqueuing and starting the item */
        //b_start,咱们这里是true
        Open::openMRLwithOptions( p_intf, itemsMRL[i], &optionsList, b_start, b_pl );
    }
}

int Open::openMRLwithOptions( intf_thread_t* p_intf,
                     const QString &mrl,
                     QStringList *options,
                     bool b_start,
                     bool b_playlist,
                     const char *title)
{
    /* Options */
    .....
    //将文件加入播放列表中
    int i_ret = playlist_AddExt( THEPL, qtu(mrl), title, b_start,
                  i_options, ppsz_options, VLC_INPUT_OPTION_TRUSTED,
                  b_playlist );
    //如果播放,在最近播放列表添加一项
    if( i_ret == VLC_SUCCESS && b_start && b_playlist )
        RecentsMRL::getInstance( p_intf )->addRecent( mrl );
    /* Free options */
    if ( ppsz_options != NULL )
    {
        for ( int i = 0; i < i_options; ++i )
            free( (char*)ppsz_options[i] );
        delete[] ppsz_options;
    }
    return i_ret;
}

2、创建播放项playlist_item_t
playlist_AddExt是创建一个输入项input_item_t,并为输入项创建一个播放项playlist_item_t,

int playlist_AddExt( playlist_t *p_playlist, const char * psz_uri,
                     const char *psz_name, bool play_now,
                     int i_options, const char *const *ppsz_options,
                     unsigned i_option_flags,
                     bool b_playlist )
{
    //创建输入项input_item_t
    input_item_t *p_input = input_item_New( psz_uri, psz_name );
    if( !p_input )
        return VLC_ENOMEM;
    //为输入项添加参数
    input_item_AddOptions( p_input, i_options, ppsz_options, i_option_flags );
    
    int i_ret = playlist_AddInput( p_playlist, p_input, play_now, b_playlist );
    input_item_Release( p_input );
    return i_ret;
}

//playlist_AddInput里面调用的是playlist_NodeAddInput
playlist_item_t * playlist_NodeAddInput( playlist_t *p_playlist,
                                         input_item_t *p_input,
                                         playlist_item_t *p_parent, int i_pos )
{
    PL_ASSERT_LOCKED;
    assert( p_input );
    assert( p_parent && p_parent->i_children != -1 );
    //根据输入项创建播放项,并注册一些事件通知
    playlist_item_t *p_item = playlist_ItemNewFromInput( p_playlist, p_input );
    if( unlikely(p_item == NULL) )
        return NULL;
    if( p_input->i_type != ITEM_TYPE_NODE )
        ARRAY_APPEND(p_playlist->items, p_item);
//将播放项加入到播放树中
    playlist_NodeInsert( p_parent, p_item, i_pos );
//通知播放列表线程需要播放了
    playlist_SendAddNotify( p_playlist, p_item );
    playlist_Preparse( p_playlist, p_item );
    return p_item;
}

三、playlist线程——更新播放项

  1. 上述的playlist_SendAddNotify函数,通过条件变量通知playlist线程有输入播放
void playlist_SendAddNotify( playlist_t *p_playlist, playlist_item_t *item )
{
    playlist_private_t *p_sys = pl_priv(p_playlist);
    PL_ASSERT_LOCKED;
        //重置当前播放项
    p_sys->b_reset_currently_playing = true;
        //唤醒播放列表线程
    vlc_cond_signal( &p_sys->signal );
        //播放列表增加播放项,界面发生改变
    var_SetAddress( p_playlist, "playlist-item-append", item );
}
  1. p_sys->signal唤醒对应的是列表线程中的条件变量,如下:
static void *Thread ( void *data )
{
    playlist_t *p_playlist = data;
    playlist_private_t *p_sys = pl_priv(p_playlist);
    bool played = false;
    PL_LOCK;
    while( !p_sys->killed )
    {
        /* Playlist in stopped state */
        assert(p_sys->p_input == NULL);
        if( !p_sys->request.b_request )
        {
    //无播放项的时候,列表线程在这等待唤醒,对应着上述界面操作后被唤醒
            vlc_cond_wait( &p_sys->signal, &p_sys->lock );
            continue;
        }
       //Next:切换到下个播放项,成功的话,进入LoopInput循环,等待下一次输入切换或者播放完毕
        while( !p_sys->killed && Next( p_playlist ) )
        {
            LoopInput( p_playlist );
            played = true;
        }
       ......
    return NULL;
}
3. 切换下一播放项
static bool Next( playlist_t *p_playlist )
{
    //NextItem:通过请求获取到当前的playlist_item_t,重置数据后返回,PlayItem进行播放
    playlist_item_t *p_item = NextItem( p_playlist );
    if( p_item == NULL )
        return false;
    msg_Dbg( p_playlist, "starting playback of new item" );
    ResyncCurrentIndex( p_playlist, p_item );
    //开始播放
    return PlayItem( p_playlist, p_item );
}

4、开始正式播放播放项,并创建一个输入线程

static bool PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
{
    ......
    if( p_renderer )
        vlc_renderer_item_hold( p_renderer );
    assert( p_sys->p_input == NULL );
    PL_UNLOCK;
    libvlc_MetadataCancel( p_playlist->obj.libvlc, p_item );
    //input_Create:创建输入线程实例
    input_thread_t *p_input_thread = input_Create( p_playlist, p_input, NULL,
                                                   p_sys->p_input_resource,
                                                   p_renderer );
    if( p_renderer )
        vlc_renderer_item_release( p_renderer );
    if( likely(p_input_thread != NULL) )
    {
        var_AddCallback( p_input_thread, "intf-event",
                         InputEvent, p_playlist );
    //input_Start:创建input线程,,会进入循环,不停地demux数据,送到解码线程
        if( input_Start( p_input_thread ) )
        {
            var_DelCallback( p_input_thread, "intf-event",
                             InputEvent, p_playlist );
            vlc_object_release( p_input_thread );
            p_input_thread = NULL;
        }
    }
   .......
    return p_input_thread != NULL;
}

四、input线程——获取输入数据,并将数据送到下一个阶层
输入线程管理单个输入的处理(如暂停、播放、重放等),vlc后台支持同时起多个输入,只是界面模块暂时不支持而已。当播放流时,上述input_Start函数里面会创建一个输入线程。

  1. input线程函数如下:
static void *Run( void *data )
{
    input_thread_private_t *priv = data;
    input_thread_t *p_input = &priv->input;
    vlc_interrupt_set(&priv->interrupt);
//线程资源初始化,并为输入数据创建多个输出通道es_out,如音频流输出、视频流输出
    if( !Init( p_input ) )
    {
        if( priv->b_can_pace_control && priv->b_out_pace_control )
        {
            /* We don't want a high input priority here or we'll
             * end-up sucking up all the CPU time */
            vlc_set_priority( priv->thread, VLC_THREAD_PRIORITY_LOW );
        }
    //进入循环
        MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */
        /* Clean up */
        End( p_input );
    }
    input_SendEventDead( p_input );
    return NULL;
}
  1. MainLoop会进入循环,不断的从demux模块中获取数据,数据经抽象层次es_out传递给decode进行解码。
static void MainLoop( input_thread_t *p_input, bool b_interactive )
{
    mtime_t i_intf_update = 0;
    mtime_t i_last_seek_mdate = 0;
    if( b_interactive && var_InheritBool( p_input, "start-paused" ) )
        ControlPause( p_input, mdate() );
    bool b_pause_after_eof = b_interactive &&
                           var_InheritBool( p_input, "play-and-pause" );
    bool b_paused_at_eof = false;
    demux_t *p_demux = input_priv(p_input)->master->p_demux;
    const bool b_can_demux = p_demux->pf_demux != NULL;
//没有停止不停循环
    while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )
    {
        mtime_t i_wakeup = -1;
        bool b_paused = input_priv(p_input)->i_state == PAUSE_S;
        /* FIXME if input_priv(p_input)->i_state == PAUSE_S the access/access_demux
         * is paused -> this may cause problem with some of them
         * The same problem can be seen when seeking while paused */
        //es_out_GetBuffering:判断是否正在缓冲数据中(后续文章再进行深入分析数据缓冲)
        if( b_paused )
            b_paused = !es_out_GetBuffering( input_priv(p_input)->p_es_out )
                    || input_priv(p_input)->master->b_eof;
        if( !b_paused )
        {
            if( !input_priv(p_input)->master->b_eof )
            {
                //是否进行强制刷新
                bool b_force_update = false;
                //解复用数据
                MainLoopDemux( p_input, &b_force_update );
                //i_wakeup : 获取下一个唤醒时间
                if( b_can_demux )
                    i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
                if( b_force_update )
                    i_intf_update = 0;
                b_paused_at_eof = false;
            }
            else if( !es_out_GetEmpty( input_priv(p_input)->p_es_out ) )
            {
                //等待解码线程解码完毕
                msg_Dbg( p_input, "waiting decoder fifos to empty" );
                i_wakeup = mdate() + INPUT_IDLE_SLEEP;
            }
            /* Pause after eof only if the input is pausable.
             * This way we won't trigger timeshifting for nothing */
            else if( b_pause_after_eof && input_priv(p_input)->b_can_pause )
            {
                //暂停播放
                if( b_paused_at_eof )
                    break;
                vlc_value_t val = { .i_int = PAUSE_S };
                msg_Dbg( p_input, "pausing at EOF (pause after each)");
                Control( p_input, INPUT_CONTROL_SET_STATE, val );
                b_paused = true;
                b_paused_at_eof = true;
            }
            else
            {
            //重复播放
                if( MainLoopTryRepeat( p_input ) )
                    break;
            }
            /* Update interface and statistics */
            mtime_t now = mdate();
            if( now >= i_intf_update )
            {
            //   刷新播放数据统计值
                MainLoopStatistics( p_input );
            //每隔250ms刷新一次,或者b_force_update 为true时强制刷新
                i_intf_update = now + INT64_C(250000);
            }
        }
        /* Handle control */
        for( ;; )
        {
            mtime_t i_deadline = i_wakeup;
            /* Postpone seeking until ES buffering is complete or at most
             * 125 ms. */
            bool b_postpone = es_out_GetBuffering( input_priv(p_input)->p_es_out )
                            && !input_priv(p_input)->master->b_eof;
            //b_postpone 为true,正在缓冲数据中
            if( b_postpone )
            {
                mtime_t now = mdate();
                //如果正在缓冲,则每个20ms,检测是否缓冲完整
                if( now < i_last_seek_mdate + INT64_C(125000)
                 && (i_deadline < 0 || i_deadline > now + INT64_C(20000)) )
                    i_deadline = now + INT64_C(20000);
                else
                    b_postpone = false;//超过125ms将b_postpone 置成false
            }
            int i_type;
            vlc_value_t val;
            //睡眠等待,直到有请求,或者到达唤醒时间
            if( ControlPop( p_input, &i_type, &val, i_deadline, b_postpone ) )
            {
                if( b_postpone )//有缓冲,继续等待直到没有缓冲或者超过125ms将b_postpone 置成false
                    continue;//
                break; //唤醒时间到了,开始继续上面大循环的解复用数据
            }
#ifndef NDEBUG
            msg_Dbg( p_input, "control type=%d", i_type );
#endif
            //根据请求类型,处理请求,这里下篇文章详解,主要处理input的控制逻辑
            if( Control( p_input, i_type, val ) )
            {
                //开始播放和seek后都会进缓冲buffering,i_last_seek_mdate 记录seek时间
                if( ControlIsSeekRequest( i_type ) )
                    i_last_seek_mdate = mdate();
                i_intf_update = 0;
            }
            /* Update the wakeup time */
            if( i_wakeup != 0 )
                i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
        }
    }
}
  1. MainLoopDemux调用demux模块注册的解复用函数
static void MainLoopDemux( input_thread_t *p_input, bool *pb_changed )
{
    input_thread_private_t* p_priv = input_priv(p_input);
    demux_t *p_demux = p_priv->master->p_demux;
    int i_ret = VLC_DEMUXER_SUCCESS;

 ......
//demux_Demux中调用demux_t结构中注册的解复用函数pf_demux,此处因为播放的是mp4文件,所以使用的是demux类型的mp4模块,mp4模块内部具体如何工作,后续章节再分析
    if( i_ret == VLC_DEMUXER_SUCCESS )
        i_ret = demux_Demux( p_demux );

    i_ret = i_ret > 0 ? VLC_DEMUXER_SUCCESS : ( i_ret < 0 ? VLC_DEMUXER_EGENERIC : VLC_DEMUXER_EOF);

    if( i_ret == VLC_DEMUXER_SUCCESS )
    {
        if( demux_TestAndClearFlags( p_demux, INPUT_UPDATE_TITLE_LIST ) )
            UpdateTitleListfromDemux( p_input );

        if( p_priv->master->b_title_demux )
        {
    //更新字幕位置
            i_ret = UpdateTitleSeekpointFromDemux( p_input );
            *pb_changed = true;
        }

        UpdateGenericFromDemux( p_input );
    }

   ......
}
  1. 上述pf_demux调用mp4模块的demux函数后,会调用es_out层的es_out_Send函数,将数据传给解码线程
static inline int es_out_Send( es_out_t *out, es_out_id_t *id,
                               block_t *p_block )
{
    return out->pf_send( out, id, p_block );
}
  1. pf_send实际调用的是es_out.c中的EsOutSend
static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block )
{
    es_out_sys_t   *p_sys = out->p_sys;
    input_thread_t *p_input = p_sys->p_input;
    assert( p_block->p_next == NULL );
   ......
    /* Decode */
    if( es->p_dec_record )
    {
        block_t *p_dup = block_Duplicate( p_block );
        if( p_dup )
            input_DecoderDecode( es->p_dec_record, p_dup,
                                 input_priv(p_input)->b_out_pace_control );
    }
//input_DecoderDecode:这里才是真正的将数据放入解码线程的
    input_DecoderDecode( es->p_dec, p_block,
                         input_priv(p_input)->b_out_pace_control );
    es_format_t fmt_dsc;
    vlc_meta_t  *p_meta_dsc;
//检查数据格式是否发生改变
    if( input_DecoderHasFormatChanged( es->p_dec, &fmt_dsc, &p_meta_dsc ) )
    {
        EsOutUpdateInfo( out, es, &fmt_dsc, p_meta_dsc );
        es_format_Clean( &fmt_dsc );
        if( p_meta_dsc )
            vlc_meta_Delete( p_meta_dsc );
    }
   //检查字幕状态
    decoder_cc_desc_t desc;
    input_DecoderGetCcDesc( es->p_dec, &desc );
    if( var_InheritInteger( p_input, "captions" ) == 708 )
        EsOutCreateCCChannels( out, VLC_CODEC_CEA708, desc.i_708_channels,
                               _("DTVCC Closed captions %u"), es );
    EsOutCreateCCChannels( out, VLC_CODEC_CEA608, desc.i_608_channels,
                           _("Closed captions %u"), es );
    vlc_mutex_unlock( &p_sys->lock );
    return VLC_SUCCESS;
}
  1. input_DecoderDecode将数据送到解码队列中
void input_DecoderDecode( decoder_t *p_dec, block_t *p_block, bool b_do_pace )
{
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    vlc_fifo_Lock( p_owner->p_fifo );
    if( !b_do_pace )
    {
        /* FIXME: ideally we would check the time amount of data
         * in the FIFO instead of its size. */
        //解码队列最大数据不能超过400MB
        if( vlc_fifo_GetBytes( p_owner->p_fifo ) > 400*1024*1024 )
        {
            msg_Warn( p_dec, "decoder/packetizer fifo full (data not "
                      "consumed quickly enough), resetting fifo!" );
            block_ChainRelease( vlc_fifo_DequeueAllUnlocked( p_owner->p_fifo ) );
            p_block->i_flags |= BLOCK_FLAG_DISCONTINUITY;
        }
    }
    else
    if( !p_owner->b_waiting )
    {   /* The FIFO is not consumed when waiting, so pacing would deadlock VLC.
         * Locking is not necessary as b_waiting is only read, not written by
         * the decoder thread. */
        //等待解码线程消耗块数据
        while( vlc_fifo_GetCount( p_owner->p_fifo ) >= 10 )
            vlc_fifo_WaitCond( p_owner->p_fifo, &p_owner->wait_fifo );
    }
    //将数据放入解码队列中
    vlc_fifo_QueueUnlocked( p_owner->p_fifo, p_block );
    vlc_fifo_Unlock( p_owner->p_fifo );
}

6.vlc_fifo_QueueUnlocked将数据追加到解码队列中

void vlc_fifo_QueueUnlocked(block_fifo_t *fifo, block_t *block)
{
//block_fifo_t使用一个单项链表实现解码队列
    vlc_assert_locked(&fifo->lock);
    assert(*(fifo->pp_last) == NULL);
    *(fifo->pp_last) = block;
    while (block != NULL)
    {
        fifo->pp_last = &block->p_next;
        fifo->i_depth++;//数据块的个数
        fifo->i_size += block->i_buffer;//数据总共字节大小
        block = block->p_next;
    }
    //唤醒解码线程
    vlc_fifo_Signal(fifo);
}

五、解码线程——解码音频和视频流数据

  1. decode线程从解码管道fifo中取出数据,解码完成后将数据输出到输出管道中,decode线程如下:
static void *DecoderThread( void *p_data )
{
    decoder_t *p_dec = (decoder_t *)p_data;
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    bool paused = false;
    /* The decoder's main loop */
    vlc_fifo_Lock( p_owner->p_fifo );
    vlc_fifo_CleanupPush( p_owner->p_fifo );
    for( ;; )
    {
        if( p_owner->flushing )
        {   /* Flush before/regardless of pause. We do not want to resume just
             * for the sake of flushing (glitches could otherwise happen). */
            int canc = vlc_savecancel();
            vlc_fifo_Unlock( p_owner->p_fifo );
            //清空输出
            DecoderProcessFlush( p_dec );
            vlc_fifo_Lock( p_owner->p_fifo );
            vlc_restorecancel( canc );
            /* Reset flushing after DecoderProcess in case input_DecoderFlush
             * is called again. This will avoid a second useless flush (but
             * harmless). */
            p_owner->flushing = false;
            continue;
        }
        if( paused != p_owner->paused )
        {   //更新输出的播放状态
            int canc = vlc_savecancel();
            mtime_t date = p_owner->pause_date;
            paused = p_owner->paused;
            vlc_fifo_Unlock( p_owner->p_fifo );
            /* NOTE: Only the audio and video outputs care about pause. */
            msg_Dbg( p_dec, "toggling %s", paused ? "resume" : "pause" );
            if( p_owner->p_vout != NULL )
                vout_ChangePause( p_owner->p_vout, paused, date );
            if( p_owner->p_aout != NULL )
                aout_DecChangePause( p_owner->p_aout, paused, date );
            vlc_restorecancel( canc );
            vlc_fifo_Lock( p_owner->p_fifo );
            continue;
        }
        if( p_owner->paused && p_owner->frames_countdown == 0 )
        {   /* Wait for resumption from pause */
            p_owner->b_idle = true;
            //唤醒输入线程
            vlc_cond_signal( &p_owner->wait_acknowledge );
            vlc_fifo_Wait( p_owner->p_fifo );
            p_owner->b_idle = false;
            continue;
        }
        vlc_cond_signal( &p_owner->wait_fifo );
        vlc_testcancel(); /* forced expedited cancellation in case of stop */
//vlc_fifo_DequeueUnlocked:从解码管道中获取块数据
        block_t *p_block = vlc_fifo_DequeueUnlocked( p_owner->p_fifo );
        if( p_block == NULL )
        {
        //b_draining:标识数据读完,需要排尽解码队列中的数据,所有数据等待输出线程消耗完毕
            if( likely(!p_owner->b_draining) )
            {   //唤醒input线程,等待有数据到来
                p_owner->b_idle = true;
                vlc_cond_signal( &p_owner->wait_acknowledge );
                vlc_fifo_Wait( p_owner->p_fifo );
                p_owner->b_idle = false;
                continue;
            }
            /* We have emptied the FIFO and there is a pending request to
             * drain. Pass p_block = NULL to decoder just once. */
        }
        vlc_fifo_Unlock( p_owner->p_fifo );
        int canc = vlc_savecancel();
    //解码数据
        DecoderProcess( p_dec, p_block );
        if( p_block == NULL )
        {   /* Draining: the decoder is drained and all decoded buffers are
             * queued to the output at this point. Now drain the output. */
        //mp4文件demux完毕后,等待音频输出中缓冲数据消耗完毕
            if( p_owner->p_aout != NULL )
                aout_DecFlush( p_owner->p_aout, true );
        }
        vlc_restorecancel( canc );
        /* TODO? Wait for draining instead of polling. */
        vlc_mutex_lock( &p_owner->lock );
        if( p_owner->b_draining && (p_block == NULL) )
        {
            p_owner->b_draining = false;
            p_owner->drained = true;//解码队列数据清空完毕
        }
        vlc_fifo_Lock( p_owner->p_fifo );
        vlc_cond_signal( &p_owner->wait_acknowledge );
        vlc_mutex_unlock( &p_owner->lock );
    }
    vlc_cleanup_pop();
    vlc_assert_unreachable();
}
  1. 处理解码
static void DecoderProcess( decoder_t *p_dec, block_t *p_block )
{
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    if( p_owner->error )
        goto error;
    /* Here, the atomic doesn't prevent to miss a reload request.
     * DecoderProcess() can still be called after the decoder module or the
     * audio output requested a reload. This will only result in a drop of an
     * input block or an output buffer. */
    enum reload reload;
    if( ( reload = atomic_exchange( &p_owner->reload, RELOAD_NO_REQUEST ) ) )
    {
        msg_Warn( p_dec, "Reloading the decoder module%s",
                  reload == RELOAD_DECODER_AOUT ? " and the audio output" : "" );
        //重新加载解码模块
        if( ReloadDecoder( p_dec, false, &p_dec->fmt_in, reload ) != VLC_SUCCESS )
            goto error;
    }
    bool packetize = p_owner->p_packetizer != NULL;
    if( p_block )
    {
        if( p_block->i_buffer <= 0 )
            goto error;
        vlc_mutex_lock( &p_owner->lock );
    //i_preroll_end:最新缓存数据的时间,更新这个时间
        DecoderUpdatePreroll( &p_owner->i_preroll_end, p_block );
        vlc_mutex_unlock( &p_owner->lock );
        if( unlikely( p_block->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
        {
            /* This block has already been packetized */
            packetize = false;
        }
    }
#ifdef ENABLE_SOUT
    if( p_owner->p_sout != NULL )
    {
        DecoderProcessSout( p_dec, p_block );
        return;
    }
#endif
    ......
        DecoderDecode( p_dec, p_block );
    return;
error:
    if( p_block )
        block_Release( p_block );
}

//调用解码模块注册的解码函数
static void DecoderDecode( decoder_t *p_dec, block_t *p_block )
{
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    //pf_decode:真正解码
    int ret = p_dec->pf_decode( p_dec, p_block );
    switch( ret )
    {
        case VLCDEC_SUCCESS:
            //更新解码成功或失败的统计值
            p_owner->pf_update_stat( p_owner, 1, 0 );
            break;
        case VLCDEC_ECRITICAL:
            p_owner->error = true;
            break;
        case VLCDEC_RELOAD:
            RequestReload( p_dec );
            if( unlikely( p_block == NULL ) )
                break;
            if( !( p_block->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
            {
                p_block->i_flags |= BLOCK_FLAG_CORE_PRIVATE_RELOADED;
                DecoderProcess( p_dec, p_block );
            }
            else /* We prefer loosing this block than an infinite recursion */
                block_Release( p_block );
            break;
        default:
            vlc_assert_unreachable();
    }
}

上述DecodeProcess解码,会调用加载解码模块时pf_decode注册的真正解码函数,这里mp4解码部分使用的是ffpmeg模块的解码函数,音频对应的是audio.c中的DecodeAudio函数,视频对应的是video.c中的DecodeVideo函数,具体解码流程后续再分析。解码后数据通过之前使用CreateDecoder创建解码结构时注册的输出函数,音频输出(pf_queue_audio)、视频输出(pf_queue_video)、子码流输出(pf_queue_sub)函数。下面就分别分析下音频和视频的输出函数
1)pf_queue_audio(实际调用的是DecoderQueueAudio)

static int DecoderQueueAudio( decoder_t *p_dec, block_t *p_aout_buf )
{
    unsigned lost = 0;
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    //输出音频数据
    int ret = DecoderPlayAudio( p_dec, p_aout_buf, &lost );
    //更新解码统计数据
    p_owner->pf_update_stat( p_owner, 1, lost );
    return ret;
}

static int DecoderPlayAudio( decoder_t *p_dec, block_t *p_audio,
                             unsigned *restrict pi_lost_sum )
{
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    bool prerolled;
    assert( p_audio != NULL );
    vlc_mutex_lock( &p_owner->lock );
    //i_preroll_end:记录最新一个包dts数据时间,音频dts和i_pts相等,早于上一个包数据,就释放
    if( p_owner->i_preroll_end > p_audio->i_pts )
    {
        vlc_mutex_unlock( &p_owner->lock );
        block_Release( p_audio );
        return -1;
    }
    prerolled = p_owner->i_preroll_end > INT64_MIN;
    p_owner->i_preroll_end = INT64_MIN;
    vlc_mutex_unlock( &p_owner->lock );
    if( unlikely(prerolled) )
    {
        msg_Dbg( p_dec, "end of audio preroll" );
        if( p_owner->p_aout )
            aout_DecFlush( p_owner->p_aout, false );
    }
    /* */
    if( p_audio->i_pts <= VLC_TS_INVALID ) // FIXME --VLC_TS_INVALID verify audio_output/*
    {
        msg_Warn( p_dec, "non-dated audio buffer received" );
        *pi_lost_sum += 1;//解码失败次数+1
        block_Release( p_audio );
        return 0;
    }
    /* */
    vlc_mutex_lock( &p_owner->lock );
    if( p_owner->b_waiting )
    {
        p_owner->b_has_data = true;//唤醒input线程
        vlc_cond_signal( &p_owner->wait_acknowledge );
    }
    /* */
    int i_rate = INPUT_RATE_DEFAULT;
    //等待数据,这块有点绕,后面专门弄一篇章讲一下input线程和decode线程同步的流程
    DecoderWaitUnblock( p_dec );
            //修正pts时间
    DecoderFixTs( p_dec, &p_audio->i_pts, NULL, &p_audio->i_length,
                  &i_rate, AOUT_MAX_ADVANCE_TIME );
    vlc_mutex_unlock( &p_owner->lock );
    audio_output_t *p_aout = p_owner->p_aout;
    if( p_aout != NULL && p_audio->i_pts > VLC_TS_INVALID
     && i_rate >= INPUT_RATE_DEFAULT/AOUT_MAX_INPUT_RATE
     && i_rate <= INPUT_RATE_DEFAULT*AOUT_MAX_INPUT_RATE
            //DecoderTimedWait,等待合适时间再播放
     && !DecoderTimedWait( p_dec, p_audio->i_pts - AOUT_MAX_PREPARE_TIME ) )
    {
                        //aout_DecPlay调用输出模块播放音频(aout->play 注册的函数)
        int status = aout_DecPlay( p_aout, p_audio, i_rate );
        if( status == AOUT_DEC_CHANGED )
        {
            /* Only reload the decoder */
            RequestReload( p_dec );
        }
        else if( status == AOUT_DEC_FAILED )
        {
            /* If we reload because the aout failed, we should release it. That
             * way, a next call to aout_update_format() won't re-use the
             * previous (failing) aout but will try to create a new one. */
            atomic_store( &p_owner->reload, RELOAD_DECODER_AOUT );
        }
    }
    else
    {
        msg_Dbg( p_dec, "discarded audio buffer" );
        *pi_lost_sum += 1;
        block_Release( p_audio );
    }
    return 0;
}

2)pf_queue_video(实际调用的是DecoderQueueVideo),DecoderQueueVideo调用的是DecoderPlayVideo
DecoderPlayVideo流程跟音频的类似,不同的是vout_PutPicture不是直接调用视频输出模块的函数,而是将数据交给视频输出线程,视频输出线程在合适的时间将视频显示

tatic int DecoderPlayVideo( decoder_t *p_dec, picture_t *p_picture,
                             unsigned *restrict pi_lost_sum )
{
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    vout_thread_t  *p_vout = p_owner->p_vout;
    ......
    /* FIXME: The *input* FIFO should not be locked here. This will not work
     * properly if/when pictures are queued asynchronously. */
    vlc_fifo_Lock( p_owner->p_fifo );
    if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
        p_owner->frames_countdown--;
    vlc_fifo_Unlock( p_owner->p_fifo );
    /* */
    if( p_vout == NULL )
        goto discard;
    if( p_picture->b_force || p_picture->date > VLC_TS_INVALID )
        /* FIXME: VLC_TS_INVALID -- verify video_output */
    {
        if( i_rate != p_owner->i_last_rate || b_first_after_wait )
        {
            /* Be sure to not display old picture after our own */
            vout_Flush( p_vout, p_picture->date );
            p_owner->i_last_rate = i_rate;
        }
  //将帧数据放入输出管道中,被视频输出线程使用,视频输出线程调用视频显示模块的display函数将数据进行显示,限于篇章,这块后续再详细分析
        vout_PutPicture( p_vout, p_picture );
    }
    else
    {
  if( b_dated )
            msg_Warn( p_dec, "early picture skipped" );
        else
            msg_Warn( p_dec, "non-dated video buffer received" );
        goto discard;
    }
    return 0;
discard:
    *pi_lost_sum += 1;
    picture_Release( p_picture );
    return 0;
}

至此,完整的mp4文件播放流程完毕,中间有很多,如音频和视频的同步,输入线程与解码线程的协同工作原理、视频输出显示逻辑等待,还未详细分析,后续再分析

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值