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
280ccb16
authored
Sep 08, 2014
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #43 from google/dev
Merge 1.0.13 to master
parents
79c2f535
6e9ba9ba
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
944 additions
and
1091 deletions
demo/src/main/AndroidManifest.xml
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
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/AndroidManifest.xml
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java
library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/MediaFormat.java
library/src/main/java/com/google/android/exoplayer/FormatHolder.java → library/src/main/java/com/google/android/exoplayer/MediaFormatHolder.java
library/src/main/java/com/google/android/exoplayer/SampleSource.java
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
library/src/main/java/com/google/android/exoplayer/chunk/Format.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/dash/mpd/MediaPresentationDescription.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionFetcher.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElement.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
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestFetcher.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java
library/src/main/java/com/google/android/exoplayer/parser/mp4/CodecSpecificDataUtil.java → library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java
demo/src/main/AndroidManifest.xml
View file @
280ccb16
...
@@ -16,8 +16,8 @@
...
@@ -16,8 +16,8 @@
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.google.android.exoplayer.demo"
package=
"com.google.android.exoplayer.demo"
android:versionCode=
"101
2
"
android:versionCode=
"101
3
"
android:versionName=
"1.0.1
2
"
android:versionName=
"1.0.1
3
"
android:theme=
"@style/RootTheme"
>
android:theme=
"@style/RootTheme"
>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
...
...
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
280ccb16
...
@@ -134,6 +134,8 @@ package com.google.android.exoplayer.demo;
...
@@ -134,6 +134,8 @@ package com.google.android.exoplayer.demo;
public
static
final
Sample
[]
MISC
=
new
Sample
[]
{
public
static
final
Sample
[]
MISC
=
new
Sample
[]
{
new
Sample
(
"Dizzy"
,
"uid:misc:dizzy"
,
"http://html5demos.com/assets/dizzy.mp4"
,
new
Sample
(
"Dizzy"
,
"uid:misc:dizzy"
,
"http://html5demos.com/assets/dizzy.mp4"
,
DemoUtil
.
TYPE_OTHER
,
false
,
true
),
DemoUtil
.
TYPE_OTHER
,
false
,
true
),
new
Sample
(
"Dizzy (https->http redirect)"
,
"uid:misc:dizzy2"
,
"https://goo.gl/MtUDEj"
,
DemoUtil
.
TYPE_OTHER
,
false
,
true
),
};
};
private
Samples
()
{}
private
Samples
()
{}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java
View file @
280ccb16
...
@@ -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 @
280ccb16
...
@@ -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/AndroidManifest.xml
View file @
280ccb16
...
@@ -21,6 +21,12 @@
...
@@ -21,6 +21,12 @@
<uses-permission
android:name=
"android.permission.ACCESS_NETWORK_STATE"
/>
<uses-permission
android:name=
"android.permission.ACCESS_NETWORK_STATE"
/>
<uses-permission
android:name=
"android.permission.ACCESS_WIFI_STATE"
/>
<uses-permission
android:name=
"android.permission.ACCESS_WIFI_STATE"
/>
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
/>
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
/>
<!--
Important: ExoPlayerLib specifies a minSdkVersion of 9 because various components provided by
the library may be of use on older devices. However, please note that the core video playback
functionality provided by the library requires API level 16 or greater.
-->
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"19"
/>
<uses-sdk
android:minSdkVersion=
"9"
android:targetSdkVersion=
"19"
/>
</manifest>
</manifest>
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
View file @
280ccb16
...
@@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
...
@@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo {
/**
/**
* The version of the library, expressed as a string.
* The version of the library, expressed as a string.
*/
*/
public
static
final
String
VERSION
=
"1.0.1
2
"
;
public
static
final
String
VERSION
=
"1.0.1
3
"
;
/**
/**
* The version of the library, expressed as an integer.
* The version of the library, expressed as an integer.
...
@@ -34,7 +34,7 @@ public class ExoPlayerLibraryInfo {
...
@@ -34,7 +34,7 @@ public class ExoPlayerLibraryInfo {
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 1002003.
* corresponding integer version 1002003.
*/
*/
public
static
final
int
VERSION_INT
=
100001
2
;
public
static
final
int
VERSION_INT
=
100001
3
;
/**
/**
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
...
...
library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java
View file @
280ccb16
...
@@ -112,7 +112,7 @@ public final class FrameworkSampleSource implements SampleSource {
...
@@ -112,7 +112,7 @@ public final class FrameworkSampleSource implements SampleSource {
}
}
@Override
@Override
public
int
readData
(
int
track
,
long
playbackPositionUs
,
FormatHolder
formatHolder
,
public
int
readData
(
int
track
,
long
playbackPositionUs
,
Media
FormatHolder
formatHolder
,
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
{
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
trackStates
[
track
]
!=
TRACK_STATE_DISABLED
);
Assertions
.
checkState
(
trackStates
[
track
]
!=
TRACK_STATE_DISABLED
);
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
View file @
280ccb16
...
@@ -27,6 +27,7 @@ import android.media.AudioTimestamp;
...
@@ -27,6 +27,7 @@ import android.media.AudioTimestamp;
import
android.media.AudioTrack
;
import
android.media.AudioTrack
;
import
android.media.MediaCodec
;
import
android.media.MediaCodec
;
import
android.media.MediaFormat
;
import
android.media.MediaFormat
;
import
android.media.audiofx.Virtualizer
;
import
android.os.ConditionVariable
;
import
android.os.ConditionVariable
;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.util.Log
;
import
android.util.Log
;
...
@@ -91,6 +92,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -91,6 +92,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
private
static
final
long
MICROS_PER_SECOND
=
1000000L
;
private
static
final
long
MICROS_PER_SECOND
=
1000000L
;
/**
* AudioTrack timestamps are deemed spurious if they are offset from the system clock by more
* than this amount. This is a fail safe that should not be required on correctly functioning
* devices.
*/
private
static
final
long
MAX_AUDIO_TIMSTAMP_OFFSET_US
=
10
*
MICROS_PER_SECOND
;
private
static
final
int
MAX_PLAYHEAD_OFFSET_COUNT
=
10
;
private
static
final
int
MAX_PLAYHEAD_OFFSET_COUNT
=
10
;
private
static
final
int
MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US
=
30000
;
private
static
final
int
MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US
=
30000
;
private
static
final
int
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
=
500000
;
private
static
final
int
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
=
500000
;
...
@@ -358,9 +366,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -358,9 +366,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
* subsequently re-enabled.
* subsequently re-enabled.
* <p>
* <p>
* The default implementation is a no-op. One reason for overriding this method would be to
* The default implementation is a no-op. One reason for overriding this method would be to
* instantiate and enable a {@link
android.media.audiofx.Virtualizer} in order to spatialize the
* instantiate and enable a {@link
Virtualizer} in order to spatialize the audio channels. For
*
audio channels. For this use case, any {@link android.media.audiofx.Virtualizer} instances
*
this use case, any {@link Virtualizer} instances should be released in {@link #onDisabled()}
*
should be released in {@link #onDisabled()}
(if not before).
* (if not before).
*
*
* @param audioSessionId The audio session id.
* @param audioSessionId The audio session id.
*/
*/
...
@@ -425,7 +433,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -425,7 +433,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
@Override
@Override
protected
boolean
isReady
()
{
protected
boolean
isReady
()
{
return
super
.
isReady
()
||
getPendingFrameCount
()
>
0
;
return
getPendingFrameCount
()
>
0
||
(
super
.
isReady
()
&&
getSourceState
()
==
SOURCE_STATE_READY_READ_MAY_FAIL
);
}
}
/**
/**
...
@@ -500,11 +509,18 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -500,11 +509,18 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
if
(
systemClockUs
-
lastTimestampSampleTimeUs
>=
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
)
{
if
(
systemClockUs
-
lastTimestampSampleTimeUs
>=
MIN_TIMESTAMP_SAMPLE_INTERVAL_US
)
{
audioTimestampSet
=
audioTimestampCompat
.
initTimestamp
(
audioTrack
);
audioTimestampSet
=
audioTimestampCompat
.
initTimestamp
(
audioTrack
);
if
(
audioTimestampSet
if
(
audioTimestampSet
)
{
&&
(
audioTimestampCompat
.
getNanoTime
()
/
1000
)
<
audioTrackResumeSystemTimeUs
)
{
// Perform sanity checks on the timestamp.
// The timestamp was set, but it corresponds to a time before the track was most recently
long
audioTimestampUs
=
audioTimestampCompat
.
getNanoTime
()
/
1000
;
// resumed.
if
(
audioTimestampUs
<
audioTrackResumeSystemTimeUs
)
{
audioTimestampSet
=
false
;
// The timestamp corresponds to a time before the track was most recently resumed.
audioTimestampSet
=
false
;
}
else
if
(
Math
.
abs
(
audioTimestampUs
-
systemClockUs
)
>
MAX_AUDIO_TIMSTAMP_OFFSET_US
)
{
// The timestamp time base is probably wrong.
audioTimestampSet
=
false
;
Log
.
w
(
TAG
,
"Spurious audio timestamp: "
+
audioTimestampCompat
.
getFramePosition
()
+
", "
+
audioTimestampUs
+
", "
+
systemClockUs
);
}
}
}
if
(
audioTrackGetLatencyMethod
!=
null
)
{
if
(
audioTrackGetLatencyMethod
!=
null
)
{
try
{
try
{
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java
View file @
280ccb16
...
@@ -79,6 +79,22 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -79,6 +79,22 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
/**
/**
* Value of {@link #sourceState} when the source is not ready.
*/
protected
static
final
int
SOURCE_STATE_NOT_READY
=
0
;
/**
* Value of {@link #sourceState} when the source is ready and we're able to read from it.
*/
protected
static
final
int
SOURCE_STATE_READY
=
1
;
/**
* Value of {@link #sourceState} when the source is ready but we might not be able to read from
* it. We transition to this state when an attempt to read a sample fails despite the source
* reporting that samples are available. This can occur when the next sample to be provided by
* the source is for another renderer.
*/
protected
static
final
int
SOURCE_STATE_READY_READ_MAY_FAIL
=
2
;
/**
* If the {@link MediaCodec} is hotswapped (i.e. replaced during playback), this is the period of
* If the {@link MediaCodec} is hotswapped (i.e. replaced during playback), this is the period of
* time during which {@link #isReady()} will report true regardless of whether the new codec has
* time during which {@link #isReady()} will report true regardless of whether the new codec has
* output frames that are ready to be rendered.
* output frames that are ready to be rendered.
...
@@ -108,7 +124,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -108,7 +124,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private
final
boolean
playClearSamplesWithoutKeys
;
private
final
boolean
playClearSamplesWithoutKeys
;
private
final
SampleSource
source
;
private
final
SampleSource
source
;
private
final
SampleHolder
sampleHolder
;
private
final
SampleHolder
sampleHolder
;
private
final
FormatHolder
formatHolder
;
private
final
Media
FormatHolder
formatHolder
;
private
final
HashSet
<
Long
>
decodeOnlyPresentationTimestamps
;
private
final
HashSet
<
Long
>
decodeOnlyPresentationTimestamps
;
private
final
MediaCodec
.
BufferInfo
outputBufferInfo
;
private
final
MediaCodec
.
BufferInfo
outputBufferInfo
;
private
final
EventListener
eventListener
;
private
final
EventListener
eventListener
;
...
@@ -128,7 +144,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -128,7 +144,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private
int
codecReconfigurationState
;
private
int
codecReconfigurationState
;
private
int
trackIndex
;
private
int
trackIndex
;
private
boolean
sourceIsReady
;
private
int
sourceState
;
private
boolean
inputStreamEnded
;
private
boolean
inputStreamEnded
;
private
boolean
outputStreamEnded
;
private
boolean
outputStreamEnded
;
private
boolean
waitingForKeys
;
private
boolean
waitingForKeys
;
...
@@ -158,7 +174,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -158,7 +174,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
this
.
eventListener
=
eventListener
;
this
.
eventListener
=
eventListener
;
codecCounters
=
new
CodecCounters
();
codecCounters
=
new
CodecCounters
();
sampleHolder
=
new
SampleHolder
(
false
);
sampleHolder
=
new
SampleHolder
(
false
);
formatHolder
=
new
FormatHolder
();
formatHolder
=
new
Media
FormatHolder
();
decodeOnlyPresentationTimestamps
=
new
HashSet
<
Long
>();
decodeOnlyPresentationTimestamps
=
new
HashSet
<
Long
>();
outputBufferInfo
=
new
MediaCodec
.
BufferInfo
();
outputBufferInfo
=
new
MediaCodec
.
BufferInfo
();
}
}
...
@@ -202,7 +218,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -202,7 +218,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
@Override
protected
void
onEnabled
(
long
timeUs
,
boolean
joining
)
{
protected
void
onEnabled
(
long
timeUs
,
boolean
joining
)
{
source
.
enable
(
trackIndex
,
timeUs
);
source
.
enable
(
trackIndex
,
timeUs
);
source
IsReady
=
false
;
source
State
=
SOURCE_STATE_NOT_READY
;
inputStreamEnded
=
false
;
inputStreamEnded
=
false
;
outputStreamEnded
=
false
;
outputStreamEnded
=
false
;
waitingForKeys
=
false
;
waitingForKeys
=
false
;
...
@@ -267,9 +283,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -267,9 +283,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
codecHotswapTimeMs
=
getState
()
==
TrackRenderer
.
STATE_STARTED
?
codecHotswapTimeMs
=
getState
()
==
TrackRenderer
.
STATE_STARTED
?
SystemClock
.
elapsedRealtime
()
:
-
1
;
SystemClock
.
elapsedRealtime
()
:
-
1
;
inputIndex
=
-
1
;
inputIndex
=
-
1
;
outputIndex
=
-
1
;
outputIndex
=
-
1
;
waitingForFirstSyncFrame
=
true
;
waitingForFirstSyncFrame
=
true
;
codecCounters
.
codecInitCount
++;
codecCounters
.
codecInitCount
++;
}
}
...
@@ -353,7 +369,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -353,7 +369,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
protected
void
seekTo
(
long
timeUs
)
throws
ExoPlaybackException
{
protected
void
seekTo
(
long
timeUs
)
throws
ExoPlaybackException
{
currentPositionUs
=
timeUs
;
currentPositionUs
=
timeUs
;
source
.
seekToUs
(
timeUs
);
source
.
seekToUs
(
timeUs
);
source
IsReady
=
false
;
source
State
=
SOURCE_STATE_NOT_READY
;
inputStreamEnded
=
false
;
inputStreamEnded
=
false
;
outputStreamEnded
=
false
;
outputStreamEnded
=
false
;
waitingForKeys
=
false
;
waitingForKeys
=
false
;
...
@@ -372,7 +388,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -372,7 +388,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
@Override
protected
void
doSomeWork
(
long
timeUs
)
throws
ExoPlaybackException
{
protected
void
doSomeWork
(
long
timeUs
)
throws
ExoPlaybackException
{
try
{
try
{
sourceIsReady
=
source
.
continueBuffering
(
timeUs
);
sourceState
=
source
.
continueBuffering
(
timeUs
)
?
(
sourceState
==
SOURCE_STATE_NOT_READY
?
SOURCE_STATE_READY
:
sourceState
)
:
SOURCE_STATE_NOT_READY
;
checkForDiscontinuity
();
checkForDiscontinuity
();
if
(
format
==
null
)
{
if
(
format
==
null
)
{
readFormat
();
readFormat
();
...
@@ -384,7 +402,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -384,7 +402,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
if
(
codec
!=
null
)
{
if
(
codec
!=
null
)
{
while
(
drainOutputBuffer
(
timeUs
))
{}
while
(
drainOutputBuffer
(
timeUs
))
{}
while
(
feedInputBuffer
())
{}
if
(
feedInputBuffer
(
true
))
{
while
(
feedInputBuffer
(
false
))
{}
}
}
}
}
}
codecCounters
.
ensureUpdated
();
codecCounters
.
ensureUpdated
();
...
@@ -429,6 +449,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -429,6 +449,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codecHotswapTimeMs
=
-
1
;
codecHotswapTimeMs
=
-
1
;
inputIndex
=
-
1
;
inputIndex
=
-
1
;
outputIndex
=
-
1
;
outputIndex
=
-
1
;
waitingForFirstSyncFrame
=
true
;
decodeOnlyPresentationTimestamps
.
clear
();
decodeOnlyPresentationTimestamps
.
clear
();
// Workaround for framework bugs.
// Workaround for framework bugs.
// See [redacted], [redacted], [redacted].
// See [redacted], [redacted], [redacted].
...
@@ -446,11 +467,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -446,11 +467,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
/**
/**
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws IOException If an error occurs reading data from the upstream source.
* @throws IOException If an error occurs reading data from the upstream source.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/
*/
private
boolean
feedInputBuffer
()
throws
IOException
,
ExoPlaybackException
{
private
boolean
feedInputBuffer
(
boolean
firstFeed
)
throws
IOException
,
ExoPlaybackException
{
if
(
inputStreamEnded
)
{
if
(
inputStreamEnded
)
{
return
false
;
return
false
;
}
}
...
@@ -478,6 +501,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -478,6 +501,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codecReconfigurationState
=
RECONFIGURATION_STATE_QUEUE_PENDING
;
codecReconfigurationState
=
RECONFIGURATION_STATE_QUEUE_PENDING
;
}
}
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
firstFeed
&&
sourceState
==
SOURCE_STATE_READY
&&
result
==
SampleSource
.
NOTHING_READ
)
{
sourceState
=
SOURCE_STATE_READY_READ_MAY_FAIL
;
}
}
}
if
(
result
==
SampleSource
.
NOTHING_READ
)
{
if
(
result
==
SampleSource
.
NOTHING_READ
)
{
...
@@ -594,7 +620,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -594,7 +620,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @param formatHolder Holds the new format.
* @param formatHolder Holds the new format.
* @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}.
* @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}.
*/
*/
private
void
onInputFormatChanged
(
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
private
void
onInputFormatChanged
(
Media
FormatHolder
formatHolder
)
throws
ExoPlaybackException
{
MediaFormat
oldFormat
=
format
;
MediaFormat
oldFormat
=
format
;
format
=
formatHolder
.
format
;
format
=
formatHolder
.
format
;
drmInitData
=
formatHolder
.
drmInitData
;
drmInitData
=
formatHolder
.
drmInitData
;
...
@@ -646,7 +672,17 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -646,7 +672,17 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
@Override
protected
boolean
isReady
()
{
protected
boolean
isReady
()
{
return
format
!=
null
&&
!
waitingForKeys
return
format
!=
null
&&
!
waitingForKeys
&&
(
sourceIsReady
||
outputIndex
>=
0
||
isWithinHotswapPeriod
());
&&
sourceState
!=
SOURCE_STATE_NOT_READY
||
outputIndex
>=
0
||
isWithinHotswapPeriod
();
}
/**
* Gets the source state.
*
* @return One of {@link #SOURCE_STATE_NOT_READY}, {@link #SOURCE_STATE_READY} and
* {@link #SOURCE_STATE_READY_READ_MAY_FAIL}.
*/
protected
final
int
getSourceState
()
{
return
sourceState
;
}
}
private
boolean
isWithinHotswapPeriod
()
{
private
boolean
isWithinHotswapPeriod
()
{
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
View file @
280ccb16
...
@@ -235,7 +235,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -235,7 +235,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
@Override
protected
boolean
isReady
()
{
protected
boolean
isReady
()
{
if
(
super
.
isReady
())
{
if
(
super
.
isReady
()
&&
(
renderedFirstFrame
||
!
codecInitialized
()
||
getSourceState
()
==
SOURCE_STATE_READY_READ_MAY_FAIL
))
{
// Ready. If we were joining then we've now joined, so clear the joining deadline.
// Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineUs
=
-
1
;
joiningDeadlineUs
=
-
1
;
return
true
;
return
true
;
...
...
library/src/main/java/com/google/android/exoplayer/MediaFormat.java
View file @
280ccb16
...
@@ -27,7 +27,7 @@ import java.util.Collections;
...
@@ -27,7 +27,7 @@ import java.util.Collections;
import
java.util.List
;
import
java.util.List
;
/**
/**
*
Encapsulates the information describing the format of media data, be it audio or video
.
*
Defines the format of an elementary media stream
.
*/
*/
public
class
MediaFormat
{
public
class
MediaFormat
{
...
...
library/src/main/java/com/google/android/exoplayer/FormatHolder.java
→
library/src/main/java/com/google/android/exoplayer/
Media
FormatHolder.java
View file @
280ccb16
...
@@ -21,7 +21,7 @@ import java.util.UUID;
...
@@ -21,7 +21,7 @@ import java.util.UUID;
/**
/**
* Holds a {@link MediaFormat} and corresponding drm scheme initialization data.
* Holds a {@link MediaFormat} and corresponding drm scheme initialization data.
*/
*/
public
final
class
FormatHolder
{
public
final
class
Media
FormatHolder
{
/**
/**
* The format of the media.
* The format of the media.
...
...
library/src/main/java/com/google/android/exoplayer/SampleSource.java
View file @
280ccb16
...
@@ -80,7 +80,7 @@ public interface SampleSource {
...
@@ -80,7 +80,7 @@ public interface SampleSource {
/**
/**
* Enable the specified track. This allows the track's format and samples to be read from
* Enable the specified track. This allows the track's format and samples to be read from
* {@link #readData(int, long, FormatHolder, SampleHolder, boolean)}.
* {@link #readData(int, long,
Media
FormatHolder, SampleHolder, boolean)}.
* <p>
* <p>
* This method should not be called until after the source has been successfully prepared.
* This method should not be called until after the source has been successfully prepared.
*
*
...
@@ -119,7 +119,7 @@ public interface SampleSource {
...
@@ -119,7 +119,7 @@ public interface SampleSource {
*
*
* @param track The track from which to read.
* @param track The track from which to read.
* @param playbackPositionUs The current playback position.
* @param playbackPositionUs The current playback position.
* @param formatHolder A {@link FormatHolder} object to populate in the case of a new format.
* @param formatHolder A {@link
Media
FormatHolder} object to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. If
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. If
* the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
* references a valid output buffer.
...
@@ -129,7 +129,7 @@ public interface SampleSource {
...
@@ -129,7 +129,7 @@ public interface SampleSource {
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
* @throws IOException If an error occurred reading from the source.
* @throws IOException If an error occurred reading from the source.
*/
*/
public
int
readData
(
int
track
,
long
playbackPositionUs
,
FormatHolder
formatHolder
,
public
int
readData
(
int
track
,
long
playbackPositionUs
,
Media
FormatHolder
formatHolder
,
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
;
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
;
/**
/**
...
...
library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
View file @
280ccb16
...
@@ -16,9 +16,9 @@
...
@@ -16,9 +16,9 @@
package
com
.
google
.
android
.
exoplayer
.
chunk
;
package
com
.
google
.
android
.
exoplayer
.
chunk
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.FormatHolder
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackInfo
;
import
com.google.android.exoplayer.TrackInfo
;
...
@@ -267,7 +267,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -267,7 +267,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
}
@Override
@Override
public
int
readData
(
int
track
,
long
playbackPositionUs
,
FormatHolder
formatHolder
,
public
int
readData
(
int
track
,
long
playbackPositionUs
,
Media
FormatHolder
formatHolder
,
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
{
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
{
Assertions
.
checkState
(
state
==
STATE_ENABLED
);
Assertions
.
checkState
(
state
==
STATE_ENABLED
);
Assertions
.
checkState
(
track
==
0
);
Assertions
.
checkState
(
track
==
0
);
...
@@ -318,6 +318,9 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
...
@@ -318,6 +318,9 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
}
}
if
(!
mediaChunk
.
prepare
())
{
if
(!
mediaChunk
.
prepare
())
{
if
(
currentLoadableException
!=
null
)
{
throw
currentLoadableException
;
}
return
NOTHING_READ
;
return
NOTHING_READ
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Format.java
View file @
280ccb16
...
@@ -20,7 +20,7 @@ import com.google.android.exoplayer.util.Assertions;
...
@@ -20,7 +20,7 @@ import com.google.android.exoplayer.util.Assertions;
import
java.util.Comparator
;
import
java.util.Comparator
;
/**
/**
*
A format definition for streams
.
*
Defines the high level format of a media stream
.
*/
*/
public
class
Format
{
public
class
Format
{
...
...
library/src/main/java/com/google/android/exoplayer/chunk/Mp4MediaChunk.java
View file @
280ccb16
...
@@ -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 @
79c2f535
/*
* 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 @
280ccb16
...
@@ -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 @
79c2f535
/*
* 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/dash/mpd/MediaPresentationDescription.java
View file @
280ccb16
...
@@ -23,6 +23,8 @@ import java.util.List;
...
@@ -23,6 +23,8 @@ import java.util.List;
*/
*/
public
final
class
MediaPresentationDescription
{
public
final
class
MediaPresentationDescription
{
public
final
long
availabilityStartTime
;
public
final
long
duration
;
public
final
long
duration
;
public
final
long
minBufferTime
;
public
final
long
minBufferTime
;
...
@@ -31,14 +33,22 @@ public final class MediaPresentationDescription {
...
@@ -31,14 +33,22 @@ public final class MediaPresentationDescription {
public
final
long
minUpdatePeriod
;
public
final
long
minUpdatePeriod
;
public
final
long
timeShiftBufferDepth
;
public
final
List
<
Period
>
periods
;
public
final
List
<
Period
>
periods
;
public
MediaPresentationDescription
(
long
duration
,
long
minBufferTime
,
boolean
dynamic
,
public
final
UtcTimingElement
utcTiming
;
long
minUpdatePeriod
,
List
<
Period
>
periods
)
{
public
MediaPresentationDescription
(
long
availabilityStartTime
,
long
duration
,
long
minBufferTime
,
boolean
dynamic
,
long
minUpdatePeriod
,
long
timeShiftBufferDepth
,
UtcTimingElement
utcTiming
,
List
<
Period
>
periods
)
{
this
.
availabilityStartTime
=
availabilityStartTime
;
this
.
duration
=
duration
;
this
.
duration
=
duration
;
this
.
minBufferTime
=
minBufferTime
;
this
.
minBufferTime
=
minBufferTime
;
this
.
dynamic
=
dynamic
;
this
.
dynamic
=
dynamic
;
this
.
minUpdatePeriod
=
minUpdatePeriod
;
this
.
minUpdatePeriod
=
minUpdatePeriod
;
this
.
timeShiftBufferDepth
=
timeShiftBufferDepth
;
this
.
utcTiming
=
utcTiming
;
this
.
periods
=
Collections
.
unmodifiableList
(
periods
);
this
.
periods
=
Collections
.
unmodifiableList
(
periods
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionFetcher.java
View file @
280ccb16
...
@@ -20,8 +20,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
...
@@ -20,8 +20,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import
android.net.Uri
;
import
android.net.Uri
;
import
org.xmlpull.v1.XmlPullParserException
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStream
;
...
@@ -60,11 +58,7 @@ public final class MediaPresentationDescriptionFetcher extends
...
@@ -60,11 +58,7 @@ public final class MediaPresentationDescriptionFetcher extends
@Override
@Override
protected
MediaPresentationDescription
parse
(
InputStream
stream
,
String
inputEncoding
,
protected
MediaPresentationDescription
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
try
{
return
parser
.
parseMediaPresentationDescription
(
stream
,
inputEncoding
,
contentId
,
baseUrl
);
return
parser
.
parseMediaPresentationDescription
(
stream
,
inputEncoding
,
contentId
,
baseUrl
);
}
catch
(
XmlPullParserException
e
)
{
throw
new
ParserException
(
e
);
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
View file @
280ccb16
...
@@ -34,8 +34,13 @@ import org.xmlpull.v1.XmlPullParserFactory;
...
@@ -34,8 +34,13 @@ import org.xmlpull.v1.XmlPullParserFactory;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStream
;
import
java.math.BigDecimal
;
import
java.text.ParseException
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Calendar
;
import
java.util.GregorianCalendar
;
import
java.util.List
;
import
java.util.List
;
import
java.util.TimeZone
;
import
java.util.regex.Matcher
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
java.util.regex.Pattern
;
...
@@ -48,6 +53,11 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -48,6 +53,11 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
private
static
final
Pattern
DURATION
=
private
static
final
Pattern
DURATION
=
Pattern
.
compile
(
"^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"
);
Pattern
.
compile
(
"^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"
);
private
static
final
Pattern
DATE_TIME_PATTERN
=
Pattern
.
compile
(
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
+
"(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?"
+
"([Zz]|((\\+|\\-)(\\d\\d):(\\d\\d)))?"
);
private
final
XmlPullParserFactory
xmlParserFactory
;
private
final
XmlPullParserFactory
xmlParserFactory
;
public
MediaPresentationDescriptionParser
()
{
public
MediaPresentationDescriptionParser
()
{
...
@@ -69,42 +79,57 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -69,42 +79,57 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
* @param baseUrl The url that any relative urls defined within the manifest are relative to.
* @param baseUrl The url that any relative urls defined within the manifest are relative to.
* @return The parsed manifest.
* @return The parsed manifest.
* @throws IOException If a problem occurred reading from the stream.
* @throws IOException If a problem occurred reading from the stream.
* @throws XmlPullParserException If a problem occurred parsing the stream as xml.
* @throws ParserException If a problem occurred parsing the xml as a DASH mpd.
* @throws ParserException If a problem occurred parsing the xml as a DASH mpd.
*/
*/
public
MediaPresentationDescription
parseMediaPresentationDescription
(
InputStream
inputStream
,
public
MediaPresentationDescription
parseMediaPresentationDescription
(
InputStream
inputStream
,
String
inputEncoding
,
String
contentId
,
Uri
baseUrl
)
throws
XmlPullParserException
,
String
inputEncoding
,
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
IOException
,
ParserException
{
try
{
XmlPullParser
xpp
=
xmlParserFactory
.
newPullParser
();
XmlPullParser
xpp
=
xmlParserFactory
.
newPullParser
();
xpp
.
setInput
(
inputStream
,
inputEncoding
);
xpp
.
setInput
(
inputStream
,
inputEncoding
);
int
eventType
=
xpp
.
next
();
int
eventType
=
xpp
.
next
();
if
(
eventType
!=
XmlPullParser
.
START_TAG
||
!
"MPD"
.
equals
(
xpp
.
getName
()))
{
if
(
eventType
!=
XmlPullParser
.
START_TAG
||
!
"MPD"
.
equals
(
xpp
.
getName
()))
{
throw
new
ParserException
(
throw
new
ParserException
(
"inputStream does not contain a valid media presentation description"
);
"inputStream does not contain a valid media presentation description"
);
}
return
parseMediaPresentationDescription
(
xpp
,
contentId
,
baseUrl
);
}
catch
(
XmlPullParserException
e
)
{
throw
new
ParserException
(
e
);
}
catch
(
ParseException
e
)
{
throw
new
ParserException
(
e
);
}
}
return
parseMediaPresentationDescription
(
xpp
,
contentId
,
baseUrl
);
}
}
private
MediaPresentationDescription
parseMediaPresentationDescription
(
XmlPullParser
xpp
,
private
MediaPresentationDescription
parseMediaPresentationDescription
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
)
throws
XmlPullParserException
,
IOException
{
String
contentId
,
Uri
baseUrl
)
throws
XmlPullParserException
,
IOException
,
ParseException
{
long
availabilityStartTime
=
parseDateTime
(
xpp
,
"availabilityStartTime"
,
-
1
);
long
durationMs
=
parseDurationMs
(
xpp
,
"mediaPresentationDuration"
);
long
durationMs
=
parseDurationMs
(
xpp
,
"mediaPresentationDuration"
);
long
minBufferTimeMs
=
parseDurationMs
(
xpp
,
"minBufferTime"
);
long
minBufferTimeMs
=
parseDurationMs
(
xpp
,
"minBufferTime"
);
String
typeString
=
xpp
.
getAttributeValue
(
null
,
"type"
);
String
typeString
=
xpp
.
getAttributeValue
(
null
,
"type"
);
boolean
dynamic
=
(
typeString
!=
null
)
?
typeString
.
equals
(
"dynamic"
)
:
false
;
boolean
dynamic
=
(
typeString
!=
null
)
?
typeString
.
equals
(
"dynamic"
)
:
false
;
long
minUpdateTimeMs
=
(
dynamic
)
?
parseDurationMs
(
xpp
,
"minimumUpdatePeriod"
,
-
1
)
:
-
1
;
long
minUpdateTimeMs
=
(
dynamic
)
?
parseDurationMs
(
xpp
,
"minimumUpdatePeriod"
,
-
1
)
:
-
1
;
long
timeShiftBufferDepthMs
=
(
dynamic
)
?
parseDurationMs
(
xpp
,
"timeShiftBufferDepth"
,
-
1
)
:
-
1
;
UtcTimingElement
utcTiming
=
null
;
List
<
Period
>
periods
=
new
ArrayList
<
Period
>();
List
<
Period
>
periods
=
new
ArrayList
<
Period
>();
do
{
do
{
xpp
.
next
();
xpp
.
next
();
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
if
(
isStartTag
(
xpp
,
"BaseURL"
))
{
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
baseUrl
=
parseBaseUrl
(
xpp
,
baseUrl
);
}
else
if
(
isStartTag
(
xpp
,
"UTCTiming"
))
{
utcTiming
=
parseUtcTiming
(
xpp
);
}
else
if
(
isStartTag
(
xpp
,
"Period"
))
{
}
else
if
(
isStartTag
(
xpp
,
"Period"
))
{
periods
.
add
(
parsePeriod
(
xpp
,
contentId
,
baseUrl
,
durationMs
));
periods
.
add
(
parsePeriod
(
xpp
,
contentId
,
baseUrl
,
durationMs
));
}
}
}
while
(!
isEndTag
(
xpp
,
"MPD"
));
}
while
(!
isEndTag
(
xpp
,
"MPD"
));
return
new
MediaPresentationDescription
(
durationMs
,
minBufferTimeMs
,
dynamic
,
minUpdateTimeMs
,
return
new
MediaPresentationDescription
(
availabilityStartTime
,
durationMs
,
minBufferTimeMs
,
periods
);
dynamic
,
minUpdateTimeMs
,
timeShiftBufferDepthMs
,
utcTiming
,
periods
);
}
private
UtcTimingElement
parseUtcTiming
(
XmlPullParser
xpp
)
{
String
schemeIdUri
=
xpp
.
getAttributeValue
(
null
,
"schemeIdUri"
);
String
value
=
xpp
.
getAttributeValue
(
null
,
"value"
);
return
new
UtcTimingElement
(
schemeIdUri
,
value
);
}
}
private
Period
parsePeriod
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
long
mpdDurationMs
)
private
Period
parsePeriod
(
XmlPullParser
xpp
,
String
contentId
,
Uri
baseUrl
,
long
mpdDurationMs
)
...
@@ -429,6 +454,62 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
...
@@ -429,6 +454,62 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return
parseDurationMs
(
xpp
,
name
,
-
1
);
return
parseDurationMs
(
xpp
,
name
,
-
1
);
}
}
private
static
long
parseDateTime
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
throws
ParseException
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
if
(
value
==
null
)
{
return
defaultValue
;
}
else
{
return
parseDateTime
(
value
);
}
}
// VisibleForTesting
static
long
parseDateTime
(
String
value
)
throws
ParseException
{
Matcher
matcher
=
DATE_TIME_PATTERN
.
matcher
(
value
);
if
(!
matcher
.
matches
())
{
throw
new
ParseException
(
"Invalid date/time format: "
+
value
,
0
);
}
int
timezoneShift
;
if
(
matcher
.
group
(
9
)
==
null
)
{
// No time zone specified.
timezoneShift
=
0
;
}
else
if
(
matcher
.
group
(
9
).
equalsIgnoreCase
(
"Z"
))
{
timezoneShift
=
0
;
}
else
{
timezoneShift
=
((
Integer
.
valueOf
(
matcher
.
group
(
12
))
*
60
+
Integer
.
valueOf
(
matcher
.
group
(
13
))));
if
(
matcher
.
group
(
11
).
equals
(
"-"
))
{
timezoneShift
*=
-
1
;
}
}
Calendar
dateTime
=
new
GregorianCalendar
(
TimeZone
.
getTimeZone
(
"GMT"
));
dateTime
.
clear
();
// Note: The month value is 0-based, hence the -1 on group(2)
dateTime
.
set
(
Integer
.
valueOf
(
matcher
.
group
(
1
)),
Integer
.
valueOf
(
matcher
.
group
(
2
))
-
1
,
Integer
.
valueOf
(
matcher
.
group
(
3
)),
Integer
.
valueOf
(
matcher
.
group
(
4
)),
Integer
.
valueOf
(
matcher
.
group
(
5
)),
Integer
.
valueOf
(
matcher
.
group
(
6
)));
if
(!
TextUtils
.
isEmpty
(
matcher
.
group
(
8
)))
{
final
BigDecimal
bd
=
new
BigDecimal
(
"0."
+
matcher
.
group
(
8
));
// we care only for milliseconds, so movePointRight(3)
dateTime
.
set
(
Calendar
.
MILLISECOND
,
bd
.
movePointRight
(
3
).
intValue
());
}
long
time
=
dateTime
.
getTimeInMillis
();
if
(
timezoneShift
!=
0
)
{
time
-=
timezoneShift
*
60000
;
}
return
time
;
}
private
static
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
private
static
long
parseDurationMs
(
XmlPullParser
xpp
,
String
name
,
long
defaultValue
)
{
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
String
value
=
xpp
.
getAttributeValue
(
null
,
name
);
if
(
value
!=
null
)
{
if
(
value
!=
null
)
{
...
...
library/src/main/java/com/google/android/exoplayer/dash/mpd/UtcTimingElement.java
0 → 100644
View file @
280ccb16
/*
* 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
.
mpd
;
/**
* Represents a UTCTiming element.
*/
public
class
UtcTimingElement
{
public
final
String
schemeIdUri
;
public
final
String
value
;
public
UtcTimingElement
(
String
schemeIdUri
,
String
value
)
{
this
.
schemeIdUri
=
schemeIdUri
;
this
.
value
=
value
;
}
}
library/src/main/java/com/google/android/exoplayer/parser/Extractor.java
0 → 100644
View file @
280ccb16
/*
* 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 @
280ccb16
...
@@ -18,11 +18,13 @@ package com.google.android.exoplayer.parser.mp4;
...
@@ -18,11 +18,13 @@ 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
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.SuppressLint
;
import
android.annotation.SuppressLint
;
...
@@ -47,7 +49,7 @@ import java.util.UUID;
...
@@ -47,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.
...
@@ -58,32 +60,6 @@ public final class FragmentedMp4Extractor {
...
@@ -58,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
};
...
@@ -196,22 +172,13 @@ public final class FragmentedMp4Extractor {
...
@@ -196,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
;
}
}
/**
/**
...
@@ -228,38 +195,27 @@ public final class FragmentedMp4Extractor {
...
@@ -228,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
{
...
@@ -286,15 +242,7 @@ public final class FragmentedMp4Extractor {
...
@@ -286,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
...
@@ -780,10 +728,10 @@ public final class FragmentedMp4Extractor {
...
@@ -780,10 +728,10 @@ public final class FragmentedMp4Extractor {
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
data
,
out
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
data
,
out
);
TrackEncryptionBox
trackEncryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentHeader
.
sampleDescriptionIndex
];
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
if
(
saiz
!=
null
)
{
if
(
saiz
!=
null
)
{
TrackEncryptionBox
trackEncryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentHeader
.
sampleDescriptionIndex
];
parseSaiz
(
trackEncryptionBox
,
saiz
.
data
,
out
);
parseSaiz
(
trackEncryptionBox
,
saiz
.
data
,
out
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
deleted
100644 → 0
View file @
79c2f535
/*
* 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 @
280ccb16
...
@@ -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 @
280ccb16
...
@@ -26,7 +26,7 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
...
@@ -26,7 +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.
mp4.CodecSpecificDataUtil
;
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
;
...
@@ -35,6 +35,7 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Stre
...
@@ -35,6 +35,7 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Stre
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
;
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.util.CodecSpecificDataUtil
;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.util.Base64
;
import
android.util.Base64
;
...
@@ -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
;
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestFetcher.java
View file @
280ccb16
...
@@ -20,8 +20,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
...
@@ -20,8 +20,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import
android.net.Uri
;
import
android.net.Uri
;
import
org.xmlpull.v1.XmlPullParserException
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStream
;
...
@@ -59,11 +57,7 @@ public final class SmoothStreamingManifestFetcher extends ManifestFetcher<Smooth
...
@@ -59,11 +57,7 @@ public final class SmoothStreamingManifestFetcher extends ManifestFetcher<Smooth
@Override
@Override
protected
SmoothStreamingManifest
parse
(
InputStream
stream
,
String
inputEncoding
,
protected
SmoothStreamingManifest
parse
(
InputStream
stream
,
String
inputEncoding
,
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
String
contentId
,
Uri
baseUrl
)
throws
IOException
,
ParserException
{
try
{
return
parser
.
parse
(
stream
,
inputEncoding
);
return
parser
.
parse
(
stream
,
inputEncoding
);
}
catch
(
XmlPullParserException
e
)
{
throw
new
ParserException
(
e
);
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
View file @
280ccb16
...
@@ -16,11 +16,11 @@
...
@@ -16,11 +16,11 @@
package
com
.
google
.
android
.
exoplayer
.
smoothstreaming
;
package
com
.
google
.
android
.
exoplayer
.
smoothstreaming
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.parser.mp4.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
android.util.Base64
;
import
android.util.Base64
;
import
android.util.Pair
;
import
android.util.Pair
;
...
@@ -60,15 +60,18 @@ public class SmoothStreamingManifestParser {
...
@@ -60,15 +60,18 @@ public class SmoothStreamingManifestParser {
* @param inputEncoding The encoding of the input.
* @param inputEncoding The encoding of the input.
* @return The parsed manifest.
* @return The parsed manifest.
* @throws IOException If a problem occurred reading from the stream.
* @throws IOException If a problem occurred reading from the stream.
* @throws XmlPullParserException If a problem occurred parsing the stream as xml.
* @throws ParserException If a problem occurred parsing the xml as a smooth streaming manifest.
* @throws ParserException If a problem occurred parsing the xml as a smooth streaming manifest.
*/
*/
public
SmoothStreamingManifest
parse
(
InputStream
inputStream
,
String
inputEncoding
)
throws
public
SmoothStreamingManifest
parse
(
InputStream
inputStream
,
String
inputEncoding
)
throws
XmlPullParserException
,
IOException
,
ParserException
{
IOException
,
ParserException
{
XmlPullParser
xmlParser
=
xmlParserFactory
.
newPullParser
();
try
{
xmlParser
.
setInput
(
inputStream
,
inputEncoding
);
XmlPullParser
xmlParser
=
xmlParserFactory
.
newPullParser
();
SmoothStreamMediaParser
smoothStreamMediaParser
=
new
SmoothStreamMediaParser
(
null
);
xmlParser
.
setInput
(
inputStream
,
inputEncoding
);
return
(
SmoothStreamingManifest
)
smoothStreamMediaParser
.
parse
(
xmlParser
);
SmoothStreamMediaParser
smoothStreamMediaParser
=
new
SmoothStreamMediaParser
(
null
);
return
(
SmoothStreamingManifest
)
smoothStreamMediaParser
.
parse
(
xmlParser
);
}
catch
(
XmlPullParserException
e
)
{
throw
new
ParserException
(
e
);
}
}
}
/**
/**
...
@@ -102,6 +105,7 @@ public class SmoothStreamingManifestParser {
...
@@ -102,6 +105,7 @@ public class SmoothStreamingManifestParser {
ParserException
{
ParserException
{
String
tagName
;
String
tagName
;
boolean
foundStartTag
=
false
;
boolean
foundStartTag
=
false
;
int
skippingElementDepth
=
0
;
while
(
true
)
{
while
(
true
)
{
int
eventType
=
xmlParser
.
getEventType
();
int
eventType
=
xmlParser
.
getEventType
();
switch
(
eventType
)
{
switch
(
eventType
)
{
...
@@ -111,24 +115,35 @@ public class SmoothStreamingManifestParser {
...
@@ -111,24 +115,35 @@ public class SmoothStreamingManifestParser {
foundStartTag
=
true
;
foundStartTag
=
true
;
parseStartTag
(
xmlParser
);
parseStartTag
(
xmlParser
);
}
else
if
(
foundStartTag
)
{
}
else
if
(
foundStartTag
)
{
if
(
handleChildInline
(
tagName
))
{
if
(
skippingElementDepth
>
0
)
{
skippingElementDepth
++;
}
else
if
(
handleChildInline
(
tagName
))
{
parseStartTag
(
xmlParser
);
parseStartTag
(
xmlParser
);
}
else
{
}
else
{
addChild
(
newChildParser
(
this
,
tagName
).
parse
(
xmlParser
));
ElementParser
childElementParser
=
newChildParser
(
this
,
tagName
);
if
(
childElementParser
==
null
)
{
skippingElementDepth
=
1
;
}
else
{
addChild
(
childElementParser
.
parse
(
xmlParser
));
}
}
}
}
}
break
;
break
;
case
XmlPullParser
.
TEXT
:
case
XmlPullParser
.
TEXT
:
if
(
foundStartTag
)
{
if
(
foundStartTag
&&
skippingElementDepth
==
0
)
{
parseText
(
xmlParser
);
parseText
(
xmlParser
);
}
}
break
;
break
;
case
XmlPullParser
.
END_TAG
:
case
XmlPullParser
.
END_TAG
:
if
(
foundStartTag
)
{
if
(
foundStartTag
)
{
tagName
=
xmlParser
.
getName
();
if
(
skippingElementDepth
>
0
)
{
parseEndTag
(
xmlParser
);
skippingElementDepth
--;
if
(!
handleChildInline
(
tagName
))
{
}
else
{
return
build
();
tagName
=
xmlParser
.
getName
();
parseEndTag
(
xmlParser
);
if
(!
handleChildInline
(
tagName
))
{
return
build
();
}
}
}
}
}
break
;
break
;
...
@@ -357,6 +372,7 @@ public class SmoothStreamingManifestParser {
...
@@ -357,6 +372,7 @@ public class SmoothStreamingManifestParser {
public
static
final
String
KEY_SYSTEM_ID
=
"SystemID"
;
public
static
final
String
KEY_SYSTEM_ID
=
"SystemID"
;
private
boolean
inProtectionHeader
;
private
UUID
uuid
;
private
UUID
uuid
;
private
byte
[]
initData
;
private
byte
[]
initData
;
...
@@ -371,16 +387,25 @@ public class SmoothStreamingManifestParser {
...
@@ -371,16 +387,25 @@ public class SmoothStreamingManifestParser {
@Override
@Override
public
void
parseStartTag
(
XmlPullParser
parser
)
{
public
void
parseStartTag
(
XmlPullParser
parser
)
{
if
(!
TAG_PROTECTION_HEADER
.
equals
(
parser
.
getName
()))
{
if
(
TAG_PROTECTION_HEADER
.
equals
(
parser
.
getName
()))
{
return
;
inProtectionHeader
=
true
;
String
uuidString
=
parser
.
getAttributeValue
(
null
,
KEY_SYSTEM_ID
);
uuid
=
UUID
.
fromString
(
uuidString
);
}
}
String
uuidString
=
parser
.
getAttributeValue
(
null
,
KEY_SYSTEM_ID
);
uuid
=
UUID
.
fromString
(
uuidString
);
}
}
@Override
@Override
public
void
parseText
(
XmlPullParser
parser
)
{
public
void
parseText
(
XmlPullParser
parser
)
{
initData
=
Base64
.
decode
(
parser
.
getText
(),
Base64
.
DEFAULT
);
if
(
inProtectionHeader
)
{
initData
=
Base64
.
decode
(
parser
.
getText
(),
Base64
.
DEFAULT
);
}
}
@Override
public
void
parseEndTag
(
XmlPullParser
parser
)
{
if
(
TAG_PROTECTION_HEADER
.
equals
(
parser
.
getName
()))
{
inProtectionHeader
=
false
;
}
}
}
@Override
@Override
...
@@ -579,9 +604,11 @@ public class SmoothStreamingManifestParser {
...
@@ -579,9 +604,11 @@ public class SmoothStreamingManifestParser {
if
(
type
==
StreamElement
.
TYPE_VIDEO
)
{
if
(
type
==
StreamElement
.
TYPE_VIDEO
)
{
maxHeight
=
parseRequiredInt
(
parser
,
KEY_MAX_HEIGHT
);
maxHeight
=
parseRequiredInt
(
parser
,
KEY_MAX_HEIGHT
);
maxWidth
=
parseRequiredInt
(
parser
,
KEY_MAX_WIDTH
);
maxWidth
=
parseRequiredInt
(
parser
,
KEY_MAX_WIDTH
);
fourCC
=
parseRequiredString
(
parser
,
KEY_FOUR_CC
);
}
else
{
}
else
{
maxHeight
=
-
1
;
maxHeight
=
-
1
;
maxWidth
=
-
1
;
maxWidth
=
-
1
;
fourCC
=
parser
.
getAttributeValue
(
null
,
KEY_FOUR_CC
);
}
}
if
(
type
==
StreamElement
.
TYPE_AUDIO
)
{
if
(
type
==
StreamElement
.
TYPE_AUDIO
)
{
...
@@ -590,14 +617,12 @@ public class SmoothStreamingManifestParser {
...
@@ -590,14 +617,12 @@ public class SmoothStreamingManifestParser {
bitPerSample
=
parseRequiredInt
(
parser
,
KEY_BITS_PER_SAMPLE
);
bitPerSample
=
parseRequiredInt
(
parser
,
KEY_BITS_PER_SAMPLE
);
packetSize
=
parseRequiredInt
(
parser
,
KEY_PACKET_SIZE
);
packetSize
=
parseRequiredInt
(
parser
,
KEY_PACKET_SIZE
);
audioTag
=
parseRequiredInt
(
parser
,
KEY_AUDIO_TAG
);
audioTag
=
parseRequiredInt
(
parser
,
KEY_AUDIO_TAG
);
fourCC
=
parseRequiredString
(
parser
,
KEY_FOUR_CC
);
}
else
{
}
else
{
samplingRate
=
-
1
;
samplingRate
=
-
1
;
channels
=
-
1
;
channels
=
-
1
;
bitPerSample
=
-
1
;
bitPerSample
=
-
1
;
packetSize
=
-
1
;
packetSize
=
-
1
;
audioTag
=
-
1
;
audioTag
=
-
1
;
fourCC
=
parser
.
getAttributeValue
(
null
,
KEY_FOUR_CC
);
}
}
value
=
parser
.
getAttributeValue
(
null
,
KEY_CODEC_PRIVATE_DATA
);
value
=
parser
.
getAttributeValue
(
null
,
KEY_CODEC_PRIVATE_DATA
);
...
...
library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
View file @
280ccb16
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
text
;
package
com
.
google
.
android
.
exoplayer
.
text
;
import
com.google.android.exoplayer.ExoPlaybackException
;
import
com.google.android.exoplayer.ExoPlaybackException
;
import
com.google.android.exoplayer.FormatHolder
;
import
com.google.android.exoplayer.
Media
FormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
...
@@ -64,7 +64,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
...
@@ -64,7 +64,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
private
final
TextRenderer
textRenderer
;
private
final
TextRenderer
textRenderer
;
private
final
SampleSource
source
;
private
final
SampleSource
source
;
private
final
SampleHolder
sampleHolder
;
private
final
SampleHolder
sampleHolder
;
private
final
FormatHolder
formatHolder
;
private
final
Media
FormatHolder
formatHolder
;
private
final
SubtitleParser
subtitleParser
;
private
final
SubtitleParser
subtitleParser
;
private
int
trackIndex
;
private
int
trackIndex
;
...
@@ -93,7 +93,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
...
@@ -93,7 +93,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
this
.
textRenderer
=
Assertions
.
checkNotNull
(
textRenderer
);
this
.
textRenderer
=
Assertions
.
checkNotNull
(
textRenderer
);
this
.
textRendererHandler
=
textRendererLooper
==
null
?
null
:
new
Handler
(
textRendererLooper
,
this
.
textRendererHandler
=
textRendererLooper
==
null
?
null
:
new
Handler
(
textRendererLooper
,
this
);
this
);
formatHolder
=
new
FormatHolder
();
formatHolder
=
new
Media
FormatHolder
();
sampleHolder
=
new
SampleHolder
(
true
);
sampleHolder
=
new
SampleHolder
(
true
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java
View file @
280ccb16
...
@@ -41,10 +41,10 @@ public final class CacheDataSource implements DataSource {
...
@@ -41,10 +41,10 @@ public final class CacheDataSource implements DataSource {
public
interface
EventListener
{
public
interface
EventListener
{
/**
/**
* Invoked when bytes have been read from
{@link #cache} since the last invocation
.
* Invoked when bytes have been read from
the cache
.
*
*
* @param cacheSizeBytes Current cache size in bytes.
* @param cacheSizeBytes Current cache size in bytes.
* @param cachedBytesRead Total bytes read from
{@link #cache} since last report
.
* @param cachedBytesRead Total bytes read from
the cache since this method was last invoked
.
*/
*/
void
onCachedBytesRead
(
long
cacheSizeBytes
,
long
cachedBytesRead
);
void
onCachedBytesRead
(
long
cacheSizeBytes
,
long
cachedBytesRead
);
...
...
library/src/main/java/com/google/android/exoplayer/
parser/mp4
/CodecSpecificDataUtil.java
→
library/src/main/java/com/google/android/exoplayer/
util
/CodecSpecificDataUtil.java
View file @
280ccb16
...
@@ -13,9 +13,7 @@
...
@@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and
* See the License for the specific language governing permissions and
* limitations under the License.
* limitations under the License.
*/
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
util
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.annotation.SuppressLint
;
import
android.annotation.SuppressLint
;
import
android.media.MediaCodecInfo.CodecProfileLevel
;
import
android.media.MediaCodecInfo.CodecProfileLevel
;
...
@@ -35,6 +33,10 @@ public final class CodecSpecificDataUtil {
...
@@ -35,6 +33,10 @@ public final class CodecSpecificDataUtil {
96000
,
88200
,
64000
,
48000
,
44100
,
32000
,
24000
,
22050
,
16000
,
12000
,
11025
,
8000
,
7350
96000
,
88200
,
64000
,
48000
,
44100
,
32000
,
24000
,
22050
,
16000
,
12000
,
11025
,
8000
,
7350
};
};
private
static
final
int
[]
AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE
=
new
int
[]
{
0
,
1
,
2
,
3
,
4
,
5
,
6
,
8
};
private
static
final
int
SPS_NAL_UNIT_TYPE
=
7
;
private
static
final
int
SPS_NAL_UNIT_TYPE
=
7
;
private
CodecSpecificDataUtil
()
{}
private
CodecSpecificDataUtil
()
{}
...
@@ -42,7 +44,7 @@ public final class CodecSpecificDataUtil {
...
@@ -42,7 +44,7 @@ public final class CodecSpecificDataUtil {
/**
/**
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
*
* @param audioSpecificConfig
* @param audioSpecificConfig
The AudioSpecificConfig to parse.
* @return A pair consisting of the sample rate in Hz and the channel count.
* @return A pair consisting of the sample rate in Hz and the channel count.
*/
*/
public
static
Pair
<
Integer
,
Integer
>
parseAudioSpecificConfig
(
byte
[]
audioSpecificConfig
)
{
public
static
Pair
<
Integer
,
Integer
>
parseAudioSpecificConfig
(
byte
[]
audioSpecificConfig
)
{
...
@@ -57,10 +59,26 @@ public final class CodecSpecificDataUtil {
...
@@ -57,10 +59,26 @@ public final class CodecSpecificDataUtil {
}
}
/**
/**
* Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
* @param audioObjectType The audio object type.
* @param sampleRateIndex The sample rate index.
* @param channelConfig The channel configuration.
* @return The AudioSpecificConfig.
*/
public
static
byte
[]
buildAudioSpecificConfig
(
int
audioObjectType
,
int
sampleRateIndex
,
int
channelConfig
)
{
byte
[]
audioSpecificConfig
=
new
byte
[
2
];
audioSpecificConfig
[
0
]
=
(
byte
)
((
audioObjectType
<<
3
)
&
0xF8
|
(
sampleRateIndex
>>
1
)
&
0x07
);
audioSpecificConfig
[
1
]
=
(
byte
)
((
sampleRateIndex
<<
7
)
&
0x80
|
(
channelConfig
<<
3
)
&
0x78
);
return
audioSpecificConfig
;
}
/**
* Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
* Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
*
* @param sampleRate The sample rate in Hz.
* @param sampleRate The sample rate in Hz.
* @param numChannels The number of channels
* @param numChannels The number of channels
.
* @return The AudioSpecificConfig.
* @return The AudioSpecificConfig.
*/
*/
public
static
byte
[]
buildAudioSpecificConfig
(
int
sampleRate
,
int
numChannels
)
{
public
static
byte
[]
buildAudioSpecificConfig
(
int
sampleRate
,
int
numChannels
)
{
...
@@ -70,10 +88,16 @@ public final class CodecSpecificDataUtil {
...
@@ -70,10 +88,16 @@ public final class CodecSpecificDataUtil {
sampleRateIndex
=
i
;
sampleRateIndex
=
i
;
}
}
}
}
int
channelConfig
=
-
1
;
for
(
int
i
=
0
;
i
<
AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE
.
length
;
++
i
)
{
if
(
numChannels
==
AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE
[
i
])
{
channelConfig
=
i
;
}
}
// The full specification for AudioSpecificConfig is stated in ISO 14496-3 Section 1.6.2.1
// The full specification for AudioSpecificConfig is stated in ISO 14496-3 Section 1.6.2.1
byte
[]
csd
=
new
byte
[
2
];
byte
[]
csd
=
new
byte
[
2
];
csd
[
0
]
=
(
byte
)
((
2
/* AAC LC */
<<
3
)
|
(
sampleRateIndex
>>
1
));
csd
[
0
]
=
(
byte
)
((
2
/* AAC LC */
<<
3
)
|
(
sampleRateIndex
>>
1
));
csd
[
1
]
=
(
byte
)
(((
sampleRateIndex
&
0x1
)
<<
7
)
|
(
numChannels
<<
3
));
csd
[
1
]
=
(
byte
)
(((
sampleRateIndex
&
0x1
)
<<
7
)
|
(
channelConfig
<<
3
));
return
csd
;
return
csd
;
}
}
...
...
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