VLC 播放器框架
VLC媒体播放器(以前是VideoLAN Client,通常称为VLC)是由VideoLAN项目开发的免费,开源,可移植,跨平台的 媒体播放器软件和流媒体 服务器。VLC适用于台式机操作系统和移动平台,例如Android,iOS, iPadOS, Tizen,Windows 10 Mobile和Windows Phone。VLC也可用于数字分发平台,例如Apple的App Store,Google Play和Microsoft Store。
VLC支持许多音频和视频压缩方法和文件格式,包括DVD视频,视频CD和流协议。它能够通过计算机网络流式传输媒体,并可以对多媒体文件进行转码。
VLC可以读取多种格式,具体取决于运行的操作系统,包括:
容器格式:3GP,ASF,AVI,DVR-MS,FLV,Matroska(MKV),MIDI,QuickTime文件格式,MP4,Ogg,OGM,WAV,MPEG-2(ES,PS,TS, PVA,MP3),AIFF,原始音频,原始DV,MXF,VOB,RM,蓝光,DVD视频,VCD,SVCD,CD音频,DVB,HEIF,AVIF
音频编码格式:AAC, AC3, ALAC, AMR,DTS, DV Audio, XM, FLAC, It, MACE, MOD, Monkey's Audio, MP3, Opus,PLS, QCP, QDM2/QDMC, RealAudio,Speex, Screamtracker 3/S3M, TTA, Vorbis, WavPack, WMA (WMA 1/2, WMA 3 partially).
捕获设备:Video4Linux(在Linux上),DirectShow(在Windows上),台式机(屏幕录像),数字电视(DVB-C,DVB-S,DVB-T,DVB-S2,DVB-T2,ATSC,Clear QAM)
网络协议:FTP,HTTP,MMS,RSS / Atom,RTMP,RTP(单播或多播),RTSP,UDP,Sat-IP,Smooth Streaming
网络流格式: Apple HLS, Flash RTMP, MPEG-DASH, MPEG Transport Stream, RTP/RTSP ISMA/3GPP PSS, Windows Media MMS Subtitles: Advanced SubStation Alpha, Closed Captions, DVB, DVD-Video, MPEG-4 Timed Text, MPL2,OGM, SubStation Alpha, SubRip, SVCD, Teletext,Text file, VobSub, WebVTT, TTML
视频编码格式:Cinepak, Dirac, DV, H.263, H.264/MPEG-4 AVC, H.265/MPEG HEVC, AV1, HuffYUV, Indeo 3, MJPEG, MPEG-1, MPEG-2, MPEG-4 Part 2, RealVideo 3&4,Sorenson, Theora, VC-1, VP5, VP6, VP8, VP9,DNxHD, ProRes and some WMV.
数码摄像机格式:MOD and TOD via USB.
VLC架构

VLC元数据
通常元数据(Metadata)指描述有关其它数据信息的数据。
可以将元数据看作是物体内部信息的高级摘要。对于一本书而言,这些数据将由作者、标题、发布日期等组成。而对于多媒体,元数据是指标题、专辑、艺术家、年代等信息。
之所以需要元数据,是因为这能够让物体更容易地被识别、查找、管理和发现。假如你正在搜索特定类型的曲目,使用元数据过滤会十分方便,将艺术家设置为“周杰伦”或者将流派设置为“摇滚”,这时歌曲列表中只会显示周杰伦的曲目或者摇滚类型的曲目。
libVLC 中,元数据类型由枚举类型 libvlc_meta_t 表示
/** Meta data types */
typedef enum libvlc_meta_t {
libvlc_meta_Title, // 标题
libvlc_meta_Artist, // 艺术家
libvlc_meta_Genre, // 流派
libvlc_meta_Copyright, // 版权
libvlc_meta_Album, // 专辑
libvlc_meta_TrackNumber, // 轨道编号
libvlc_meta_Description, // 描述
libvlc_meta_Rating, // 评分
libvlc_meta_Date, // 日期
libvlc_meta_Setting, // 设置
libvlc_meta_URL, // 地址
libvlc_meta_Language, // 语言
libvlc_meta_NowPlaying, // 正在播放
libvlc_meta_Publisher, // 发行商
libvlc_meta_EncodedBy, // 编码者
libvlc_meta_ArtworkURL, // 专辑图片地址
libvlc_meta_TrackID, // 轨道 ID
libvlc_meta_TrackTotal, // 轨道总数
libvlc_meta_Director, // 导演
libvlc_meta_Season, // 第几季
libvlc_meta_Episode, // 插曲
libvlc_meta_ShowName, // 显示名
libvlc_meta_Actors, // 表演者
libvlc_meta_AlbumArtist, // 专辑艺术家
libvlc_meta_DiscNumber, // 碟号
libvlc_meta_DiscTotal // 总碟数
/* Add new meta types HERE */
} libvlc_meta_t;
使用 libvlc_media_get_meta() 读取媒体的元数据,但在读取前需先调用 libvlc_media_parse() 解析媒体文件;通过 libvlc_media_set_meta() 设置媒体的元数据,然后调用 libvlc_media_save_meta() 保存设置结果。
VLC事件机制
libVLC 是异步播放媒体文件,在此过程中,它会产生一些事件。例如,可通过监听 libvlc_MediaPlayerEndReached 事件来实现循环播放(当媒体播放结束时获得通知,然后再次播放)。
具体可使用 libvlc_media_player_event_manager() 函数创建事件管理器,然后调用 libvlc_event_attach() 函数将事件处理程序与某个事件相关联。
VLC 轨道信息
一个视频文件看起来是一个整体,但实际上一般会被分为音频和视频两部分。例如将电影导入视频剪辑软件,可以很容易地看到音频和视频是分离的(视频在视频轨道上,音频在音频轨道上)。
libVLC中,媒体轨道信息由结构体libvlc_media_track_t表示:
typedef struct libvlc_media_track_t
{
/* Codec fourcc */
uint32_t i_codec; // 编解码器
uint32_t i_original_fourcc; // 四字符代码
int i_id; // 轨道 ID
libvlc_track_type_t i_type; // 类型
/* Codec specific */
int i_profile; // 编码级别
int i_level; // 级别的等级
union {
libvlc_audio_track_t *audio; // 音频轨道
libvlc_video_track_t *video; // 视频轨道
libvlc_subtitle_track_t *subtitle; // 字幕轨道
};
unsigned int i_bitrate; // 码率
char *psz_language; // 语言
char *psz_description; // 描述
} libvlc_media_track_t;
轨道类型由枚举 libvlc_track_type_t 表示:
typedef enum libvlc_track_type_t
{
libvlc_track_unknown = -1, // 未知
libvlc_track_audio = 0, // 音频
libvlc_track_video = 1, // 视频
libvlc_track_text = 2 // 文字
} libvlc_track_type_t;
音频轨道由 libvlc_audio_track_t 表示:
typedef struct libvlc_audio_track_t
{
unsigned i_channels; // 声道
unsigned i_rate; // 采样率
} libvlc_audio_track_t;
视频轨道由 libvlc_video_track_t 表示:
typedef struct libvlc_video_track_t
{
unsigned i_height; // 视频高度
unsigned i_width; // 视频宽度
unsigned i_sar_num; // 像素宽高比的分子
unsigned i_sar_den; // 像素宽高比的分母
unsigned i_frame_rate_num; // 帧率的分子
unsigned i_frame_rate_den; // 帧率的分母
libvlc_video_orient_t i_orientation; // 方向
libvlc_video_projection_t i_projection;
libvlc_video_viewpoint_t pose; /**< Initial view point */
} libvlc_video_track_t;
倘若要计算帧率(FPS),可以通过 i_frame_rate_num/i_frame_rate_den 获得
字幕轨道由 libvlc_subtitle_track_t 表示:
typedef struct libvlc_subtitle_track_t
{
char *psz_encoding; // 编码
} libvlc_subtitle_track_t;
Android
编译库
可以打开VLC Android的 官方仓库 或者 Github仓库 查看源码
该项目是一个Android平台的VLC播放器的APP,并没有将用于播放的libvlc的Java接口绑定像iOS一样单独的正式的发布出来,因此如果我们需要最新版本的话,最好自行进行编译。根据官方说明,应使用Debian/Ubuntu平台进行编译。具体可查看关于 Android平台的详细编译指南
在编译前,需要进行一些准备,以Ubuntu为例,首先安装用于编译的依赖环境
sudo apt install automake ant autopoint cmake build-essential libtool-bin \
patch pkg-config protobuf-compiler ragel subversion unzip git \
openjdk-8-jre openjdk-8-jdk flex python wget
然后配置Android的命令行开发环境(SDK、NDK)
接下来需要配置环境变量,打开配置文件sudo vim /etc/profile
export ANDROID_SDK=/home/arcticfox/android-sdk
export ANDROID_NDK=$ANDROID_SDK/ndk/21.3.6528147
Ubuntu上直接使用apt install安装JDK后,可使用命令update-alternatives --config java 搜索出JDK的真正安装路径:
There is only one alternative in link group java (providing /usr/bin/java): /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
这里的/usr/lib/jvm/java-8-openjdk-amd64即为真实路径,将其配置到环境变量中
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
最后下载vlc-android的源码(不要拉取Github上的仓库,编译可能存在错误)
git clone https://code.videolan.org/videolan/vlc-android.git
进入到vlc-android目录进行编译
sh buildsystem/compile.sh -l -a all -r
经过漫长的编译(可能需要几个小时),完成后会在libvlc/build/outputs中生成aar包
[javac] warning: [options] bootstrap class path not set in conjunction with -source 5
[javac] error: Source option 5 is no longer supported. Use 6 or later.
[javac] error: Target option 1.5 is no longer supported. Use 1.6 or later.
注意,如果编译存在以上Java相关错误,执行java -version检查一下Java版本,新版本的Ubuntu默认带有一个 openjdk-11的版本,优先级可能高于我们安装的openjdk-8,但VLC的库指定使用openjdk-8,因此需要先卸载 openjdk-11
sudo apt-get purge openjdk-11-*
然后使用命令dpkg --list | grep -i jdk查看一下当前的安装情况
项目中集成编译好的 aar 包
在工程根目录下的app路径下,创建libs文件夹,并将 aar 包拷贝进去
配置libs目录
android {
...
repositories{
flatDir{
dirs 'libs'
}
}
}
添加本地依赖
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//使用本地aar
implementation(name: 'libvlc-all-3.3.14', ext: 'aar')
}
vlc-android 快速集成
大多数时候,我们不会对VLC做修改或者深度定制,因此我们可以使用VLC内部的一个gradle仓库,直接从仓库拉取依赖,快速集成,而不是自己编译。
我们可以 访问仓库地址 查看最新的版本号
在工程的根build.gradle文件中配置仓库地址:
allprojects {
repositories {
google()
maven {
url "https://dl.bintray.com/videolan/Android"
}
jcenter()
}
}
在模块的build.gradle文件中配置依赖
dependencies {
// ......
// 这里指定的是目前最新的3.3.14版本,大家根据实际情况替换
implementation 'org.videolan.android:libvlc-all:3.3.14'
}
最后同步一下gradle,下载依赖
关于该库的简单用法,可以参考VLC官方专门提供的一个 示例工程
public class MainActivity extends AppCompatActivity {
private MediaPlayer mMediaPlayer;
TextureView mTextureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextureView = findViewById(R.id.video);
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
initPlayer(surfaceTexture);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
});
}
private void initPlayer(SurfaceTexture surface) {
ArrayList<String> args = new ArrayList<>();
// 给出尽可能多的调试日志输出
args.add("-vvv");
LibVLC libVLC = new LibVLC(this, args);
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
mMediaPlayer = new MediaPlayer(libVLC);
// 设置视频输出的Surface
mMediaPlayer.getVLCVout().setVideoSurface(surface);
// 播放前必须调用,附加视图
mMediaPlayer.getVLCVout().attachViews();
mMediaPlayer.setScale(0);
mMediaPlayer.setAspectRatio(mTextureView.getWidth() + ":" + mTextureView.getHeight());
mMediaPlayer.getVLCVout().setWindowSize(mTextureView.getWidth(), mTextureView.getHeight());
// 网络视频地址
Media media = new Media(libVLC, Uri.parse("https://v-cdn.zjol.com.cn/277003.mp4"));
mMediaPlayer.setMedia(media);
media.release();
mMediaPlayer.play();
}
}
扩展Android接口
由于libVLC的安卓包装尚未暴露截屏功能接口,只能手动扩展,然后自行编译打包。
- 暴露截屏功能
在libvlc/jni/libvlcjni-mediaplayer.c里面增加JNI函数
jboolean
Java_org_videolan_libvlc_MediaPlayer_nativeTakeSnapShot(JNIEnv *env, jobject thiz,jint number, jstring path, jint width,jint height)
{
vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz);
/* Get C string */
const char* psz_path = (*env)->GetStringUTFChars(env, path, 0);
int res = libvlc_video_take_snapshot(p_obj->u.p_mp, (int)number,psz_path , (int)width,(int)height);
if(res == 0) return true;
LOGE("libvlc_video_take_snapshot(%s,%d)", psz_path, res);
return false;
}
在libvlc/src/org/videolan/libvlc/MediaPlayer.java增加native方法和包装方法
/*
* number:视频标识(默认为0)
* path:保存路径
* width:宽(0代表原始宽)
* height:高(0代表原始高)
*/
public boolean takeSnapShot(int number, String path, int width, int height) {
return nativeTakeSnapShot(number, path, width, height);
}
private native boolean nativeTakeSnapShot(int number, String path, int width, int height);
iOS
VLCKit库是VLC在MacOS上的绑定,iOS上则是MobileVLCKit。其API 文档见 这里
注意,VLC 对iOS的绑定未暴露CVPixelBufferRef,见issues,因此在Flutter中无法使用外接纹理的方式实现播放器插件。
MobileVLCKit 的基本使用示例:
#import <MobileVLCKit/MobileVLCKit.h>
- (void)initVLCPlayer{
int width = self.view.bounds.size.width;
UIView *video = [[UIView alloc]initWithFrame:CGRectMake(0, 0, width, 250)];
[self.view addSubview:video];
player = [[VLCMediaPlayer alloc]initWithOptions:nil];
player.drawable = video;
char str[20]={0};
sprintf(str, "%d:250",width);
[player setVideoAspectRatio: str];
[player setScaleFactor:0];
player.media = [VLCMedia mediaWithURL:[NSURL URLWithString:@"https://v-cdn.zjol.com.cn/276996.mp4"]];
}
公众号“编程之路从0到1”