- 什么是视频封装
- mp4 format信息
- mp4 streams信息
- ts文件format信息
- ts文件stream信息
- 使用ffmpeg制作多track视频
- ExoPlayer track selection
- ExoPlayer获取 tracks信息
- 修改Track Selection参数
- 停止某个Track Type
- 指定某一个特殊的tracks
- 总结
- 参考文献
什么是视频封装
在开始介绍音视频的Track Selection之前,我们先了解一下视频封装,比如常见的mp4文件, ts文件,就是一个视频封装文件,mp4和ts就是一种封装格式,这里之所以选择mp4和ts这两种封装格式,是因为这两种封装格式具有代表性,前者适用于点播,后者常用语广播电视,当然也不是绝对的。
mp4本地文件就不说了,看一下新浪微博的点播视频,用的就是mp4
ts流一般用广播电视,IPTV 这个是网上的一个IPTV的源,从里面找了一个CCTV4源。
http://117.169.120.140:8080/live/cctv-4/.m3u8
m3u8文件其实就是一个文本文件,它是苹果出的HLS(HTTP Live Stream)的一部分。
HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些.
下面就是一个m3u8文件,可以看到里面包含了好几个ts文件,每个ts文件对应的是一小段片段。如果是直播,会定时,请求这个m3u8文件,然后下载里面的ts片段并播放。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:1648731453
#EXTINF:10.0,5260052
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133801_133811.ts
#EXTINF:10.0,5380372
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133811_133821.ts
#EXTINF:10.0,5312504
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133821_133831.ts
#EXTINF:10.0,5333936
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133831_133841.ts
#EXTINF:10.0,5366272
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133841_133851.ts
#EXTINF:10.0,5317204
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133851_133901.ts
#EXTINF:10.0,5319272
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133901_133911.ts
#EXTINF:10.0,5330928
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133911_133921.ts
#EXTINF:10.0,5354052
http://117.169.120.140:8080/wh7f454c46tw2466423005_613384962/live/cctv-4/HD-4000k-1080P-cctv4_20220426_133921_133931.ts
我们都知道,文件的扩展名是用来识别文件类型的。通过给他指定扩展名,我们可以告诉自己,也可以告诉操作系统用什么打开方式打开这个文件。
比如一个mp4后缀名的文件,打开方式里会有一些默认的视频软件。
同样一个mp3后缀名的文件,打开方式里也会有一些默认的音频软件。
文件的后缀名是可以随便改的,Linux或者mac上可以用file命令查看实际的文件格式。
? file audio.mp3
audio.mp3: Audio file with ID3 version 2.3.0, contains:MPEG ADTS, layer III, v1, 320 kbps, 44.1 kHz, Stereo
? file run_android_emulator.mp4
run_android_emulator.mp4: ISO Media, MP4 v2 [ISO 14496-14]
它其实是利用文件头标识来进行文件类型判断的,下面是mp3和mp4对应的文件头标识。
扩展名 | 文件头标识(HEX) | 文件描述 |
MP3 | 49 44 33 | MPEG-1 Audio Layer 3 (MP3) audio file |
mp4 | 00 00 00 18 66 74 79 70 33 67 70 35 | MPEG-4 video files |
更直观的我们可以通过hexdump命令来查看文件的十六进制形式。
一个封装文件或者一个封装格式通常由下面的元素构成:
- 文件标识头
- 多媒体信息(多媒体元数据)
- 音视频(字幕)轨
- 视频索引块组成
整个解封装的流程:
- 读取文件头判断文件格式,比如mp4, ts,然后选择对应的Extractor.
- 解析多媒体信息
- 解析视频帧的索引块
- 最后根据索引去定位并读取音视频数据。
如下图:
下面我们用一种稍微直观点的方式,看一下封装文件中包含了哪些信息。
ffprobe是ffmpeg中的一个库,我们借助这个工具可以看一下一个封装文件中的信息。
mp4 format信息
ffprobe -show_format run_android_emulator.mp4 -print_format json
{
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'run_android_emulator.mp4':
Metadata:
major_brand : mp42
minor_version : 1
compatible_brands: mp41mp42isom
creation_time : 2021-10-30T04:16:21.000000Z
Duration: 00:02:24.50, start: 0.000000, bitrate: 2168 kb/s
Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
creation_time : 2021-10-30T04:16:21.000000Z
handler_name : Core Media Audio
Stream #0:1(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1280x720 [SAR 1:1 DAR 16:9], 2032 kb/s, 30 fps, 30 tbr, 30k tbn, 60k tbc (default)
Metadata:
creation_time : 2021-10-30T04:16:21.000000Z
handler_name : Core Media Video
"format": {
"filename": "run_android_emulator.mp4", //文件名称
"nb_streams": 2, //视频av stream的个数
"nb_programs": 0,
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"format_long_name": "QuickTime / MOV",
"start_time": "0.000000",
"duration": "144.500000",
"size": "39162388",
"bit_rate": "2168159",
"probe_score": 100,
"tags": {
"major_brand": "mp42",
"minor_version": "1",
"compatible_brands": "mp41mp42isom",
"creation_time": "2021-10-30T04:16:21.000000Z"
}
}
}
mp4 streams信息
ffprobe run_android_emulator.mp4 -show_streams -print_format json
{
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'run_android_emulator.mp4':
Metadata:
major_brand : mp42
minor_version : 1
compatible_brands: mp41mp42isom
creation_time : 2021-10-30T04:16:21.000000Z
Duration: 00:02:24.50, start: 0.000000, bitrate: 2168 kb/s
Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
Metadata:
creation_time : 2021-10-30T04:16:21.000000Z
handler_name : Core Media Audio
Stream #0:1(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1280x720 [SAR 1:1 DAR 16:9], 2032 kb/s, 30 fps, 30 tbr, 30k tbn, 60k tbc (default)
Metadata:
creation_time : 2021-10-30T04:16:21.000000Z
handler_name : Core Media Video
"streams": [
{
"index": 0,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
"codec_time_base": "1/48000",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "fltp",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/48000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 6936000,
"duration": "144.500000",
"bit_rate": "127307",
"max_bit_rate": "128000",
"nb_frames": "6776",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2021-10-30T04:16:21.000000Z",
"language": "eng",
"handler_name": "Core Media Audio"
}
},
{
"index": 1,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "High",
"codec_type": "video",
"codec_time_base": "1/60",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1280,
"height": 720,
"coded_width": 1280,
"coded_height": 720,
"has_b_frames": 0,
"sample_aspect_ratio": "1:1",
"display_aspect_ratio": "16:9",
"pix_fmt": "yuv420p",
"level": 31,
"color_range": "tv",
"color_space": "bt709",
"color_transfer": "bt709",
"color_primaries": "bt709",
"chroma_location": "left",
"field_order": "progressive",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "30/1",
"avg_frame_rate": "30/1",
"time_base": "1/30000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 4335000,
"duration": "144.500000",
"bit_rate": "2032370",
"bits_per_raw_sample": "8",
"nb_frames": "4335",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2021-10-30T04:16:21.000000Z",
"language": "und",
"handler_name": "Core Media Video"
}
}
]
}
ts文件format信息
"format": {
"filename": "0_00_192.tts",
"nb_streams": 31,
"nb_programs": 5,
"format_name": "mpegts",
"format_long_name": "MPEG-TS (MPEG-2 Transport Stream)",
"start_time": "63983.328467",
"duration": "193.067422",
"size": "735749184",
"bit_rate": "30486725",
"probe_score": 50
}
ts文件stream信息
ts的stream信息太多了,这里就不贴了。
使用ffmpeg制作多track视频
? ffmpeg -i run_android_emulator.mp4 -i video_track.mp4 -i audio.mp3 -i audio2.mp3 -map 0:v -map 1:v -map 2:a -map 3:a -c copy -shortest output_video_audio_track.mp4
上面的命令就是将run_android_emulator.mp4和video_track.mp4这两个视频以及audio.mp3 和 audio2.mp3这两个音频合并成一个mp4视频, 这个过程不需要重新编码,所以速度非常的快。
然后再用ffmpeg查看一下合并成的mp4文件信息:
? ffmpeg -i output_video_audio_track.mp4 -hide_banner
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output_video_audio_track.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.5.100
Duration: 00:00:25.03, start: 0.000000, bitrate: 2923 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720 [SAR 1:1 DAR 16:9], 2052 kb/s, 30 fps, 30 tbr, 30k tbn (default)
Metadata:
handler_name : Core Media Video
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 480x360 [SAR 1:1 DAR 4:3], 413 kb/s, 30 fps, 30 tbr, 30k tbn (default)
Metadata:
handler_name : (C) 2007 Google Inc. v08.13.2007.
vendor_id : [0][0][0][0]
Stream #0:2[0x3](und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 320 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Stream #0:3[0x4](und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
ExoPlayer track selection
前面介绍了一下音视频文件关于封装的一些基础知识,大概知道了音视频中Track的概念,下面就介绍一下Track Selection并且它在ExoPlayer中的运用。Track Selection决定了播放器去播哪个track, 对应上面的stream。比如一个视频文件里有多个video track, 多个audio track,或者多个字幕track.播视频的时候,你需要选择播哪个video track,哪个audio track和哪个字幕track. 在ExoPlayer使用TrackSelectionParameters 这个类负责Track Selection的一些设置, 也可以给track selection添加限制规则。
ExoPlayer获取 tracks信息
ExoPlayer需要prepare后,才能获取到当前视频可用的tracks信息,可以通过Player.Listener.onTracksInfoChanged
监听tracks信息的变化。这个触发的时机是:
- 当Player preparation完成。
- 当可以的tracks或者selected的tracks信息发生变化。
- 当playlist item发生变化。
player.addListener(new Player.Listener() {
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
for (TracksInfo.TrackGroupInfo groupInfo : tracksInfo.getTrackGroupInfos()) {
@C.TrackType int trackType = groupInfo.getTrackType();
boolean trackInGroupIsSelected = groupInfo.isSelected();
boolean trackInGroupIsSupported = groupInfo.isSupported();
Log.d(TAG, "TrackGroupInfo trackType: " + trackType);
Log.d(TAG, "TrackGroupInfo trackInGroupIsSelected: " + trackInGroupIsSelected);
Log.d(TAG, "TrackGroupInfo trackInGroupIsSupported: " + trackInGroupIsSupported);
TrackGroup group = groupInfo.getTrackGroup();
for (int i = 0; i < group.length; i++) {
boolean isSupported = groupInfo.isTrackSupported(i);
boolean isSelected = groupInfo.isTrackSelected(i);
Format trackFormat = group.getFormat(i);
Log.d(TAG, "TrackGroup isSupported: " + isSupported);
Log.d(TAG, "TrackGroup isSelected: " + isSelected);
Log.d(TAG, "TrackGroup trackFormat: " + trackFormat.toString());
}
}
}
});
如果想获取当前的TracksInfo可以使用下面的方法
player.getCurrentTracksInfo()
- TracksInfo包含了一组TrackGroupInfo` ,每个TrackGroupInfo包含了Track的type, format详情,播放器是否支持当前track,是否被select.进一步的解释是,TrackGroupInfo对应的是一组视频轨、或者音频轨、或者字幕轨。TrackGroupInfo再往下分是TrackGroup,例如TrackGroupInfo对应的是一组视频轨,那么TrackGroup就是这一组视频轨中的某一个视频轨。
- 一个track是supported表示Playe能够decode和render这个track.
- 一个track是selected表示track selector选择这个track播放,track select的配置放在TrackSelectionParameters中。需要注意如果是多条track的TrackGroup被selected,那么播放器会自适应播放(比如,多个video track 有不同的码率)。注释,虽然TrackGourp是selected,但是其真正select的是group里的一个track select.
修改Track Selection参数
Selection流程可以通过Player.setTrackSelectionParameters方法,设置 TrackSelectionParameters类。这些更新可以在播放前或者播放过程中都可以生效。建议在设置Track Selection的时候,先获取一下当前的配置,然后在当前配置的基础上去修改。
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setMaxVideoSizeSd()
.setPreferredAudioLanguage("hu")
.build());
停止某个Track Type
比如关闭Video track 这个类型,这样就只有音频能播放了,这样就可以试下Audio Only的功能了,比如如果想实现Android版B站有后台播放的功能,直接可以将Video Track设置为Disable就可以。
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_VIDEO))
.build());
还有另外一种方式,可以实现这个功能,通过创建一个空的video overrides, 这个overrides下面再介绍,相当于指定某一个track,其他的track就不会被selected了。
TrackSelectionOverrides overrides =
new TrackSelectionOverrides.Builder()
.addOverride(
new TrackSelectionOverride(
disabledTrackGroup,
/* select no tracks for this group */ ImmutableList.of()))
.build();
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon().setTrackSelectionOverrides(overrides).build());
指定某一个特殊的tracks
指定某一个特殊的TrackGroup为Selected Tracks.
TrackSelectionOverrides overrides =
new TrackSelectionOverrides.Builder()
.setOverrideForType(new TrackSelectionOverride(audioTrackGroup))
.build();
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon().setTrackSelectionOverrides(overrides).build());
总结
ExoPlayer支持本地音视频文件的播放,比如上面用ffmpeg生成了一个多track视频文件。同时,ExoPlayer也支持一些流媒体协议比如:DASH,HLS等,如下是ExoPlayer支持的流媒体协议。
这些流媒体里同样可以获取到Track信息和Track Selection.具体这里就不详细介绍了,有兴趣的可以研究一下。
参考文献
- ExoPlayer官方文档
- 走进音视频的世界
- 利用文件头标志判断文件类型
- MpegTS流解复用程序实现
- ts流格式解析
- ts格式介绍
- ffmpeg、ffplay、ffprobe命令使用
- FFmpeg 视频处理入门教程
- ExoPlay实现框架
- Sample地址
本文暂时没有评论,来添加一个吧(●'◡'●)