当前位置: 首页 > 工具软件 > vlc-android > 使用案例 >

VLC-Android消息机制介绍

邹华皓
2023-12-01

VLC通过variables来接受外部控制事件和向外部发送事件,variables同时也可以作为modules之间简单通讯的载体。本文通过举例详细介绍下variables的功能。

接受外部控制事件

以seek为例,vlc-android在seek时可以调用MediaPlayer.java的setTime()方法。

/**

 * Sets the movie time (in ms), if any media is being played.

 * @param time: Time in ms.

 * @return the movie time (in ms), or -1 if there is no media.

 */

 public native long setTime(long time);

之后会调用libvlcjni-mediaplayer.c的Java_org_videolan_libvlc_MediaPlayer_setTime()

void

Java_org_videolan_libvlc_MediaPlayer_setTime(JNIEnv *env, jobject thiz,

                                             jlong time)

{

    vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);

    if (!p_obj)

        return;

#if defined(LIBVLC_VERSION_MAJOR) && LIBVLC_VERSION_MAJOR < 4

    libvlc_media_player_set_time(p_obj->u.p_mp, time);

#else

    libvlc_media_player_set_time(p_obj->u.p_mp, time, false);

#endif

}

之后就是media_player.c的libvlc_media_player_set_time()

void libvlc_media_player_set_time( libvlc_media_player_t *p_mi,

                                   libvlc_time_t i_time )

{

    input_thread_t *p_input_thread;

    p_input_thread = libvlc_get_input_thread ( p_mi );

    if( !p_input_thread )

        return;

    var_SetInteger( p_input_thread, "time", to_mtime(i_time) );

    vlc_object_release( p_input_thread );

}

追到这里终于看到了本文的主角var了,这里给p_input_thread名为的"time"的var赋值,可以看出var应该类似ffmpeg的AVDictionaryEntry,保存的是键值对。p_input_thread的time一更新,就触发了seek逻辑,怎么看怎么像是设置了回调函数,追一下p_input_thread的init流程,果不其然:

//var callback注册流程

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

 * This function creates a new input, and returns a pointer

 * to its description. On error, it returns NULL.

 *

 * XXX Do not forget to update vlc_input.h if you add new variables.

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

static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,

                               const char *psz_header, bool b_preparsing,

                               input_resource_t *p_resource,

                               vlc_renderer_item_t *p_renderer )

{

    ......

     

    //这里的var是vlc内部用的,建议不要set

    /* Create Object Variables for private use only */

    input_ConfigVarInit( p_input );

    //这些var是公开的,可以get和set,time变量就是在这里创建并注册的回调函数

    /* Create Objects variables for public Get and Set */

    input_ControlVarInit( p_input );

    ......

}

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

 * input_ControlVarInit:

 *  Create all control object variables with their callbacks

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

void input_ControlVarInit ( input_thread_t *p_input )

{

    ......

    //创建time var

    /* Time */

    var_Create( p_input, "time", VLC_VAR_INTEGER );

    ......

    //注册回调函数,var一旦改变就会调用

    /* Add all callbacks

     * XXX we put callback only in non preparsing mode. We need to create the variable

     * unless someone want to check all var_Get/var_Change return value ... */

    if( !input_priv(p_input)->b_preparsing )

        InputAddCallbacks( p_input, p_input_callbacks );

}

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

 * Callbacks managements:

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

static void InputAddCallbacks( input_thread_t *p_input,

                               const vlc_input_callback_t *p_callbacks )

{

    int i;

    for( i = 0; p_callbacks[i].psz_name != NULL; i++ )

        var_AddCallback( p_input,

                         p_callbacks[i].psz_name,

                         p_callbacks[i].callback, NULL );

}

//input添加的全部回调如下

/* List all callbacks added by input */

#define CALLBACK(name,cb) { name, cb }

static const vlc_input_callback_t p_input_callbacks[] =

{

    CALLBACK( "state", StateCallback ),

    CALLBACK( "rate", RateCallback ),

    CALLBACK( "position", PositionCallback ),

    CALLBACK( "time", TimeCallback ),

    CALLBACK( "time-offset", TimeOffsetCallback ),

    CALLBACK( "bookmark", BookmarkCallback ),

    CALLBACK( "program", ProgramCallback ),

    CALLBACK( "title", TitleCallback ),

    CALLBACK( "chapter", SeekpointCallback ),

    CALLBACK( "audio-delay", EsDelayCallback ),

    CALLBACK( "spu-delay", EsDelayCallback ),

    CALLBACK( "video-es", EsVideoCallback ),

    CALLBACK( "audio-es", EsAudioCallback ),

    CALLBACK( "spu-es", EsSpuCallback ),

    CALLBACK( "record", RecordCallback ),

    CALLBACK( "frame-next", FrameNextCallback ),

    CALLBACK( NULL, NULL )

};

time对应的回调函数是TimeCallback,一旦time被更新,就会调用TimeCallback

static int TimeCallback( vlc_object_t *p_this, char const *psz_cmd,

                         vlc_value_t oldval, vlc_value_t newval, void *p_data )

{

    input_thread_t *p_input = (input_thread_t*)p_this;

    VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data);

    //这里更新了position变量,即MediaPlayer getCurrentPosition的返回值得到了更新

    //intf-event涉及到event的上报,我们在第二部分详细讲

    /* Update "position" for better intf behaviour */

    const int64_t i_length = var_GetInteger( p_input, "length" );

    if( i_length > 0 && newval.i_int >= 0 && newval.i_int <= i_length )

    {

        vlc_value_t val;

        val.f_float = (double)newval.i_int/(double)i_length;

        var_Change( p_input, "position", VLC_VAR_SETVALUE, &val, NULL );

        /*

         * Notify the intf that a new event has been occurred.

         * XXX this is a bit hackish but it's the only way to do it now.

         */

        var_SetInteger( p_input, "intf-event", INPUT_EVENT_POSITION );

    }

    //这里是最关键的部分,为input下发了INPUT_CONTROL_SET_TIME

    input_ControlPush( p_input, INPUT_CONTROL_SET_TIME, &newval );

    return VLC_SUCCESS;

}

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

 * Control

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

void input_ControlPush( input_thread_t *p_input,

                        int i_type, vlc_value_t *p_val )

{

    input_thread_private_t *sys = input_priv(p_input);

    vlc_mutex_lock( &sys->lock_control );

    //退出或队列溢出就直接release掉不处理

    if( sys->is_stopped || sys->i_control >= INPUT_CONTROL_FIFO_SIZE )

    {

        if( sys->is_stopped )

            msg_Dbg( p_input, "input control stopped, trashing type=%d",

                     i_type );

        else

            msg_Err( p_input, "input control fifo overflow, trashing type=%d",

                     i_type );

        if( p_val )

            ControlRelease( i_type, *p_val );

    }

    else

    {

        input_control_t c;

        c.i_type = i_type;

        if( p_val )

            c.val = *p_val;

        else

            memset( &c.val, 0, sizeof(c.val) );

        //这里其实就是把control放到了input的control队列中

        sys->control[sys->i_control++] = c;

        vlc_cond_signal( &sys->wait_control );

    }

    vlc_mutex_unlock( &sys->lock_control );

}

至此vlc将seek事件放到了input_thread的control队列里,那么一定有一个地方在不停地从队列中取事件,这部分逻辑在MainLoop中:

//MainLoop主要做两件事:1.获取demux后的数据;2.处理control事件。即MainLoop其实是VLC的读线程和事件处理线程

//这里将读数据和事件处理放在同一线程里可能会存在隐患,因为读数据是耗时操作,可能会导致事件处理不及时。ffplay在这一点上做的比较好,事件处理是和video刷新放在同一线程,可以确保每两个视频帧间能够处理消息;ijkplayer则是在jni层单独创建了事件处理线程,确保播放器事件的及时响应。

/**

 * MainLoop

 * The main input loop.

 */

static void MainLoop( input_thread_t *p_input, bool b_interactive )

{

     

    ......

    while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )

    {

         

        ......

        //这里调用MainLoopDemux获取解封装后的数据

        if( !b_paused )

        {

            if( !input_priv(p_input)->master->b_eof )

            {

                bool b_force_update = false;

                MainLoopDemux( p_input, &b_force_update );

                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 );

                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;

            if( b_postpone )

            {

                mtime_t now = mdate();

                /* Recheck ES buffer level every 20 ms when seeking */

                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;

            }

            int i_type;

            vlc_value_t val;

            //这里从control队列中获取control事件

            if( ControlPop( p_input, &i_type, &val, i_deadline, b_postpone ) )

            {

                if( b_postpone )

                    continue;

                break/* Wake-up time reached */

            }

#ifndef NDEBUG

            msg_Dbg( p_input, "control type=%d", i_type );

#endif

            //这里是control事件生效的位置

            if( Control( p_input, i_type, val ) )

            {

                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 );

        }

    }

}

//不同control类型的处理逻辑

static bool Control( input_thread_t *p_input,

                     int i_type, vlc_value_t val )

{

    const mtime_t i_control_date = mdate();

    /* FIXME b_force_update is abused, it should be carefully checked */

    bool b_force_update = false;

    if( !p_input )

        return b_force_update;

    switch( i_type )

    {

        ......

        case INPUT_CONTROL_SET_TIME:

        {

            int64_t i_time;

            int i_ret;

            if( input_priv(p_input)->b_recording )

            {

                msg_Err( p_input, "INPUT_CONTROL_SET_TIME ignored while recording" );

                break;

            }

            i_time = val.i_int;

            if( i_time < 0 )

                i_time = 0;

            //flush esout及解码器

            /* Reset the decoders states and clock sync (before calling the demuxer */

            es_out_SetTime( input_priv(p_input)->p_es_out, -1 );

            //demux seek

            i_ret = demux_Control( input_priv(p_input)->master->p_demux,

                                   DEMUX_SET_TIME, i_time,

                                   !input_priv(p_input)->b_fast_seek );

            if( i_ret )

            {

                int64_t i_length;

                /* Emulate it with a SET_POS */

                if( !demux_Control( input_priv(p_input)->master->p_demux,

                                    DEMUX_GET_LENGTH, &i_length ) && i_length > 0 )

                {

                    double f_pos = (double)i_time / (double)i_length;

                    i_ret = demux_Control( input_priv(p_input)->master->p_demux,

                                            DEMUX_SET_POSITION, f_pos,

                                            !input_priv(p_input)->b_fast_seek );

                }

            }

            if( i_ret )

            {

                msg_Warn( p_input, "INPUT_CONTROL_SET_TIME %"PRId64

                         " failed or not possible", i_time );

            }

            else

            {

                if( input_priv(p_input)->i_slave > 0 )

                    SlaveSeek( p_input );

                input_priv(p_input)->master->b_eof = false;

                b_force_update = true;

            }

            break;

        }

        ......

        default:

            msg_Err( p_input, "not yet implemented" );

            break;

    }

    ControlRelease( i_type, val );

    return b_force_update;

}

至此,上层发送的seek事件成功传递到了input_thread,并由input_thread下发给各module,至于各module的处理这里就不过多展开了,无非就是flush数据,修改一些内部标志位等。

向外部发送事件

之前在介绍VLC Buffering机制的时候曾提到过VLC会上报Cache event,当时没有详细介绍Cache event是怎么上报的,今天详细追下:

在es_out.c的EsOutDecodersStopBuffering()函数中会调用input_SendEventCache( p_sys->p_input, 1.0 )通知上层buffering完成,最终其实是修改了intf-event var的值,这个值在第一部分中也出现过,通过i_type可以得知它可以表示很多不同类型的event。

void input_SendEventCache( input_thread_t *p_input, double f_level )

{

    vlc_value_t val;

    val.f_float = f_level;

    var_Change( p_input, "cache", VLC_VAR_SETVALUE, &val, NULL );

    Trigger( p_input, INPUT_EVENT_CACHE );

}

static void Trigger( input_thread_t *p_input, int i_type )

{

    var_SetInteger( p_input, "intf-event", i_type );

}

接下来就是找intf-event的回调函数了,你会发现在input.c和var.c中都没有注册回调函数,因为input没必要自己通知自己这些event,全局搜下代码,发现是在media_player.c中注册的:

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

 * Tell media player to start playing.

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

int libvlc_media_player_play( libvlc_media_player_t *p_mi )

{

     

    ......

    var_AddCallback( p_input_thread, "can-seek", input_seekable_changed, p_mi );

    var_AddCallback( p_input_thread, "can-pause", input_pausable_changed, p_mi );

    var_AddCallback( p_input_thread, "program-scrambled", input_scrambled_changed, p_mi );

    var_AddCallback( p_input_thread, "intf-event", input_event_changed, p_mi );

    add_es_callbacks( p_input_thread, p_mi );

    ......

    p_mi->input.p_thread = p_input_thread;

    unlock_input(p_mi);

    return 0;

}

//这里可以看到我们第一节提到过的position event和本节提到的cache event

//这里又将每个event转换成了上层能够识别的libvlc_MediaPlayerxxx event

static int

input_event_changed( vlc_object_t * p_this, char const * psz_cmd,

                     vlc_value_t oldval, vlc_value_t newval,

                     void * p_userdata )

{

    VLC_UNUSED(oldval); VLC_UNUSED(psz_cmd);

    input_thread_t * p_input = (input_thread_t *)p_this;

    libvlc_media_player_t * p_mi = p_userdata;

    libvlc_event_t event;

    assert( !strcmp( psz_cmd, "intf-event" ) );

    ......

     

    else if( newval.i_int == INPUT_EVENT_POSITION )

    {

        if( var_GetInteger( p_input, "state" ) != PLAYING_S )

            return VLC_SUCCESS; /* Don't send the position while stopped */

        /* */

        event.type = libvlc_MediaPlayerPositionChanged;

        event.u.media_player_position_changed.new_position =

                                          var_GetFloat( p_input, "position" );

        libvlc_event_send( &p_mi->event_manager, &event );

        /* */

        event.type = libvlc_MediaPlayerTimeChanged;

        event.u.media_player_time_changed.new_time =

           from_mtime(var_GetInteger( p_input, "time" ));

        libvlc_event_send( &p_mi->event_manager, &event );

    }

    else if( newval.i_int == INPUT_EVENT_CACHE )

    {

        event.type = libvlc_MediaPlayerBuffering;

        event.u.media_player_buffering.new_cache = (100 *

            var_GetFloat( p_input, "cache" ));

        libvlc_event_send( &p_mi->event_manager, &event );

    }

     

    ......

    return VLC_SUCCESS;

}

media_player.c将VLC的event类型转换成了libvlc_MediaPlayer对应的event类型,在libvlcjni-mediaplayer.c中注册了处理这些event的回调函数:

//这里只是为event的参数进行了赋值,并没有真正发送event

static bool

MediaPlayer_event_cb(vlcjni_object *p_obj, const libvlc_event_t *p_ev,

                     java_event *p_java_event)

{

    switch (p_ev->type)

    {

        case libvlc_MediaPlayerBuffering:

            p_java_event->argf1 = p_ev->u.media_player_buffering.new_cache;

            break;

        case libvlc_MediaPlayerPositionChanged:

            p_java_event->argf1 = p_ev->u.media_player_position_changed.new_position;

            break;

        case libvlc_MediaPlayerTimeChanged:

            p_java_event->arg1 = p_ev->u.media_player_time_changed.new_time;

            break;

        case libvlc_MediaPlayerVout:

            p_java_event->arg1 = p_ev->u.media_player_vout.new_count;

            break;

        case libvlc_MediaPlayerESAdded:

        case libvlc_MediaPlayerESDeleted:

        case libvlc_MediaPlayerESSelected:

            p_java_event->arg1 = p_ev->u.media_player_es_changed.i_type;

            p_java_event->arg2 = p_ev->u.media_player_es_changed.i_id;

            break;

        case libvlc_MediaPlayerSeekableChanged:

            p_java_event->arg1 = p_ev->u.media_player_seekable_changed.new_seekable;

            break;

        case libvlc_MediaPlayerPausableChanged:

            p_java_event->arg1 = p_ev->u.media_player_pausable_changed.new_pausable;

            break;

        case libvlc_MediaPlayerLengthChanged:

            p_java_event->arg1 = p_ev->u.media_player_length_changed.new_length;

            break;

        case libvlc_MediaPlayerFrameDrop:

            p_java_event->arg1 = p_ev->u.media_player_framedrop.lateMs;

            break;

        case libvlc_MediaPlayerEncounteredError:

            p_java_event->arg1 = p_ev->u.media_player_error.type;

            break;

    }

    p_java_event->type = p_ev->type;

    return true;

}

//注意这里是调用的VLCJniObject_attachEvents注册回调函数

static void

MediaPlayer_newCommon(JNIEnv *env, jobject thiz, vlcjni_object *p_obj,

                      jobject jwindow)

{

     

    ......

    VLCJniObject_attachEvents(p_obj, MediaPlayer_event_cb,

                              libvlc_media_player_event_manager(p_obj->u.p_mp),

                              mp_events);

}

//注意这个函数内部设置了两个callback

void

VLCJniObject_attachEvents(vlcjni_object *p_obj,

                          event_cb pf_event_cb,

                          libvlc_event_manager_t *p_event_manager,

                          const int *p_events)

{

    if (!pf_event_cb || !p_event_manager || !p_events

        || p_obj->p_owner->p_event_manager

        || p_obj->p_owner->p_events)

        return;

    assert(p_obj->p_libvlc);

    //这里把MediaPlayer_event_cb赋值给了p_owner->pf_event_cb

    p_obj->p_owner->pf_event_cb = pf_event_cb;

    p_obj->p_owner->p_event_manager = p_event_manager;

    p_obj->p_owner->p_events = p_events;

    //把自己的VLCJniObject_eventCallback也传了下去

    for(int i = 0; p_obj->p_owner->p_events[i] != -1; ++i)

        libvlc_event_attach(p_obj->p_owner->p_event_manager,

                            p_obj->p_owner->p_events[i],

                            VLCJniObject_eventCallback, p_obj);

}

//VLCJniObject_eventCallback真正的把event发送到了Java层

static void

VLCJniObject_eventCallback(const libvlc_event_t *ev, void *data)

{

    vlcjni_object *p_obj = data;

    JNIEnv *env = NULL;

    assert(p_obj->p_libvlc);

    java_event jevent = { -1000.0 };

    if (!p_obj->p_owner->pf_event_cb(p_obj, ev, &jevent))

        return;

    if (!(env = jni_get_env(THREAD_NAME)))

        return;

    //熟悉的JNI调Java代码

    if (p_obj->p_owner->weak)

        (*env)->CallVoidMethod(env, p_obj->p_owner->weak,

                               fields.VLCObject.dispatchEventFromNativeID,

                               jevent.type, jevent.arg1, jevent.arg2,

                               jevent.argf1);

    else

        (*env)->CallStaticVoidMethod(env, fields.VLCObject.clazz,

                                     fields.VLCObject.dispatchEventFromWeakNativeID,

                                     p_obj->p_owner->weakCompat,

                                     jevent.type, jevent.arg1, jevent.arg2,

                                     jevent.argf1);

}

之后就是java层的内容了,从这部分我们可以学习下VLC的设计模式:

//VLCObject.java

//这里运用了java的模板模式,提高了代码复用

private synchronized void dispatchEventFromNative(int eventType, long arg1, long arg2, float argf1) {

    if (isReleased())

        return;

    final T event = onEventNative(eventType, arg1, arg2, argf1);

    class EventRunnable implements Runnable {

        private final VLCEvent.Listener<T> listener;

        private final T event;

        private EventRunnable(VLCEvent.Listener<T> listener, T event) {

            this.listener = listener;

            this.event = event;

        }

        @Override

        public void run() {

            //上层设置的event listener

            listener.onEvent(event);

            event.release();

        }

    }

    //handler机制发送runnable消息

    if (event != null && mEventListener != null && mHandler != null)

        mHandler.post(new EventRunnable(mEventListener, event));

}

//MediaPlayer继承了VLCObject并重写了onEventNative方法,生成需要的event

//继承VLCObject的还有Media、MediaDiscoverer、MediaList、RendererItem、RendererDiscoverer,它们处理的event类型各不相同,实现了代码解耦

@Override

protected synchronized Event onEventNative(int eventType, long arg1, long arg2, float argf1) {

    switch (eventType) {

        case Event.MediaChanged:

        case Event.Stopped:

        case Event.EndReached:

        case Event.EncounteredError:

            mVoutCount = 0;

            notify();

            return new Event(eventType, arg1);

        case Event.Opening:

        case Event.Buffering:

            return new Event(eventType, argf1);

        case Event.Playing:

        case Event.Paused:

            return new Event(eventType);

        case Event.TimeChanged:

            return new Event(eventType, arg1);

        case Event.LengthChanged:

            return new Event(eventType, arg1);

        case Event.PositionChanged:

            return new Event(eventType, argf1);

        case Event.Vout:

            mVoutCount = (int) arg1;

            notify();

            return new Event(eventType, arg1);

        case Event.ESAdded:

        case Event.ESDeleted:

        case Event.ESSelected:

            return new Event(eventType, arg1, arg2);

        case Event.SeekableChanged:

        case Event.PausableChanged:

            return new Event(eventType, arg1);

        case Event.RenderStart:

            return new Event(eventType);

        case Event.FrameDrop:

            return new Event(eventType, arg1);

    }

    return null;

}

至此,VLC上报event的流程也介绍完了,native层的逻辑同第一部分类似,Java层的点睛之笔就是运用了模板模式将event进行了分类(播放器event、metadata event、MediaList event等)。

modules间通讯

我们知道vlc内部有很多耗时操作,例如demux的读操作,播放器退出时,demux如何及时返回呢?

//播放器退出时会将stopping var设成true

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

 * Stop playing.

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

void libvlc_media_player_stop( libvlc_media_player_t *p_mi )

{

    var_SetBool( p_mi, "stopping"true);

    ......

}

//demux的interrupt_cb会实时检测stopping变量,一旦stopping被置为true,demux就能立刻退出

static int demux_interrupt_cb(void *ctx)

{

    demux_t     *p_demux = (demux_t*)ctx;

    bool stopping = var_InheritBool( p_demux, "stopping" );

    if (stopping)

        msg_Dbg( p_demux, "demux_interrrupt_cb trigger");

    return stopping;

}

 类似资料: