Share
Embed
Video Stream on Android (DevFest Tokyo 2016)
Introduction to Video Stream
- Http Live Streaming (HLS)
- Dynamic Adaptive Streaming over HTTP (DASH)
- H.264
- H.265
- Fragmented MP4
- ExoPlayer
- DRM (Widevine, PlayReady)
Daichi Furiya (Wasabeef)
October 09, 2016
Transcript
109 slides
-
About me 降矢 大地 (Daichi Furiya) Google Developer Expert AbemaTV,
Inc. wasabeef @wasabeef_jp -
Video compression 30fps 1sec 60KB × 30 = 1.8MB 1min
1.8MB × 60 = 108MB 1hour 108MB × 10 = 1.1GB 60KB 65KB 55KB 50KB -
Video compression 60KB 5KB 2KB 7KB 30fps 1sec 60KB +
(4KB × 30) = 180KB 1min 60KB + (4KB × 1800) = 7.2MB 1hour 7.2MB × 10 = 72MB -
.avi .mp4 .mkv .ogg .flv etc.. Container format Video H.264
H.265 Divx VP9 Audio AAC WMA Voribis PCM Caption SAMI, SMIL Hi-Caption CMML, DXFP Meta Date Author Title -
# ffmpeg -i BigBuckBunny.mp4 \ -vcodec libx264 \ -s 1280x720
\ -acodec aac -b:a 256k \ -flags +loop-global_header \ -bsf h264_mp4toannexb \ -f segment -segment_format mpegts \ -segment_time 10 \ -segment_list output.m3u8 output_%04d.ts Converting MP4 to HLS With FFmpeg -
#EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10, no desc output0000.ts #EXTINF:10, no desc
output0001.ts #EXTINF:10, no desc 〜 ... 〜 output180.ts #EXT-X-ENDLIST playlist.m3u8 -
playlist.m3u8 Segment1.ts Length: 10sec Segment2.ts Length: 10sec Segment3.ts Length: 10sec
Segment4.ts Length: 10sec Header Footer -
playlist.m3u8 Segment1.ts Length: 10sec Segment2.ts Length: 10sec Advertising.ts Length: 10sec
Segment3.ts Length: 10sec Header Footer -
#EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000 playlist1.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111 playlist2.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444 playlist3.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777 playlist4.m3u8 playlist.m3u8 -
playlist.m3u8 playlist1.m3u8 BANDWIDTH: 200000 Header playlist2.m3u8 BANDWIDTH: 311111 playlist3.m3u8 BANDWIDTH:
484444 playlist4.m3u8 BANDWIDTH: 737777 -
Microsoft, Netflix, Google, Ericsson, Samsung, Adobe Adaptive bitrate streaming fMP4,
WebM, MPEG2-TS MPEG-DASH -
manifest.mdp <?xml version="1.0" ?> <MPD mediaPresentationDuration="PT9M56.458S" minBufferTime="PT20.00S" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
<-- Created with Bento4 mp4-dash.py, VERSION=1.7.0-613 --> <Period> <-- Video --> <AdaptationSet maxHeight="180" maxWidth="320" mimeType="video/mp4" minHeight="180" minWidth="320" segmentAlignment="true" startWithSAP="1"> <ContentProtection schemeIdUri="urn:mpeg:dash:abmprotection:2016" value="0x55ea5d616e90eaca972bc31a9fb2c7f8,0xaccca4b41de3d9afb029070eb564be40"> </ContentProtection> <SegmentTemplate duration="20000" initialization="$/init.mp4" media="$/seg-$.m4s" startNumber="1" timescale="1000"/> <Representation bandwidth="702842" codecs="avc1.42C00D" frameRate="24" height="180" id="../video_enc/1" scanType="progressive" width="320"/> </AdaptationSet> <-- Audio --> <AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1"> <SegmentTemplate duration="20000" initialization="$/init.mp4" media="$/seg-$.m4s" startNumber="1" timescale="1000"/> <Representation audioSamplingRate="48000" bandwidth="163859" codecs="mp4a.40.2" id="../audio/und/mp4a"> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> </Representation> </AdaptationSet> </Period> </MPD> -
<mdp> - Root manifest.mdp <period> - A start time and
duration. <AdaptationSet> - Audio/Video content <Representation> - Bitrate and Aspect -
MediaPlayer String url = “http://wasabeef.jp/sample.mp4”; MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(STREAM_MUSIC); mediaPlayer.setDataSource(url); mediaPlayer.prepare(); mediaPlayer.setSurface(surface); mediaPlayer.start(); // mediaPlayer.release(); -
API Level 16 Easy to extend and customize HLS, MPEG-DASH,
SmoothStreaming DRM (WideVine, PlayReady) ExoPlayer -
Devices Use Case Version API Level Audio 4.1 16 Video
4.1 16 WideVine 4.3 18 PlayReady AndroidTV AndroidTV -
Formats (Containers) Feature HLS MPEG-DASH fMP4 ✕ ◯ WebM ✕
◯ Matroska ✕ ◯ MPEG-TS ◯ ✕ ADTS(AAC) ◯ ✕ MP3 ◯ ✕ -
Formats (Closed Caption) Feature HLS MPEG-DASH TTML ✕ ◯ WebVTT
◯ ◯ Tx3g ✕ ◯ SubRip ✕ ◯ EIA-608 ◯ ✕ -
Formats (Content Protection) Feature HLS MPEG-DASH AES-128 ◯ ✕ WideVine
✕ ◯ PlayReady ◯ △ (AndroidTV) -
A greatly simplified demo app. A VideoView equivalent in SimpleExoPlayerView.
Improved Audio extension APIs with pre- built extensions for ffmpeg, opus, and flac. Playlist Support. New features -
ExoPlayer MediaSource Parser Extractor DataSource MediaCodecRenderer MediaCodec AudioTrack ExoPlayer Network
Buffering Extraction Decoding Rendering -
MediaSource // Measures bandwidth during playback. Can be null if
not required. DefaultBandwidthMeter bandwidth = new DefaultBandwidthMeter(); // Produces DataSource instances through which media data is loaded. DataSource.Factory dataSource = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "wasabeef"), bandwidthMeter); // Produces Extractor instances for parsing the media data. ExtractorsFactory extractors = new DefaultExtractorsFactory(); // This is the MediaSource representing the media to be played. MediaSource videoSource = new ExtractorMediaSource(mp4VideoUri, dataSource, extractors, null, null); // Prepare the player with the source. player.prepare(videoSource); -
#EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10, no desc output0000.ts #EXTINF:10, no desc
output0001.ts #EXTINF:10, no desc 〜 省略 〜 output180.ts #EXT-X-ENDLIST Parser -
HlsPlaylistParser private static final String TAG_VERSION = "#EXT-X-VERSION"; private static
final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; private static final String TAG_MEDIA = "#EXT-X-MEDIA"; private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; private static final String TAG_MEDIA_DURATION = “#EXTINF"; // ... -
DataSource // Measures bandwidth during playback. Can be null if
not required. DefaultBandwidthMeter bandwidth = new DefaultBandwidthMeter(); // Produces DataSource instances through which media data is loaded. DataSource.Factory dataSource = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "wasabeef"), bandwidthMeter); // Produces Extractor instances for parsing the media data. ExtractorsFactory extractors = new DefaultExtractorsFactory(); // This is the MediaSource representing the media to be played. MediaSource videoSource = new ExtractorMediaSource(mp4VideoUri, dataSource, extractors, null, null); // Prepare the player with the source. player.prepare(videoSource); -
A Hollywood grade DRM is not always needed, sometimes it’s
enough to just add another layer of security through AES encryption. HLS Encryption -
HLS Encryption - DataSource @Override public long open(DataSpec dataSpec) throws
IOException { Assertions.checkState(dataSource == null); // Choose the correct source for the scheme. String scheme = dataSpec.uri.getScheme(); if (Util.isLocalFileUri(dataSpec.uri)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { dataSource = assetDataSource; } else { dataSource = fileDataSource; } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = assetDataSource; } else if (SCHEME_CONTENT.equals(scheme)) { dataSource = contentDataSource; } else { dataSource = baseDataSource; } // Open the source and return. return dataSource.open(dataSpec); } -
HLS Encryption - DataSource @Override public long open(DataSpec dataSpec) throws
IOException { Assertions.checkState(dataSource == null); // Choose the correct source for the scheme. String scheme = dataSpec.uri.getScheme(); if (Util.isLocalFileUri(dataSpec.uri)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { dataSource = assetDataSource; } else { dataSource = fileDataSource; } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = assetDataSource; } else if (SCHEME_CONTENT.equals(scheme)) { dataSource = contentDataSource; } else if (SCHEME_WASABEEF.equals(scheme)) { dataSource = wasabeefDataSource; } else { dataSource = baseDataSource; } // Open the source and return. return dataSource.open(dataSpec); } -
The timed metadata information can be used by clients as
cuepoints, for information display, to invoke time- aligned actions, and so on. This information is available to the client on the playback timeline in the form of ID3 tags in HLS. ID3v2 Tags -
SimpleExoPlayer.java @Override public void onMetadata(List<Id3Frame> id3Frames) { if (id3Output !=
null) { id3Output.onMetadata(id3Frames); } } -
manifest.mdp <?xml version="1.0" ?> <MPD mediaPresentationDuration="PT9M56.458S" minBufferTime="PT20.00S" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
<-- Created with Bento4 mp4-dash.py, VERSION=1.7.0-613 --> <Period> <-- Video --> <AdaptationSet maxHeight="180" maxWidth="320" mimeType="video/mp4" minHeight="180" minWidth="320" segmentAlignment="true" startWithSAP="1"> <ContentProtection schemeIdUri="urn:mpeg:dash:abmprotection:2016" value="0x55ea5d616e90eaca972bc31a9fb2c7f8,0xaccca4b41de3d9afb029070eb564be40"> </ContentProtection> <SegmentTemplate duration="20000" initialization="$/init.mp4" media="$/seg-$.m4s" startNumber="1" timescale="1000"/> <Representation bandwidth="702842" codecs="avc1.42C00D" frameRate="24" height="180" id="../video_enc/1" scanType="progressive" width="320"/> </AdaptationSet> <-- Audio --> <AdaptationSet mimeType="audio/mp4" segmentAlignment="true" startWithSAP="1"> <SegmentTemplate duration="20000" initialization="$/init.mp4" media="$/seg-$.m4s" startNumber="1" timescale="1000"/> <Representation audioSamplingRate="48000" bandwidth="163859" codecs="mp4a.40.2" id="../audio/und/mp4a"> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> </Representation> </AdaptationSet> </Period> </MPD> -
Content Protection - DataSource @Override public long open(DataSpec dataSpec) throws
IOException { Assertions.checkState(dataSource == null); // Choose the correct source for the scheme. String scheme = dataSpec.uri.getScheme(); if (Util.isLocalFileUri(dataSpec.uri)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { dataSource = assetDataSource; } else { dataSource = fileDataSource; } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = assetDataSource; } else if (SCHEME_CONTENT.equals(scheme)) { dataSource = contentDataSource; } else if (SCHEME_WASABEEF.equals(scheme)) { dataSource = wasabeefDataSource; } else { dataSource = baseDataSource; } // Open the source and return. return dataSource.open(dataSpec); } -
DRM PlayReady Widevine(M) Widevine(C) FairPlay Android (4.3+) ✕ ◯ ◯
✕ Android (3+) ✕ ✕ ◯ ✕ iOS (6+) ✕ ✕ ✕ ◯ Windows Phone ◯ ✕ ✕ ✕ $ISPNF ✕ ◯ ✕ ✕ 'JSFGPY ✕ ◯ ✕ ✕ *& ◯ ✕ ✕ ✕ Edge (Win10+) ◯ ✕ ✕ ✕ Safari(8+) ✕ ✕ ✕ ◯ Opera (31+) ✕ ◯ ✕ ✕ Chromecast ◯ ◯ ✕ ✕ Android TV ◯ ◯ ✕ ✕ Apple TV ✕ ✕ ✕ ✕ Amazno Fire TV ◯ ✕ ✕ ✕ -
DRM HttpMediaDrmCallback callback = new HttpMediaDrmCallback(licenseUrl, dataSource, null); FrameworkMediaDrm mediaDrm
= FrameworkMediaDrm.newInstance(uuid); DrmSessionManager<FrameworkMediaCrypto> dsm = new StreamingDrmSessionManager<> (uuid, mediaDrm, callback, null, handler, logger); DefaultLoadControl dlc = new DefaultLoadControl() ExoPlayer player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, dlc, dsm, false);