Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
52a300f1
authored
Sep 08, 2014
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Merge fMP4/H264 and WebM/VP9 DASH implementations.
parent
1ddd5c6e
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
620 additions
and
991 deletions
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java → library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
library/src/main/java/com/google/android/exoplayer/parser/Extractor.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java
View file @
52a300f1
...
@@ -28,8 +28,7 @@ import com.google.android.exoplayer.chunk.Format;
...
@@ -28,8 +28,7 @@ import com.google.android.exoplayer.chunk.Format;
import
com.google.android.exoplayer.chunk.FormatEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
;
import
com.google.android.exoplayer.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.dash.DashMp4ChunkSource
;
import
com.google.android.exoplayer.dash.DashChunkSource
;
import
com.google.android.exoplayer.dash.DashWebmChunkSource
;
import
com.google.android.exoplayer.dash.mpd.AdaptationSet
;
import
com.google.android.exoplayer.dash.mpd.AdaptationSet
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher
;
...
@@ -163,14 +162,8 @@ public class DashVodRendererBuilder implements RendererBuilder,
...
@@ -163,14 +162,8 @@ public class DashVodRendererBuilder implements RendererBuilder,
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
ChunkSource
videoChunkSource
;
ChunkSource
videoChunkSource
;
String
mimeType
=
videoRepresentations
[
0
].
format
.
mimeType
;
String
mimeType
=
videoRepresentations
[
0
].
format
.
mimeType
;
if
(
mimeType
.
equals
(
MimeTypes
.
VIDEO_MP4
))
{
if
(
mimeType
.
equals
(
MimeTypes
.
VIDEO_MP4
)
||
mimeType
.
equals
(
MimeTypes
.
VIDEO_WEBM
))
{
videoChunkSource
=
new
DashMp4ChunkSource
(
videoDataSource
,
videoChunkSource
=
new
DashChunkSource
(
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
}
else
if
(
mimeType
.
equals
(
MimeTypes
.
VIDEO_WEBM
))
{
// TODO: Figure out how to query supported vpX resolutions. For now, restrict to standard
// definition streams.
videoRepresentations
=
getSdRepresentations
(
videoRepresentations
);
videoChunkSource
=
new
DashWebmChunkSource
(
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
}
else
{
}
else
{
throw
new
IllegalStateException
(
"Unexpected mime type: "
+
mimeType
);
throw
new
IllegalStateException
(
"Unexpected mime type: "
+
mimeType
);
...
@@ -200,7 +193,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
...
@@ -200,7 +193,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
Format
format
=
representation
.
format
;
Format
format
=
representation
.
format
;
audioTrackNames
[
i
]
=
format
.
id
+
" ("
+
format
.
numChannels
+
"ch, "
+
audioTrackNames
[
i
]
=
format
.
id
+
" ("
+
format
.
numChannels
+
"ch, "
+
format
.
audioSamplingRate
+
"Hz)"
;
format
.
audioSamplingRate
+
"Hz)"
;
audioChunkSources
[
i
]
=
new
Dash
Mp4
ChunkSource
(
audioDataSource
,
audioChunkSources
[
i
]
=
new
DashChunkSource
(
audioDataSource
,
audioEvaluator
,
representation
);
audioEvaluator
,
representation
);
}
}
audioChunkSource
=
new
MultiTrackChunkSource
(
audioChunkSources
);
audioChunkSource
=
new
MultiTrackChunkSource
(
audioChunkSources
);
...
...
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java
View file @
52a300f1
...
@@ -26,7 +26,7 @@ import com.google.android.exoplayer.chunk.ChunkSource;
...
@@ -26,7 +26,7 @@ import com.google.android.exoplayer.chunk.ChunkSource;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.FormatEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator
;
import
com.google.android.exoplayer.dash.Dash
Mp4
ChunkSource
;
import
com.google.android.exoplayer.dash.DashChunkSource
;
import
com.google.android.exoplayer.dash.mpd.AdaptationSet
;
import
com.google.android.exoplayer.dash.mpd.AdaptationSet
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher
;
...
@@ -116,7 +116,7 @@ import java.util.ArrayList;
...
@@ -116,7 +116,7 @@ import java.util.ArrayList;
// Build the video renderer.
// Build the video renderer.
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
DataSource
videoDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
Dash
Mp4
ChunkSource
(
videoDataSource
,
ChunkSource
videoChunkSource
=
new
DashChunkSource
(
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
new
AdaptiveEvaluator
(
bandwidthMeter
),
videoRepresentations
);
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
...
@@ -125,7 +125,7 @@ import java.util.ArrayList;
...
@@ -125,7 +125,7 @@ import java.util.ArrayList;
// Build the audio renderer.
// Build the audio renderer.
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
DataSource
audioDataSource
=
new
HttpDataSource
(
userAgent
,
null
,
bandwidthMeter
);
ChunkSource
audioChunkSource
=
new
Dash
Mp4
ChunkSource
(
audioDataSource
,
ChunkSource
audioChunkSource
=
new
DashChunkSource
(
audioDataSource
,
new
FormatEvaluator
.
FixedEvaluator
(),
audioRepresentation
);
new
FormatEvaluator
.
FixedEvaluator
(),
audioRepresentation
);
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
AUDIO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
AUDIO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
View file @
52a300f1
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.chunk;
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.chunk;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.
mp4.FragmentedMp4
Extractor
;
import
com.google.android.exoplayer.parser.Extractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
...
@@ -32,7 +32,7 @@ import java.util.UUID;
...
@@ -32,7 +32,7 @@ import java.util.UUID;
*/
*/
public
final
class
Mp4MediaChunk
extends
MediaChunk
{
public
final
class
Mp4MediaChunk
extends
MediaChunk
{
private
final
FragmentedMp4
Extractor
extractor
;
private
final
Extractor
extractor
;
private
final
boolean
maybeSelfContained
;
private
final
boolean
maybeSelfContained
;
private
final
long
sampleOffsetUs
;
private
final
long
sampleOffsetUs
;
...
@@ -57,7 +57,7 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -57,7 +57,7 @@ public final class Mp4MediaChunk extends MediaChunk {
*/
*/
public
Mp4MediaChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
Format
format
,
public
Mp4MediaChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
Format
format
,
int
trigger
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
int
trigger
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
FragmentedMp4
Extractor
extractor
,
boolean
maybeSelfContained
,
long
sampleOffsetUs
)
{
Extractor
extractor
,
boolean
maybeSelfContained
,
long
sampleOffsetUs
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
,
startTimeUs
,
endTimeUs
,
nextChunkIndex
);
super
(
dataSource
,
dataSpec
,
format
,
trigger
,
startTimeUs
,
endTimeUs
,
nextChunkIndex
);
this
.
extractor
=
extractor
;
this
.
extractor
=
extractor
;
this
.
maybeSelfContained
=
maybeSelfContained
;
this
.
maybeSelfContained
=
maybeSelfContained
;
...
@@ -89,7 +89,7 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -89,7 +89,7 @@ public final class Mp4MediaChunk extends MediaChunk {
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
Assertions
.
checkState
(
inputStream
!=
null
);
int
result
=
extractor
.
read
(
inputStream
,
null
);
int
result
=
extractor
.
read
(
inputStream
,
null
);
prepared
=
(
result
&
FragmentedMp4
Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
prepared
=
(
result
&
Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
else
{
}
else
{
// We know there isn't a moov atom. The extractor must have parsed one from a separate
// We know there isn't a moov atom. The extractor must have parsed one from a separate
// initialization chunk.
// initialization chunk.
...
@@ -107,7 +107,7 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -107,7 +107,7 @@ public final class Mp4MediaChunk extends MediaChunk {
public
boolean
sampleAvailable
()
throws
ParserException
{
public
boolean
sampleAvailable
()
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
int
result
=
extractor
.
read
(
inputStream
,
null
);
int
result
=
extractor
.
read
(
inputStream
,
null
);
return
(
result
&
FragmentedMp4
Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
return
(
result
&
Extractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
}
@Override
@Override
...
@@ -115,7 +115,7 @@ public final class Mp4MediaChunk extends MediaChunk {
...
@@ -115,7 +115,7 @@ public final class Mp4MediaChunk extends MediaChunk {
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
Assertions
.
checkState
(
inputStream
!=
null
);
int
result
=
extractor
.
read
(
inputStream
,
holder
);
int
result
=
extractor
.
read
(
inputStream
,
holder
);
boolean
sampleRead
=
(
result
&
FragmentedMp4
Extractor
.
RESULT_READ_SAMPLE
)
!=
0
;
boolean
sampleRead
=
(
result
&
Extractor
.
RESULT_READ_SAMPLE
)
!=
0
;
if
(
sampleRead
)
{
if
(
sampleRead
)
{
holder
.
timeUs
-=
sampleOffsetUs
;
holder
.
timeUs
-=
sampleOffsetUs
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/WebmMediaChunk.java
deleted
100644 → 0
View file @
1ddd5c6e
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.util.Map
;
import
java.util.UUID
;
/**
* A WebM {@link MediaChunk}.
*/
public
final
class
WebmMediaChunk
extends
MediaChunk
{
private
final
WebmExtractor
extractor
;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param format The format of the stream to which this chunk belongs.
* @param extractor The extractor that will be used to extract the samples.
* @param trigger The reason for this chunk being selected.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
*/
public
WebmMediaChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
Format
format
,
int
trigger
,
WebmExtractor
extractor
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
,
startTimeUs
,
endTimeUs
,
nextChunkIndex
);
this
.
extractor
=
extractor
;
}
@Override
public
void
seekToStart
()
{
seekTo
(
0
,
false
);
}
@Override
public
boolean
seekTo
(
long
positionUs
,
boolean
allowNoop
)
{
boolean
isDiscontinuous
=
extractor
.
seekTo
(
positionUs
,
allowNoop
);
if
(
isDiscontinuous
)
{
resetReadPosition
();
}
return
isDiscontinuous
;
}
@Override
public
boolean
prepare
()
{
return
true
;
}
@Override
public
boolean
sampleAvailable
()
throws
ParserException
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
int
result
=
extractor
.
read
(
inputStream
,
null
);
return
(
result
&
WebmExtractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
;
}
@Override
public
boolean
read
(
SampleHolder
holder
)
{
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
Assertions
.
checkState
(
inputStream
!=
null
);
int
result
=
extractor
.
read
(
inputStream
,
holder
);
return
(
result
&
WebmExtractor
.
RESULT_READ_SAMPLE
)
!=
0
;
}
@Override
public
MediaFormat
getMediaFormat
()
{
return
extractor
.
getFormat
();
}
@Override
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
// TODO: Add support for Pssh to WebmExtractor
return
null
;
}
}
library/src/main/java/com/google/android/exoplayer/dash/Dash
Mp4
ChunkSource.java
→
library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
View file @
52a300f1
...
@@ -29,10 +29,13 @@ import com.google.android.exoplayer.chunk.MediaChunk;
...
@@ -29,10 +29,13 @@ import com.google.android.exoplayer.chunk.MediaChunk;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.dash.mpd.RangedUri
;
import
com.google.android.exoplayer.dash.mpd.RangedUri
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.parser.Extractor
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.net.Uri
;
import
android.net.Uri
;
...
@@ -42,9 +45,11 @@ import java.util.HashMap;
...
@@ -42,9 +45,11 @@ import java.util.HashMap;
import
java.util.List
;
import
java.util.List
;
/**
/**
* An {@link ChunkSource} for Mp4 DASH streams.
* An {@link ChunkSource} for DASH streams.
* <p>
* This implementation currently supports fMP4 and webm.
*/
*/
public
class
Dash
Mp4
ChunkSource
implements
ChunkSource
{
public
class
DashChunkSource
implements
ChunkSource
{
private
final
TrackInfo
trackInfo
;
private
final
TrackInfo
trackInfo
;
private
final
DataSource
dataSource
;
private
final
DataSource
dataSource
;
...
@@ -55,7 +60,7 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -55,7 +60,7 @@ public class DashMp4ChunkSource implements ChunkSource {
private
final
Format
[]
formats
;
private
final
Format
[]
formats
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
HashMap
<
String
,
FragmentedMp4
Extractor
>
extractors
;
private
final
HashMap
<
String
,
Extractor
>
extractors
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
boolean
lastChunkWasInitialization
;
private
boolean
lastChunkWasInitialization
;
...
@@ -65,12 +70,12 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -65,12 +70,12 @@ public class DashMp4ChunkSource implements ChunkSource {
* @param evaluator Selects from the available formats.
* @param evaluator Selects from the available formats.
* @param representations The representations to be considered by the source.
* @param representations The representations to be considered by the source.
*/
*/
public
Dash
Mp4
ChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
public
DashChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
Representation
...
representations
)
{
Representation
...
representations
)
{
this
.
dataSource
=
dataSource
;
this
.
dataSource
=
dataSource
;
this
.
evaluator
=
evaluator
;
this
.
evaluator
=
evaluator
;
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
extractors
=
new
HashMap
<
String
,
FragmentedMp4
Extractor
>();
this
.
extractors
=
new
HashMap
<
String
,
Extractor
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
...
@@ -82,7 +87,9 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -82,7 +87,9 @@ public class DashMp4ChunkSource implements ChunkSource {
formats
[
i
]
=
representations
[
i
].
format
;
formats
[
i
]
=
representations
[
i
].
format
;
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
extractors
.
put
(
formats
[
i
].
id
,
new
FragmentedMp4Extractor
());
Extractor
extractor
=
formats
[
i
].
mimeType
.
startsWith
(
MimeTypes
.
VIDEO_WEBM
)
?
new
WebmExtractor
()
:
new
FragmentedMp4Extractor
();
extractors
.
put
(
formats
[
i
].
id
,
extractor
);
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
DashSegmentIndex
segmentIndex
=
representations
[
i
].
getIndex
();
DashSegmentIndex
segmentIndex
=
representations
[
i
].
getIndex
();
if
(
segmentIndex
!=
null
)
{
if
(
segmentIndex
!=
null
)
{
...
@@ -142,7 +149,7 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -142,7 +149,7 @@ public class DashMp4ChunkSource implements ChunkSource {
}
}
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
FragmentedMp4
Extractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
Extractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
...
@@ -191,35 +198,39 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -191,35 +198,39 @@ public class DashMp4ChunkSource implements ChunkSource {
}
}
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
RangedUri
indexUri
,
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
RangedUri
indexUri
,
Representation
representation
,
FragmentedMp4
Extractor
extractor
,
DataSource
dataSource
,
Representation
representation
,
Extractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
int
trigger
)
{
int
expectedExtractorResult
=
FragmentedMp4
Extractor
.
RESULT_END_OF_STREAM
;
int
expectedExtractorResult
=
Extractor
.
RESULT_END_OF_STREAM
;
long
indexAnchor
=
0
;
long
indexAnchor
=
0
;
RangedUri
requestUri
;
RangedUri
requestUri
;
if
(
initializationUri
!=
null
)
{
if
(
initializationUri
!=
null
)
{
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
// the two requests together to request both at once.
expectedExtractorResult
|=
FragmentedMp4
Extractor
.
RESULT_READ_INIT
;
expectedExtractorResult
|=
Extractor
.
RESULT_READ_INIT
;
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
if
(
requestUri
!=
null
)
{
if
(
requestUri
!=
null
)
{
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_INDEX
;
expectedExtractorResult
|=
Extractor
.
RESULT_READ_INDEX
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
if
(
extractor
.
hasRelativeIndexOffsets
())
{
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
}
}
else
{
}
else
{
requestUri
=
initializationUri
;
requestUri
=
initializationUri
;
}
}
}
else
{
}
else
{
requestUri
=
indexUri
;
requestUri
=
indexUri
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
if
(
extractor
.
hasRelativeIndexOffsets
())
{
expectedExtractorResult
|=
FragmentedMp4Extractor
.
RESULT_READ_INDEX
;
indexAnchor
=
indexUri
.
start
+
indexUri
.
length
;
}
expectedExtractorResult
|=
Extractor
.
RESULT_READ_INDEX
;
}
}
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
representation
.
getCacheKey
());
representation
.
getCacheKey
());
return
new
Initialization
Mp4
Loadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
return
new
InitializationLoadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
extractor
,
expectedExtractorResult
,
indexAnchor
);
extractor
,
expectedExtractorResult
,
indexAnchor
);
}
}
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
FragmentedMp4
Extractor
extractor
,
DataSource
dataSource
,
int
segmentNum
,
int
trigger
)
{
Extractor
extractor
,
DataSource
dataSource
,
int
segmentNum
,
int
trigger
)
{
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
int
nextSegmentNum
=
segmentNum
==
lastSegmentNum
?
-
1
:
segmentNum
+
1
;
int
nextSegmentNum
=
segmentNum
==
lastSegmentNum
?
-
1
:
segmentNum
+
1
;
long
startTimeUs
=
segmentIndex
.
getTimeUs
(
segmentNum
);
long
startTimeUs
=
segmentIndex
.
getTimeUs
(
segmentNum
);
...
@@ -232,15 +243,15 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -232,15 +243,15 @@ public class DashMp4ChunkSource implements ChunkSource {
endTimeUs
,
nextSegmentNum
,
extractor
,
false
,
0
);
endTimeUs
,
nextSegmentNum
,
extractor
,
false
,
0
);
}
}
private
class
Initialization
Mp4
Loadable
extends
Chunk
{
private
class
InitializationLoadable
extends
Chunk
{
private
final
FragmentedMp4
Extractor
extractor
;
private
final
Extractor
extractor
;
private
final
int
expectedExtractorResult
;
private
final
int
expectedExtractorResult
;
private
final
long
indexAnchor
;
private
final
long
indexAnchor
;
private
final
Uri
uri
;
private
final
Uri
uri
;
public
Initialization
Mp4
Loadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
public
InitializationLoadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
Format
format
,
FragmentedMp4
Extractor
extractor
,
int
expectedExtractorResult
,
Format
format
,
Extractor
extractor
,
int
expectedExtractorResult
,
long
indexAnchor
)
{
long
indexAnchor
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
this
.
extractor
=
extractor
;
this
.
extractor
=
extractor
;
...
@@ -256,7 +267,7 @@ public class DashMp4ChunkSource implements ChunkSource {
...
@@ -256,7 +267,7 @@ public class DashMp4ChunkSource implements ChunkSource {
throw
new
ParserException
(
"Invalid extractor result. Expected "
throw
new
ParserException
(
"Invalid extractor result. Expected "
+
expectedExtractorResult
+
", got "
+
result
);
+
expectedExtractorResult
+
", got "
+
result
);
}
}
if
((
result
&
FragmentedMp4
Extractor
.
RESULT_READ_INDEX
)
!=
0
)
{
if
((
result
&
Extractor
.
RESULT_READ_INDEX
)
!=
0
)
{
segmentIndexes
.
put
(
format
.
id
,
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
getIndex
(),
uri
,
indexAnchor
));
new
DashWrappingSegmentIndex
(
extractor
.
getIndex
(),
uri
,
indexAnchor
));
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
deleted
100644 → 0
View file @
1ddd5c6e
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
dash
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.TrackInfo
;
import
com.google.android.exoplayer.chunk.Chunk
;
import
com.google.android.exoplayer.chunk.ChunkOperationHolder
;
import
com.google.android.exoplayer.chunk.ChunkSource
;
import
com.google.android.exoplayer.chunk.Format
;
import
com.google.android.exoplayer.chunk.Format.DecreasingBandwidthComparator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.WebmMediaChunk
;
import
com.google.android.exoplayer.dash.mpd.RangedUri
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.parser.webm.DefaultWebmExtractor
;
import
com.google.android.exoplayer.parser.webm.WebmExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
android.net.Uri
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.List
;
/**
* An {@link ChunkSource} for WebM DASH streams.
*/
public
class
DashWebmChunkSource
implements
ChunkSource
{
private
final
TrackInfo
trackInfo
;
private
final
DataSource
dataSource
;
private
final
FormatEvaluator
evaluator
;
private
final
Evaluation
evaluation
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
private
final
Format
[]
formats
;
private
final
HashMap
<
String
,
Representation
>
representations
;
private
final
HashMap
<
String
,
DefaultWebmExtractor
>
extractors
;
private
final
HashMap
<
String
,
DashSegmentIndex
>
segmentIndexes
;
private
boolean
lastChunkWasInitialization
;
/**
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param evaluator Selects from the available formats.
* @param representations The representations to be considered by the source.
*/
public
DashWebmChunkSource
(
DataSource
dataSource
,
FormatEvaluator
evaluator
,
Representation
...
representations
)
{
this
.
dataSource
=
dataSource
;
this
.
evaluator
=
evaluator
;
this
.
formats
=
new
Format
[
representations
.
length
];
this
.
extractors
=
new
HashMap
<
String
,
DefaultWebmExtractor
>();
this
.
segmentIndexes
=
new
HashMap
<
String
,
DashSegmentIndex
>();
this
.
representations
=
new
HashMap
<
String
,
Representation
>();
this
.
trackInfo
=
new
TrackInfo
(
representations
[
0
].
format
.
mimeType
,
representations
[
0
].
periodDurationMs
*
1000
);
this
.
evaluation
=
new
Evaluation
();
int
maxWidth
=
0
;
int
maxHeight
=
0
;
for
(
int
i
=
0
;
i
<
representations
.
length
;
i
++)
{
formats
[
i
]
=
representations
[
i
].
format
;
maxWidth
=
Math
.
max
(
formats
[
i
].
width
,
maxWidth
);
maxHeight
=
Math
.
max
(
formats
[
i
].
height
,
maxHeight
);
extractors
.
put
(
formats
[
i
].
id
,
new
DefaultWebmExtractor
());
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
DashSegmentIndex
segmentIndex
=
representations
[
i
].
getIndex
();
if
(
segmentIndex
!=
null
)
{
segmentIndexes
.
put
(
formats
[
i
].
id
,
segmentIndex
);
}
}
this
.
maxWidth
=
maxWidth
;
this
.
maxHeight
=
maxHeight
;
Arrays
.
sort
(
formats
,
new
DecreasingBandwidthComparator
());
}
@Override
public
final
void
getMaxVideoDimensions
(
MediaFormat
out
)
{
if
(
trackInfo
.
mimeType
.
startsWith
(
"video"
))
{
out
.
setMaxVideoDimensions
(
maxWidth
,
maxHeight
);
}
}
@Override
public
final
TrackInfo
getTrackInfo
()
{
return
trackInfo
;
}
@Override
public
void
enable
()
{
evaluator
.
enable
();
}
@Override
public
void
disable
(
List
<?
extends
MediaChunk
>
queue
)
{
evaluator
.
disable
();
}
@Override
public
void
continueBuffering
(
long
playbackPositionUs
)
{
// Do nothing
}
@Override
public
final
void
getChunkOperation
(
List
<?
extends
MediaChunk
>
queue
,
long
seekPositionUs
,
long
playbackPositionUs
,
ChunkOperationHolder
out
)
{
evaluation
.
queueSize
=
queue
.
size
();
if
(
evaluation
.
format
==
null
||
!
lastChunkWasInitialization
)
{
evaluator
.
evaluate
(
queue
,
playbackPositionUs
,
formats
,
evaluation
);
}
Format
selectedFormat
=
evaluation
.
format
;
out
.
queueSize
=
evaluation
.
queueSize
;
if
(
selectedFormat
==
null
)
{
out
.
chunk
=
null
;
return
;
}
else
if
(
out
.
queueSize
==
queue
.
size
()
&&
out
.
chunk
!=
null
&&
out
.
chunk
.
format
.
id
.
equals
(
selectedFormat
.
id
))
{
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged.
return
;
}
Representation
selectedRepresentation
=
representations
.
get
(
selectedFormat
.
id
);
WebmExtractor
extractor
=
extractors
.
get
(
selectedRepresentation
.
format
.
id
);
RangedUri
pendingInitializationUri
=
null
;
RangedUri
pendingIndexUri
=
null
;
if
(
extractor
.
getFormat
()
==
null
)
{
pendingInitializationUri
=
selectedRepresentation
.
getInitializationUri
();
}
if
(!
segmentIndexes
.
containsKey
(
selectedRepresentation
.
format
.
id
))
{
pendingIndexUri
=
selectedRepresentation
.
getIndexUri
();
}
if
(
pendingInitializationUri
!=
null
||
pendingIndexUri
!=
null
)
{
// We have initialization and/or index requests to make.
Chunk
initializationChunk
=
newInitializationChunk
(
pendingInitializationUri
,
pendingIndexUri
,
selectedRepresentation
,
extractor
,
dataSource
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
true
;
out
.
chunk
=
initializationChunk
;
return
;
}
int
nextSegmentNum
;
DashSegmentIndex
segmentIndex
=
segmentIndexes
.
get
(
selectedRepresentation
.
format
.
id
);
if
(
queue
.
isEmpty
())
{
nextSegmentNum
=
segmentIndex
.
getSegmentNum
(
seekPositionUs
);
}
else
{
nextSegmentNum
=
queue
.
get
(
out
.
queueSize
-
1
).
nextChunkIndex
;
}
if
(
nextSegmentNum
==
-
1
)
{
out
.
chunk
=
null
;
return
;
}
Chunk
nextMediaChunk
=
newMediaChunk
(
selectedRepresentation
,
segmentIndex
,
extractor
,
dataSource
,
nextSegmentNum
,
evaluation
.
trigger
);
lastChunkWasInitialization
=
false
;
out
.
chunk
=
nextMediaChunk
;
}
@Override
public
IOException
getError
()
{
return
null
;
}
@Override
public
void
onChunkLoadError
(
Chunk
chunk
,
Exception
e
)
{
// Do nothing.
}
private
Chunk
newInitializationChunk
(
RangedUri
initializationUri
,
RangedUri
indexUri
,
Representation
representation
,
WebmExtractor
extractor
,
DataSource
dataSource
,
int
trigger
)
{
int
expectedExtractorResult
=
WebmExtractor
.
RESULT_END_OF_STREAM
;
RangedUri
requestUri
;
if
(
initializationUri
!=
null
)
{
// It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request both at once.
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INIT
;
requestUri
=
initializationUri
.
attemptMerge
(
indexUri
);
if
(
requestUri
!=
null
)
{
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INDEX
;
}
else
{
requestUri
=
initializationUri
;
}
}
else
{
requestUri
=
indexUri
;
expectedExtractorResult
|=
WebmExtractor
.
RESULT_READ_INDEX
;
}
DataSpec
dataSpec
=
new
DataSpec
(
requestUri
.
getUri
(),
requestUri
.
start
,
requestUri
.
length
,
representation
.
getCacheKey
());
return
new
InitializationWebmLoadable
(
dataSource
,
dataSpec
,
trigger
,
representation
.
format
,
extractor
,
expectedExtractorResult
);
}
private
Chunk
newMediaChunk
(
Representation
representation
,
DashSegmentIndex
segmentIndex
,
WebmExtractor
extractor
,
DataSource
dataSource
,
int
segmentNum
,
int
trigger
)
{
int
lastSegmentNum
=
segmentIndex
.
getLastSegmentNum
();
int
nextSegmentNum
=
segmentNum
==
lastSegmentNum
?
-
1
:
segmentNum
+
1
;
long
startTimeUs
=
segmentIndex
.
getTimeUs
(
segmentNum
);
long
endTimeUs
=
segmentNum
<
lastSegmentNum
?
segmentIndex
.
getTimeUs
(
segmentNum
+
1
)
:
startTimeUs
+
segmentIndex
.
getDurationUs
(
segmentNum
);
RangedUri
segmentUri
=
segmentIndex
.
getSegmentUrl
(
segmentNum
);
DataSpec
dataSpec
=
new
DataSpec
(
segmentUri
.
getUri
(),
segmentUri
.
start
,
segmentUri
.
length
,
representation
.
getCacheKey
());
return
new
WebmMediaChunk
(
dataSource
,
dataSpec
,
representation
.
format
,
trigger
,
extractor
,
startTimeUs
,
endTimeUs
,
nextSegmentNum
);
}
private
class
InitializationWebmLoadable
extends
Chunk
{
private
final
WebmExtractor
extractor
;
private
final
int
expectedExtractorResult
;
private
final
Uri
uri
;
public
InitializationWebmLoadable
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
Format
format
,
WebmExtractor
extractor
,
int
expectedExtractorResult
)
{
super
(
dataSource
,
dataSpec
,
format
,
trigger
);
this
.
extractor
=
extractor
;
this
.
expectedExtractorResult
=
expectedExtractorResult
;
this
.
uri
=
dataSpec
.
uri
;
}
@Override
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
int
result
=
extractor
.
read
(
stream
,
null
);
if
(
result
!=
expectedExtractorResult
)
{
throw
new
ParserException
(
"Invalid extractor result. Expected "
+
expectedExtractorResult
+
", got "
+
result
);
}
if
((
result
&
WebmExtractor
.
RESULT_READ_INDEX
)
!=
0
)
{
segmentIndexes
.
put
(
format
.
id
,
new
DashWrappingSegmentIndex
(
extractor
.
getIndex
(),
uri
,
0
));
}
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/Extractor.java
0 → 100644
View file @
52a300f1
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
java.util.Map
;
import
java.util.UUID
;
/**
* Facilitates extraction of media samples from a container format.
*/
public
interface
Extractor
{
/**
* An attempt to read from the input stream returned insufficient data.
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
* The end of the input stream was reached.
*/
public
static
final
int
RESULT_END_OF_STREAM
=
2
;
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
=
4
;
/**
* Initialization data was read. The parsed data can be read using {@link #getFormat()} and
* {@link #getPsshInfo}.
*/
public
static
final
int
RESULT_READ_INIT
=
8
;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public
static
final
int
RESULT_READ_INDEX
=
16
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
32
;
/**
* Returns the segment index parsed from the stream.
*
* @return The segment index, or null if a SIDX atom has yet to be parsed.
*/
public
SegmentIndex
getIndex
();
/**
* Returns true if the offsets in the index returned by {@link #getIndex()} are relative to the
* first byte following the initialization data, or false if they are absolute (i.e. relative to
* the first byte of the stream).
*
* @return True if the offsets are relative to the first byte following the initialization data.
* False otherwise.
*/
public
boolean
hasRelativeIndexOffsets
();
/**
* Returns the format of the samples contained within the media stream.
*
* @return The sample media format, or null if the format has yet to be parsed.
*/
public
MediaFormat
getFormat
();
/**
* Returns the pssh information parsed from the stream.
*
* @return The pssh information. May be null if pssh data has yet to be parsed, or if the stream
* does not contain any pssh data.
*/
public
Map
<
UUID
,
byte
[]>
getPsshInfo
();
/**
* Consumes data from a {@link NonBlockingInputStream}.
* <p>
* The read terminates if the end of the input stream is reached, if an attempt to read from the
* input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate
* both the reason for termination and data that was parsed during the read.
*
* @param inputStream The input stream from which data should be read.
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
* {@link #RESULT_NEED_SAMPLE_HOLDER} will be returned once a sample has been reached.
* @return One or more of the {@code RESULT_*} flags defined in this class.
* @throws ParserException If an error occurs parsing the media data.
*/
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
throws
ParserException
;
/**
* Seeks to a position before or equal to the requested time.
*
* @param seekTimeUs The desired seek time in microseconds.
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* fragment run, is equal to or greater than the time of the current sample, and if there
* does not exist a sync frame between these two times.
* @return True if the operation resulted in a change of state. False if it was a no-op.
*/
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
);
}
library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java
View file @
52a300f1
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer.parser.mp4;
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer.parser.mp4;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.Extractor
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.parser.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.parser.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.parser.mp4.Atom.LeafAtom
;
import
com.google.android.exoplayer.parser.mp4.Atom.LeafAtom
;
...
@@ -48,7 +49,7 @@ import java.util.UUID;
...
@@ -48,7 +49,7 @@ import java.util.UUID;
* <p>
* <p>
* This implementation only supports de-muxed (i.e. single track) streams.
* This implementation only supports de-muxed (i.e. single track) streams.
*/
*/
public
final
class
FragmentedMp4Extractor
{
public
final
class
FragmentedMp4Extractor
implements
Extractor
{
/**
/**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
...
@@ -59,32 +60,6 @@ public final class FragmentedMp4Extractor {
...
@@ -59,32 +60,6 @@ public final class FragmentedMp4Extractor {
*/
*/
public
static
final
int
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
=
1
;
public
static
final
int
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
=
1
;
/**
* An attempt to read from the input stream returned insufficient data.
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
* The end of the input stream was reached.
*/
public
static
final
int
RESULT_END_OF_STREAM
=
2
;
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
=
4
;
/**
* A moov atom was read. The parsed data can be read using {@link #getFormat()} and
* {@link #getPsshInfo}.
*/
public
static
final
int
RESULT_READ_INIT
=
8
;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public
static
final
int
RESULT_READ_INDEX
=
16
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
32
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
...
@@ -197,22 +172,13 @@ public final class FragmentedMp4Extractor {
...
@@ -197,22 +172,13 @@ public final class FragmentedMp4Extractor {
}
}
/**
/**
* Returns the segment index parsed from the stream.
* Sideloads track information into the extractor.
*
* @return The segment index, or null if a SIDX atom has yet to be parsed.
*/
public
SegmentIndex
getIndex
()
{
return
segmentIndex
;
}
/**
* Returns the pssh information parsed from the stream.
*
*
* @return The pssh information. May be null if the MOOV atom has yet to be parsed of if it did
* @param track The track to sideload.
* not contain any pssh information.
*/
*/
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
public
void
setTrack
(
Track
track
)
{
return
psshData
.
isEmpty
()
?
null
:
psshData
;
this
.
extendsDefaults
=
new
DefaultSampleValues
(
0
,
0
,
0
,
0
);
this
.
track
=
track
;
}
}
/**
/**
...
@@ -229,38 +195,27 @@ public final class FragmentedMp4Extractor {
...
@@ -229,38 +195,27 @@ public final class FragmentedMp4Extractor {
psshData
.
put
(
uuid
,
data
);
psshData
.
put
(
uuid
,
data
);
}
}
/**
@Override
* Returns the format of the samples contained within the media stream.
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
*
return
psshData
.
isEmpty
()
?
null
:
psshData
;
* @return The sample media format, or null if a MOOV atom has yet to be parsed.
*/
public
MediaFormat
getFormat
()
{
return
track
==
null
?
null
:
track
.
mediaFormat
;
}
}
/**
@Override
* Sideloads track information into the extractor.
public
SegmentIndex
getIndex
()
{
*
return
segmentIndex
;
* @param track The track to sideload.
*/
public
void
setTrack
(
Track
track
)
{
this
.
extendsDefaults
=
new
DefaultSampleValues
(
0
,
0
,
0
,
0
);
this
.
track
=
track
;
}
}
/**
@Override
* Consumes data from a {@link NonBlockingInputStream}.
public
boolean
hasRelativeIndexOffsets
()
{
* <p>
return
true
;
* The read terminates if the end of the input stream is reached, if an attempt to read from the
}
* input stream returned 0 bytes of data, or if a sample is read. The returned flags indicate
* both the reason for termination and data that was parsed during the read.
@Override
*
public
MediaFormat
getFormat
()
{
* @param inputStream The input stream from which data should be read.
return
track
==
null
?
null
:
track
.
mediaFormat
;
* @param out A {@link SampleHolder} into which the next sample should be read. If null then
}
* {@link #RESULT_NEED_SAMPLE_HOLDER} will be returned once a sample has been reached.
* @return One or more of the {@code RESULT_*} flags defined in this class.
@Override
* @throws ParserException If an error occurs parsing the media data.
*/
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
throws
ParserException
{
throws
ParserException
{
try
{
try
{
...
@@ -287,15 +242,7 @@ public final class FragmentedMp4Extractor {
...
@@ -287,15 +242,7 @@ public final class FragmentedMp4Extractor {
}
}
}
}
/**
@Override
* Seeks to a position before or equal to the requested time.
*
* @param seekTimeUs The desired seek time in microseconds.
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* fragment run, is equal to or greater than the time of the current sample, and if there
* does not exist a sync frame between these two times.
* @return True if the operation resulted in a change of state. False if it was a no-op.
*/
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
pendingSeekTimeMs
=
(
int
)
(
seekTimeUs
/
1000
);
pendingSeekTimeMs
=
(
int
)
(
seekTimeUs
/
1000
);
if
(
allowNoop
&&
fragmentRun
!=
null
if
(
allowNoop
&&
fragmentRun
!=
null
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
deleted
100644 → 0
View file @
1ddd5c6e
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
import
java.util.concurrent.TimeUnit
;
/**
* Default version of an extractor to facilitate data retrieval from the WebM container format.
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
@TargetApi
(
16
)
public
final
class
DefaultWebmExtractor
implements
WebmExtractor
{
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
private
static
final
int
UNKNOWN
=
-
1
;
// Element IDs
private
static
final
int
ID_EBML
=
0x1A45DFA3
;
private
static
final
int
ID_EBML_READ_VERSION
=
0x42F7
;
private
static
final
int
ID_DOC_TYPE
=
0x4282
;
private
static
final
int
ID_DOC_TYPE_READ_VERSION
=
0x4285
;
private
static
final
int
ID_SEGMENT
=
0x18538067
;
private
static
final
int
ID_INFO
=
0x1549A966
;
private
static
final
int
ID_TIMECODE_SCALE
=
0x2AD7B1
;
private
static
final
int
ID_DURATION
=
0x4489
;
private
static
final
int
ID_CLUSTER
=
0x1F43B675
;
private
static
final
int
ID_TIME_CODE
=
0xE7
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
static
final
int
ID_TRACKS
=
0x1654AE6B
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_CODEC_ID
=
0x86
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
private
static
final
int
ID_CUES
=
0x1C53BB6B
;
private
static
final
int
ID_CUE_POINT
=
0xBB
;
private
static
final
int
ID_CUE_TIME
=
0xB3
;
private
static
final
int
ID_CUE_TRACK_POSITIONS
=
0xB7
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
// SimpleBlock Lacing Values
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_XIPH
=
1
;
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
final
EbmlReader
reader
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
sampleHolder
;
private
int
readResults
;
private
long
segmentStartOffsetBytes
=
UNKNOWN
;
private
long
segmentEndOffsetBytes
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
UNKNOWN
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
long
cuesSizeBytes
=
UNKNOWN
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
long
simpleBlockTimecodeUs
=
UNKNOWN
;
private
MediaFormat
format
;
private
SegmentIndex
cues
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
public
DefaultWebmExtractor
()
{
this
(
new
DefaultEbmlReader
());
}
/* package */
DefaultWebmExtractor
(
EbmlReader
reader
)
{
this
.
reader
=
reader
;
this
.
reader
.
setEventHandler
(
new
InnerEbmlEventHandler
());
}
@Override
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
this
.
sampleHolder
=
sampleHolder
;
this
.
readResults
=
0
;
while
((
readResults
&
READ_TERMINATING_RESULTS
)
==
0
)
{
int
ebmlReadResult
=
reader
.
read
(
inputStream
);
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_NEED_MORE_DATA
)
{
readResults
|=
WebmExtractor
.
RESULT_NEED_MORE_DATA
;
}
else
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_END_OF_STREAM
)
{
readResults
|=
WebmExtractor
.
RESULT_END_OF_STREAM
;
}
}
this
.
sampleHolder
=
null
;
return
readResults
;
}
@Override
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
if
(
allowNoop
&&
cues
!=
null
&&
clusterTimecodeUs
!=
UNKNOWN
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
if
(
clusterIndex
>=
0
&&
seekTimeUs
<
clusterTimecodeUs
+
cues
.
durationsUs
[
clusterIndex
])
{
return
false
;
}
}
clusterTimecodeUs
=
UNKNOWN
;
simpleBlockTimecodeUs
=
UNKNOWN
;
reader
.
reset
();
return
true
;
}
@Override
public
SegmentIndex
getIndex
()
{
return
cues
;
}
@Override
public
MediaFormat
getFormat
()
{
return
format
;
}
/* package */
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
case
ID_INFO:
case
ID_CLUSTER:
case
ID_TRACKS:
case
ID_TRACK_ENTRY:
case
ID_VIDEO:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
case
ID_TIMECODE_SCALE:
case
ID_TIME_CODE:
case
ID_PIXEL_WIDTH:
case
ID_PIXEL_HEIGHT:
case
ID_CUE_TIME:
case
ID_CUE_CLUSTER_POSITION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
case
ID_CODEC_ID:
return
EbmlReader
.
TYPE_STRING
;
case
ID_SIMPLE_BLOCK:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
/* package */
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentStartOffsetBytes
!=
UNKNOWN
||
segmentEndOffsetBytes
!=
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Multiple Segment elements not supported"
);
}
segmentStartOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
;
segmentEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
break
;
case
ID_CUES:
cuesSizeBytes
=
headerSizeBytes
+
contentsSizeBytes
;
cueTimesUs
=
new
LongArray
();
cueClusterPositions
=
new
LongArray
();
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onMasterElementEnd
(
int
id
)
{
switch
(
id
)
{
case
ID_CUES:
buildCues
();
return
false
;
case
ID_VIDEO:
buildFormat
();
return
true
;
default
:
return
true
;
}
}
/* package */
boolean
onIntegerElement
(
int
id
,
long
value
)
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
IllegalArgumentException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if
(
value
<
1
||
value
>
2
)
{
throw
new
IllegalArgumentException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
break
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
break
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
break
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
break
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
break
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onFloatElement
(
int
id
,
double
value
)
{
if
(
id
==
ID_DURATION
)
{
durationUs
=
scaleTimecodeToUs
((
long
)
value
);
}
return
true
;
}
/* package */
boolean
onStringElement
(
int
id
,
String
value
)
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"DocType "
+
value
+
" not supported"
);
}
break
;
case
ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9".
if
(!
CODEC_ID_VP9
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"CodecID "
+
value
+
" not supported"
);
}
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
if
(
id
==
ID_SIMPLE_BLOCK
)
{
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// If we don't have a sample holder then don't consume the data.
if
(
sampleHolder
==
null
)
{
readResults
|=
RESULT_NEED_SAMPLE_HOLDER
;
return
false
;
}
// Value of trackNumber is not used but needs to be read.
reader
.
readVarint
(
inputStream
);
// Next three bytes have timecode and flags.
reader
.
readBytes
(
inputStream
,
simpleBlockTimecodeAndFlags
,
3
);
// First two bytes of the three are the relative timecode.
int
timecode
=
(
simpleBlockTimecodeAndFlags
[
0
]
<<
8
)
|
(
simpleBlockTimecodeAndFlags
[
1
]
&
0xff
);
long
timecodeUs
=
scaleTimecodeToUs
(
timecode
);
// Last byte of the three has some flags and the lacing value.
boolean
keyframe
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x80
)
==
0x80
;
boolean
invisible
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x08
)
==
0x08
;
int
lacing
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x06
)
>>
1
;
// Validate lacing and set info into sample holder.
switch
(
lacing
)
{
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
sampleHolder
.
decodeOnly
=
invisible
;
sampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
case
LACING_XIPH:
default
:
throw
new
IllegalStateException
(
"Lacing mode "
+
lacing
+
" not supported"
);
}
ByteBuffer
outputData
=
sampleHolder
.
data
;
if
(
sampleHolder
.
allowDataBufferReplacement
&&
(
sampleHolder
.
data
==
null
||
sampleHolder
.
data
.
capacity
()
<
sampleHolder
.
size
))
{
outputData
=
ByteBuffer
.
allocate
(
sampleHolder
.
size
);
sampleHolder
.
data
=
outputData
;
}
if
(
outputData
==
null
)
{
reader
.
skipBytes
(
inputStream
,
sampleHolder
.
size
);
sampleHolder
.
size
=
0
;
}
else
{
reader
.
readBytes
(
inputStream
,
outputData
,
sampleHolder
.
size
);
}
readResults
|=
RESULT_READ_SAMPLE
;
}
return
true
;
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
TimeUnit
.
NANOSECONDS
.
toMicros
(
unscaledTimecode
*
timecodeScale
);
}
/**
* Build a video {@link MediaFormat} containing recently gathered Video information, if needed.
*
* <p>Replaces the previous {@link #format} only if video width/height have changed.
* {@link #format} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown.
*/
private
void
buildFormat
()
{
if
(
pixelWidth
!=
UNKNOWN
&&
pixelHeight
!=
UNKNOWN
&&
(
format
==
null
||
format
.
width
!=
pixelWidth
||
format
.
height
!=
pixelHeight
))
{
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
readResults
|=
RESULT_READ_INIT
;
}
else
if
(
format
==
null
)
{
throw
new
IllegalStateException
(
"Unable to build format"
);
}
}
/**
* Build a {@link SegmentIndex} containing recently gathered Cues information.
*
* <p>{@link #cues} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown.
*/
private
void
buildCues
()
{
if
(
segmentStartOffsetBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Segment start/end offsets unknown"
);
}
else
if
(
durationUs
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Duration unknown"
);
}
else
if
(
cuesSizeBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Cues size unknown"
);
}
else
if
(
cueTimesUs
==
null
||
cueClusterPositions
==
null
||
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
IllegalStateException
(
"Invalid/missing cue points"
);
}
int
cuePointsSize
=
cueTimesUs
.
size
();
int
[]
sizes
=
new
int
[
cuePointsSize
];
long
[]
offsets
=
new
long
[
cuePointsSize
];
long
[]
durationsUs
=
new
long
[
cuePointsSize
];
long
[]
timesUs
=
new
long
[
cuePointsSize
];
for
(
int
i
=
0
;
i
<
cuePointsSize
;
i
++)
{
timesUs
[
i
]
=
cueTimesUs
.
get
(
i
);
offsets
[
i
]
=
segmentStartOffsetBytes
+
cueClusterPositions
.
get
(
i
);
}
for
(
int
i
=
0
;
i
<
cuePointsSize
-
1
;
i
++)
{
sizes
[
i
]
=
(
int
)
(
offsets
[
i
+
1
]
-
offsets
[
i
]);
durationsUs
[
i
]
=
timesUs
[
i
+
1
]
-
timesUs
[
i
];
}
sizes
[
cuePointsSize
-
1
]
=
(
int
)
(
segmentEndOffsetBytes
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cues
=
new
SegmentIndex
((
int
)
cuesSizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
readResults
|=
RESULT_READ_INDEX
;
}
/**
* Passes events through to {@link DefaultWebmExtractor} as
* callbacks from {@link EbmlReader} are received.
*/
private
final
class
InnerEbmlEventHandler
implements
EbmlEventHandler
{
@Override
public
int
getElementType
(
int
id
)
{
return
DefaultWebmExtractor
.
this
.
getElementType
(
id
);
}
@Override
public
void
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
DefaultWebmExtractor
.
this
.
onMasterElementStart
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
);
}
@Override
public
void
onMasterElementEnd
(
int
id
)
{
DefaultWebmExtractor
.
this
.
onMasterElementEnd
(
id
);
}
@Override
public
void
onIntegerElement
(
int
id
,
long
value
)
{
DefaultWebmExtractor
.
this
.
onIntegerElement
(
id
,
value
);
}
@Override
public
void
onFloatElement
(
int
id
,
double
value
)
{
DefaultWebmExtractor
.
this
.
onFloatElement
(
id
,
value
);
}
@Override
public
void
onStringElement
(
int
id
,
String
value
)
{
DefaultWebmExtractor
.
this
.
onStringElement
(
id
,
value
);
}
@Override
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
return
DefaultWebmExtractor
.
this
.
onBinaryElement
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
,
inputStream
);
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
View file @
52a300f1
...
@@ -17,76 +17,460 @@ package com.google.android.exoplayer.parser.webm;
...
@@ -17,76 +17,460 @@ package com.google.android.exoplayer.parser.webm;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.Extractor
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
import
java.util.Map
;
import
java.util.UUID
;
import
java.util.concurrent.TimeUnit
;
/**
/**
*
E
xtractor to facilitate data retrieval from the WebM container format.
*
An e
xtractor to facilitate data retrieval from the WebM container format.
*
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
*/
public
interface
WebmExtractor
{
@TargetApi
(
16
)
public
final
class
WebmExtractor
implements
Extractor
{
/**
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
* An attempt to read from the input stream returned insufficient data.
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
*/
private
static
final
int
UNKNOWN
=
-
1
;
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
* The end of the input stream was reached.
*/
public
static
final
int
RESULT_END_OF_STREAM
=
2
;
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
=
4
;
/**
* Initialization data was read. The parsed data can be read using {@link #getFormat()}.
*/
public
static
final
int
RESULT_READ_INIT
=
8
;
/**
* A sidx atom was read. The parsed data can be read using {@link #getIndex()}.
*/
public
static
final
int
RESULT_READ_INDEX
=
16
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
32
;
/**
// Element IDs
* Consumes data from a {@link NonBlockingInputStream}.
private
static
final
int
ID_EBML
=
0x1A45DFA3
;
*
private
static
final
int
ID_EBML_READ_VERSION
=
0x42F7
;
* @param inputStream The input stream from which data should be read
private
static
final
int
ID_DOC_TYPE
=
0x4282
;
* @param sampleHolder A {@link SampleHolder} into which the sample should be read
private
static
final
int
ID_DOC_TYPE_READ_VERSION
=
0x4285
;
* @return One or more of the {@code RESULT_*} flags defined in this class.
*/
private
static
final
int
ID_SEGMENT
=
0x18538067
;
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
);
private
static
final
int
ID_INFO
=
0x1549A966
;
private
static
final
int
ID_TIMECODE_SCALE
=
0x2AD7B1
;
private
static
final
int
ID_DURATION
=
0x4489
;
private
static
final
int
ID_CLUSTER
=
0x1F43B675
;
private
static
final
int
ID_TIME_CODE
=
0xE7
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
static
final
int
ID_TRACKS
=
0x1654AE6B
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_CODEC_ID
=
0x86
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
private
static
final
int
ID_CUES
=
0x1C53BB6B
;
private
static
final
int
ID_CUE_POINT
=
0xBB
;
private
static
final
int
ID_CUE_TIME
=
0xB3
;
private
static
final
int
ID_CUE_TRACK_POSITIONS
=
0xB7
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
// SimpleBlock Lacing Values
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_XIPH
=
1
;
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
final
EbmlReader
reader
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
sampleHolder
;
private
int
readResults
;
private
long
segmentStartOffsetBytes
=
UNKNOWN
;
private
long
segmentEndOffsetBytes
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
UNKNOWN
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
long
cuesSizeBytes
=
UNKNOWN
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
long
simpleBlockTimecodeUs
=
UNKNOWN
;
private
MediaFormat
format
;
private
SegmentIndex
cues
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
public
WebmExtractor
()
{
this
(
new
DefaultEbmlReader
());
}
/* package */
WebmExtractor
(
EbmlReader
reader
)
{
this
.
reader
=
reader
;
this
.
reader
.
setEventHandler
(
new
InnerEbmlEventHandler
());
}
@Override
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
this
.
sampleHolder
=
sampleHolder
;
this
.
readResults
=
0
;
while
((
readResults
&
READ_TERMINATING_RESULTS
)
==
0
)
{
int
ebmlReadResult
=
reader
.
read
(
inputStream
);
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_NEED_MORE_DATA
)
{
readResults
|=
WebmExtractor
.
RESULT_NEED_MORE_DATA
;
}
else
if
(
ebmlReadResult
==
EbmlReader
.
READ_RESULT_END_OF_STREAM
)
{
readResults
|=
WebmExtractor
.
RESULT_END_OF_STREAM
;
}
}
this
.
sampleHolder
=
null
;
return
readResults
;
}
@Override
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
if
(
allowNoop
&&
cues
!=
null
&&
clusterTimecodeUs
!=
UNKNOWN
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
if
(
clusterIndex
>=
0
&&
seekTimeUs
<
clusterTimecodeUs
+
cues
.
durationsUs
[
clusterIndex
])
{
return
false
;
}
}
clusterTimecodeUs
=
UNKNOWN
;
simpleBlockTimecodeUs
=
UNKNOWN
;
reader
.
reset
();
return
true
;
}
@Override
public
SegmentIndex
getIndex
()
{
return
cues
;
}
@Override
public
boolean
hasRelativeIndexOffsets
()
{
return
false
;
}
@Override
public
MediaFormat
getFormat
()
{
return
format
;
}
@Override
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
// TODO: Parse pssh data from Webm streams.
return
null
;
}
/* package */
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
case
ID_INFO:
case
ID_CLUSTER:
case
ID_TRACKS:
case
ID_TRACK_ENTRY:
case
ID_VIDEO:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
case
ID_TIMECODE_SCALE:
case
ID_TIME_CODE:
case
ID_PIXEL_WIDTH:
case
ID_PIXEL_HEIGHT:
case
ID_CUE_TIME:
case
ID_CUE_CLUSTER_POSITION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
case
ID_CODEC_ID:
return
EbmlReader
.
TYPE_STRING
;
case
ID_SIMPLE_BLOCK:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
/* package */
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentStartOffsetBytes
!=
UNKNOWN
||
segmentEndOffsetBytes
!=
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Multiple Segment elements not supported"
);
}
segmentStartOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
;
segmentEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
break
;
case
ID_CUES:
cuesSizeBytes
=
headerSizeBytes
+
contentsSizeBytes
;
cueTimesUs
=
new
LongArray
();
cueClusterPositions
=
new
LongArray
();
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onMasterElementEnd
(
int
id
)
{
switch
(
id
)
{
case
ID_CUES:
buildCues
();
return
false
;
case
ID_VIDEO:
buildFormat
();
return
true
;
default
:
return
true
;
}
}
/* package */
boolean
onIntegerElement
(
int
id
,
long
value
)
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
IllegalArgumentException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if
(
value
<
1
||
value
>
2
)
{
throw
new
IllegalArgumentException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
break
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
break
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
break
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
break
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
break
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onFloatElement
(
int
id
,
double
value
)
{
if
(
id
==
ID_DURATION
)
{
durationUs
=
scaleTimecodeToUs
((
long
)
value
);
}
return
true
;
}
/* package */
boolean
onStringElement
(
int
id
,
String
value
)
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"DocType "
+
value
+
" not supported"
);
}
break
;
case
ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9".
if
(!
CODEC_ID_VP9
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"CodecID "
+
value
+
" not supported"
);
}
break
;
default
:
// pass
}
return
true
;
}
/* package */
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
if
(
id
==
ID_SIMPLE_BLOCK
)
{
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// If we don't have a sample holder then don't consume the data.
if
(
sampleHolder
==
null
)
{
readResults
|=
RESULT_NEED_SAMPLE_HOLDER
;
return
false
;
}
// Value of trackNumber is not used but needs to be read.
reader
.
readVarint
(
inputStream
);
// Next three bytes have timecode and flags.
reader
.
readBytes
(
inputStream
,
simpleBlockTimecodeAndFlags
,
3
);
// First two bytes of the three are the relative timecode.
int
timecode
=
(
simpleBlockTimecodeAndFlags
[
0
]
<<
8
)
|
(
simpleBlockTimecodeAndFlags
[
1
]
&
0xff
);
long
timecodeUs
=
scaleTimecodeToUs
(
timecode
);
// Last byte of the three has some flags and the lacing value.
boolean
keyframe
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x80
)
==
0x80
;
boolean
invisible
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x08
)
==
0x08
;
int
lacing
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x06
)
>>
1
;
// Validate lacing and set info into sample holder.
switch
(
lacing
)
{
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
sampleHolder
.
decodeOnly
=
invisible
;
sampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
case
LACING_XIPH:
default
:
throw
new
IllegalStateException
(
"Lacing mode "
+
lacing
+
" not supported"
);
}
ByteBuffer
outputData
=
sampleHolder
.
data
;
if
(
sampleHolder
.
allowDataBufferReplacement
&&
(
sampleHolder
.
data
==
null
||
sampleHolder
.
data
.
capacity
()
<
sampleHolder
.
size
))
{
outputData
=
ByteBuffer
.
allocate
(
sampleHolder
.
size
);
sampleHolder
.
data
=
outputData
;
}
if
(
outputData
==
null
)
{
reader
.
skipBytes
(
inputStream
,
sampleHolder
.
size
);
sampleHolder
.
size
=
0
;
}
else
{
reader
.
readBytes
(
inputStream
,
outputData
,
sampleHolder
.
size
);
}
readResults
|=
RESULT_READ_SAMPLE
;
}
return
true
;
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
TimeUnit
.
NANOSECONDS
.
toMicros
(
unscaledTimecode
*
timecodeScale
);
}
/**
/**
*
Seeks to a position before or equal to the requested time
.
*
Build a video {@link MediaFormat} containing recently gathered Video information, if needed
.
*
*
* @param seekTimeUs The desired seek time in microseconds
* <p>Replaces the previous {@link #format} only if video width/height have changed.
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
* {@link #format} is guaranteed to not be null after calling this method. In
* segment, is equal to or greater than the time of the current sample, and if there does not
* the event that it can't be built, an {@link IllegalStateException} will be thrown.
* exist a sync frame between these two times
* @return True if the operation resulted in a change of state. False if it was a no-op
*/
*/
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
);
private
void
buildFormat
()
{
if
(
pixelWidth
!=
UNKNOWN
&&
pixelHeight
!=
UNKNOWN
&&
(
format
==
null
||
format
.
width
!=
pixelWidth
||
format
.
height
!=
pixelHeight
))
{
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
readResults
|=
RESULT_READ_INIT
;
}
else
if
(
format
==
null
)
{
throw
new
IllegalStateException
(
"Unable to build format"
);
}
}
/**
/**
*
Returns the cues for the media stream
.
*
Build a {@link SegmentIndex} containing recently gathered Cues information
.
*
*
*
@return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
*
<p>{@link #cues} is guaranteed to not be null after calling this method. In
*
prepared
*
the event that it can't be built, an {@link IllegalStateException} will be thrown.
*/
*/
public
SegmentIndex
getIndex
();
private
void
buildCues
()
{
if
(
segmentStartOffsetBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Segment start/end offsets unknown"
);
}
else
if
(
durationUs
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Duration unknown"
);
}
else
if
(
cuesSizeBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Cues size unknown"
);
}
else
if
(
cueTimesUs
==
null
||
cueClusterPositions
==
null
||
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
IllegalStateException
(
"Invalid/missing cue points"
);
}
int
cuePointsSize
=
cueTimesUs
.
size
();
int
[]
sizes
=
new
int
[
cuePointsSize
];
long
[]
offsets
=
new
long
[
cuePointsSize
];
long
[]
durationsUs
=
new
long
[
cuePointsSize
];
long
[]
timesUs
=
new
long
[
cuePointsSize
];
for
(
int
i
=
0
;
i
<
cuePointsSize
;
i
++)
{
timesUs
[
i
]
=
cueTimesUs
.
get
(
i
);
offsets
[
i
]
=
segmentStartOffsetBytes
+
cueClusterPositions
.
get
(
i
);
}
for
(
int
i
=
0
;
i
<
cuePointsSize
-
1
;
i
++)
{
sizes
[
i
]
=
(
int
)
(
offsets
[
i
+
1
]
-
offsets
[
i
]);
durationsUs
[
i
]
=
timesUs
[
i
+
1
]
-
timesUs
[
i
];
}
sizes
[
cuePointsSize
-
1
]
=
(
int
)
(
segmentEndOffsetBytes
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cues
=
new
SegmentIndex
((
int
)
cuesSizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
readResults
|=
RESULT_READ_INDEX
;
}
/**
/**
* Returns the format of the samples contained within the media stream.
* Passes events through to {@link WebmExtractor} as
*
* callbacks from {@link EbmlReader} are received.
* @return The sample media format, or null if the extracted is not yet prepared
*/
*/
public
MediaFormat
getFormat
();
private
final
class
InnerEbmlEventHandler
implements
EbmlEventHandler
{
@Override
public
int
getElementType
(
int
id
)
{
return
WebmExtractor
.
this
.
getElementType
(
id
);
}
@Override
public
void
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
WebmExtractor
.
this
.
onMasterElementStart
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
);
}
@Override
public
void
onMasterElementEnd
(
int
id
)
{
WebmExtractor
.
this
.
onMasterElementEnd
(
id
);
}
@Override
public
void
onIntegerElement
(
int
id
,
long
value
)
{
WebmExtractor
.
this
.
onIntegerElement
(
id
,
value
);
}
@Override
public
void
onFloatElement
(
int
id
,
double
value
)
{
WebmExtractor
.
this
.
onFloatElement
(
id
,
value
);
}
@Override
public
void
onStringElement
(
int
id
,
String
value
)
{
WebmExtractor
.
this
.
onStringElement
(
id
,
value
);
}
@Override
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
return
WebmExtractor
.
this
.
onBinaryElement
(
id
,
elementOffsetBytes
,
headerSizeBytes
,
contentsSizeBytes
,
inputStream
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
View file @
52a300f1
...
@@ -26,6 +26,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
...
@@ -26,6 +26,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.chunk.Mp4MediaChunk
;
import
com.google.android.exoplayer.parser.Extractor
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.parser.mp4.Track
;
import
com.google.android.exoplayer.parser.mp4.Track
;
import
com.google.android.exoplayer.parser.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.parser.mp4.TrackEncryptionBox
;
...
@@ -227,7 +228,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
...
@@ -227,7 +228,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
}
private
static
MediaChunk
newMediaChunk
(
Format
formatInfo
,
Uri
uri
,
String
cacheKey
,
private
static
MediaChunk
newMediaChunk
(
Format
formatInfo
,
Uri
uri
,
String
cacheKey
,
FragmentedMp4
Extractor
extractor
,
DataSource
dataSource
,
int
chunkIndex
,
Extractor
extractor
,
DataSource
dataSource
,
int
chunkIndex
,
boolean
isLast
,
long
chunkStartTimeUs
,
long
nextChunkStartTimeUs
,
int
trigger
)
{
boolean
isLast
,
long
chunkStartTimeUs
,
long
nextChunkStartTimeUs
,
int
trigger
)
{
int
nextChunkIndex
=
isLast
?
-
1
:
chunkIndex
+
1
;
int
nextChunkIndex
=
isLast
?
-
1
:
chunkIndex
+
1
;
long
nextStartTimeUs
=
isLast
?
-
1
:
nextChunkStartTimeUs
;
long
nextStartTimeUs
=
isLast
?
-
1
:
nextChunkStartTimeUs
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment