前言

因为需要视频录制和截图,所以编译了一下VLC,做下记录;该版本已经提供了视频录制接口,需要新增截图功能

特别注意:如果需要视频录制和截图,需要关闭硬件解码

media.setHWDecoderEnabled(false, false);

 

前篇

如何编译VLC

时间

2019-05-11

开始

主要讲的是思路,结果很简单;

1、根据Vlc 提供MediaPlayer类录制接口

public boolean record(String directory) {
    return this.nativeRecord(directory);
}
private native boolean nativeRecord(String var1);

调用的是jni接口进行录制;

定位视频录制接口jni层位置,文件位置:./android-vlc-20190510/libvlc/jni/libvlcjni-mediaplayer.c

android 视频流截屏 黑屏_vlc截图

 

2、查看Java_org_videolan_libvlc_MediaPlayer_nativeRecord函数

jboolean
Java_org_videolan_libvlc_MediaPlayer_nativeRecord(JNIEnv *env, jobject thiz,
                                                  jstring jdirectory)
{
    vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);
    const char *psz_directory;

    if (!p_obj)
        return false;

    int (*record_func)(libvlc_media_player_t *, const char *) =
        dlsym(RTLD_DEFAULT, "libvlc_media_player_record");

    if (!record_func)
        return false;

    if (jdirectory)
    {
        psz_directory = (*env)->GetStringUTFChars(env, jdirectory, 0);
        if (!psz_directory)
        {
            throw_Exception(env, VLCJNI_EX_ILLEGAL_ARGUMENT, "directory invalid");
            return false;
        }
    }
    else
        psz_directory = NULL;

    jboolean ret = record_func(p_obj->u.p_mp, psz_directory) == 0;

    if (psz_directory)
    {
        (*env)->ReleaseStringUTFChars(env, jdirectory, psz_directory);
    }

    return ret;
}

 发现没什么内容,最主要的是下一句:

dlsym(RTLD_DEFAULT, "libvlc_media_player_record");

这是一个动态链接函数&变量地址的函数;拿到函数指针后,直接调用函数进行视频录制,所以这个libvlc_media_player_record在哪里?

经过搜索,最后定位两个文件:

1、./android-vlc-20190510/vlc/lib/media_player.c

2、./android-vlc-20190510vlc/include/vlc/libvlc_media_player.h

文件media_player.c文件内容:

int libvlc_media_player_record( libvlc_media_player_t *p_mi,
                                const char *directory )
{
    vlc_value_t val = { .psz_string = (char *)directory };
    const bool enable = directory != NULL;
    input_thread_t *p_input_thread = libvlc_get_input_thread ( p_mi );
    if( !p_input_thread )
        return VLC_EGENERIC;

    if( enable )
        var_Set( p_mi, "input-record-path", val );

    var_SetBool( p_input_thread, "record", enable);

    vlc_object_release( p_input_thread );
    return VLC_SUCCESS;
}

文件libvlc_media_player.h内容:

LIBVLC_API int libvlc_media_player_record(libvlc_media_player_t *p_mi,
                                          const char *directory);

 

是的,libvlc_media_player.h中的是声明,media_player.c是实现,我还在libvlc_media_player.h中看到了惊喜

/**
 * Take a snapshot of the current video window.
 *
 * If i_width AND i_height is 0, original size is used.
 * If i_width XOR i_height is 0, original aspect-ratio is preserved.
 *
 * \param p_mi media player instance
 * \param num number of video output (typically 0 for the first/only one)
 * \param psz_filepath the path of a file or a folder to save the screenshot into
 * \param i_width the snapshot's width
 * \param i_height the snapshot's height
 * \return 0 on success, -1 if the video was not found
 */
LIBVLC_API
int libvlc_video_take_snapshot( libvlc_media_player_t *p_mi, unsigned num,
                                const char *psz_filepath, unsigned int i_width,
                                unsigned int i_height );

这个不就是我们想要的?

为了验证猜想,我在vlc目录继续搜索了一下libvlc_video_take_snapshot关键字

很相识的结果,这两个文件看起来很不错

1、./android-vlc-20190510/vlc/lib/video.c

2、./android-vlc-20190510vlc/include/vlc/libvlc_media_player.h

打开video.c文件看一眼

int
libvlc_video_take_snapshot( libvlc_media_player_t *p_mi, unsigned num,
                            const char *psz_filepath,
                            unsigned int i_width, unsigned int i_height )
{
    assert( psz_filepath );

    vout_thread_t *p_vout = GetVout (p_mi, num);
    if (p_vout == NULL)
        return -1;

    /* FIXME: This is not atomic. All parameters should be passed at once
     * (obviously _not_ with var_*()). Also, the libvlc object should not be
     * used for the callbacks: that breaks badly if there are concurrent
     * media players in the instance. */
    var_Create( p_vout, "snapshot-width", VLC_VAR_INTEGER );
    var_SetInteger( p_vout, "snapshot-width", i_width);
    var_Create( p_vout, "snapshot-height", VLC_VAR_INTEGER );
    var_SetInteger( p_vout, "snapshot-height", i_height );
    var_Create( p_vout, "snapshot-path", VLC_VAR_STRING );
    var_SetString( p_vout, "snapshot-path", psz_filepath );
    var_Create( p_vout, "snapshot-format", VLC_VAR_STRING );
    var_SetString( p_vout, "snapshot-format", "png" );
    var_TriggerCallback( p_vout, "video-snapshot" );
    vlc_object_release( p_vout );
    return 0;
}

对啊,这就是截图的实现啊~

 

思路有了吧!

2、添加截图接口

./android-vlc-20190510/libvlc/jni/libvlcjni-mediaplayer.c文件中Java_org_videolan_libvlc_MediaPlayer_nativeRecord函数后添加

jboolean
Java_org_videolan_libvlc_MediaPlayer_nativeSnapshot(JNIEnv *env, jobject thiz,
                                                  jstring jdirectory,jint w,jint h)
{
    vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);
    const char *psz_directory;

    if (!p_obj)
        return false;
	
    int (*snapshot_func)(libvlc_media_player_t *p_mi, unsigned num,const char *psz_filepath, unsigned int i_width,unsigned int i_height) =dlsym(RTLD_DEFAULT, "libvlc_video_take_snapshot");

    if (!snapshot_func)
        return false;

    if (jdirectory)
    {
        psz_directory = (*env)->GetStringUTFChars(env, jdirectory, 0);
        if (!psz_directory)
        {
            throw_Exception(env, VLCJNI_EX_ILLEGAL_ARGUMENT, "directory invalid");
            return false;
        }
    }
    else
        psz_directory = NULL;

    jboolean ret = snapshot_func(p_obj->u.p_mp, 0,psz_directory,0,0) == 0;

    if (psz_directory)
    {
        (*env)->ReleaseStringUTFChars(env, jdirectory, psz_directory);
    }

    return ret;
}

根据函数声明,我把num,width,height都传入0,具体含义看函数说明;

根据jni接口函数声明,我们知道libvlcjni-mediaplayer.c对应的是MediaPlayer类,所以我们需要去修改MediaPlayer.java

这个文件在:/home/xiaozd/Github/android-vlc-20190510/libvlc/src/org/videolan/libvlc/MediaPlayer.java

在nativeRecord函数下方添加nativeSnapshot函数

private native boolean nativeSnapshot(String directory);

再record函数下方添加getSnapshot函数

public boolean getSnapshot(String directory) {
        return nativeSnapshot(directory);
    }

完成~

3、重新编译

执行./compile.sh 

编译完成即可获得aar

 

4、注意

截图路径需要到文件名如:xxx/xxx/xxx.jpg

视频录制路径只能到文件夹如:xxx/xxx/xxx/