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
d64036c5
authored
Oct 01, 2014
by
Andrey Udovenko
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add basic HLS support (VOD and Live) with EXT-X-DISCONTINUITY.
parent
dd30632a
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
2588 additions
and
0 deletions
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunk.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkOperationHolder.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParser.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParser.java
library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java
library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
library/src/main/java/com/google/android/exoplayer/parser/ts/BitsArray.java
library/src/main/java/com/google/android/exoplayer/parser/ts/TsExtractor.java
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
View file @
d64036c5
...
@@ -47,6 +47,8 @@ public class DemoUtil {
...
@@ -47,6 +47,8 @@ public class DemoUtil {
public
static
final
int
TYPE_DASH_VOD
=
0
;
public
static
final
int
TYPE_DASH_VOD
=
0
;
public
static
final
int
TYPE_SS
=
1
;
public
static
final
int
TYPE_SS
=
1
;
public
static
final
int
TYPE_OTHER
=
2
;
public
static
final
int
TYPE_OTHER
=
2
;
public
static
final
int
TYPE_HLS_MASTER
=
3
;
public
static
final
int
TYPE_HLS_MEDIA
=
4
;
public
static
final
boolean
EXPOSE_EXPERIMENTAL_FEATURES
=
false
;
public
static
final
boolean
EXPOSE_EXPERIMENTAL_FEATURES
=
false
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
View file @
d64036c5
...
@@ -58,6 +58,8 @@ public class SampleChooserActivity extends Activity {
...
@@ -58,6 +58,8 @@ public class SampleChooserActivity extends Activity {
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SMOOTHSTREAMING
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
SMOOTHSTREAMING
);
sampleAdapter
.
add
(
new
Header
(
"Misc"
));
sampleAdapter
.
add
(
new
Header
(
"Misc"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
MISC
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
MISC
);
sampleAdapter
.
add
(
new
Header
(
"HLS"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
HLS
);
if
(
DemoUtil
.
EXPOSE_EXPERIMENTAL_FEATURES
)
{
if
(
DemoUtil
.
EXPOSE_EXPERIMENTAL_FEATURES
)
{
sampleAdapter
.
add
(
new
Header
(
"YouTube WebM DASH (Experimental)"
));
sampleAdapter
.
add
(
new
Header
(
"YouTube WebM DASH (Experimental)"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_WEBM
);
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_WEBM
);
...
...
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
d64036c5
...
@@ -131,6 +131,15 @@ package com.google.android.exoplayer.demo;
...
@@ -131,6 +131,15 @@ package com.google.android.exoplayer.demo;
+
"22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0"
,
DemoUtil
.
TYPE_DASH_VOD
,
true
,
true
),
+
"22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0"
,
DemoUtil
.
TYPE_DASH_VOD
,
true
,
true
),
};
};
public
static
final
Sample
[]
HLS
=
new
Sample
[]
{
new
Sample
(
"Apple master playlist"
,
"uid:hls:applemaster"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+
"bipbop_4x3_variant.m3u8"
,
DemoUtil
.
TYPE_HLS_MASTER
,
false
,
true
),
new
Sample
(
"Apple single media playlist"
,
"uid:hls:applesinglemedia"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
+
"prog_index.m3u8"
,
DemoUtil
.
TYPE_HLS_MEDIA
,
false
,
true
),
};
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
),
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
View file @
d64036c5
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer.demo.full.player.DashVodRendererBuilder;
...
@@ -23,6 +23,7 @@ import com.google.android.exoplayer.demo.full.player.DashVodRendererBuilder;
import
com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.HlsRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.text.CaptionStyleCompat
;
import
com.google.android.exoplayer.text.CaptionStyleCompat
;
import
com.google.android.exoplayer.text.SubtitleView
;
import
com.google.android.exoplayer.text.SubtitleView
;
...
@@ -173,6 +174,12 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
...
@@ -173,6 +174,12 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
case
DemoUtil
.
TYPE_DASH_VOD
:
case
DemoUtil
.
TYPE_DASH_VOD
:
return
new
DashVodRendererBuilder
(
userAgent
,
contentUri
.
toString
(),
contentId
,
return
new
DashVodRendererBuilder
(
userAgent
,
contentUri
.
toString
(),
contentId
,
new
WidevineTestMediaDrmCallback
(
contentId
),
debugTextView
);
new
WidevineTestMediaDrmCallback
(
contentId
),
debugTextView
);
case
DemoUtil
.
TYPE_HLS_MASTER
:
return
new
HlsRendererBuilder
(
userAgent
,
contentUri
.
toString
(),
contentId
,
HlsRendererBuilder
.
TYPE_MASTER
);
case
DemoUtil
.
TYPE_HLS_MEDIA
:
return
new
HlsRendererBuilder
(
userAgent
,
contentUri
.
toString
(),
contentId
,
HlsRendererBuilder
.
TYPE_MEDIA
);
default
:
default
:
return
new
DefaultRendererBuilder
(
this
,
contentUri
,
debugTextView
);
return
new
DefaultRendererBuilder
(
this
,
contentUri
,
debugTextView
);
}
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
0 → 100644
View file @
d64036c5
/*
* 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
.
demo
.
full
.
player
;
import
com.google.android.exoplayer.DefaultLoadControl
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.hls.HlsChunkSource
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylistParser
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.HttpDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
import
android.media.MediaCodec
;
import
android.net.Uri
;
import
java.io.IOException
;
import
java.util.Collections
;
/**
* A {@link RendererBuilder} for HLS.
*/
public
class
HlsRendererBuilder
implements
RendererBuilder
,
ManifestCallback
<
HlsMasterPlaylist
>
{
public
static
final
int
TYPE_MASTER
=
0
;
public
static
final
int
TYPE_MEDIA
=
1
;
private
static
final
int
BUFFER_SEGMENT_SIZE
=
64
*
1024
;
private
static
final
int
VIDEO_BUFFER_SEGMENTS
=
200
;
private
final
String
userAgent
;
private
final
String
url
;
private
final
String
contentId
;
private
final
int
playlistType
;
private
DemoPlayer
player
;
private
RendererBuilderCallback
callback
;
public
HlsRendererBuilder
(
String
userAgent
,
String
url
,
String
contentId
,
int
playlistType
)
{
this
.
userAgent
=
userAgent
;
this
.
url
=
url
;
this
.
contentId
=
contentId
;
this
.
playlistType
=
playlistType
;
}
@Override
public
void
buildRenderers
(
DemoPlayer
player
,
RendererBuilderCallback
callback
)
{
this
.
player
=
player
;
this
.
callback
=
callback
;
switch
(
playlistType
)
{
case
TYPE_MASTER:
HlsMasterPlaylistParser
parser
=
new
HlsMasterPlaylistParser
();
ManifestFetcher
<
HlsMasterPlaylist
>
mediaPlaylistFetcher
=
new
ManifestFetcher
<
HlsMasterPlaylist
>(
parser
,
contentId
,
url
);
mediaPlaylistFetcher
.
singleLoad
(
player
.
getMainHandler
().
getLooper
(),
this
);
break
;
case
TYPE_MEDIA:
onManifest
(
contentId
,
newSimpleMasterPlaylist
(
url
));
break
;
}
}
@Override
public
void
onManifestError
(
String
contentId
,
IOException
e
)
{
callback
.
onRenderersError
(
e
);
}
@Override
public
void
onManifest
(
String
contentId
,
HlsMasterPlaylist
manifest
)
{
LoadControl
loadControl
=
new
DefaultLoadControl
(
new
BufferPool
(
BUFFER_SEGMENT_SIZE
));
DataSource
dataSource
=
new
HttpDataSource
(
userAgent
,
null
,
null
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
manifest
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
2
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
player
.
getMainHandler
(),
player
,
50
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
sampleSource
);
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
renderers
[
DemoPlayer
.
TYPE_VIDEO
]
=
videoRenderer
;
renderers
[
DemoPlayer
.
TYPE_AUDIO
]
=
audioRenderer
;
callback
.
onRenderers
(
null
,
null
,
renderers
);
}
private
HlsMasterPlaylist
newSimpleMasterPlaylist
(
String
mediaPlaylistUrl
)
{
return
new
HlsMasterPlaylist
(
Uri
.
parse
(
""
),
Collections
.
singletonList
(
new
Variant
(
mediaPlaylistUrl
,
0
)));
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunk.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.Allocation
;
import
com.google.android.exoplayer.upstream.Allocator
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSourceStream
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
/**
* An abstract base class for {@link Loadable} implementations that load chunks of data required
* for the playback of streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public
abstract
class
HlsChunk
implements
Loadable
{
/**
* The reason for a {@link HlsChunkSource} having generated this chunk. For reporting only.
* Possible values for this variable are defined by the specific {@link HlsChunkSource}
* implementations.
*/
public
final
int
trigger
;
private
final
DataSource
dataSource
;
private
final
DataSpec
dataSpec
;
private
DataSourceStream
dataSourceStream
;
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param trigger See {@link #trigger}.
*/
public
HlsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
)
{
Assertions
.
checkState
(
dataSpec
.
length
<=
Integer
.
MAX_VALUE
);
this
.
dataSource
=
Assertions
.
checkNotNull
(
dataSource
);
this
.
dataSpec
=
Assertions
.
checkNotNull
(
dataSpec
);
this
.
trigger
=
trigger
;
}
/**
* Initializes the {@link HlsChunk}.
*
* @param allocator An {@link Allocator} from which the {@link Allocation} needed to contain the
* data can be obtained.
*/
public
final
void
init
(
Allocator
allocator
)
{
Assertions
.
checkState
(
dataSourceStream
==
null
);
dataSourceStream
=
new
DataSourceStream
(
dataSource
,
dataSpec
,
allocator
);
}
/**
* Releases the {@link HlsChunk}, releasing any backing {@link Allocation}s.
*/
public
final
void
release
()
{
if
(
dataSourceStream
!=
null
)
{
dataSourceStream
.
close
();
dataSourceStream
=
null
;
}
}
/**
* Gets the length of the chunk in bytes.
*
* @return The length of the chunk in bytes, or {@link C#LENGTH_UNBOUNDED} if the length has yet
* to be determined.
*/
public
final
long
getLength
()
{
return
dataSourceStream
.
getLength
();
}
/**
* Whether the whole of the data has been consumed.
*
* @return True if the whole of the data has been consumed. False otherwise.
*/
public
final
boolean
isReadFinished
()
{
return
dataSourceStream
.
isEndOfStream
();
}
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
public
final
boolean
isLoadFinished
()
{
return
dataSourceStream
.
isLoadFinished
();
}
/**
* Gets the number of bytes that have been loaded.
*
* @return The number of bytes that have been loaded.
*/
public
final
long
bytesLoaded
()
{
return
dataSourceStream
.
getLoadPosition
();
}
/**
* Causes loaded data to be consumed.
*
* @throws IOException If an error occurs consuming the loaded data.
*/
public
final
void
consume
()
throws
IOException
{
Assertions
.
checkState
(
dataSourceStream
!=
null
);
consumeStream
(
dataSourceStream
);
}
/**
* Invoked by {@link #consume()}. Implementations may override this method if they wish to
* consume the loaded data at this point.
* <p>
* The default implementation is a no-op.
*
* @param stream The stream of loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
// Do nothing.
}
protected
final
NonBlockingInputStream
getNonBlockingInputStream
()
{
return
dataSourceStream
;
}
protected
final
void
resetReadPosition
()
{
if
(
dataSourceStream
!=
null
)
{
dataSourceStream
.
resetReadPosition
();
}
else
{
// We haven't been initialized yet, so the read position must already be 0.
}
}
// Loadable implementation
@Override
public
final
void
cancelLoad
()
{
dataSourceStream
.
cancelLoad
();
}
@Override
public
final
boolean
isLoadCanceled
()
{
return
dataSourceStream
.
isLoadCanceled
();
}
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
dataSourceStream
.
load
();
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkOperationHolder.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
/**
* Holds a hls chunk operation, which consists of a {@link HlsChunk} to load together with the
* number of {@link TsChunk}s that should be retained on the queue.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public
final
class
HlsChunkOperationHolder
{
/**
* The number of {@link TsChunk}s to retain in a queue.
*/
public
int
queueSize
;
/**
* The chunk.
*/
public
HlsChunk
chunk
;
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.parser.ts.TsExtractor
;
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.Util
;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
java.io.ByteArrayInputStream
;
import
java.io.IOException
;
import
java.util.List
;
/**
* A temporary test source of HLS chunks.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public
class
HlsChunkSource
{
private
final
DataSource
dataSource
;
private
final
TsExtractor
extractor
;
private
final
HlsMasterPlaylist
masterPlaylist
;
private
final
HlsMediaPlaylistParser
mediaPlaylistParser
;
/* package */
HlsMediaPlaylist
mediaPlaylist
;
/* package */
boolean
mediaPlaylistWasLive
;
/* package */
long
lastMediaPlaylistLoadTimeMs
;
// TODO: Once proper m3u8 parsing is in place, actually use the url!
public
HlsChunkSource
(
DataSource
dataSource
,
HlsMasterPlaylist
masterPlaylist
)
{
this
.
dataSource
=
dataSource
;
this
.
masterPlaylist
=
masterPlaylist
;
extractor
=
new
TsExtractor
();
mediaPlaylistParser
=
new
HlsMediaPlaylistParser
();
}
public
long
getDurationUs
()
{
return
mediaPlaylistWasLive
?
TrackRenderer
.
UNKNOWN_TIME_US
:
mediaPlaylist
.
durationUs
;
}
/**
* Adaptive implementations must set the maximum video dimensions on the supplied
* {@link MediaFormat}. Other implementations do nothing.
* <p>
* Only called when the source is enabled.
*
* @param out The {@link MediaFormat} on which the maximum video dimensions should be set.
*/
public
void
getMaxVideoDimensions
(
MediaFormat
out
)
{
// TODO: Implement this.
}
/**
* Updates the provided {@link HlsChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link HlsSampleSource}.
* <p>
* The next operation comprises of a possibly shortened queue length (shortened if the
* implementation wishes for the caller to discard {@link TsChunk}s from the queue), together
* with the next {@link HlsChunk} to load. The next chunk may be a {@link TsChunk} to be added to
* the queue, or another {@link HlsChunk} type (e.g. to load initialization data), or null if the
* source is not able to provide a chunk in its current state.
*
* @param queue A representation of the currently buffered {@link TsChunk}s.
* @param seekPositionUs If the queue is empty, this parameter must specify the seek position. If
* the queue is non-empty then this parameter is ignored.
* @param playbackPositionUs The current playback position.
* @param out A holder for the next operation, whose {@link HlsChunkOperationHolder#queueSize} is
* initially equal to the length of the queue, and whose {@linkHls ChunkOperationHolder#chunk}
* is initially equal to null or a {@link TsChunk} previously supplied by the
* {@link HlsChunkSource} that the caller has not yet finished loading. In the latter case the
* chunk can either be replaced or left unchanged. Note that leaving the chunk unchanged is
* both preferred and more efficient than replacing it with a new but identical chunk.
*/
public
void
getChunkOperation
(
List
<
TsChunk
>
queue
,
long
seekPositionUs
,
long
playbackPositionUs
,
HlsChunkOperationHolder
out
)
{
if
(
out
.
chunk
!=
null
)
{
// We already have a chunk. Keep it.
return
;
}
if
(
mediaPlaylist
==
null
)
{
out
.
chunk
=
newMediaPlaylistChunk
();
return
;
}
int
chunkMediaSequence
=
0
;
if
(
mediaPlaylistWasLive
)
{
if
(
queue
.
isEmpty
())
{
chunkMediaSequence
=
getLiveStartChunkMediaSequence
();
}
else
{
// For live nextChunkIndex contains chunk media sequence number.
chunkMediaSequence
=
queue
.
get
(
queue
.
size
()
-
1
).
nextChunkIndex
;
// If the updated playlist is far ahead and doesn't even have the last chunk from the
// queue, then try to catch up, skip a few chunks and start as if it was a new playlist.
if
(
chunkMediaSequence
<
mediaPlaylist
.
mediaSequence
)
{
// TODO: Trigger discontinuity in this case.
chunkMediaSequence
=
getLiveStartChunkMediaSequence
();
}
}
}
else
{
if
(
queue
.
isEmpty
())
{
chunkMediaSequence
=
Util
.
binarySearchFloor
(
mediaPlaylist
.
segments
,
seekPositionUs
,
true
,
true
)
+
mediaPlaylist
.
mediaSequence
;
}
else
{
chunkMediaSequence
=
queue
.
get
(
queue
.
size
()
-
1
).
nextChunkIndex
;
}
}
if
(
chunkMediaSequence
==
-
1
)
{
out
.
chunk
=
null
;
return
;
}
int
chunkIndex
=
chunkMediaSequence
-
mediaPlaylist
.
mediaSequence
;
// If the end of the playlist is reached.
if
(
chunkIndex
>=
mediaPlaylist
.
segments
.
size
())
{
if
(
mediaPlaylist
.
live
&&
shouldRerequestMediaPlaylist
())
{
out
.
chunk
=
newMediaPlaylistChunk
();
}
else
{
out
.
chunk
=
null
;
}
return
;
}
HlsMediaPlaylist
.
Segment
segment
=
mediaPlaylist
.
segments
.
get
(
chunkIndex
);
Uri
chunkUri
=
Util
.
getMergedUri
(
mediaPlaylist
.
baseUri
,
segment
.
url
);
DataSpec
dataSpec
=
new
DataSpec
(
chunkUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
long
startTimeUs
=
segment
.
startTimeUs
;
long
endTimeUs
=
startTimeUs
+
(
long
)
(
segment
.
durationSecs
*
1000000
);
int
nextChunkMediaSequence
=
chunkMediaSequence
+
1
;
if
(!
mediaPlaylist
.
live
&&
chunkIndex
==
mediaPlaylist
.
segments
.
size
()
-
1
)
{
nextChunkMediaSequence
=
-
1
;
}
out
.
chunk
=
new
TsChunk
(
dataSource
,
dataSpec
,
0
,
extractor
,
startTimeUs
,
endTimeUs
,
nextChunkMediaSequence
,
segment
.
discontinuity
);
}
private
boolean
shouldRerequestMediaPlaylist
()
{
// Don't re-request media playlist more often than one-half of the target duration.
long
timeSinceLastMediaPlaylistLoadMs
=
SystemClock
.
elapsedRealtime
()
-
lastMediaPlaylistLoadTimeMs
;
return
timeSinceLastMediaPlaylistLoadMs
>=
(
mediaPlaylist
.
targetDurationSecs
*
1000
)
/
2
;
}
private
int
getLiveStartChunkMediaSequence
()
{
// For live start playback from the third chunk from the end.
int
chunkIndex
=
mediaPlaylist
.
segments
.
size
()
>
3
?
mediaPlaylist
.
segments
.
size
()
-
3
:
0
;
return
chunkIndex
+
mediaPlaylist
.
mediaSequence
;
}
private
MediaPlaylistChunk
newMediaPlaylistChunk
()
{
Uri
mediaPlaylistUri
=
Util
.
getMergedUri
(
masterPlaylist
.
baseUri
,
masterPlaylist
.
variants
.
get
(
0
).
url
);
DataSpec
dataSpec
=
new
DataSpec
(
mediaPlaylistUri
,
0
,
C
.
LENGTH_UNBOUNDED
,
null
);
Uri
mediaPlaylistBaseUri
=
Util
.
parseBaseUri
(
mediaPlaylistUri
.
toString
());
return
new
MediaPlaylistChunk
(
dataSource
,
dataSpec
,
0
,
mediaPlaylistBaseUri
);
}
private
class
MediaPlaylistChunk
extends
HlsChunk
{
private
final
Uri
baseUri
;
public
MediaPlaylistChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
Uri
baseUri
)
{
super
(
dataSource
,
dataSpec
,
trigger
);
this
.
baseUri
=
baseUri
;
}
@Override
protected
void
consumeStream
(
NonBlockingInputStream
stream
)
throws
IOException
{
byte
[]
data
=
new
byte
[(
int
)
stream
.
getAvailableByteCount
()];
stream
.
read
(
data
,
0
,
data
.
length
);
lastMediaPlaylistLoadTimeMs
=
SystemClock
.
elapsedRealtime
();
mediaPlaylist
=
mediaPlaylistParser
.
parse
(
new
ByteArrayInputStream
(
data
),
null
,
null
,
baseUri
);
mediaPlaylistWasLive
|=
mediaPlaylist
.
live
;
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylist.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
android.net.Uri
;
import
java.util.List
;
/**
* Represents an HLS master playlist.
*/
public
final
class
HlsMasterPlaylist
{
/**
* Variant stream reference.
*/
public
static
final
class
Variant
{
public
final
int
bandwidth
;
public
final
String
url
;
public
Variant
(
String
url
,
int
bandwidth
)
{
this
.
bandwidth
=
bandwidth
;
this
.
url
=
url
;
}
}
public
final
Uri
baseUri
;
public
final
List
<
Variant
>
variants
;
public
HlsMasterPlaylist
(
Uri
baseUri
,
List
<
Variant
>
variants
)
{
this
.
baseUri
=
baseUri
;
this
.
variants
=
variants
;
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParser.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer.util.ManifestParser
;
import
android.net.Uri
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.regex.Pattern
;
/**
* HLS Master playlists parsing logic.
*/
public
final
class
HlsMasterPlaylistParser
implements
ManifestParser
<
HlsMasterPlaylist
>
{
private
static
final
String
STREAM_INF_TAG
=
"#EXT-X-STREAM-INF"
;
private
static
final
String
BANDWIDTH_ATTR
=
"BANDWIDTH"
;
private
static
final
Pattern
BANDWIDTH_ATTR_REGEX
=
Pattern
.
compile
(
BANDWIDTH_ATTR
+
"=(\\d+)\\b"
);
@Override
public
HlsMasterPlaylist
parse
(
InputStream
inputStream
,
String
inputEncoding
,
String
contentId
,
Uri
baseUri
)
throws
IOException
{
return
parseMasterPlaylist
(
inputStream
,
inputEncoding
,
baseUri
);
}
private
static
HlsMasterPlaylist
parseMasterPlaylist
(
InputStream
inputStream
,
String
inputEncoding
,
Uri
baseUri
)
throws
IOException
{
BufferedReader
reader
=
new
BufferedReader
((
inputEncoding
==
null
)
?
new
InputStreamReader
(
inputStream
)
:
new
InputStreamReader
(
inputStream
,
inputEncoding
));
List
<
Variant
>
variants
=
new
ArrayList
<
Variant
>();
int
bandwidth
=
0
;
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
line
=
line
.
trim
();
if
(
line
.
isEmpty
())
{
continue
;
}
if
(
line
.
startsWith
(
STREAM_INF_TAG
))
{
bandwidth
=
HlsParserUtil
.
parseIntAttr
(
line
,
BANDWIDTH_ATTR_REGEX
,
BANDWIDTH_ATTR
);
}
else
if
(!
line
.
startsWith
(
"#"
))
{
variants
.
add
(
new
Variant
(
line
,
bandwidth
));
bandwidth
=
0
;
}
}
return
new
HlsMasterPlaylist
(
baseUri
,
Collections
.
unmodifiableList
(
variants
));
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylist.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
android.net.Uri
;
import
java.util.List
;
/**
* Represents an HLS media playlist.
*/
public
final
class
HlsMediaPlaylist
{
/**
* Media segment reference.
*/
public
static
final
class
Segment
implements
Comparable
<
Long
>
{
public
final
boolean
discontinuity
;
public
final
double
durationSecs
;
public
final
String
url
;
public
final
long
startTimeUs
;
public
Segment
(
String
uri
,
double
durationSecs
,
boolean
discontinuity
,
long
startTimeUs
)
{
this
.
url
=
uri
;
this
.
durationSecs
=
durationSecs
;
this
.
discontinuity
=
discontinuity
;
this
.
startTimeUs
=
startTimeUs
;
}
@Override
public
int
compareTo
(
Long
startTimeUs
)
{
return
(
int
)
(
this
.
startTimeUs
-
startTimeUs
);
}
}
public
final
Uri
baseUri
;
public
final
int
mediaSequence
;
public
final
int
targetDurationSecs
;
public
final
int
version
;
public
final
List
<
Segment
>
segments
;
public
final
boolean
live
;
public
final
long
durationUs
;
public
HlsMediaPlaylist
(
Uri
baseUri
,
int
mediaSequence
,
int
targetDurationSecs
,
int
version
,
boolean
live
,
List
<
Segment
>
segments
)
{
this
.
baseUri
=
baseUri
;
this
.
mediaSequence
=
mediaSequence
;
this
.
targetDurationSecs
=
targetDurationSecs
;
this
.
version
=
version
;
this
.
live
=
live
;
this
.
segments
=
segments
;
if
(
this
.
segments
.
size
()
>
0
)
{
Segment
lastSegment
=
segments
.
get
(
this
.
segments
.
size
()
-
1
);
this
.
durationUs
=
lastSegment
.
startTimeUs
+
(
long
)
(
lastSegment
.
durationSecs
*
1000000
);
}
else
{
this
.
durationUs
=
0
;
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsMediaPlaylistParser.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer.util.ManifestParser
;
import
android.net.Uri
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.regex.Pattern
;
/**
* HLS Media playlists parsing logic.
*/
public
final
class
HlsMediaPlaylistParser
implements
ManifestParser
<
HlsMediaPlaylist
>
{
private
static
final
String
DISCONTINUITY_TAG
=
"#EXT-X-DISCONTINUITY"
;
private
static
final
String
MEDIA_DURATION_TAG
=
"#EXTINF"
;
private
static
final
String
MEDIA_SEQUENCE_TAG
=
"#EXT-X-MEDIA-SEQUENCE"
;
private
static
final
String
TARGET_DURATION_TAG
=
"#EXT-X-TARGETDURATION"
;
private
static
final
String
VERSION_TAG
=
"#EXT-X-VERSION"
;
private
static
final
String
ENDLIST_TAG
=
"#EXT-X-ENDLIST"
;
private
static
final
Pattern
MEDIA_DURATION_REGEX
=
Pattern
.
compile
(
MEDIA_DURATION_TAG
+
":([\\d.]+),"
);
private
static
final
Pattern
MEDIA_SEQUENCE_REGEX
=
Pattern
.
compile
(
MEDIA_SEQUENCE_TAG
+
":(\\d+)\\b"
);
private
static
final
Pattern
TARGET_DURATION_REGEX
=
Pattern
.
compile
(
TARGET_DURATION_TAG
+
":(\\d+)\\b"
);
private
static
final
Pattern
VERSION_REGEX
=
Pattern
.
compile
(
VERSION_TAG
+
":(\\d+)\\b"
);
@Override
public
HlsMediaPlaylist
parse
(
InputStream
inputStream
,
String
inputEncoding
,
String
contentId
,
Uri
baseUri
)
throws
IOException
{
return
parseMediaPlaylist
(
inputStream
,
inputEncoding
,
baseUri
);
}
private
static
HlsMediaPlaylist
parseMediaPlaylist
(
InputStream
inputStream
,
String
inputEncoding
,
Uri
baseUri
)
throws
IOException
{
BufferedReader
reader
=
new
BufferedReader
((
inputEncoding
==
null
)
?
new
InputStreamReader
(
inputStream
)
:
new
InputStreamReader
(
inputStream
,
inputEncoding
));
int
mediaSequence
=
0
;
int
targetDurationSecs
=
0
;
int
version
=
1
;
// Default version == 1.
boolean
live
=
true
;
List
<
Segment
>
segments
=
new
ArrayList
<
Segment
>();
double
segmentDurationSecs
=
0.0
;
boolean
segmentDiscontinuity
=
false
;
long
segmentStartTimeUs
=
0
;
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
line
=
line
.
trim
();
if
(
line
.
isEmpty
())
{
continue
;
}
if
(
line
.
startsWith
(
TARGET_DURATION_TAG
))
{
targetDurationSecs
=
HlsParserUtil
.
parseIntAttr
(
line
,
TARGET_DURATION_REGEX
,
TARGET_DURATION_TAG
);
}
else
if
(
line
.
startsWith
(
MEDIA_SEQUENCE_TAG
))
{
mediaSequence
=
HlsParserUtil
.
parseIntAttr
(
line
,
MEDIA_SEQUENCE_REGEX
,
MEDIA_SEQUENCE_TAG
);
}
else
if
(
line
.
startsWith
(
VERSION_TAG
))
{
version
=
HlsParserUtil
.
parseIntAttr
(
line
,
VERSION_REGEX
,
VERSION_TAG
);
}
else
if
(
line
.
startsWith
(
MEDIA_DURATION_TAG
))
{
segmentDurationSecs
=
HlsParserUtil
.
parseDoubleAttr
(
line
,
MEDIA_DURATION_REGEX
,
MEDIA_DURATION_TAG
);
}
else
if
(
line
.
equals
(
DISCONTINUITY_TAG
))
{
segmentDiscontinuity
=
true
;
}
else
if
(!
line
.
startsWith
(
"#"
))
{
segments
.
add
(
new
Segment
(
line
,
segmentDurationSecs
,
segmentDiscontinuity
,
segmentStartTimeUs
));
segmentStartTimeUs
+=
(
long
)
(
segmentDurationSecs
*
1000000
);
segmentDiscontinuity
=
false
;
segmentDurationSecs
=
0.0
;
}
else
if
(
line
.
equals
(
ENDLIST_TAG
))
{
live
=
false
;
break
;
}
}
return
new
HlsMediaPlaylist
(
baseUri
,
mediaSequence
,
targetDurationSecs
,
version
,
live
,
Collections
.
unmodifiableList
(
segments
));
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.ParserException
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* Utility methods for HLS manifest parsing.
*/
/* package */
class
HlsParserUtil
{
private
HlsParserUtil
()
{}
public
static
String
parseStringAttr
(
String
line
,
Pattern
pattern
,
String
tag
)
throws
ParserException
{
Matcher
matcher
=
pattern
.
matcher
(
line
);
if
(
matcher
.
find
()
&&
matcher
.
groupCount
()
==
1
)
{
return
matcher
.
group
(
1
);
}
throw
new
ParserException
(
String
.
format
(
"Couldn't match %s tag in %s"
,
tag
,
line
));
}
public
static
int
parseIntAttr
(
String
line
,
Pattern
pattern
,
String
tag
)
throws
ParserException
{
return
Integer
.
parseInt
(
parseStringAttr
(
line
,
pattern
,
tag
));
}
public
static
double
parseDoubleAttr
(
String
line
,
Pattern
pattern
,
String
tag
)
throws
ParserException
{
return
Double
.
parseDouble
(
parseStringAttr
(
line
,
pattern
,
tag
));
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackInfo
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.os.SystemClock
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.Iterator
;
import
java.util.LinkedList
;
import
java.util.List
;
/**
* A {@link SampleSource} for HLS streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/
public
class
HlsSampleSource
implements
SampleSource
,
Loader
.
Callback
{
private
static
final
int
NO_RESET_PENDING
=
-
1
;
private
final
LoadControl
loadControl
;
private
final
HlsChunkSource
chunkSource
;
private
final
HlsChunkOperationHolder
currentLoadableHolder
;
private
final
LinkedList
<
TsChunk
>
mediaChunks
;
private
final
List
<
TsChunk
>
readOnlyHlsChunks
;
private
final
int
bufferSizeContribution
;
private
final
boolean
frameAccurateSeeking
;
private
int
remainingReleaseCount
;
private
boolean
prepared
;
private
int
trackCount
;
private
int
enabledTrackCount
;
private
boolean
[]
trackEnabledStates
;
private
boolean
[]
pendingDiscontinuities
;
private
TrackInfo
[]
trackInfos
;
private
MediaFormat
[]
downstreamMediaFormats
;
private
long
downstreamPositionUs
;
private
long
lastSeekPositionUs
;
private
long
pendingResetTime
;
private
long
lastPerformedBufferOperation
;
private
Loader
loader
;
private
IOException
currentLoadableException
;
private
boolean
currentLoadableExceptionFatal
;
private
int
currentLoadableExceptionCount
;
private
long
currentLoadableExceptionTimestamp
;
private
boolean
pendingTimestampOffsetUpdate
;
private
long
timestampOffsetUs
;
public
HlsSampleSource
(
HlsChunkSource
chunkSource
,
LoadControl
loadControl
,
int
bufferSizeContribution
,
boolean
frameAccurateSeeking
,
int
downstreamRendererCount
)
{
this
.
chunkSource
=
chunkSource
;
this
.
loadControl
=
loadControl
;
this
.
bufferSizeContribution
=
bufferSizeContribution
;
this
.
frameAccurateSeeking
=
frameAccurateSeeking
;
this
.
remainingReleaseCount
=
downstreamRendererCount
;
currentLoadableHolder
=
new
HlsChunkOperationHolder
();
mediaChunks
=
new
LinkedList
<
TsChunk
>();
readOnlyHlsChunks
=
Collections
.
unmodifiableList
(
mediaChunks
);
}
@Override
public
boolean
prepare
()
{
if
(
prepared
)
{
return
true
;
}
if
(
loader
==
null
)
{
loader
=
new
Loader
(
"Loader:HLS"
);
loadControl
.
register
(
this
,
bufferSizeContribution
);
}
updateLoadControl
();
if
(
mediaChunks
.
isEmpty
())
{
return
false
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getFirst
();
if
(
mediaChunk
.
prepare
())
{
trackCount
=
mediaChunk
.
getTrackCount
();
trackEnabledStates
=
new
boolean
[
trackCount
];
pendingDiscontinuities
=
new
boolean
[
trackCount
];
downstreamMediaFormats
=
new
MediaFormat
[
trackCount
];
trackInfos
=
new
TrackInfo
[
trackCount
];
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
MediaFormat
format
=
mediaChunk
.
getMediaFormat
(
i
);
trackInfos
[
i
]
=
new
TrackInfo
(
format
.
mimeType
,
chunkSource
.
getDurationUs
());
}
prepared
=
true
;
}
return
prepared
;
}
@Override
public
int
getTrackCount
()
{
Assertions
.
checkState
(
prepared
);
return
trackCount
;
}
@Override
public
TrackInfo
getTrackInfo
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
trackInfos
[
track
];
}
@Override
public
void
enable
(
int
track
,
long
timeUs
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(!
trackEnabledStates
[
track
]);
enabledTrackCount
++;
trackEnabledStates
[
track
]
=
true
;
downstreamMediaFormats
[
track
]
=
null
;
if
(
enabledTrackCount
==
1
)
{
downstreamPositionUs
=
timeUs
;
lastSeekPositionUs
=
timeUs
;
restartFrom
(
timeUs
);
}
}
@Override
public
void
disable
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
trackEnabledStates
[
track
]);
enabledTrackCount
--;
trackEnabledStates
[
track
]
=
false
;
pendingDiscontinuities
[
track
]
=
false
;
if
(
enabledTrackCount
==
0
)
{
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearHlsChunks
();
clearCurrentLoadable
();
}
}
}
@Override
public
boolean
continueBuffering
(
long
playbackPositionUs
)
throws
IOException
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
enabledTrackCount
>
0
);
downstreamPositionUs
=
playbackPositionUs
;
updateLoadControl
();
if
(
isPendingReset
()
||
mediaChunks
.
isEmpty
())
{
return
false
;
}
else
if
(
mediaChunks
.
getFirst
().
sampleAvailable
())
{
// There's a sample available to be read from the current chunk.
return
true
;
}
else
{
// It may be the case that the current chunk has been fully read but not yet discarded and
// that the next chunk has an available sample. Return true if so, otherwise false.
return
mediaChunks
.
size
()
>
1
&&
mediaChunks
.
get
(
1
).
sampleAvailable
();
}
}
@Override
public
int
readData
(
int
track
,
long
playbackPositionUs
,
MediaFormatHolder
formatHolder
,
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
{
Assertions
.
checkState
(
prepared
);
if
(
pendingDiscontinuities
[
track
])
{
pendingDiscontinuities
[
track
]
=
false
;
return
DISCONTINUITY_READ
;
}
if
(
onlyReadDiscontinuity
)
{
return
NOTHING_READ
;
}
downstreamPositionUs
=
playbackPositionUs
;
if
(
isPendingReset
())
{
if
(
currentLoadableException
!=
null
)
{
throw
currentLoadableException
;
}
return
NOTHING_READ
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getFirst
();
if
(
mediaChunk
.
readDiscontinuity
())
{
pendingTimestampOffsetUpdate
=
true
;
for
(
int
i
=
0
;
i
<
pendingDiscontinuities
.
length
;
i
++)
{
pendingDiscontinuities
[
i
]
=
true
;
}
pendingDiscontinuities
[
track
]
=
false
;
return
DISCONTINUITY_READ
;
}
if
(
mediaChunk
.
isReadFinished
())
{
// We've read all of the samples from the current media chunk.
if
(
mediaChunks
.
size
()
>
1
)
{
discardDownstreamHlsChunk
();
mediaChunk
=
mediaChunks
.
getFirst
();
return
readData
(
track
,
playbackPositionUs
,
formatHolder
,
sampleHolder
,
false
);
}
else
if
(
mediaChunk
.
isLastChunk
())
{
return
END_OF_STREAM
;
}
return
NOTHING_READ
;
}
if
(!
mediaChunk
.
prepare
())
{
if
(
currentLoadableException
!=
null
)
{
throw
currentLoadableException
;
}
return
NOTHING_READ
;
}
MediaFormat
mediaFormat
=
mediaChunk
.
getMediaFormat
(
track
);
if
(
mediaFormat
!=
null
&&
!
mediaFormat
.
equals
(
downstreamMediaFormats
[
track
],
true
))
{
chunkSource
.
getMaxVideoDimensions
(
mediaFormat
);
formatHolder
.
format
=
mediaFormat
;
downstreamMediaFormats
[
track
]
=
mediaFormat
;
return
FORMAT_READ
;
}
if
(
mediaChunk
.
read
(
track
,
sampleHolder
))
{
if
(
pendingTimestampOffsetUpdate
)
{
pendingTimestampOffsetUpdate
=
false
;
timestampOffsetUs
=
sampleHolder
.
timeUs
-
mediaChunk
.
startTimeUs
;
}
sampleHolder
.
timeUs
-=
timestampOffsetUs
;
sampleHolder
.
decodeOnly
=
frameAccurateSeeking
&&
sampleHolder
.
timeUs
<
lastSeekPositionUs
;
return
SAMPLE_READ
;
}
else
{
if
(
currentLoadableException
!=
null
)
{
throw
currentLoadableException
;
}
return
NOTHING_READ
;
}
}
@Override
public
void
seekToUs
(
long
timeUs
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
enabledTrackCount
>
0
);
downstreamPositionUs
=
timeUs
;
lastSeekPositionUs
=
timeUs
;
if
(
pendingResetTime
==
timeUs
)
{
return
;
}
for
(
int
i
=
0
;
i
<
pendingDiscontinuities
.
length
;
i
++)
{
pendingDiscontinuities
[
i
]
=
true
;
}
TsChunk
mediaChunk
=
getHlsChunk
(
timeUs
);
if
(
mediaChunk
==
null
)
{
restartFrom
(
timeUs
);
}
else
{
pendingTimestampOffsetUpdate
=
true
;
mediaChunk
.
reset
();
discardDownstreamHlsChunks
(
mediaChunk
);
updateLoadControl
();
}
}
private
TsChunk
getHlsChunk
(
long
timeUs
)
{
Iterator
<
TsChunk
>
mediaChunkIterator
=
mediaChunks
.
iterator
();
while
(
mediaChunkIterator
.
hasNext
())
{
TsChunk
mediaChunk
=
mediaChunkIterator
.
next
();
if
(
timeUs
<
mediaChunk
.
startTimeUs
)
{
return
null
;
}
else
if
(
mediaChunk
.
isLastChunk
()
||
timeUs
<
mediaChunk
.
endTimeUs
)
{
return
mediaChunk
;
}
}
return
null
;
}
@Override
public
long
getBufferedPositionUs
()
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
enabledTrackCount
>
0
);
if
(
isPendingReset
())
{
return
pendingResetTime
;
}
TsChunk
mediaChunk
=
mediaChunks
.
getLast
();
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(
currentLoadable
!=
null
&&
mediaChunk
==
currentLoadable
)
{
// Linearly interpolate partially-fetched chunk times.
long
chunkLength
=
mediaChunk
.
getLength
();
if
(
chunkLength
!=
C
.
LENGTH_UNBOUNDED
)
{
return
mediaChunk
.
startTimeUs
+
((
mediaChunk
.
endTimeUs
-
mediaChunk
.
startTimeUs
)
*
mediaChunk
.
bytesLoaded
())
/
chunkLength
;
}
else
{
return
mediaChunk
.
startTimeUs
;
}
}
else
if
(
mediaChunk
.
isLastChunk
())
{
return
TrackRenderer
.
END_OF_TRACK_US
;
}
else
{
return
mediaChunk
.
endTimeUs
;
}
}
@Override
public
void
release
()
{
Assertions
.
checkState
(
remainingReleaseCount
>
0
);
if
(--
remainingReleaseCount
==
0
&&
loader
!=
null
)
{
loadControl
.
unregister
(
this
);
loader
.
release
();
loader
=
null
;
}
}
@Override
public
void
onLoadCompleted
(
Loadable
loadable
)
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
try
{
currentLoadable
.
consume
();
}
catch
(
IOException
e
)
{
currentLoadableException
=
e
;
currentLoadableExceptionCount
++;
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
currentLoadableExceptionFatal
=
true
;
}
finally
{
if
(!
isTsChunk
(
currentLoadable
))
{
currentLoadable
.
release
();
}
if
(!
currentLoadableExceptionFatal
)
{
clearCurrentLoadable
();
}
updateLoadControl
();
}
}
@Override
public
void
onLoadCanceled
(
Loadable
loadable
)
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(!
isTsChunk
(
currentLoadable
))
{
currentLoadable
.
release
();
}
clearCurrentLoadable
();
if
(
enabledTrackCount
>
0
)
{
restartFrom
(
pendingResetTime
);
}
else
{
clearHlsChunks
();
loadControl
.
trimAllocator
();
}
}
@Override
public
void
onLoadError
(
Loadable
loadable
,
IOException
e
)
{
currentLoadableException
=
e
;
currentLoadableExceptionCount
++;
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
updateLoadControl
();
}
private
void
restartFrom
(
long
timeUs
)
{
pendingResetTime
=
timeUs
;
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearHlsChunks
();
clearCurrentLoadable
();
updateLoadControl
();
}
}
private
void
clearHlsChunks
()
{
discardDownstreamHlsChunks
(
null
);
}
private
void
clearCurrentLoadable
()
{
currentLoadableHolder
.
chunk
=
null
;
currentLoadableException
=
null
;
currentLoadableExceptionCount
=
0
;
currentLoadableExceptionFatal
=
false
;
}
private
void
updateLoadControl
()
{
long
loadPositionUs
;
if
(
isPendingReset
())
{
loadPositionUs
=
pendingResetTime
;
}
else
{
TsChunk
lastHlsChunk
=
mediaChunks
.
getLast
();
loadPositionUs
=
lastHlsChunk
.
nextChunkIndex
==
-
1
?
-
1
:
lastHlsChunk
.
endTimeUs
;
}
boolean
isBackedOff
=
currentLoadableException
!=
null
&&
!
currentLoadableExceptionFatal
;
boolean
nextLoader
=
loadControl
.
update
(
this
,
downstreamPositionUs
,
loadPositionUs
,
isBackedOff
||
loader
.
isLoading
(),
currentLoadableExceptionFatal
);
if
(
currentLoadableExceptionFatal
)
{
return
;
}
long
now
=
SystemClock
.
elapsedRealtime
();
if
(
isBackedOff
)
{
long
elapsedMillis
=
now
-
currentLoadableExceptionTimestamp
;
if
(
elapsedMillis
>=
getRetryDelayMillis
(
currentLoadableExceptionCount
))
{
resumeFromBackOff
();
}
return
;
}
if
(!
loader
.
isLoading
())
{
if
(
currentLoadableHolder
.
chunk
==
null
||
now
-
lastPerformedBufferOperation
>
1000
)
{
lastPerformedBufferOperation
=
now
;
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetTime
,
downstreamPositionUs
,
currentLoadableHolder
);
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
}
if
(
nextLoader
)
{
maybeStartLoading
();
}
}
}
/**
* Resumes loading.
* <p>
* If the {@link HlsChunkSource} returns a chunk equivalent to the backed off chunk B, then the
* loading of B will be resumed. In all other cases B will be discarded and the new chunk will
* be loaded.
*/
private
void
resumeFromBackOff
()
{
currentLoadableException
=
null
;
HlsChunk
backedOffChunk
=
currentLoadableHolder
.
chunk
;
if
(!
isTsChunk
(
backedOffChunk
))
{
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetTime
,
downstreamPositionUs
,
currentLoadableHolder
);
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
if
(
currentLoadableHolder
.
chunk
==
backedOffChunk
)
{
// HlsChunk was unchanged. Resume loading.
loader
.
startLoading
(
backedOffChunk
,
this
);
}
else
{
backedOffChunk
.
release
();
maybeStartLoading
();
}
return
;
}
if
(
backedOffChunk
==
mediaChunks
.
getFirst
())
{
// We're not able to clear the first media chunk, so we have no choice but to continue
// loading it.
loader
.
startLoading
(
backedOffChunk
,
this
);
return
;
}
// The current loadable is the last media chunk. Remove it before we invoke the chunk source,
// and add it back again afterwards.
TsChunk
removedChunk
=
mediaChunks
.
removeLast
();
Assertions
.
checkState
(
backedOffChunk
==
removedChunk
);
currentLoadableHolder
.
queueSize
=
readOnlyHlsChunks
.
size
();
chunkSource
.
getChunkOperation
(
readOnlyHlsChunks
,
pendingResetTime
,
downstreamPositionUs
,
currentLoadableHolder
);
mediaChunks
.
add
(
removedChunk
);
if
(
currentLoadableHolder
.
chunk
==
backedOffChunk
)
{
// HlsChunk was unchanged. Resume loading.
loader
.
startLoading
(
backedOffChunk
,
this
);
}
else
{
// This call will remove and release at least one chunk from the end of mediaChunks. Since
// the current loadable is the last media chunk, it is guaranteed to be removed.
discardUpstreamHlsChunks
(
currentLoadableHolder
.
queueSize
);
clearCurrentLoadable
();
maybeStartLoading
();
}
}
private
void
maybeStartLoading
()
{
HlsChunk
currentLoadable
=
currentLoadableHolder
.
chunk
;
if
(
currentLoadable
==
null
)
{
// Nothing to load.
return
;
}
currentLoadable
.
init
(
loadControl
.
getAllocator
());
if
(
isTsChunk
(
currentLoadable
))
{
TsChunk
mediaChunk
=
(
TsChunk
)
currentLoadable
;
if
(
isPendingReset
())
{
pendingTimestampOffsetUpdate
=
true
;
mediaChunk
.
reset
();
pendingResetTime
=
NO_RESET_PENDING
;
}
mediaChunks
.
add
(
mediaChunk
);
}
loader
.
startLoading
(
currentLoadable
,
this
);
}
/**
* Discards downstream media chunks until {@code untilChunk} if found. {@code untilChunk} is not
* itself discarded. Null can be passed to discard all media chunks.
*
* @param untilChunk The first media chunk to keep, or null to discard all media chunks.
*/
private
void
discardDownstreamHlsChunks
(
TsChunk
untilChunk
)
{
if
(
mediaChunks
.
isEmpty
()
||
untilChunk
==
mediaChunks
.
getFirst
())
{
return
;
}
while
(!
mediaChunks
.
isEmpty
()
&&
untilChunk
!=
mediaChunks
.
getFirst
())
{
mediaChunks
.
removeFirst
().
release
();
}
}
/**
* Discards the first downstream media chunk.
*/
private
void
discardDownstreamHlsChunk
()
{
mediaChunks
.
removeFirst
().
release
();
}
/**
* Discard upstream media chunks until the queue length is equal to the length specified.
*
* @param queueLength The desired length of the queue.
*/
private
void
discardUpstreamHlsChunks
(
int
queueLength
)
{
while
(
mediaChunks
.
size
()
>
queueLength
)
{
mediaChunks
.
removeLast
().
release
();
}
}
private
boolean
isTsChunk
(
HlsChunk
chunk
)
{
return
chunk
instanceof
TsChunk
;
}
private
boolean
isPendingReset
()
{
return
pendingResetTime
!=
NO_RESET_PENDING
;
}
private
long
getRetryDelayMillis
(
long
errorCount
)
{
return
Math
.
min
((
errorCount
-
1
)
*
1000
,
5000
);
}
protected
final
int
usToMs
(
long
timeUs
)
{
return
(
int
)
(
timeUs
/
1000
);
}
}
library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
0 → 100644
View file @
d64036c5
/*
* 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
.
hls
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.ts.TsExtractor
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
/**
* A MPEG2TS chunk.
*/
public
final
class
TsChunk
extends
HlsChunk
{
/**
* The start time of the media contained by the chunk.
*/
public
final
long
startTimeUs
;
/**
* The end time of the media contained by the chunk.
*/
public
final
long
endTimeUs
;
/**
* The index of the next media chunk, or -1 if this is the last media chunk in the stream.
*/
public
final
int
nextChunkIndex
;
/**
* The encoding discontinuity indicator.
*/
private
final
boolean
discontinuity
;
private
final
TsExtractor
extractor
;
private
boolean
pendingDiscontinuity
;
/**
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @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.
* @param discontinuity The encoding discontinuity indicator.
*/
public
TsChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
trigger
,
TsExtractor
extractor
,
long
startTimeUs
,
long
endTimeUs
,
int
nextChunkIndex
,
boolean
discontinuity
)
{
super
(
dataSource
,
dataSpec
,
trigger
);
this
.
startTimeUs
=
startTimeUs
;
this
.
endTimeUs
=
endTimeUs
;
this
.
nextChunkIndex
=
nextChunkIndex
;
this
.
extractor
=
extractor
;
this
.
discontinuity
=
discontinuity
;
this
.
pendingDiscontinuity
=
discontinuity
;
}
public
boolean
readDiscontinuity
()
{
if
(
pendingDiscontinuity
)
{
extractor
.
reset
();
pendingDiscontinuity
=
false
;
return
true
;
}
return
false
;
}
public
boolean
prepare
()
{
return
extractor
.
prepare
(
getNonBlockingInputStream
());
}
public
int
getTrackCount
()
{
return
extractor
.
getTrackCount
();
}
public
boolean
sampleAvailable
()
{
// TODO: Maybe optimize this to not require looping over the tracks.
if
(!
prepare
())
{
return
false
;
}
// TODO: Optimize this to not require looping over the tracks.
NonBlockingInputStream
inputStream
=
getNonBlockingInputStream
();
int
trackCount
=
extractor
.
getTrackCount
();
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
int
result
=
extractor
.
read
(
inputStream
,
i
,
null
);
if
((
result
&
TsExtractor
.
RESULT_NEED_SAMPLE_HOLDER
)
!=
0
)
{
return
true
;
}
}
return
false
;
}
public
boolean
read
(
int
track
,
SampleHolder
holder
)
{
int
result
=
extractor
.
read
(
getNonBlockingInputStream
(),
track
,
holder
);
return
(
result
&
TsExtractor
.
RESULT_READ_SAMPLE
)
!=
0
;
}
public
void
reset
()
{
extractor
.
reset
();
pendingDiscontinuity
=
discontinuity
;
resetReadPosition
();
}
public
MediaFormat
getMediaFormat
(
int
track
)
{
return
extractor
.
getFormat
(
track
);
}
public
boolean
isLastChunk
()
{
return
nextChunkIndex
==
-
1
;
}
}
library/src/main/java/com/google/android/exoplayer/parser/ts/BitsArray.java
0 → 100644
View file @
d64036c5
/*
* 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
.
ts
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
public
final
class
BitsArray
{
private
byte
[]
data
;
// The length of the valid data.
private
int
limit
;
// The offset within the data, stored as the current byte offset, and the bit offset within that
// byte (from 0 to 7).
private
int
byteOffset
;
private
int
bitOffset
;
/**
* Resets the state.
*/
public
void
reset
()
{
byteOffset
=
0
;
bitOffset
=
0
;
limit
=
0
;
}
/**
* Gets the current byte offset.
*
* @return The current byte offset.
*/
public
int
getByteOffset
()
{
return
byteOffset
;
}
/**
* Sets the current byte offset.
*
* @param byteOffset The byte offset to set.
*/
public
void
setByteOffset
(
int
byteOffset
)
{
this
.
byteOffset
=
byteOffset
;
}
/**
* Appends data from a {@link NonBlockingInputStream}.
*
* @param inputStream The {@link NonBlockingInputStream} whose data should be appended.
* @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended. May be 0 if no data was available
* from the stream. -1 is returned if the end of the stream has been reached.
*/
public
int
append
(
NonBlockingInputStream
inputStream
,
int
length
)
{
expand
(
length
);
int
bytesRead
=
inputStream
.
read
(
data
,
limit
,
length
);
if
(
bytesRead
==
-
1
)
{
return
-
1
;
}
limit
+=
bytesRead
;
return
bytesRead
;
}
/**
* Appends data from another {@link BitsArray}.
*
* @param bitsArray The {@link BitsArray} whose data should be appended.
* @param length The number of bytes to read and append.
*/
public
void
append
(
BitsArray
bitsArray
,
int
length
)
{
expand
(
length
);
bitsArray
.
readBytes
(
data
,
limit
,
length
);
limit
+=
length
;
}
private
void
expand
(
int
length
)
{
if
(
data
==
null
)
{
data
=
new
byte
[
length
];
return
;
}
if
(
data
.
length
-
limit
<
length
)
{
byte
[]
newBuffer
=
new
byte
[
limit
+
length
];
System
.
arraycopy
(
data
,
0
,
newBuffer
,
0
,
limit
);
data
=
newBuffer
;
}
}
/**
* Clears data that has already been read, moving the remaining data to the start of the buffer.
*/
public
void
clearReadData
()
{
System
.
arraycopy
(
data
,
byteOffset
,
data
,
0
,
limit
-
byteOffset
);
limit
-=
byteOffset
;
byteOffset
=
0
;
}
/**
* Reads a single unsigned byte.
*
* @return The value of the parsed byte.
*/
public
int
readUnsignedByte
()
{
byte
b
;
if
(
bitOffset
!=
0
)
{
b
=
(
byte
)
((
data
[
byteOffset
]
<<
bitOffset
)
|
(
data
[
byteOffset
+
1
]
>>
(
8
-
bitOffset
)));
}
else
{
b
=
data
[
byteOffset
];
}
byteOffset
++;
// Converting a signed byte into unsigned.
return
b
&
0xFF
;
}
/**
* Reads up to 32 bits.
*
* @param n The number of bits to read.
* @return An integer whose bottom n bits hold the read data.
*/
public
int
readBits
(
int
n
)
{
return
(
int
)
readBitsLong
(
n
);
}
/**
* Reads up to 64 bits.
*
* @param n The number of bits to read.
* @return A long whose bottom n bits hold the read data.
*/
public
long
readBitsLong
(
int
n
)
{
if
(
n
==
0
)
{
return
0
;
}
long
retval
=
0
;
// While n >= 8, read whole bytes.
while
(
n
>=
8
)
{
n
-=
8
;
retval
|=
(
readUnsignedByte
()
<<
n
);
}
if
(
n
>
0
)
{
int
nextBit
=
bitOffset
+
n
;
byte
writeMask
=
(
byte
)
(
0xFF
>>
(
8
-
n
));
if
(
nextBit
>
8
)
{
// Combine bits from current byte and next byte.
retval
|=
(((
getUnsignedByte
(
byteOffset
)
<<
(
nextBit
-
8
)
|
(
getUnsignedByte
(
byteOffset
+
1
)
>>
(
16
-
nextBit
)))
&
writeMask
));
byteOffset
++;
}
else
{
// Bits to be read only within current byte.
retval
|=
((
getUnsignedByte
(
byteOffset
)
>>
(
8
-
nextBit
))
&
writeMask
);
if
(
nextBit
==
8
)
{
byteOffset
++;
}
}
bitOffset
=
nextBit
%
8
;
}
return
retval
;
}
private
int
getUnsignedByte
(
int
offset
)
{
return
data
[
offset
]
&
0xFF
;
}
/**
* Skips bits and moves current reading position forward.
*
* @param n The number of bits to skip.
*/
public
void
skipBits
(
int
n
)
{
byteOffset
+=
(
n
/
8
);
bitOffset
+=
(
n
%
8
);
if
(
bitOffset
>
7
)
{
byteOffset
++;
bitOffset
-=
8
;
}
}
/**
* Skips bytes and moves current reading position forward.
*
* @param n The number of bytes to skip.
*/
public
void
skipBytes
(
int
n
)
{
byteOffset
+=
n
;
}
/**
* Reads multiple bytes and copies them into provided byte array.
* <p>
* The read position must be at a whole byte boundary for this method to be called.
*
* @param out The byte array to copy read data.
* @param offset The offset in the out byte array.
* @param length The length of the data to read
* @throws IllegalStateException If the method is called with the read position not at a whole
* byte boundary.
*/
public
void
readBytes
(
byte
[]
out
,
int
offset
,
int
length
)
{
Assertions
.
checkState
(
bitOffset
==
0
);
System
.
arraycopy
(
data
,
byteOffset
,
out
,
offset
,
length
);
byteOffset
+=
length
;
}
/**
* @return The number of whole bytes that are available to read.
*/
public
int
bytesLeft
()
{
return
limit
-
byteOffset
;
}
/**
* @return Whether or not there is any data available.
*/
public
boolean
isEmpty
()
{
return
limit
==
0
;
}
// TODO: Find a better place for this method.
/**
* Finds the next Adts sync word.
*
* @return The offset from the current position to the start of the next Adts sync word. If an
* Adts sync word is not found, then the offset to the end of the data is returned.
*/
public
int
findNextAdtsSyncWord
()
{
for
(
int
i
=
byteOffset
;
i
<
limit
-
1
;
i
++)
{
int
syncBits
=
(
getUnsignedByte
(
i
)
<<
8
)
|
getUnsignedByte
(
i
+
1
);
if
((
syncBits
&
0xFFF0
)
==
0xFFF0
&&
syncBits
!=
0xFFFF
)
{
return
i
-
byteOffset
;
}
}
return
limit
-
byteOffset
;
}
//TODO: Find a better place for this method.
/**
* Finds the next NAL unit.
*
* @param nalUnitType The type of the NAL unit to search for.
* @param offset The additional offset in the data to start the search from.
* @return The offset from the current position to the start of the NAL unit. If a NAL unit is
* not found, then the offset to the end of the data is returned.
*/
public
int
findNextNalUnit
(
int
nalUnitType
,
int
offset
)
{
for
(
int
i
=
byteOffset
+
offset
;
i
<
limit
-
3
;
i
++)
{
// Check for NAL unit start code prefix == 0x000001.
if
((
data
[
i
]
==
0
&&
data
[
i
+
1
]
==
0
&&
data
[
i
+
2
]
==
1
)
&&
(
nalUnitType
==
(
data
[
i
+
3
]
&
0x1F
)))
{
return
i
-
byteOffset
;
}
}
return
limit
-
byteOffset
;
}
}
library/src/main/java/com/google/android/exoplayer/parser/ts/TsExtractor.java
0 → 100644
View file @
d64036c5
/*
* 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
.
ts
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.SuppressLint
;
import
android.media.MediaExtractor
;
import
android.util.Log
;
import
android.util.Pair
;
import
android.util.SparseArray
;
import
java.nio.ByteBuffer
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.Queue
;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
*/
public
final
class
TsExtractor
{
/**
* An attempt to read from the input stream returned insufficient data.
*/
public
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
/**
* A media sample was read.
*/
public
static
final
int
RESULT_READ_SAMPLE
=
2
;
/**
* The next thing to be read is a sample, but a {@link SampleHolder} was not supplied.
*/
public
static
final
int
RESULT_NEED_SAMPLE_HOLDER
=
4
;
private
static
final
String
TAG
=
"TsExtractor"
;
private
static
final
int
TS_PACKET_SIZE
=
188
;
private
static
final
int
TS_SYNC_BYTE
=
0x47
;
// First byte of each TS packet.
private
static
final
int
TS_PAT_PID
=
0
;
private
static
final
int
TS_STREAM_TYPE_AAC
=
0x0F
;
private
static
final
int
TS_STREAM_TYPE_H264
=
0x1B
;
private
static
final
int
DEFAULT_BUFFER_SEGMENT_SIZE
=
64
*
1024
;
private
final
BitsArray
tsPacketBuffer
;
private
final
SparseArray
<
PesPayloadReader
>
pesPayloadReaders
;
// Indexed by streamType
private
final
SparseArray
<
TsPayloadReader
>
tsPayloadReaders
;
// Indexed by pid
private
final
Queue
<
Sample
>
samplesPool
;
private
boolean
prepared
;
public
TsExtractor
()
{
tsPacketBuffer
=
new
BitsArray
();
pesPayloadReaders
=
new
SparseArray
<
PesPayloadReader
>();
tsPayloadReaders
=
new
SparseArray
<
TsPayloadReader
>();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
samplesPool
=
new
LinkedList
<
Sample
>();
}
/**
* Gets the number of available tracks.
* <p>
* This method should only be called after the extractor has been prepared.
*
* @return The number of available tracks.
*/
public
int
getTrackCount
()
{
Assertions
.
checkState
(
prepared
);
return
pesPayloadReaders
.
size
();
}
/**
* Gets the format of the specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track index.
* @return The corresponding format.
*/
public
MediaFormat
getFormat
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
pesPayloadReaders
.
valueAt
(
track
).
getMediaFormat
();
}
/**
* Resets the extractor's internal state.
*/
public
void
reset
()
{
prepared
=
false
;
tsPacketBuffer
.
reset
();
tsPayloadReaders
.
clear
();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
// Clear each reader before discarding it, so as to recycle any queued Sample objects.
for
(
int
i
=
0
;
i
<
pesPayloadReaders
.
size
();
i
++)
{
pesPayloadReaders
.
valueAt
(
i
).
clear
();
}
pesPayloadReaders
.
clear
();
}
/**
* Attempts to prepare the extractor. The extractor is prepared once it has read sufficient data
* to have established the available tracks and their corresponding media formats.
* <p>
* Calling this method is a no-op if the extractor is already prepared.
*
* @param inputStream The input stream from which data can be read.
* @return True if the extractor was prepared. False if more data is required.
*/
public
boolean
prepare
(
NonBlockingInputStream
inputStream
)
{
while
(!
prepared
)
{
if
(
readTSPacket
(
inputStream
)
==
-
1
)
{
return
false
;
}
prepared
=
checkPrepared
();
}
return
true
;
}
private
boolean
checkPrepared
()
{
int
pesPayloadReaderCount
=
pesPayloadReaders
.
size
();
if
(
pesPayloadReaderCount
==
0
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
pesPayloadReaderCount
;
i
++)
{
if
(!
pesPayloadReaders
.
valueAt
(
i
).
hasMediaFormat
())
{
return
false
;
}
}
return
true
;
}
/**
* Consumes data from a {@link NonBlockingInputStream}.
* <p>
* The read terminates if the end of the input stream is reached, if insufficient data is
* available to read a sample, 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 track The track from which to 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.
*/
public
int
read
(
NonBlockingInputStream
inputStream
,
int
track
,
SampleHolder
out
)
{
Assertions
.
checkState
(
prepared
);
Queue
<
Sample
>
queue
=
pesPayloadReaders
.
valueAt
(
track
).
samplesQueue
;
// Keep reading if the buffer is empty.
while
(
queue
.
isEmpty
())
{
if
(
readTSPacket
(
inputStream
)
==
-
1
)
{
return
RESULT_NEED_MORE_DATA
;
}
}
if
(!
queue
.
isEmpty
()
&&
out
==
null
)
{
return
RESULT_NEED_SAMPLE_HOLDER
;
}
Sample
sample
=
queue
.
remove
();
convert
(
sample
,
out
);
recycleSample
(
sample
);
return
RESULT_READ_SAMPLE
;
}
/**
* Read a single TS packet.
*/
private
int
readTSPacket
(
NonBlockingInputStream
inputStream
)
{
// Read entire single TS packet.
if
(
inputStream
.
getAvailableByteCount
()
<
TS_PACKET_SIZE
)
{
return
-
1
;
}
tsPacketBuffer
.
reset
();
int
bytesRead
=
tsPacketBuffer
.
append
(
inputStream
,
TS_PACKET_SIZE
);
if
(
bytesRead
!=
TS_PACKET_SIZE
)
{
return
-
1
;
}
// Parse TS header.
// Check sync byte.
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
if
(
syncByte
!=
TS_SYNC_BYTE
)
{
return
0
;
}
// Skip transportErrorIndicator.
tsPacketBuffer
.
skipBits
(
1
);
int
payloadUnitStartIndicator
=
tsPacketBuffer
.
readBits
(
1
);
// Skip transportPriority.
tsPacketBuffer
.
skipBits
(
1
);
int
pid
=
tsPacketBuffer
.
readBits
(
13
);
// Skip transport_scrambling_control.
tsPacketBuffer
.
skipBits
(
2
);
int
adaptationFieldExist
=
tsPacketBuffer
.
readBits
(
1
);
int
payloadExist
=
tsPacketBuffer
.
readBits
(
1
);
// Skip continuityCounter.
tsPacketBuffer
.
skipBits
(
4
);
// Read Adaptation Field.
if
(
adaptationFieldExist
==
1
)
{
int
afLength
=
tsPacketBuffer
.
readBits
(
8
);
tsPacketBuffer
.
skipBytes
(
afLength
);
}
// Read Payload.
if
(
payloadExist
==
1
)
{
TsPayloadReader
payloadReader
=
tsPayloadReaders
.
get
(
pid
);
if
(
payloadReader
==
null
)
{
return
0
;
}
payloadReader
.
read
(
tsPacketBuffer
,
payloadUnitStartIndicator
);
}
return
0
;
}
private
void
convert
(
Sample
in
,
SampleHolder
out
)
{
if
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
in
.
size
)
{
if
(
out
.
allowDataBufferReplacement
)
{
out
.
data
=
ByteBuffer
.
allocate
(
in
.
size
);
}
else
{
throw
new
IndexOutOfBoundsException
(
"Buffer too small, and replacement not enabled"
);
}
}
out
.
data
.
put
(
in
.
data
,
0
,
in
.
size
);
out
.
size
=
in
.
size
;
out
.
flags
=
in
.
flags
;
out
.
timeUs
=
in
.
timeUs
;
}
private
Sample
getSample
()
{
if
(
samplesPool
.
isEmpty
())
{
return
new
Sample
(
DEFAULT_BUFFER_SEGMENT_SIZE
);
}
return
samplesPool
.
remove
();
}
private
void
recycleSample
(
Sample
sample
)
{
sample
.
reset
();
samplesPool
.
add
(
sample
);
}
/**
* Parses payload data.
*/
private
abstract
static
class
TsPayloadReader
{
public
abstract
void
read
(
BitsArray
tsBuffer
,
int
payloadUnitStartIndicator
);
}
/**
* Parses Program Association Table data.
*/
private
class
PatReader
extends
TsPayloadReader
{
@Override
public
void
read
(
BitsArray
tsBuffer
,
int
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
==
1
)
{
int
pointerField
=
tsBuffer
.
readBits
(
8
);
tsBuffer
.
skipBytes
(
pointerField
);
}
// Skip PAT header.
tsBuffer
.
skipBits
(
64
);
// 8+1+1+2+12+16+2+5+1+8+8
// Only read the first program and take it.
// Skip program_number.
tsBuffer
.
skipBits
(
16
+
3
);
int
pid
=
tsBuffer
.
readBits
(
13
);
// Pick the first program.
if
(
tsPayloadReaders
.
get
(
pid
)
==
null
)
{
tsPayloadReaders
.
put
(
pid
,
new
PmtReader
());
}
// Skip other programs if exist.
// Skip CRC_32.
}
}
/**
* Parses Program Map Table.
*/
private
class
PmtReader
extends
TsPayloadReader
{
@Override
public
void
read
(
BitsArray
tsBuffer
,
int
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
==
1
)
{
int
pointerField
=
tsBuffer
.
readBits
(
8
);
tsBuffer
.
skipBytes
(
pointerField
);
}
// Skip table_id, section_syntax_indicator, etc.
tsBuffer
.
skipBits
(
12
);
// 8+1+1+2
int
sectionLength
=
tsBuffer
.
readBits
(
12
);
// Skip the rest of the PMT header.
tsBuffer
.
skipBits
(
60
);
// 16+2+5+1+8+8+3+13+4
int
programInfoLength
=
tsBuffer
.
readBits
(
12
);
// Read descriptors.
readDescriptors
(
tsBuffer
,
programInfoLength
);
int
entriesSize
=
sectionLength
-
9
/* size of the rest of the fields before descriptors */
-
programInfoLength
-
4
/* CRC size */
;
while
(
entriesSize
>
0
)
{
int
streamType
=
tsBuffer
.
readBits
(
8
);
tsBuffer
.
skipBits
(
3
);
int
elementaryPid
=
tsBuffer
.
readBits
(
13
);
tsBuffer
.
skipBits
(
4
);
int
esInfoLength
=
tsBuffer
.
readBits
(
12
);
readDescriptors
(
tsBuffer
,
esInfoLength
);
entriesSize
-=
esInfoLength
+
5
;
if
(
pesPayloadReaders
.
get
(
streamType
)
!=
null
)
{
continue
;
}
PesPayloadReader
pesPayloadReader
=
null
;
switch
(
streamType
)
{
case
TS_STREAM_TYPE_AAC:
pesPayloadReader
=
new
AdtsReader
();
break
;
case
TS_STREAM_TYPE_H264:
pesPayloadReader
=
new
H264Reader
();
break
;
}
if
(
pesPayloadReader
!=
null
)
{
pesPayloadReaders
.
put
(
streamType
,
pesPayloadReader
);
tsPayloadReaders
.
put
(
elementaryPid
,
new
PesReader
(
pesPayloadReader
));
}
}
// Skip CRC_32.
}
private
void
readDescriptors
(
BitsArray
tsBuffer
,
int
descriptorsSize
)
{
while
(
descriptorsSize
>
0
)
{
// Skip tag.
tsBuffer
.
skipBits
(
8
);
int
descriptorsLength
=
tsBuffer
.
readBits
(
8
);
if
(
descriptorsLength
>
0
)
{
// Skip entire descriptor data.
tsBuffer
.
skipBytes
(
descriptorsLength
);
}
descriptorsSize
-=
descriptorsSize
+
2
;
}
}
}
/**
* Parses PES packet data and extracts samples.
*/
private
class
PesReader
extends
TsPayloadReader
{
// Reusable buffer for incomplete PES data.
private
final
BitsArray
pesBuffer
;
// Parses PES payload and extracts individual samples.
private
final
PesPayloadReader
pesPayloadReader
;
public
PesReader
(
PesPayloadReader
pesPayloadReader
)
{
this
.
pesPayloadReader
=
pesPayloadReader
;
pesBuffer
=
new
BitsArray
();
}
@Override
public
void
read
(
BitsArray
tsBuffer
,
int
payloadUnitStartIndicator
)
{
if
(
payloadUnitStartIndicator
==
1
&&
!
pesBuffer
.
isEmpty
())
{
readPES
();
}
pesBuffer
.
append
(
tsBuffer
,
tsBuffer
.
bytesLeft
());
}
/**
* Parses completed PES data.
*/
private
void
readPES
()
{
int
packetStartCodePrefix
=
pesBuffer
.
readBits
(
24
);
if
(
packetStartCodePrefix
!=
0x000001
)
{
// Error.
}
// TODO: Read and use stream_id.
// Skip stream_id.
pesBuffer
.
skipBits
(
8
);
int
pesPacketLength
=
pesBuffer
.
readBits
(
16
);
// Skip some fields/flags.
// TODO: might need to use data_alignment_indicator.
pesBuffer
.
skipBits
(
8
);
// 2+2+1+1+1+1
int
ptsFlag
=
pesBuffer
.
readBits
(
1
);
// Skip DTS flag.
pesBuffer
.
skipBits
(
1
);
// Skip some fields/flags.
pesBuffer
.
skipBits
(
6
);
// 1+1+1+1+1+1
int
pesHeaderDataLength
=
pesBuffer
.
readBits
(
8
);
if
(
pesHeaderDataLength
==
0
)
{
pesHeaderDataLength
=
pesBuffer
.
bytesLeft
();
}
long
timeUs
=
0
;
if
(
ptsFlag
==
1
)
{
// Skip prefix.
pesBuffer
.
skipBits
(
4
);
long
pts
=
pesBuffer
.
readBitsLong
(
3
)
<<
30
;
pesBuffer
.
skipBits
(
1
);
pts
|=
pesBuffer
.
readBitsLong
(
15
)
<<
15
;
pesBuffer
.
skipBits
(
1
);
pts
|=
pesBuffer
.
readBitsLong
(
15
);
pesBuffer
.
skipBits
(
1
);
timeUs
=
pts
*
1000000
/
90000
;
// Skip the rest of the header.
pesBuffer
.
skipBytes
(
pesHeaderDataLength
-
5
);
}
else
{
// Skip the rest of the header.
pesBuffer
.
skipBytes
(
pesHeaderDataLength
);
}
int
payloadSize
;
if
(
pesPacketLength
==
0
)
{
// If pesPacketLength is not specified read all available data.
payloadSize
=
pesBuffer
.
bytesLeft
();
}
else
{
payloadSize
=
pesPacketLength
-
pesHeaderDataLength
-
3
;
}
pesPayloadReader
.
read
(
pesBuffer
,
payloadSize
,
timeUs
);
pesBuffer
.
reset
();
}
}
/**
* Extracts individual samples from continuous byte stream.
*/
private
abstract
class
PesPayloadReader
{
public
final
Queue
<
Sample
>
samplesQueue
;
private
MediaFormat
mediaFormat
;
protected
PesPayloadReader
()
{
this
.
samplesQueue
=
new
LinkedList
<
Sample
>();
}
public
boolean
hasMediaFormat
()
{
return
mediaFormat
!=
null
;
}
public
MediaFormat
getMediaFormat
()
{
return
mediaFormat
;
}
protected
void
setMediaFormat
(
MediaFormat
mediaFormat
)
{
this
.
mediaFormat
=
mediaFormat
;
}
public
abstract
void
read
(
BitsArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
public
void
clear
()
{
while
(!
samplesQueue
.
isEmpty
())
{
recycleSample
(
samplesQueue
.
remove
());
}
}
/**
* Creates a new Sample and adds it to the queue.
*
* @param buffer The buffer to read sample data.
* @param sampleSize The size of the sample data.
* @param sampleTimeUs The sample time stamp.
*/
protected
void
addSample
(
BitsArray
buffer
,
int
sampleSize
,
long
sampleTimeUs
,
int
flags
)
{
Sample
sample
=
getSample
();
addToSample
(
sample
,
buffer
,
sampleSize
);
sample
.
flags
=
flags
;
sample
.
timeUs
=
sampleTimeUs
;
samplesQueue
.
add
(
sample
);
}
protected
void
addToSample
(
Sample
sample
,
BitsArray
buffer
,
int
size
)
{
if
(
sample
.
data
.
length
-
sample
.
size
<
size
)
{
sample
.
expand
(
size
-
sample
.
data
.
length
+
sample
.
size
);
}
buffer
.
readBytes
(
sample
.
data
,
sample
.
size
,
size
);
sample
.
size
+=
size
;
}
}
/**
* Parses a continuous H264 byte stream and extracts individual frames.
*/
private
class
H264Reader
extends
PesPayloadReader
{
// IDR picture.
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
// Access unit delimiter.
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
// Used to store uncompleted sample data.
private
Sample
currentSample
;
public
H264Reader
()
{
// TODO: Parse the format from the stream.
setMediaFormat
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
1920
,
1080
,
null
));
}
@Override
public
void
read
(
BitsArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
// Read leftover frame data from previous PES packet.
pesPayloadSize
-=
readOneH264Frame
(
pesBuffer
,
true
);
if
(
pesBuffer
.
bytesLeft
()
<=
0
||
pesPayloadSize
<=
0
)
{
return
;
}
// Single PES packet should contain only one new H.264 frame.
if
(
currentSample
!=
null
)
{
samplesQueue
.
add
(
currentSample
);
}
currentSample
=
getSample
();
pesPayloadSize
-=
readOneH264Frame
(
pesBuffer
,
false
);
currentSample
.
timeUs
=
pesTimeUs
;
if
(
pesPayloadSize
>
0
)
{
Log
.
e
(
TAG
,
"PES packet contains more frame data than expected"
);
}
}
@SuppressLint
(
"InlinedApi"
)
private
int
readOneH264Frame
(
BitsArray
pesBuffer
,
boolean
remainderOnly
)
{
int
offset
=
remainderOnly
?
0
:
3
;
int
audStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_AUD
,
offset
);
int
idrStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_IDR
,
offset
);
if
(
audStart
>
0
)
{
if
(
currentSample
!=
null
)
{
addToSample
(
currentSample
,
pesBuffer
,
audStart
);
if
(
idrStart
<
audStart
)
{
currentSample
.
flags
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
}
}
else
{
pesBuffer
.
skipBytes
(
audStart
);
}
return
audStart
;
}
return
0
;
}
@Override
public
void
clear
()
{
super
.
clear
();
if
(
currentSample
!=
null
)
{
recycleSample
(
currentSample
);
currentSample
=
null
;
}
}
}
/**
* Parses a continuous ADTS byte stream and extracts individual frames.
*/
private
class
AdtsReader
extends
PesPayloadReader
{
private
final
BitsArray
adtsBuffer
;
private
long
timeUs
;
public
AdtsReader
()
{
adtsBuffer
=
new
BitsArray
();
}
@Override
public
void
read
(
BitsArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
boolean
needToProcessLeftOvers
=
!
adtsBuffer
.
isEmpty
();
adtsBuffer
.
append
(
pesBuffer
,
pesPayloadSize
);
// If there are leftovers from previous PES packet, process it with last calculated timeUs.
if
(
needToProcessLeftOvers
&&
!
readOneAacFrame
(
timeUs
))
{
return
;
}
int
frameIndex
=
0
;
do
{
long
frameDuration
=
0
;
// If frameIndex > 0, audioMediaFormat should be already parsed.
// If frameIndex == 0, timeUs = pesTimeUs anyway.
if
(
hasMediaFormat
())
{
frameDuration
=
1000000L
*
1024L
/
getMediaFormat
().
sampleRate
;
}
timeUs
=
pesTimeUs
+
frameIndex
*
frameDuration
;
frameIndex
++;
}
while
(
readOneAacFrame
(
timeUs
));
}
@SuppressLint
(
"InlinedApi"
)
private
boolean
readOneAacFrame
(
long
timeUs
)
{
if
(
adtsBuffer
.
isEmpty
())
{
return
false
;
}
int
offsetToSyncWord
=
adtsBuffer
.
findNextAdtsSyncWord
();
adtsBuffer
.
skipBytes
(
offsetToSyncWord
);
int
adtsStartOffset
=
adtsBuffer
.
getByteOffset
();
if
(
adtsBuffer
.
bytesLeft
()
<
7
)
{
adtsBuffer
.
setByteOffset
(
adtsStartOffset
);
adtsBuffer
.
clearReadData
();
return
false
;
}
adtsBuffer
.
skipBits
(
15
);
int
hasCRC
=
adtsBuffer
.
readBits
(
1
);
if
(!
hasMediaFormat
())
{
int
audioObjectType
=
adtsBuffer
.
readBits
(
2
)
+
1
;
int
sampleRateIndex
=
adtsBuffer
.
readBits
(
4
);
adtsBuffer
.
skipBits
(
1
);
int
channelConfig
=
adtsBuffer
.
readBits
(
3
);
byte
[]
audioSpecificConfig
=
CodecSpecificDataUtil
.
buildAudioSpecificConfig
(
audioObjectType
,
sampleRateIndex
,
channelConfig
);
Pair
<
Integer
,
Integer
>
audioParams
=
CodecSpecificDataUtil
.
parseAudioSpecificConfig
(
audioSpecificConfig
);
MediaFormat
mediaFormat
=
MediaFormat
.
createAudioFormat
(
MimeTypes
.
AUDIO_AAC
,
MediaFormat
.
NO_VALUE
,
audioParams
.
second
,
audioParams
.
first
,
Collections
.
singletonList
(
audioSpecificConfig
));
setMediaFormat
(
mediaFormat
);
}
else
{
adtsBuffer
.
skipBits
(
10
);
}
adtsBuffer
.
skipBits
(
4
);
int
frameSize
=
adtsBuffer
.
readBits
(
13
);
adtsBuffer
.
skipBits
(
13
);
// Decrement frame size by ADTS header size and CRC.
if
(
hasCRC
==
0
)
{
// Skip CRC.
adtsBuffer
.
skipBytes
(
2
);
frameSize
-=
9
;
}
else
{
frameSize
-=
7
;
}
if
(
frameSize
>
adtsBuffer
.
bytesLeft
())
{
adtsBuffer
.
setByteOffset
(
adtsStartOffset
);
adtsBuffer
.
clearReadData
();
return
false
;
}
addSample
(
adtsBuffer
,
frameSize
,
timeUs
,
MediaExtractor
.
SAMPLE_FLAG_SYNC
);
return
true
;
}
@Override
public
void
clear
()
{
super
.
clear
();
adtsBuffer
.
reset
();
}
}
/**
* Simplified version of SampleHolder for internal buffering.
*/
private
static
class
Sample
{
public
byte
[]
data
;
public
int
flags
;
public
int
size
;
public
long
timeUs
;
public
Sample
(
int
length
)
{
data
=
new
byte
[
length
];
}
public
void
expand
(
int
length
)
{
byte
[]
newBuffer
=
new
byte
[
data
.
length
+
length
];
System
.
arraycopy
(
data
,
0
,
newBuffer
,
0
,
size
);
data
=
newBuffer
;
}
public
void
reset
()
{
flags
=
0
;
size
=
0
;
timeUs
=
0
;
}
}
}
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