VLC-Android退出流程

本文详细介绍了VLC-Android在退出过程中,从Java层到Native层的停止调用流程,包括主线程(input_thread)的停止、es_out模块的清理、解码器(decoder)的关闭,以及aout和vout模块的退出。在分析一个播放器退出ANR问题时,发现解码器卡住导致播放器无法正常退出,深入探讨了解码线程与MediaCodec交互的可能问题及其解决方案。

最近在分析一个播放器退出ANR的问题时,详细地跟踪了VLC的退出流程,这里问题的分析先按下不表,我们先结合代码详细了解下播放器的退出流程。

1. java—>jni—>native的Stop调用流程,没什么好说的,直接上代码

//MediaPlayer.java

/**

 * Stops the playing media

 *

 */

public void stop() {

    synchronized (this) {

        mPlayRequested = false;

        mPlaying = false;

        mAudioReset = true;

    }

    //调用native方法

    nativeStop();

    if (mAfd != nulltry {

        mAfd.close();

    catch (IOException ignored) {}

}

//libvlcjni-mediaplayer.c

void

Java_org_videolan_libvlc_MediaPlayer_nativeStop(JNIEnv *env, jobject thiz)

{

    LOGI("libvlc_MediaPlayer_nativeStop +++ ");

    vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);

    if (!p_obj)

    {

        LOGI("libvlc_MediaPlayer_nativeStop -- ");

        return;

    }

    //jni只是桥梁,这里调用真正的stop

    libvlc_media_player_stop(p_obj->u.p_mp);

    LOGI("libvlc_MediaPlayer_nativeStop ---");

}

//media_player.c

/*

 * Release the associated input thread.

 *

 * Object lock is NOT held.

 * Input lock is held or instance is being destroyed.

 */

static void release_input_thread( libvlc_media_player_t *p_mi )

{

    assert( p_mi );

    input_thread_t *p_input_thread = p_mi->input.p_thread;

    if( !p_input_thread )

        return;

    p_mi->input.p_thread = NULL;

    media_detach_preparsed_event( p_mi->p_md );

    //删除回调函数

    var_DelCallback( p_input_thread, "can-seek",

                     input_seekable_changed, p_mi );

    var_DelCallback( p_input_thread, "can-pause",

                    input_pausable_changed, p_mi );

    var_DelCallback( p_input_thread, "program-scrambled",

                    input_scrambled_changed, p_mi );

    var_DelCallback( p_input_thread, "intf-event",

                     input_event_changed, p_mi );

    del_es_callbacks( p_input_thread, p_mi );

    //这里先后调用了input_thread的input_Stop和input_Close函数

    /* We owned this one */

    input_Stop( p_input_thread );

    input_Close( p_input_thread );

}

2.input_thread本身是一个while循环,是播放的主线程,不停地demux数据并处理控制事件。在退出主线程前,需要先将播放器的其他线程全部退出。

/**

 * Close an input

 *

 * It does not call input_Stop itself.

 */

void input_Close( input_thread_t *p_input )

{

    if( input_priv(p_input)->is_running )

        //input_Close函数关闭了播放主线程

        vlc_join( input_priv(p_input)->thread, NULL );

    vlc_interrupt_deinit( &input_priv(p_input)->interrupt );

    vlc_object_release( p_input );

}

/*****************************************************************************

 * Run: main thread loop

 * This is the "normal" thread that spawns the input processing chain,

 * reads the stream, cleans up and waits

 *****************************************************************************/

static void *Run( void *data )

{

    input_thread_private_t *priv = data;

    input_thread_t *p_input = &priv->input;

    vlc_interrupt_set(&priv->interrupt);

    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函数中有一个while循环,在播放退出时该函数返回

        MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */

        //在主线程退出前做一些必要的清理工作

        /* Clean up */

        End( p_input );

    }

    input_SendEventDead( p_input );

    return NULL;

}

/*****************************************************************************

 * End: end the input thread

 *****************************************************************************/

static void End( input_thread_t * p_input )

{

    input_thread_private_t *priv = input_priv(p_input);

    //更新播放器状态

    /* We are at the end */

    input_ChangeState( p_input, END_S );

    //将后续的control事件全部清空

    /* Clean control variables */

    input_ControlVarStop( p_input );

    //停止es_out的活动,这里比较重要,实现了解码和显示线程的释放及相关模块的释放

    /* Stop es out activity */

    es_out_SetMode( priv->p_es_out, ES_OUT_MODE_NONE );

    /* Delete slave */

    forint i = 0; i < priv->i_slave; i++ )

        InputSourceDestroy( priv->slave[i] );

    free( priv->slave );

    /* Clean up master */

    InputSourceDestroy( priv->master );

    priv->i_title = 0;

    priv->title = NULL;

    priv->i_title_offset = 0;

    priv->i_seekpoint_offset = 0;

    //删除es_out modules

    /* Unload all modules */

    if( priv->p_es_out )

        es_out_Delete( priv->p_es_out );

    es_out_SetMode( priv->p_es_out_display, ES_OUT_MODE_END );

    ......

}

3.es_out中将每一个track unselect,销毁对应的decoder和thread

static int EsOutControlLocked( es_out_t *out, int i_query, va_list args )

{

    ......

    case ES_OUT_SET_MODE:

    {

        const int i_mode = va_arg( args, int );

        assert( i_mode == ES_OUT_MODE_NONE || i_mode == ES_OUT_MODE_ALL ||

                i_mode == ES_OUT_MODE_AUTO || i_mode == ES_OUT_MODE_PARTIAL ||

                i_mode == ES_OUT_MODE_END );

        ......

        p_sys->b_active = i_mode != ES_OUT_MODE_NONE;

        p_sys->i_mode = i_mode;

        /* Reapply policy mode */

        //这里把所有track都unselect了

        forint i = 0; i < p_sys->i_es; i++ )

        {

            if( EsIsSelected( p_sys->es[i] ) )

                EsUnselect( out, p_sys->es[i],

                            p_sys->es[i]->p_pgrm == p_sys->p_pgrm );

        }

        forint i = 0; i < p_sys->i_es; i++ )

            EsOutSelect( out, p_sys->es[i], false );

        if( i_mode == ES_OUT_MODE_END )

            EsOutTerminate( out );

        return VLC_SUCCESS;

    }

    ......

}

static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update )

{

    es_out_sys_t   *p_sys = out->p_sys;

    input_thread_t *p_input = p_sys->p_input;

    if( !EsIsSelected( es ) )

    {

        msg_Warn( p_input, "ES 0x%x is already unselected", es->i_id );

        return;

    }

    if( es->p_master )

    {

        if( es->p_master->p_dec )

        {

            int i_channel = EsOutGetClosedCaptionsChannel( &es->fmt );

            if( i_channel != -1 )

                input_DecoderSetCcState( es->p_master->p_dec, es->fmt.i_codec,

                                         i_channel, false );

        }

    }

    //这里一般会走这个分支销毁track对应的decoder

    else

    {

        EsDeleteCCChannels( out, es );

        EsDestroyDecoder( out, es );

    }

    if( !b_update )

        return;

    /* Mark it as unselected */

    input_SendEventEsSelect( p_input, es->fmt.i_cat, -1 );

    if( EsFmtIsTeletext( &es->fmt ) )

        input_SendEventTeletextSelect( p_input, -1 );

}

static void EsDestroyDecoder( es_out_t *out, es_out_id_t *p_es )

{

    VLC_UNUSED(out);

    if( !p_es->p_dec )

        return;

    //这里调用了input_DecoderDelete函数删除decoder

    input_DecoderDelete( p_es->p_dec );

    p_es->p_dec = NULL;

    if( p_es->p_dec_record )

    {

        input_DecoderDelete( p_es->p_dec_record );

        p_es->p_dec_record = NULL;

    }

}

4.decoder主要完成两件事,退出解码线程,删除decoder module及释放相关的资源

/**

 * Kills a decoder thread and waits until it's finished

 *

 * \param p_input the input thread

 * \param p_es the es descriptor

 * \return nothing

 */

void input_DecoderDelete( decoder_t *p_dec )

{

    decoder_owner_sys_t *p_owner = p_dec->p_owner;

    vlc_cancel( p_owner->thread );

    vlc_fifo_Lock( p_owner->p_fifo );

    /* Signal DecoderTimedWait */

    /****注意一下这个变量,后面分析问题会用到****/

    p_owner->flushing = true;

    vlc_cond_signal( &p_owner->wait_timed );

    vlc_fifo_Unlock( p_owner->p_fifo );

    /* Make sure we aren't waiting/decoding anymore */

    vlc_mutex_lock( &p_owner->lock );

    p_owner->b_waiting = false;

    vlc_cond_signal( &p_owner->wait_request );

    /* If the video output is paused or slow, or if the picture pool size was

     * under-estimated (e.g. greedy video filter, buggy decoder...), the

     * the picture pool may be empty, and the decoder thread or any decoder

     * module worker threads may be stuck waiting for free picture buffers.

     *

     * This unblocks the thread, allowing the decoder module to join all its

     * worker threads (if any) and the decoder thread to terminate. */

    if( p_owner->p_vout != NULL )

        vout_Cancel( p_owner->p_vout, true );

    vlc_mutex_unlock( &p_owner->lock );

    //这里退出解码线程

    vlc_join( p_owner->thread, NULL );

    /* */

    if( p_dec->p_owner->cc.b_supported )

    {

        forint i = 0; i < MAX_CC_DECODERS; i++ )

            input_DecoderSetCcState( p_dec, VLC_CODEC_CEA608, i, false );

    }

    //删除decoder模块

    /* Delete decoder */

    DeleteDecoder( p_dec );

}

static void DeleteDecoder( decoder_t * p_dec )

{

    decoder_owner_sys_t *p_owner = p_dec->p_owner;

    msg_Dbg( p_dec, "killing decoder fourcc `%4.4s'",

             (char*)&p_dec->fmt_in.i_codec );

    const bool b_flush_spu = p_dec->fmt_out.i_cat == SPU_ES;

    //释放decoder module

    UnloadDecoder( p_dec );

    //把解码器队列里的所有待解码的packets全部释放掉了

    /* Free all packets still in the decoder fifo. */

    block_FifoRelease( p_owner->p_fifo );

    /* Cleanup */

    //删除对应的aout和vout模块

    if( p_owner->p_aout )

    {

        /* TODO: REVISIT gap-less audio */

        aout_DecFlush( p_owner->p_aout, false );

        aout_DecDelete( p_owner->p_aout );

        input_resource_PutAout( p_owner->p_resource, p_owner->p_aout );

        if( p_owner->p_input != NULL )

            input_SendEventAout( p_owner->p_input );

    }

    if( p_owner->p_vout )

    {

        /* Reset the cancel state that was set before joining the decoder

         * thread */

        vout_Cancel( p_owner->p_vout, false );

        input_resource_RequestVout( p_owner->p_resource, p_owner->p_vout, NULL,

                                    0true );

        if( p_owner->p_input != NULL )

            input_SendEventVout( p_owner->p_input );

    }

    es_format_Clean( &p_owner->fmt );

    if( b_flush_spu )

    {

        vout_thread_t *p_vout = input_resource_HoldVout( p_owner->p_resource );

        if( p_vout )

        {

            if( p_owner->p_spu_vout == p_vout )

                vout_FlushSubpictureChannel( p_vout, p_owner->i_spu_channel );

            vlc_object_release( p_vout );

        }

    }

    if( p_owner->p_description )

        vlc_meta_Delete( p_owner->p_description );

    if( p_owner->p_packetizer )

    {

        UnloadDecoder( p_owner->p_packetizer );

        vlc_object_release( p_owner->p_packetizer );

    }

    vlc_cond_destroy( &p_owner->wait_timed );

    vlc_cond_destroy( &p_owner->wait_fifo );

    vlc_cond_destroy( &p_owner->wait_acknowledge );

    vlc_cond_destroy( &p_owner->wait_request );

    vlc_mutex_destroy( &p_owner->lock );

    vlc_object_release( p_dec );

    free( p_owner );

}

接下来aout和vout模块的退出流程就不再赘述了,流程大致和decoder部分一样,无非就是退出线程和释放module,感兴趣的话可以跟着函数继续追一下。

最后总结一下VLC的Stop流程,首先是java层通过jni调用到native层,native层通知input_thread主线程退出,input_thread主线程先通知es_out删除decoder和output模块,最后删除es_out模块并退出。

下面来介绍下这次碰到的问题,现象时解码器卡主导致播放器无法退出了,通过加日志确认,是在input_DecoderDelete函数退出解码线程的地方卡住了,即解码线程退不出去了。解码线程名为DecoderThread,它调用到Android MediaCodec的流程为DecoderThread->DecoderThread_ProcessInput->DecoderThread_DecodeBlock->DecodeBlock→QueueBlockLocked(这里不放具体代码了,感兴趣的话自己追一下)。函数里又是一个while循环,不停往MediaCodec里喂数据:

static int QueueBlockLocked(decoder_t *p_dec, block_t *p_in_block,

                            bool b_drain)

{

    decoder_sys_t *p_sys = p_dec->p_sys;

    block_t *p_block = NULL;

    assert(p_sys->api.b_started);

    if ((p_sys->api.i_quirks & MC_API_QUIRKS_NEED_CSD) && !p_sys->i_csd_count

     && !p_sys->b_adaptive)

        return VLC_EGENERIC; /* Wait for CSDs */

    /* Queue CSD blocks and input blocks */

    while (b_drain || (p_block = GetNextBlock(p_sys, p_in_block)))

    {

        //第一反应就是这里出问题了,-1代表无限等待,如果解码器卡住了就一直不会返回,线程就卡住了

        vlc_mutex_unlock(&p_sys->lock);

        int i_index = p_sys->api.dequeue_in(&p_sys->api, -1);

        vlc_mutex_lock(&p_sys->lock);

        //这里判断如果abort就强制退出

        if (p_sys->b_aborted)

            return VLC_EGENERIC;

        bool b_config = false;

        vlc_tick_t i_ts = 0;

        p_sys->b_input_dequeued = true;

        const void *p_buf = NULL;

        size_t i_size = 0;

        if (i_index >= 0)

        {

            assert(b_drain || p_block != NULL);

            if (p_block != NULL)

            {

                b_config = (p_block->i_flags & BLOCK_FLAG_CSD);

                if (!b_config)

                {

                    i_ts = p_block->i_pts;

                    if (!i_ts && p_block->i_dts)

                        i_ts = p_block->i_dts;

                }

                p_buf = p_block->p_buffer;

                i_size = p_block->i_buffer;

            }

            if (p_sys->api.queue_in(&p_sys->api, i_index, p_buf, i_size,

                                    i_ts, b_config) == 0)

            {

                if (!b_config && p_block != NULL)

                {

                    if (p_block->i_flags & BLOCK_FLAG_PREROLL)

                        p_sys->i_preroll_end = i_ts;

                    /* One input buffer is queued, signal OutThread that will

                     * fetch output buffers */

                    p_sys->b_output_ready = true;

                    vlc_cond_broadcast(&p_sys->cond);

                    assert(p_block == p_in_block),

                    p_in_block = NULL;

                }

                if (b_drain)

                    break;

            else

            {

                msg_Err(p_dec, "queue_in failed");

                goto error;

            }

        }

        else

        {

            msg_Err(p_dec, "dequeue_in failed");

            goto error;

        }

    }

    if (b_drain)

    {

        msg_Warn(p_dec, "EOS sent, waiting for OutThread");

        /* Wait for the OutThread to stop (and process all remaining output

         * frames. Use a timeout here since we can't know if all decoders will

         * behave correctly. */

        vlc_tick_t deadline = vlc_tick_now() + INT64_C(3000000);

        while (!p_sys->b_aborted && !p_sys->b_drained

            && vlc_cond_timedwait(&p_sys->dec_cond, &p_sys->lock, deadline) == 0);

        if (!p_sys->b_drained)

        {

            msg_Err(p_dec, "OutThread timed out");

            AbortDecoderLocked(p_sys);

        }

        p_sys->b_drained = false;

    }

    return VLC_SUCCESS;

error:

    AbortDecoderLocked(p_sys);

    return VLC_EGENERIC;

}

看上面的代码,第一反应就是dequeue_in的地方没有设置超时时间才出了问题,没有考虑到解码器卡主不返回的情况。如果设置超时时间,dequeue_in函数超时返回后走到abort的判断强制退出,问题是不是就解决了?答案是否定的。我们看下p_sys->b_aborted是在哪里被置为true的:

static void AbortDecoderLocked(decoder_sys_t *p_sys)

{

    if (!p_sys->b_aborted)

    {

        p_sys->b_aborted = true;

        vlc_cond_broadcast(&p_sys->cond);

    }

}

AbortDecoderLocked被调用的地方比较多,通过加日志可以确认,退出播放时它是被DecodeFlushLocked函数调用了。回想一下,在input_DecoderDelete函数中,有重点提到过将p_owner->flushing变量置为了true。
p_owner->flushing又是怎么通知decoder flush的呢,答案就在解码线程里:

/**

 * The decoding main loop

 *

 * \param p_dec the decoder

 */

static void *DecoderThread( void *p_data )

{

    vlc_input_decoder_t *p_owner = (vlc_input_decoder_t *)p_data;

    float rate = 1.f;

    vlc_tick_t delay = 0;

    bool paused = false;

    /* The decoder's main loop */

    vlc_fifo_Lock( p_owner->p_fifo );

    while( !p_owner->aborting )

    {

        //decoder是在解码线程里监测p_owner->flushing变量,来决定是否需要flush decoder的

        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). */

            vlc_fifo_Unlock( p_owner->p_fifo );

            /* Flush the decoder (and the output) */

            DecoderThread_Flush( p_owner );

            vlc_fifo_Lock( p_owner->p_fifo );

            /* Reset flushing after DecoderThread_ProcessInput in case vlc_input_decoder_Flush

             * is called again. This will avoid a second useless flush (but

             * harmless). */

            p_owner->flushing = false;

            continue;

        }

        ......

        //回想一下,我们是在这里调用MediaCodec进行解码的,即使我们在MediaCodec中的dequeue_in中添加了超时时间,但是因为b_aborted没有被置为true,

        //所以QueueBlockLocked函数的while循环不会退出,函数不会返回。

        //DecoderThread_ProcessInput不返回,解码线程就永远不会进到上面的flush判断逻辑里,decoder不被flush,b_aborted就永远为false,发生了死锁。

        DecoderThread_ProcessInput( p_owner, p_block );

        ......

    }

    vlc_fifo_Unlock( p_owner->p_fifo );

    return NULL;

}

VLC的线程模型是我最头疼的部分,各个线程之间存在着非常复杂的依赖关系,如果解码器行为不规范很容易出现死锁,要想解决这些问题需要添加大量的日志,但问题的根本原因往往十分简单,十分令人抓狂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值