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
8f0d576f
authored
Feb 13, 2015
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #293 from google/dev
dev -> dev-webm-vp9-opus
parents
ccac9fad
40411269
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
2553 additions
and
1833 deletions
demo/src/main/AndroidManifest.xml
demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
library/src/main/java/com/google/android/exoplayer/C.java
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.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/audio/AudioTrack.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/TrackFragment.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/hls/BitArrayChunk.java → library/src/main/java/com/google/android/exoplayer/hls/DataChunk.java
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.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/hls/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java
library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java
library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java
library/src/main/java/com/google/android/exoplayer/hls/parser/PesPayloadReader.java
library/src/main/java/com/google/android/exoplayer/hls/parser/RollingSampleBuffer.java
library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java
library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java
library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4TrackSampleTable.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
library/src/main/java/com/google/android/exoplayer/util/BitArray.java
library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java
library/src/main/java/com/google/android/exoplayer/util/ParsableByteArray.java
demo/src/main/AndroidManifest.xml
View file @
8f0d576f
...
@@ -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=
"1
1
00"
android:versionCode=
"1
2
00"
android:versionName=
"1.
1
.00"
android:versionName=
"1.
2
.00"
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/PlayerActivity.java
View file @
8f0d576f
...
@@ -92,9 +92,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -92,9 +92,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private
DemoPlayer
player
;
private
DemoPlayer
player
;
private
boolean
playerNeedsPrepare
;
private
boolean
playerNeedsPrepare
;
private
boolean
autoPlay
=
true
;
private
long
playerPosition
;
private
long
playerPosition
;
private
boolean
enableBackgroundAudio
=
false
;
private
boolean
enableBackgroundAudio
;
private
Uri
contentUri
;
private
Uri
contentUri
;
private
int
contentType
;
private
int
contentType
;
...
@@ -166,10 +165,10 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -166,10 +165,10 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
if
(!
enableBackgroundAudio
)
{
if
(!
enableBackgroundAudio
)
{
releasePlayer
();
releasePlayer
();
}
else
{
}
else
{
player
.
blockingClearSurface
(
);
player
.
setBackgrounded
(
true
);
}
}
audioCapabilitiesReceiver
.
unregister
();
audioCapabilitiesReceiver
.
unregister
();
shutterView
.
setVisibility
(
View
.
VISIBLE
);
}
}
@Override
@Override
...
@@ -183,7 +182,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -183,7 +182,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
@Override
@Override
public
void
onClick
(
View
view
)
{
public
void
onClick
(
View
view
)
{
if
(
view
==
retryButton
)
{
if
(
view
==
retryButton
)
{
autoPlay
=
true
;
preparePlayer
();
preparePlayer
();
}
}
}
}
...
@@ -192,11 +190,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -192,11 +190,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
@Override
@Override
public
void
onAudioCapabilitiesChanged
(
AudioCapabilities
audioCapabilities
)
{
public
void
onAudioCapabilitiesChanged
(
AudioCapabilities
audioCapabilities
)
{
boolean
audioCapabilitiesChanged
=
!
audioCapabilities
.
equals
(
this
.
audioCapabilities
);
if
(
player
==
null
||
audioCapabilitiesChanged
)
{
this
.
audioCapabilities
=
audioCapabilities
;
this
.
audioCapabilities
=
audioCapabilities
;
releasePlayer
();
releasePlayer
();
autoPlay
=
true
;
preparePlayer
();
preparePlayer
();
}
else
if
(
player
!=
null
)
{
player
.
setBackgrounded
(
false
);
}
}
}
// Internal methods
// Internal methods
...
@@ -239,15 +240,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -239,15 +240,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
updateButtonVisibilities
();
updateButtonVisibilities
();
}
}
player
.
setSurface
(
surfaceView
.
getHolder
().
getSurface
());
player
.
setSurface
(
surfaceView
.
getHolder
().
getSurface
());
maybeStartPlayback
();
}
private
void
maybeStartPlayback
()
{
if
(
autoPlay
&&
(
player
.
getSurface
().
isValid
()
||
player
.
getSelectedTrackIndex
(
DemoPlayer
.
TYPE_VIDEO
)
==
DemoPlayer
.
DISABLED_TRACK
))
{
player
.
setPlayWhenReady
(
true
);
player
.
setPlayWhenReady
(
true
);
autoPlay
=
false
;
}
}
}
private
void
releasePlayer
()
{
private
void
releasePlayer
()
{
...
@@ -468,7 +461,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
...
@@ -468,7 +461,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
public
void
surfaceCreated
(
SurfaceHolder
holder
)
{
public
void
surfaceCreated
(
SurfaceHolder
holder
)
{
if
(
player
!=
null
)
{
if
(
player
!=
null
)
{
player
.
setSurface
(
holder
.
getSurface
());
player
.
setSurface
(
holder
.
getSurface
());
maybeStartPlayback
();
}
}
}
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
View file @
8f0d576f
...
@@ -179,10 +179,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -179,10 +179,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private
Surface
surface
;
private
Surface
surface
;
private
InternalRendererBuilderCallback
builderCallback
;
private
InternalRendererBuilderCallback
builderCallback
;
private
TrackRenderer
videoRenderer
;
private
TrackRenderer
videoRenderer
;
private
int
videoTrackToRestore
;
private
MultiTrackChunkSource
[]
multiTrackSources
;
private
MultiTrackChunkSource
[]
multiTrackSources
;
private
String
[][]
trackNames
;
private
String
[][]
trackNames
;
private
int
[]
selectedTracks
;
private
int
[]
selectedTracks
;
private
boolean
backgrounded
;
private
TextListener
textListener
;
private
TextListener
textListener
;
private
Id3MetadataListener
id3MetadataListener
;
private
Id3MetadataListener
id3MetadataListener
;
...
@@ -233,7 +235,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -233,7 +235,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public
void
setSurface
(
Surface
surface
)
{
public
void
setSurface
(
Surface
surface
)
{
this
.
surface
=
surface
;
this
.
surface
=
surface
;
pushSurface
AndVideoTrack
(
false
);
pushSurface
(
false
);
}
}
public
Surface
getSurface
()
{
public
Surface
getSurface
()
{
...
@@ -242,7 +244,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -242,7 +244,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public
void
blockingClearSurface
()
{
public
void
blockingClearSurface
()
{
surface
=
null
;
surface
=
null
;
pushSurface
AndVideoTrack
(
true
);
pushSurface
(
true
);
}
}
public
String
[]
getTracks
(
int
type
)
{
public
String
[]
getTracks
(
int
type
)
{
...
@@ -258,14 +260,24 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -258,14 +260,24 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
return
;
return
;
}
}
selectedTracks
[
type
]
=
index
;
selectedTracks
[
type
]
=
index
;
if
(
type
==
TYPE_VIDEO
)
{
pushSurfaceAndVideoTrack
(
false
);
}
else
{
pushTrackSelection
(
type
,
true
);
pushTrackSelection
(
type
,
true
);
if
(
type
==
TYPE_TEXT
&&
index
==
DISABLED_TRACK
&&
textListener
!=
null
)
{
if
(
type
==
TYPE_TEXT
&&
index
==
DISABLED_TRACK
&&
textListener
!=
null
)
{
textListener
.
onText
(
null
);
textListener
.
onText
(
null
);
}
}
}
}
public
void
setBackgrounded
(
boolean
backgrounded
)
{
if
(
this
.
backgrounded
==
backgrounded
)
{
return
;
}
this
.
backgrounded
=
backgrounded
;
if
(
backgrounded
)
{
videoTrackToRestore
=
getSelectedTrackIndex
(
TYPE_VIDEO
);
selectTrack
(
TYPE_VIDEO
,
DISABLED_TRACK
);
blockingClearSurface
();
}
else
{
selectTrack
(
TYPE_VIDEO
,
videoTrackToRestore
);
}
}
}
public
void
prepare
()
{
public
void
prepare
()
{
...
@@ -307,7 +319,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -307,7 +319,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
this
.
trackNames
=
trackNames
;
this
.
trackNames
=
trackNames
;
this
.
multiTrackSources
=
multiTrackSources
;
this
.
multiTrackSources
=
multiTrackSources
;
rendererBuildingState
=
RENDERER_BUILDING_STATE_BUILT
;
rendererBuildingState
=
RENDERER_BUILDING_STATE_BUILT
;
pushSurfaceAndVideoTrack
(
false
);
pushSurface
(
false
);
pushTrackSelection
(
TYPE_VIDEO
,
true
);
pushTrackSelection
(
TYPE_AUDIO
,
true
);
pushTrackSelection
(
TYPE_AUDIO
,
true
);
pushTrackSelection
(
TYPE_TEXT
,
true
);
pushTrackSelection
(
TYPE_TEXT
,
true
);
player
.
prepare
(
renderers
);
player
.
prepare
(
renderers
);
...
@@ -550,7 +563,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -550,7 +563,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
}
}
}
private
void
pushSurface
AndVideoTrack
(
boolean
blockForSurfacePush
)
{
private
void
pushSurface
(
boolean
blockForSurfacePush
)
{
if
(
rendererBuildingState
!=
RENDERER_BUILDING_STATE_BUILT
)
{
if
(
rendererBuildingState
!=
RENDERER_BUILDING_STATE_BUILT
)
{
return
;
return
;
}
}
...
@@ -562,7 +575,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
...
@@ -562,7 +575,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
player
.
sendMessage
(
player
.
sendMessage
(
videoRenderer
,
MediaCodecVideoTrackRenderer
.
MSG_SET_SURFACE
,
surface
);
videoRenderer
,
MediaCodecVideoTrackRenderer
.
MSG_SET_SURFACE
,
surface
);
}
}
pushTrackSelection
(
TYPE_VIDEO
,
surface
!=
null
&&
surface
.
isValid
());
}
}
private
void
pushTrackSelection
(
int
type
,
boolean
allowRendererEnable
)
{
private
void
pushTrackSelection
(
int
type
,
boolean
allowRendererEnable
)
{
...
...
library/src/main/java/com/google/android/exoplayer/C.java
View file @
8f0d576f
...
@@ -15,6 +15,8 @@
...
@@ -15,6 +15,8 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
;
package
com
.
google
.
android
.
exoplayer
;
import
android.media.MediaExtractor
;
/**
/**
* Defines constants that are generally useful throughout the library.
* Defines constants that are generally useful throughout the library.
*/
*/
...
@@ -40,6 +42,12 @@ public final class C {
...
@@ -40,6 +42,12 @@ public final class C {
*/
*/
public
static
final
String
UTF8_NAME
=
"UTF-8"
;
public
static
final
String
UTF8_NAME
=
"UTF-8"
;
/**
* Sample flag that indicates the sample is a synchronization sample.
*/
@SuppressWarnings
(
"InlinedApi"
)
public
static
final
int
SAMPLE_FLAG_SYNC
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
private
C
()
{}
private
C
()
{}
}
}
library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java
View file @
8f0d576f
...
@@ -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.
1
.0"
;
public
static
final
String
VERSION
=
"1.
2
.0"
;
/**
/**
* 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 001002003.
* corresponding integer version 001002003.
*/
*/
public
static
final
int
VERSION_INT
=
00100
1
000
;
public
static
final
int
VERSION_INT
=
00100
2
000
;
/**
/**
* 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/MediaCodecTrackRenderer.java
View file @
8f0d576f
...
@@ -440,9 +440,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -440,9 +440,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
checkForDiscontinuity
();
checkForDiscontinuity
();
if
(
format
==
null
)
{
if
(
format
==
null
)
{
readFormat
();
readFormat
();
}
else
if
(
codec
==
null
&&
!
shouldInitCodec
()
&&
getState
()
==
TrackRenderer
.
STATE_STARTED
)
{
}
discardSamples
(
positionUs
);
}
else
{
if
(
codec
==
null
&&
shouldInitCodec
())
{
if
(
codec
==
null
&&
shouldInitCodec
())
{
maybeInitCodec
();
maybeInitCodec
();
}
}
...
@@ -452,7 +450,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -452,7 +450,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
while
(
feedInputBuffer
(
false
))
{}
while
(
feedInputBuffer
(
false
))
{}
}
}
}
}
}
codecCounters
.
ensureUpdated
();
codecCounters
.
ensureUpdated
();
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
throw
new
ExoPlaybackException
(
e
);
throw
new
ExoPlaybackException
(
e
);
...
@@ -466,21 +463,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -466,21 +463,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
}
}
}
private
void
discardSamples
(
long
positionUs
)
throws
IOException
,
ExoPlaybackException
{
sampleHolder
.
data
=
null
;
int
result
=
SampleSource
.
SAMPLE_READ
;
while
(
result
==
SampleSource
.
SAMPLE_READ
&&
currentPositionUs
<=
positionUs
)
{
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
if
(!
sampleHolder
.
decodeOnly
)
{
currentPositionUs
=
sampleHolder
.
timeUs
;
}
}
else
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
onInputFormatChanged
(
formatHolder
);
}
}
}
private
void
checkForDiscontinuity
()
throws
IOException
,
ExoPlaybackException
{
private
void
checkForDiscontinuity
()
throws
IOException
,
ExoPlaybackException
{
if
(
codec
==
null
)
{
if
(
codec
==
null
)
{
return
;
return
;
...
@@ -590,7 +572,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
...
@@ -590,7 +572,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
if
(
waitingForFirstSyncFrame
)
{
if
(
waitingForFirstSyncFrame
)
{
// TODO: Find out if it's possible to supply samples prior to the first sync
// TODO: Find out if it's possible to supply samples prior to the first sync
// frame for HE-AAC.
// frame for HE-AAC.
if
((
sampleHolder
.
flags
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
==
0
)
{
if
((
sampleHolder
.
flags
&
C
.
SAMPLE_FLAG_SYNC
)
==
0
)
{
sampleHolder
.
data
.
clear
();
sampleHolder
.
data
.
clear
();
if
(
codecReconfigurationState
==
RECONFIGURATION_STATE_QUEUE_PENDING
)
{
if
(
codecReconfigurationState
==
RECONFIGURATION_STATE_QUEUE_PENDING
)
{
// The buffer we just cleared contained reconfiguration data. We need to re-write this
// The buffer we just cleared contained reconfiguration data. We need to re-write this
...
...
library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
View file @
8f0d576f
...
@@ -353,7 +353,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
...
@@ -353,7 +353,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
@Override
protected
boolean
shouldInitCodec
()
{
protected
boolean
shouldInitCodec
()
{
return
super
.
shouldInitCodec
()
&&
surface
!=
null
;
return
super
.
shouldInitCodec
()
&&
surface
!=
null
&&
surface
.
isValid
()
;
}
}
// Override configureCodec to provide the surface.
// Override configureCodec to provide the surface.
...
...
library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java
View file @
8f0d576f
...
@@ -479,7 +479,9 @@ public final class AudioTrack {
...
@@ -479,7 +479,9 @@ public final class AudioTrack {
/** Returns whether enough data has been supplied via {@link #handleBuffer} to begin playback. */
/** Returns whether enough data has been supplied via {@link #handleBuffer} to begin playback. */
public
boolean
hasEnoughDataToBeginPlayback
()
{
public
boolean
hasEnoughDataToBeginPlayback
()
{
return
submittedBytes
>=
minBufferSize
;
// The value of minBufferSize can be slightly less than what's actually required for playback
// to start, hence the multiplication factor.
return
submittedBytes
>
(
minBufferSize
*
3
)
/
2
;
}
}
/** Sets the playback volume. */
/** Sets the playback volume. */
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
View file @
8f0d576f
...
@@ -638,7 +638,7 @@ public final class FragmentedMp4Extractor implements Extractor {
...
@@ -638,7 +638,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
}
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
out
.
initEncryptionData
(
senc
.
length
()
-
senc
.
getPosition
());
out
.
initEncryptionData
(
senc
.
bytesLeft
());
out
.
fillEncryptionData
(
senc
);
out
.
fillEncryptionData
(
senc
);
}
}
...
@@ -696,7 +696,7 @@ public final class FragmentedMp4Extractor implements Extractor {
...
@@ -696,7 +696,7 @@ public final class FragmentedMp4Extractor implements Extractor {
offset
+=
sizes
[
i
];
offset
+=
sizes
[
i
];
}
}
return
new
SegmentIndex
(
atom
.
l
ength
(),
sizes
,
offsets
,
durationsUs
,
timesUs
);
return
new
SegmentIndex
(
atom
.
l
imit
(),
sizes
,
offsets
,
durationsUs
,
timesUs
);
}
}
private
int
readEncryptionData
(
NonBlockingInputStream
inputStream
)
{
private
int
readEncryptionData
(
NonBlockingInputStream
inputStream
)
{
...
@@ -762,7 +762,6 @@ public final class FragmentedMp4Extractor implements Extractor {
...
@@ -762,7 +762,6 @@ public final class FragmentedMp4Extractor implements Extractor {
return
0
;
return
0
;
}
}
@SuppressLint
(
"InlinedApi"
)
private
int
readSample
(
NonBlockingInputStream
inputStream
,
int
sampleSize
,
SampleHolder
out
)
{
private
int
readSample
(
NonBlockingInputStream
inputStream
,
int
sampleSize
,
SampleHolder
out
)
{
if
(
out
==
null
)
{
if
(
out
==
null
)
{
return
RESULT_NEED_SAMPLE_HOLDER
;
return
RESULT_NEED_SAMPLE_HOLDER
;
...
@@ -770,7 +769,7 @@ public final class FragmentedMp4Extractor implements Extractor {
...
@@ -770,7 +769,7 @@ public final class FragmentedMp4Extractor implements Extractor {
out
.
timeUs
=
fragmentRun
.
getSamplePresentationTime
(
sampleIndex
)
*
1000L
;
out
.
timeUs
=
fragmentRun
.
getSamplePresentationTime
(
sampleIndex
)
*
1000L
;
out
.
flags
=
0
;
out
.
flags
=
0
;
if
(
fragmentRun
.
sampleIsSyncFrameTable
[
sampleIndex
])
{
if
(
fragmentRun
.
sampleIsSyncFrameTable
[
sampleIndex
])
{
out
.
flags
|=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
out
.
flags
|=
C
.
SAMPLE_FLAG_SYNC
;
lastSyncSampleIndex
=
sampleIndex
;
lastSyncSampleIndex
=
sampleIndex
;
}
}
if
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
sampleSize
)
{
if
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
sampleSize
)
{
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/TrackFragment.java
View file @
8f0d576f
...
@@ -113,7 +113,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
...
@@ -113,7 +113,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
* @param length The length in bytes of the encryption data.
* @param length The length in bytes of the encryption data.
*/
*/
public
void
initEncryptionData
(
int
length
)
{
public
void
initEncryptionData
(
int
length
)
{
if
(
sampleEncryptionData
==
null
||
sampleEncryptionData
.
l
ength
()
<
length
)
{
if
(
sampleEncryptionData
==
null
||
sampleEncryptionData
.
l
imit
()
<
length
)
{
sampleEncryptionData
=
new
ParsableByteArray
(
length
);
sampleEncryptionData
=
new
ParsableByteArray
(
length
);
}
}
sampleEncryptionDataLength
=
length
;
sampleEncryptionDataLength
=
length
;
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
View file @
8f0d576f
...
@@ -25,9 +25,6 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
...
@@ -25,9 +25,6 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Arrays
;
...
@@ -42,7 +39,6 @@ import java.util.concurrent.TimeUnit;
...
@@ -42,7 +39,6 @@ import java.util.concurrent.TimeUnit;
* 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>.
*/
*/
@TargetApi
(
16
)
public
final
class
WebmExtractor
implements
Extractor
{
public
final
class
WebmExtractor
implements
Extractor
{
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
...
@@ -412,7 +408,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -412,7 +408,7 @@ public final class WebmExtractor implements Extractor {
case
LACING_NONE:
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
sampleHolder
.
flags
=
keyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
sampleHolder
.
decodeOnly
=
invisible
;
sampleHolder
.
decodeOnly
=
invisible
;
sampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
sampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
sampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
...
...
library/src/main/java/com/google/android/exoplayer/hls/
BitArray
Chunk.java
→
library/src/main/java/com/google/android/exoplayer/hls/
Data
Chunk.java
View file @
8f0d576f
...
@@ -17,19 +17,21 @@ package com.google.android.exoplayer.hls;
...
@@ -17,19 +17,21 @@ package com.google.android.exoplayer.hls;
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.BitArray
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.Arrays
;
/**
/**
* An abstract base class for {@link HlsChunk} implementations where the data should be loaded into
* An abstract base class for {@link HlsChunk} implementations where the data should be loaded into
* a {@
link BitArray} and subsequently
consumed.
* a {@
code byte[]} before being
consumed.
*/
*/
public
abstract
class
BitArray
Chunk
extends
HlsChunk
{
public
abstract
class
Data
Chunk
extends
HlsChunk
{
private
static
final
int
READ_GRANULARITY
=
16
*
1024
;
private
static
final
int
READ_GRANULARITY
=
16
*
1024
;
private
final
BitArray
bitArray
;
private
byte
[]
data
;
private
int
limit
;
private
volatile
boolean
loadFinished
;
private
volatile
boolean
loadFinished
;
private
volatile
boolean
loadCanceled
;
private
volatile
boolean
loadCanceled
;
...
@@ -39,26 +41,27 @@ public abstract class BitArrayChunk extends HlsChunk {
...
@@ -39,26 +41,27 @@ public abstract class BitArrayChunk extends HlsChunk {
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* {@link Integer#MAX_VALUE}.
* @param
bitArray The {@link BitArray} into which the data should be loaded
.
* @param
data An optional recycled array that can be used as a holder for the data
.
*/
*/
public
BitArrayChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
BitArray
bitArray
)
{
public
DataChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
byte
[]
data
)
{
super
(
dataSource
,
dataSpec
);
super
(
dataSource
,
dataSpec
);
this
.
bitArray
=
bitArray
;
this
.
data
=
data
;
}
}
@Override
@Override
public
void
consume
()
throws
IOException
{
public
void
consume
()
throws
IOException
{
consume
(
bitArray
);
consume
(
data
,
limit
);
}
}
/**
/**
* Invoked by {@link #consume()}. Implementations should override this method to consume the
* Invoked by {@link #consume()}. Implementations should override this method to consume the
* loaded data.
* loaded data.
*
*
* @param bitArray The {@link BitArray} containing the loaded data.
* @param data An array containing the data.
* @param limit The limit of the data.
* @throws IOException If an error occurs consuming the loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
*/
protected
abstract
void
consume
(
BitArray
bitArray
)
throws
IOException
;
protected
abstract
void
consume
(
byte
[]
data
,
int
limit
)
throws
IOException
;
/**
/**
* Whether the whole of the chunk has been loaded.
* Whether the whole of the chunk has been loaded.
...
@@ -85,11 +88,15 @@ public abstract class BitArrayChunk extends HlsChunk {
...
@@ -85,11 +88,15 @@ public abstract class BitArrayChunk extends HlsChunk {
@Override
@Override
public
final
void
load
()
throws
IOException
,
InterruptedException
{
public
final
void
load
()
throws
IOException
,
InterruptedException
{
try
{
try
{
bitArray
.
reset
();
dataSource
.
open
(
dataSpec
);
dataSource
.
open
(
dataSpec
);
limit
=
0
;
int
bytesRead
=
0
;
int
bytesRead
=
0
;
while
(
bytesRead
!=
-
1
&&
!
loadCanceled
)
{
while
(
bytesRead
!=
-
1
&&
!
loadCanceled
)
{
bytesRead
=
bitArray
.
append
(
dataSource
,
READ_GRANULARITY
);
maybeExpandData
();
bytesRead
=
dataSource
.
read
(
data
,
limit
,
READ_GRANULARITY
);
if
(
bytesRead
!=
-
1
)
{
limit
+=
bytesRead
;
}
}
}
loadFinished
=
!
loadCanceled
;
loadFinished
=
!
loadCanceled
;
}
finally
{
}
finally
{
...
@@ -97,4 +104,14 @@ public abstract class BitArrayChunk extends HlsChunk {
...
@@ -97,4 +104,14 @@ public abstract class BitArrayChunk extends HlsChunk {
}
}
}
}
private
void
maybeExpandData
()
{
if
(
data
==
null
)
{
data
=
new
byte
[
READ_GRANULARITY
];
}
else
if
(
data
.
length
<
limit
+
READ_GRANULARITY
)
{
// The new length is calculated as (data.length + READ_GRANULARITY) rather than
// (limit + READ_GRANULARITY) in order to avoid small increments in the length.
data
=
Arrays
.
copyOf
(
data
,
data
.
length
+
READ_GRANULARITY
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java
View file @
8f0d576f
...
@@ -17,14 +17,14 @@ package com.google.android.exoplayer.hls;
...
@@ -17,14 +17,14 @@ package com.google.android.exoplayer.hls;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.hls.
TsExtractor.SamplePool
;
import
com.google.android.exoplayer.hls.
parser.TsExtractor
;
import
com.google.android.exoplayer.upstream.Aes128DataSource
;
import
com.google.android.exoplayer.upstream.Aes128DataSource
;
import
com.google.android.exoplayer.upstream.BandwidthMeter
;
import
com.google.android.exoplayer.upstream.BandwidthMeter
;
import
com.google.android.exoplayer.upstream.BufferPool
;
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.HttpDataSource.InvalidResponseCodeException
;
import
com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.BitArray
;
import
com.google.android.exoplayer.util.Util
;
import
com.google.android.exoplayer.util.Util
;
import
android.net.Uri
;
import
android.net.Uri
;
...
@@ -35,6 +35,7 @@ import java.io.ByteArrayInputStream;
...
@@ -35,6 +35,7 @@ import java.io.ByteArrayInputStream;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.math.BigInteger
;
import
java.math.BigInteger
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Locale
;
...
@@ -82,6 +83,11 @@ public class HlsChunkSource {
...
@@ -82,6 +83,11 @@ public class HlsChunkSource {
public
static
final
int
ADAPTIVE_MODE_ABRUPT
=
3
;
public
static
final
int
ADAPTIVE_MODE_ABRUPT
=
3
;
/**
/**
* The default target buffer size in bytes.
*/
public
static
final
int
DEFAULT_TARGET_BUFFER_SIZE
=
18
*
1024
*
1024
;
/**
* The default target buffer duration in milliseconds.
* The default target buffer duration in milliseconds.
*/
*/
public
static
final
long
DEFAULT_TARGET_BUFFER_DURATION_MS
=
40000
;
public
static
final
long
DEFAULT_TARGET_BUFFER_DURATION_MS
=
40000
;
...
@@ -101,20 +107,21 @@ public class HlsChunkSource {
...
@@ -101,20 +107,21 @@ public class HlsChunkSource {
private
static
final
String
TAG
=
"HlsChunkSource"
;
private
static
final
String
TAG
=
"HlsChunkSource"
;
private
static
final
float
BANDWIDTH_FRACTION
=
0.8f
;
private
static
final
float
BANDWIDTH_FRACTION
=
0.8f
;
private
final
SamplePool
samplePool
=
new
TsExtractor
.
SamplePool
()
;
private
final
BufferPool
bufferPool
;
private
final
DataSource
upstreamDataSource
;
private
final
DataSource
upstreamDataSource
;
private
final
HlsPlaylistParser
playlistParser
;
private
final
HlsPlaylistParser
playlistParser
;
private
final
Variant
[]
enabledVariants
;
private
final
Variant
[]
enabledVariants
;
private
final
BandwidthMeter
bandwidthMeter
;
private
final
BandwidthMeter
bandwidthMeter
;
private
final
BitArray
bitArray
;
private
final
int
adaptiveMode
;
private
final
int
adaptiveMode
;
private
final
Uri
baseUri
;
private
final
Uri
baseUri
;
private
final
int
maxWidth
;
private
final
int
maxWidth
;
private
final
int
maxHeight
;
private
final
int
maxHeight
;
private
final
int
targetBufferSize
;
private
final
long
targetBufferDurationUs
;
private
final
long
targetBufferDurationUs
;
private
final
long
minBufferDurationToSwitchUpUs
;
private
final
long
minBufferDurationToSwitchUpUs
;
private
final
long
maxBufferDurationToSwitchDownUs
;
private
final
long
maxBufferDurationToSwitchDownUs
;
/* package */
byte
[]
scratchSpace
;
/* package */
final
HlsMediaPlaylist
[]
mediaPlaylists
;
/* package */
final
HlsMediaPlaylist
[]
mediaPlaylists
;
/* package */
final
boolean
[]
mediaPlaylistBlacklistFlags
;
/* package */
final
boolean
[]
mediaPlaylistBlacklistFlags
;
/* package */
final
long
[]
lastMediaPlaylistLoadTimesMs
;
/* package */
final
long
[]
lastMediaPlaylistLoadTimesMs
;
...
@@ -130,8 +137,8 @@ public class HlsChunkSource {
...
@@ -130,8 +137,8 @@ public class HlsChunkSource {
public
HlsChunkSource
(
DataSource
dataSource
,
String
playlistUrl
,
HlsPlaylist
playlist
,
public
HlsChunkSource
(
DataSource
dataSource
,
String
playlistUrl
,
HlsPlaylist
playlist
,
BandwidthMeter
bandwidthMeter
,
int
[]
variantIndices
,
int
adaptiveMode
)
{
BandwidthMeter
bandwidthMeter
,
int
[]
variantIndices
,
int
adaptiveMode
)
{
this
(
dataSource
,
playlistUrl
,
playlist
,
bandwidthMeter
,
variantIndices
,
adaptiveMode
,
this
(
dataSource
,
playlistUrl
,
playlist
,
bandwidthMeter
,
variantIndices
,
adaptiveMode
,
DEFAULT_TARGET_BUFFER_
DURATION_MS
,
DEFAULT_MIN_BUFFER_TO_SWITCH_UP
_MS
,
DEFAULT_TARGET_BUFFER_
SIZE
,
DEFAULT_TARGET_BUFFER_DURATION
_MS
,
DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS
);
DEFAULT_M
IN_BUFFER_TO_SWITCH_UP_MS
,
DEFAULT_M
AX_BUFFER_TO_SWITCH_DOWN_MS
);
}
}
/**
/**
...
@@ -144,9 +151,10 @@ public class HlsChunkSource {
...
@@ -144,9 +151,10 @@ public class HlsChunkSource {
* @param adaptiveMode The mode for switching from one variant to another. One of
* @param adaptiveMode The mode for switching from one variant to another. One of
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
* {@link #ADAPTIVE_MODE_SPLICE}.
* {@link #ADAPTIVE_MODE_SPLICE}.
* @param targetBufferSize The targeted buffer size in bytes. The buffer will not be filled more
* than one chunk beyond this amount of data.
* @param targetBufferDurationMs The targeted duration of media to buffer ahead of the current
* @param targetBufferDurationMs The targeted duration of media to buffer ahead of the current
* playback position. Note that the greater this value, the greater the amount of memory
* playback position. The buffer will not be filled more than one chunk beyond this position.
* that will be consumed.
* @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered
* @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered
* for a switch to a higher quality variant to be considered.
* for a switch to a higher quality variant to be considered.
* @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered
* @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered
...
@@ -154,17 +162,18 @@ public class HlsChunkSource {
...
@@ -154,17 +162,18 @@ public class HlsChunkSource {
*/
*/
public
HlsChunkSource
(
DataSource
dataSource
,
String
playlistUrl
,
HlsPlaylist
playlist
,
public
HlsChunkSource
(
DataSource
dataSource
,
String
playlistUrl
,
HlsPlaylist
playlist
,
BandwidthMeter
bandwidthMeter
,
int
[]
variantIndices
,
int
adaptiveMode
,
BandwidthMeter
bandwidthMeter
,
int
[]
variantIndices
,
int
adaptiveMode
,
long
targetBufferDurationMs
,
long
minBufferDurationToSwitchUpMs
,
int
targetBufferSize
,
long
targetBufferDurationMs
,
long
minBufferDurationToSwitchUpMs
,
long
maxBufferDurationToSwitchDownMs
)
{
long
maxBufferDurationToSwitchDownMs
)
{
this
.
upstreamDataSource
=
dataSource
;
this
.
upstreamDataSource
=
dataSource
;
this
.
bandwidthMeter
=
bandwidthMeter
;
this
.
bandwidthMeter
=
bandwidthMeter
;
this
.
adaptiveMode
=
adaptiveMode
;
this
.
adaptiveMode
=
adaptiveMode
;
this
.
targetBufferSize
=
targetBufferSize
;
targetBufferDurationUs
=
targetBufferDurationMs
*
1000
;
targetBufferDurationUs
=
targetBufferDurationMs
*
1000
;
minBufferDurationToSwitchUpUs
=
minBufferDurationToSwitchUpMs
*
1000
;
minBufferDurationToSwitchUpUs
=
minBufferDurationToSwitchUpMs
*
1000
;
maxBufferDurationToSwitchDownUs
=
maxBufferDurationToSwitchDownMs
*
1000
;
maxBufferDurationToSwitchDownUs
=
maxBufferDurationToSwitchDownMs
*
1000
;
baseUri
=
playlist
.
baseUri
;
baseUri
=
playlist
.
baseUri
;
bitArray
=
new
BitArray
();
playlistParser
=
new
HlsPlaylistParser
();
playlistParser
=
new
HlsPlaylistParser
();
bufferPool
=
new
BufferPool
(
256
*
1024
);
if
(
playlist
.
type
==
HlsPlaylist
.
TYPE_MEDIA
)
{
if
(
playlist
.
type
==
HlsPlaylist
.
TYPE_MEDIA
)
{
enabledVariants
=
new
Variant
[]
{
new
Variant
(
0
,
playlistUrl
,
0
,
null
,
-
1
,
-
1
)};
enabledVariants
=
new
Variant
[]
{
new
Variant
(
0
,
playlistUrl
,
0
,
null
,
-
1
,
-
1
)};
...
@@ -225,8 +234,9 @@ public class HlsChunkSource {
...
@@ -225,8 +234,9 @@ public class HlsChunkSource {
public
HlsChunk
getChunkOperation
(
TsChunk
previousTsChunk
,
long
seekPositionUs
,
public
HlsChunk
getChunkOperation
(
TsChunk
previousTsChunk
,
long
seekPositionUs
,
long
playbackPositionUs
)
{
long
playbackPositionUs
)
{
if
(
previousTsChunk
!=
null
&&
(
previousTsChunk
.
isLastChunk
if
(
previousTsChunk
!=
null
&&
(
previousTsChunk
.
isLastChunk
||
previousTsChunk
.
endTimeUs
-
playbackPositionUs
>=
targetBufferDurationUs
))
{
||
previousTsChunk
.
endTimeUs
-
playbackPositionUs
>=
targetBufferDurationUs
)
// We're either finished, or we have the target amount of data buffered.
||
bufferPool
.
getAllocatedSize
()
>=
targetBufferSize
)
{
// We're either finished, or we have the target amount of data or time buffered.
return
null
;
return
null
;
}
}
...
@@ -324,7 +334,7 @@ public class HlsChunkSource {
...
@@ -324,7 +334,7 @@ public class HlsChunkSource {
// Configure the extractor that will read the chunk.
// Configure the extractor that will read the chunk.
TsExtractor
extractor
;
TsExtractor
extractor
;
if
(
previousTsChunk
==
null
||
segment
.
discontinuity
||
switchingVariant
||
liveDiscontinuity
)
{
if
(
previousTsChunk
==
null
||
segment
.
discontinuity
||
switchingVariant
||
liveDiscontinuity
)
{
extractor
=
new
TsExtractor
(
startTimeUs
,
s
amplePool
,
switchingVariantSpliced
);
extractor
=
new
TsExtractor
(
startTimeUs
,
s
witchingVariantSpliced
,
bufferPool
);
}
else
{
}
else
{
extractor
=
previousTsChunk
.
extractor
;
extractor
=
previousTsChunk
.
extractor
;
}
}
...
@@ -526,7 +536,7 @@ public class HlsChunkSource {
...
@@ -526,7 +536,7 @@ public class HlsChunkSource {
return
true
;
return
true
;
}
}
private
class
MediaPlaylistChunk
extends
BitArray
Chunk
{
private
class
MediaPlaylistChunk
extends
Data
Chunk
{
@SuppressWarnings
(
"hiding"
)
@SuppressWarnings
(
"hiding"
)
/* package */
final
int
variantIndex
;
/* package */
final
int
variantIndex
;
...
@@ -535,37 +545,38 @@ public class HlsChunkSource {
...
@@ -535,37 +545,38 @@ public class HlsChunkSource {
public
MediaPlaylistChunk
(
int
variantIndex
,
DataSource
dataSource
,
DataSpec
dataSpec
,
public
MediaPlaylistChunk
(
int
variantIndex
,
DataSource
dataSource
,
DataSpec
dataSpec
,
Uri
playlistBaseUri
)
{
Uri
playlistBaseUri
)
{
super
(
dataSource
,
dataSpec
,
bitArray
);
super
(
dataSource
,
dataSpec
,
scratchSpace
);
this
.
variantIndex
=
variantIndex
;
this
.
variantIndex
=
variantIndex
;
this
.
playlistBaseUri
=
playlistBaseUri
;
this
.
playlistBaseUri
=
playlistBaseUri
;
}
}
@Override
@Override
protected
void
consume
(
BitArray
data
)
throws
IOException
{
protected
void
consume
(
byte
[]
data
,
int
limit
)
throws
IOException
{
HlsPlaylist
playlist
=
playlistParser
.
parse
(
HlsPlaylist
playlist
=
playlistParser
.
parse
(
new
ByteArrayInputStream
(
data
,
0
,
limit
),
new
ByteArrayInputStream
(
data
.
getData
(),
0
,
data
.
bytesLeft
()),
null
,
null
,
null
,
null
,
playlistBaseUri
);
playlistBaseUri
);
Assertions
.
checkState
(
playlist
.
type
==
HlsPlaylist
.
TYPE_MEDIA
);
Assertions
.
checkState
(
playlist
.
type
==
HlsPlaylist
.
TYPE_MEDIA
);
HlsMediaPlaylist
mediaPlaylist
=
(
HlsMediaPlaylist
)
playlist
;
HlsMediaPlaylist
mediaPlaylist
=
(
HlsMediaPlaylist
)
playlist
;
setMediaPlaylist
(
variantIndex
,
mediaPlaylist
);
setMediaPlaylist
(
variantIndex
,
mediaPlaylist
);
// Recycle the allocation.
scratchSpace
=
data
;
}
}
}
}
private
class
EncryptionKeyChunk
extends
BitArray
Chunk
{
private
class
EncryptionKeyChunk
extends
Data
Chunk
{
private
final
String
iv
;
private
final
String
iv
;
public
EncryptionKeyChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
String
iv
)
{
public
EncryptionKeyChunk
(
DataSource
dataSource
,
DataSpec
dataSpec
,
String
iv
)
{
super
(
dataSource
,
dataSpec
,
bitArray
);
super
(
dataSource
,
dataSpec
,
scratchSpace
);
this
.
iv
=
iv
;
this
.
iv
=
iv
;
}
}
@Override
@Override
protected
void
consume
(
BitArray
data
)
throws
IOException
{
protected
void
consume
(
byte
[]
data
,
int
limit
)
throws
IOException
{
byte
[]
secretKey
=
new
byte
[
data
.
bytesLeft
()]
;
initEncryptedDataSource
(
dataSpec
.
uri
,
iv
,
Arrays
.
copyOf
(
data
,
limit
))
;
data
.
readBytes
(
secretKey
,
0
,
secretKey
.
length
);
// Recycle the allocation.
initEncryptedDataSource
(
dataSpec
.
uri
,
iv
,
secretKey
)
;
scratchSpace
=
data
;
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java
View file @
8f0d576f
...
@@ -21,6 +21,7 @@ import com.google.android.exoplayer.SampleHolder;
...
@@ -21,6 +21,7 @@ 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
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.hls.parser.TsExtractor
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Assertions
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java
View file @
8f0d576f
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
hls
;
package
com
.
google
.
android
.
exoplayer
.
hls
;
import
com.google.android.exoplayer.hls.parser.TsExtractor
;
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
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
deleted
100644 → 0
View file @
ccac9fad
/*
* 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.SampleHolder
;
import
com.google.android.exoplayer.text.eia608.Eia608Parser
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.BitArray
;
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.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.List
;
import
java.util.TreeSet
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
*/
public
final
class
TsExtractor
{
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
TS_STREAM_TYPE_ID3
=
0x15
;
private
static
final
int
TS_STREAM_TYPE_EIA608
=
0x100
;
// 0xFF + 1
private
static
final
long
MAX_PTS
=
0x1FFFFFFFF
L
;
private
final
BitArray
tsPacketBuffer
;
private
final
SparseArray
<
SampleQueue
>
sampleQueues
;
// Indexed by streamType
private
final
SparseArray
<
TsPayloadReader
>
tsPayloadReaders
;
// Indexed by pid
private
final
SamplePool
samplePool
;
private
final
boolean
shouldSpliceIn
;
private
final
long
firstSampleTimestamp
;
// Accessed only by the consuming thread.
private
boolean
spliceConfigured
;
// Accessed only by the loading thread.
private
long
timestampOffsetUs
;
private
long
lastPts
;
// Accessed by both the loading and consuming threads.
private
volatile
boolean
prepared
;
/* package */
volatile
long
largestParsedTimestampUs
;
public
TsExtractor
(
long
firstSampleTimestamp
,
SamplePool
samplePool
,
boolean
shouldSpliceIn
)
{
this
.
firstSampleTimestamp
=
firstSampleTimestamp
;
this
.
samplePool
=
samplePool
;
this
.
shouldSpliceIn
=
shouldSpliceIn
;
tsPacketBuffer
=
new
BitArray
();
sampleQueues
=
new
SparseArray
<
SampleQueue
>();
tsPayloadReaders
=
new
SparseArray
<
TsPayloadReader
>();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
largestParsedTimestampUs
=
Long
.
MIN_VALUE
;
lastPts
=
Long
.
MIN_VALUE
;
}
/**
* 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
sampleQueues
.
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
sampleQueues
.
valueAt
(
track
).
getMediaFormat
();
}
/**
* Whether the extractor is prepared.
*
* @return True if the extractor is prepared. False otherwise.
*/
public
boolean
isPrepared
()
{
return
prepared
;
}
/**
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
* <p>
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
*/
public
void
release
()
{
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
sampleQueues
.
valueAt
(
i
).
release
();
}
}
/**
* Attempts to configure a splice from this extractor to the next.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
*
* @param nextExtractor The extractor being spliced to.
*/
public
void
configureSpliceTo
(
TsExtractor
nextExtractor
)
{
Assertions
.
checkState
(
prepared
);
if
(
spliceConfigured
||
!
nextExtractor
.
shouldSpliceIn
||
!
nextExtractor
.
isPrepared
())
{
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
// the next extractor isn't ready to be spliced in.
return
;
}
boolean
spliceConfigured
=
true
;
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
spliceConfigured
&=
sampleQueues
.
valueAt
(
i
).
configureSpliceTo
(
nextExtractor
.
sampleQueues
.
valueAt
(
i
));
}
this
.
spliceConfigured
=
spliceConfigured
;
return
;
}
/**
* Gets the largest timestamp of any sample parsed by the extractor.
*
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
*/
public
long
getLargestSampleTimestamp
()
{
return
largestParsedTimestampUs
;
}
/**
* Gets the next sample for the specified track.
*
* @param track The track from which to read.
* @param out A {@link SampleHolder} into which the next sample should be read.
* @return True if a sample was read. False otherwise.
*/
public
boolean
getSample
(
int
track
,
SampleHolder
out
)
{
Assertions
.
checkState
(
prepared
);
SampleQueue
sampleQueue
=
sampleQueues
.
valueAt
(
track
);
Sample
sample
=
sampleQueue
.
poll
();
if
(
sample
==
null
)
{
return
false
;
}
convert
(
sample
,
out
);
sampleQueue
.
recycle
(
sample
);
return
true
;
}
/**
* Discards samples for the specified track up to the specified time.
*
* @param track The track from which samples should be discarded.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public
void
discardUntil
(
int
track
,
long
timeUs
)
{
Assertions
.
checkState
(
prepared
);
sampleQueues
.
valueAt
(
track
).
discardUntil
(
timeUs
);
}
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
* specified track.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for the specified track. False otherwise.
*/
public
boolean
hasSamples
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
sampleQueues
.
valueAt
(
track
).
peek
()
!=
null
;
}
private
boolean
checkPrepared
()
{
int
pesPayloadReaderCount
=
sampleQueues
.
size
();
if
(
pesPayloadReaderCount
==
0
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
pesPayloadReaderCount
;
i
++)
{
if
(!
sampleQueues
.
valueAt
(
i
).
hasMediaFormat
())
{
return
false
;
}
}
return
true
;
}
/**
* Reads up to a single TS packet.
*
* @param dataSource The {@link DataSource} from which to read.
* @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source.
*/
public
int
read
(
DataSource
dataSource
)
throws
IOException
{
int
read
=
tsPacketBuffer
.
append
(
dataSource
,
TS_PACKET_SIZE
-
tsPacketBuffer
.
bytesLeft
());
if
(
read
==
-
1
)
{
return
-
1
;
}
if
(
tsPacketBuffer
.
bytesLeft
()
!=
TS_PACKET_SIZE
)
{
return
read
;
}
// Parse TS header.
// Check sync byte.
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
if
(
syncByte
!=
TS_SYNC_BYTE
)
{
return
read
;
}
// Skip transportErrorIndicator.
tsPacketBuffer
.
skipBits
(
1
);
boolean
payloadUnitStartIndicator
=
tsPacketBuffer
.
readBit
();
// Skip transportPriority.
tsPacketBuffer
.
skipBits
(
1
);
int
pid
=
tsPacketBuffer
.
readBits
(
13
);
// Skip transport_scrambling_control.
tsPacketBuffer
.
skipBits
(
2
);
boolean
adaptationFieldExists
=
tsPacketBuffer
.
readBit
();
boolean
payloadExists
=
tsPacketBuffer
.
readBit
();
// Skip continuityCounter.
tsPacketBuffer
.
skipBits
(
4
);
// Read the adaptation field.
if
(
adaptationFieldExists
)
{
int
adaptationFieldLength
=
tsPacketBuffer
.
readBits
(
8
);
tsPacketBuffer
.
skipBytes
(
adaptationFieldLength
);
}
// Read Payload.
if
(
payloadExists
)
{
TsPayloadReader
payloadReader
=
tsPayloadReaders
.
get
(
pid
);
if
(
payloadReader
!=
null
)
{
payloadReader
.
read
(
tsPacketBuffer
,
payloadUnitStartIndicator
);
}
}
if
(!
prepared
)
{
prepared
=
checkPrepared
();
}
tsPacketBuffer
.
reset
();
return
read
;
}
@SuppressLint
(
"InlinedApi"
)
private
void
convert
(
Sample
in
,
SampleHolder
out
)
{
if
(
out
.
data
==
null
||
out
.
data
.
capacity
()
<
in
.
size
)
{
out
.
replaceBuffer
(
in
.
size
);
}
if
(
out
.
data
!=
null
)
{
out
.
data
.
put
(
in
.
data
,
0
,
in
.
size
);
}
out
.
size
=
in
.
size
;
out
.
flags
=
in
.
isKeyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
out
.
timeUs
=
in
.
timeUs
;
}
/**
* Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound.
*
* @param pts The raw PTS value.
* @return The corresponding time in microseconds.
*/
/* package */
long
ptsToTimeUs
(
long
pts
)
{
if
(
lastPts
!=
Long
.
MIN_VALUE
)
{
// The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1),
// and we need to snap to the one closest to lastPts.
long
closestWrapCount
=
(
lastPts
+
(
MAX_PTS
/
2
))
/
MAX_PTS
;
long
ptsWrapBelow
=
pts
+
(
MAX_PTS
*
(
closestWrapCount
-
1
));
long
ptsWrapAbove
=
pts
+
(
MAX_PTS
*
closestWrapCount
);
pts
=
Math
.
abs
(
ptsWrapBelow
-
lastPts
)
<
Math
.
abs
(
ptsWrapAbove
-
lastPts
)
?
ptsWrapBelow
:
ptsWrapAbove
;
}
// Calculate the corresponding timestamp.
long
timeUs
=
(
pts
*
C
.
MICROS_PER_SECOND
)
/
90000
;
// If we haven't done the initial timestamp adjustment, do it now.
if
(
lastPts
==
Long
.
MIN_VALUE
)
{
timestampOffsetUs
=
firstSampleTimestamp
-
timeUs
;
}
// Record the adjusted PTS to adjust for wraparound next time.
lastPts
=
pts
;
return
timeUs
+
timestampOffsetUs
;
}
/**
* Parses payload data.
*/
private
abstract
static
class
TsPayloadReader
{
public
abstract
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
);
}
/**
* Parses Program Association Table data.
*/
private
class
PatReader
extends
TsPayloadReader
{
@Override
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
int
pointerField
=
tsBuffer
.
readBits
(
8
);
tsBuffer
.
skipBytes
(
pointerField
);
}
tsBuffer
.
skipBits
(
12
);
// 8+1+1+2
int
sectionLength
=
tsBuffer
.
readBits
(
12
);
tsBuffer
.
skipBits
(
40
);
// 16+2+5+1+8+8
int
programCount
=
(
sectionLength
-
9
)
/
4
;
for
(
int
i
=
0
;
i
<
programCount
;
i
++)
{
tsBuffer
.
skipBits
(
19
);
int
pid
=
tsBuffer
.
readBits
(
13
);
tsPayloadReaders
.
put
(
pid
,
new
PmtReader
());
}
// Skip CRC_32.
}
}
/**
* Parses Program Map Table.
*/
private
class
PmtReader
extends
TsPayloadReader
{
@Override
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
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
);
// Skip the descriptors.
tsBuffer
.
skipBytes
(
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
);
// Skip the descriptors.
tsBuffer
.
skipBytes
(
esInfoLength
);
entriesSize
-=
esInfoLength
+
5
;
if
(
sampleQueues
.
get
(
streamType
)
!=
null
)
{
continue
;
}
PesPayloadReader
pesPayloadReader
=
null
;
switch
(
streamType
)
{
case
TS_STREAM_TYPE_AAC:
pesPayloadReader
=
new
AdtsReader
(
samplePool
);
break
;
case
TS_STREAM_TYPE_H264:
SeiReader
seiReader
=
new
SeiReader
(
samplePool
);
sampleQueues
.
put
(
TS_STREAM_TYPE_EIA608
,
seiReader
);
pesPayloadReader
=
new
H264Reader
(
samplePool
,
seiReader
);
break
;
case
TS_STREAM_TYPE_ID3:
pesPayloadReader
=
new
Id3Reader
(
samplePool
);
break
;
}
if
(
pesPayloadReader
!=
null
)
{
sampleQueues
.
put
(
streamType
,
pesPayloadReader
);
tsPayloadReaders
.
put
(
elementaryPid
,
new
PesReader
(
pesPayloadReader
));
}
}
// Skip CRC_32.
}
}
/**
* Parses PES packet data and extracts samples.
*/
private
class
PesReader
extends
TsPayloadReader
{
// Reusable buffer for incomplete PES data.
private
final
BitArray
pesBuffer
;
// Parses PES payload and extracts individual samples.
private
final
PesPayloadReader
pesPayloadReader
;
private
int
packetLength
;
public
PesReader
(
PesPayloadReader
pesPayloadReader
)
{
this
.
pesPayloadReader
=
pesPayloadReader
;
this
.
packetLength
=
-
1
;
pesBuffer
=
new
BitArray
();
}
@Override
public
void
read
(
BitArray
tsBuffer
,
boolean
payloadUnitStartIndicator
)
{
if
(
payloadUnitStartIndicator
&&
!
pesBuffer
.
isEmpty
())
{
if
(
packetLength
==
0
)
{
// The length of the previous packet was unspecified. We've now seen the start of the
// next one, so consume the previous packet's body.
readPacketBody
();
}
else
{
// Either we didn't have enough data to read the length of the previous packet, or we
// did read the length but didn't receive that amount of data. Neither case is expected.
Log
.
w
(
TAG
,
"Unexpected packet fragment of length "
+
pesBuffer
.
bytesLeft
());
pesBuffer
.
reset
();
packetLength
=
-
1
;
}
}
pesBuffer
.
append
(
tsBuffer
,
tsBuffer
.
bytesLeft
());
if
(
packetLength
==
-
1
&&
pesBuffer
.
bytesLeft
()
>=
6
)
{
// We haven't read the start of the packet, but have enough data to do so.
readPacketStart
();
}
if
(
packetLength
>
0
&&
pesBuffer
.
bytesLeft
()
>=
packetLength
)
{
// The packet length was specified and we now have the whole packet. Read it.
readPacketBody
();
}
}
private
void
readPacketStart
()
{
int
startCodePrefix
=
pesBuffer
.
readBits
(
24
);
if
(
startCodePrefix
!=
0x000001
)
{
Log
.
w
(
TAG
,
"Unexpected start code prefix: "
+
startCodePrefix
);
pesBuffer
.
reset
();
packetLength
=
-
1
;
}
else
{
// TODO: Read and use stream_id.
pesBuffer
.
skipBits
(
8
);
// Skip stream_id.
packetLength
=
pesBuffer
.
readBits
(
16
);
}
}
private
void
readPacketBody
()
{
// Skip some fields/flags.
// TODO: might need to use data_alignment_indicator.
pesBuffer
.
skipBits
(
8
);
// 2+2+1+1+1+1
boolean
ptsFlag
=
pesBuffer
.
readBit
();
// Skip DTS flag.
pesBuffer
.
skipBits
(
1
);
// Skip some fields/flags.
pesBuffer
.
skipBits
(
6
);
// 1+1+1+1+1+1
int
headerDataLength
=
pesBuffer
.
readBits
(
8
);
if
(
headerDataLength
==
0
)
{
headerDataLength
=
pesBuffer
.
bytesLeft
();
}
long
timeUs
=
0
;
if
(
ptsFlag
)
{
// 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
=
ptsToTimeUs
(
pts
);
// Skip the rest of the header.
pesBuffer
.
skipBytes
(
headerDataLength
-
5
);
}
else
{
// Skip the rest of the header.
pesBuffer
.
skipBytes
(
headerDataLength
);
}
int
payloadSize
;
if
(
packetLength
==
0
)
{
// If pesPacketLength is not specified read all available data.
payloadSize
=
pesBuffer
.
bytesLeft
();
}
else
{
payloadSize
=
packetLength
-
headerDataLength
-
3
;
}
pesPayloadReader
.
read
(
pesBuffer
,
payloadSize
,
timeUs
);
pesBuffer
.
reset
();
packetLength
=
-
1
;
}
}
/**
* A queue of extracted samples together with their corresponding {@link MediaFormat}.
*/
private
abstract
class
SampleQueue
{
@SuppressWarnings
(
"hiding"
)
private
final
SamplePool
samplePool
;
// Accessed only by the consuming thread.
private
boolean
needKeyframe
;
private
long
lastReadTimeUs
;
private
long
spliceOutTimeUs
;
// Accessed by both the loading and consuming threads.
private
volatile
MediaFormat
mediaFormat
;
protected
SampleQueue
(
SamplePool
samplePool
)
{
this
.
samplePool
=
samplePool
;
needKeyframe
=
true
;
lastReadTimeUs
=
Long
.
MIN_VALUE
;
spliceOutTimeUs
=
Long
.
MIN_VALUE
;
}
public
boolean
hasMediaFormat
()
{
return
mediaFormat
!=
null
;
}
public
MediaFormat
getMediaFormat
()
{
return
mediaFormat
;
}
protected
void
setMediaFormat
(
MediaFormat
mediaFormat
)
{
this
.
mediaFormat
=
mediaFormat
;
}
/**
* Removes and returns the next sample from the queue.
* <p>
* The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples
* queued prior to the first keyframe are discarded.
*
* @return The next sample from the queue, or null if a sample isn't available.
*/
public
Sample
poll
()
{
Sample
head
=
peek
();
if
(
head
!=
null
)
{
internalPollSample
();
needKeyframe
=
false
;
lastReadTimeUs
=
head
.
timeUs
;
}
return
head
;
}
/**
* Like {@link #poll()}, except the returned sample is not removed from the queue.
*
* @return The next sample from the queue, or null if a sample isn't available.
*/
public
Sample
peek
()
{
Sample
head
=
internalPeekSample
();
if
(
needKeyframe
)
{
// Peeking discard of samples until we find a keyframe or run out of available samples.
while
(
head
!=
null
&&
!
head
.
isKeyframe
)
{
recycle
(
head
);
internalPollSample
();
head
=
internalPeekSample
();
}
}
if
(
head
==
null
)
{
return
null
;
}
if
(
spliceOutTimeUs
!=
Long
.
MIN_VALUE
&&
head
.
timeUs
>=
spliceOutTimeUs
)
{
// The sample is later than the time this queue is spliced out.
recycle
(
head
);
internalPollSample
();
return
null
;
}
return
head
;
}
/**
* Discards samples from the queue up to the specified time.
*
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public
void
discardUntil
(
long
timeUs
)
{
Sample
head
=
peek
();
while
(
head
!=
null
&&
head
.
timeUs
<
timeUs
)
{
recycle
(
head
);
internalPollSample
();
head
=
internalPeekSample
();
// We're discarding at least one sample, so any subsequent read will need to start at
// a keyframe.
needKeyframe
=
true
;
}
lastReadTimeUs
=
Long
.
MIN_VALUE
;
}
/**
* Clears the queue.
*/
public
void
release
()
{
Sample
toRecycle
=
internalPollSample
();
while
(
toRecycle
!=
null
)
{
recycle
(
toRecycle
);
toRecycle
=
internalPollSample
();
}
}
/**
* Recycles a sample.
*
* @param sample The sample to recycle.
*/
public
void
recycle
(
Sample
sample
)
{
samplePool
.
recycle
(
sample
);
}
/**
* Attempts to configure a splice from this queue to the next.
*
* @param nextQueue The queue being spliced to.
* @return Whether the splice was configured successfully.
*/
public
boolean
configureSpliceTo
(
SampleQueue
nextQueue
)
{
if
(
spliceOutTimeUs
!=
Long
.
MIN_VALUE
)
{
// We've already configured the splice.
return
true
;
}
long
firstPossibleSpliceTime
;
Sample
nextSample
=
internalPeekSample
();
if
(
nextSample
!=
null
)
{
firstPossibleSpliceTime
=
nextSample
.
timeUs
;
}
else
{
firstPossibleSpliceTime
=
lastReadTimeUs
+
1
;
}
Sample
nextQueueSample
=
nextQueue
.
internalPeekSample
();
while
(
nextQueueSample
!=
null
&&
(
nextQueueSample
.
timeUs
<
firstPossibleSpliceTime
||
!
nextQueueSample
.
isKeyframe
))
{
// Discard samples from the next queue for as long as they are before the earliest possible
// splice time, or not keyframes.
nextQueue
.
internalPollSample
();
nextQueueSample
=
nextQueue
.
internalPeekSample
();
}
if
(
nextQueueSample
!=
null
)
{
// We've found a keyframe in the next queue that can serve as the splice point. Set the
// splice point now.
spliceOutTimeUs
=
nextQueueSample
.
timeUs
;
return
true
;
}
return
false
;
}
/**
* Obtains a Sample object to use.
*
* @param type The type of the sample.
* @return The sample.
*/
protected
Sample
getSample
(
int
type
)
{
return
samplePool
.
get
(
type
);
}
/**
* Creates a new Sample and adds it to the queue.
*
* @param type The type of the sample.
* @param buffer The buffer to read sample data.
* @param sampleSize The size of the sample data.
* @param sampleTimeUs The sample time stamp.
* @param isKeyframe True if the sample is a keyframe. False otherwise.
*/
protected
void
addSample
(
int
type
,
BitArray
buffer
,
int
sampleSize
,
long
sampleTimeUs
,
boolean
isKeyframe
)
{
Sample
sample
=
getSample
(
type
);
addToSample
(
sample
,
buffer
,
sampleSize
);
sample
.
isKeyframe
=
isKeyframe
;
sample
.
timeUs
=
sampleTimeUs
;
addSample
(
sample
);
}
protected
void
addSample
(
Sample
sample
)
{
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sample
.
timeUs
);
internalQueueSample
(
sample
);
}
protected
void
addToSample
(
Sample
sample
,
BitArray
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
;
}
protected
abstract
Sample
internalPeekSample
();
protected
abstract
Sample
internalPollSample
();
protected
abstract
void
internalQueueSample
(
Sample
sample
);
}
/**
* Extracts individual samples from continuous byte stream, preserving original order.
*/
private
abstract
class
PesPayloadReader
extends
SampleQueue
{
private
final
ConcurrentLinkedQueue
<
Sample
>
internalQueue
;
protected
PesPayloadReader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
internalQueue
=
new
ConcurrentLinkedQueue
<
Sample
>();
}
@Override
protected
final
Sample
internalPeekSample
()
{
return
internalQueue
.
peek
();
}
@Override
protected
final
Sample
internalPollSample
()
{
return
internalQueue
.
poll
();
}
@Override
protected
final
void
internalQueueSample
(
Sample
sample
)
{
internalQueue
.
add
(
sample
);
}
public
abstract
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
}
/**
* Parses a continuous H264 byte stream and extracts individual frames.
*/
private
class
H264Reader
extends
PesPayloadReader
{
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
private
static
final
int
NAL_UNIT_TYPE_PPS
=
8
;
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
public
final
SeiReader
seiReader
;
// Used to store uncompleted sample data.
private
Sample
currentSample
;
public
H264Reader
(
SamplePool
samplePool
,
SeiReader
seiReader
)
{
super
(
samplePool
);
this
.
seiReader
=
seiReader
;
}
@Override
public
void
release
()
{
super
.
release
();
if
(
currentSample
!=
null
)
{
recycle
(
currentSample
);
currentSample
=
null
;
}
}
@Override
public
void
read
(
BitArray
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
)
{
if
(!
hasMediaFormat
()
&&
currentSample
.
isKeyframe
)
{
parseMediaFormat
(
currentSample
);
}
seiReader
.
read
(
currentSample
.
data
,
currentSample
.
size
,
currentSample
.
timeUs
);
addSample
(
currentSample
);
}
currentSample
=
getSample
(
Sample
.
TYPE_VIDEO
);
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
(
BitArray
pesBuffer
,
boolean
remainderOnly
)
{
int
offset
=
remainderOnly
?
0
:
3
;
int
audStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_AUD
,
offset
);
if
(
currentSample
!=
null
)
{
int
idrStart
=
pesBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_IDR
,
offset
);
if
(
idrStart
<
audStart
)
{
currentSample
.
isKeyframe
=
true
;
}
addToSample
(
currentSample
,
pesBuffer
,
audStart
);
}
else
{
pesBuffer
.
skipBytes
(
audStart
);
}
return
audStart
;
}
private
void
parseMediaFormat
(
Sample
sample
)
{
BitArray
bitArray
=
new
BitArray
(
sample
.
data
,
sample
.
size
);
// Locate the SPS and PPS units.
int
spsOffset
=
bitArray
.
findNextNalUnit
(
NAL_UNIT_TYPE_SPS
,
0
);
int
ppsOffset
=
bitArray
.
findNextNalUnit
(
NAL_UNIT_TYPE_PPS
,
0
);
if
(
spsOffset
==
bitArray
.
bytesLeft
()
||
ppsOffset
==
bitArray
.
bytesLeft
())
{
return
;
}
int
spsLength
=
bitArray
.
findNextNalUnit
(-
1
,
spsOffset
+
3
)
-
spsOffset
;
int
ppsLength
=
bitArray
.
findNextNalUnit
(-
1
,
ppsOffset
+
3
)
-
ppsOffset
;
byte
[]
spsData
=
new
byte
[
spsLength
];
byte
[]
ppsData
=
new
byte
[
ppsLength
];
System
.
arraycopy
(
bitArray
.
getData
(),
spsOffset
,
spsData
,
0
,
spsLength
);
System
.
arraycopy
(
bitArray
.
getData
(),
ppsOffset
,
ppsData
,
0
,
ppsLength
);
List
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>();
initializationData
.
add
(
spsData
);
initializationData
.
add
(
ppsData
);
// Unescape the SPS unit.
byte
[]
unescapedSps
=
unescapeStream
(
spsData
,
0
,
spsLength
);
bitArray
.
reset
(
unescapedSps
,
unescapedSps
.
length
);
// Parse the SPS unit
// Skip the NAL header.
bitArray
.
skipBytes
(
4
);
int
profileIdc
=
bitArray
.
readBits
(
8
);
// Skip 6 constraint bits, 2 reserved bits and level_idc.
bitArray
.
skipBytes
(
2
);
// Skip seq_parameter_set_id.
bitArray
.
readUnsignedExpGolombCodedInt
();
int
chromaFormatIdc
=
1
;
// Default is 4:2:0
if
(
profileIdc
==
100
||
profileIdc
==
110
||
profileIdc
==
122
||
profileIdc
==
244
||
profileIdc
==
44
||
profileIdc
==
83
||
profileIdc
==
86
||
profileIdc
==
118
||
profileIdc
==
128
||
profileIdc
==
138
)
{
chromaFormatIdc
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
chromaFormatIdc
==
3
)
{
// Skip separate_colour_plane_flag
bitArray
.
skipBits
(
1
);
}
// Skip bit_depth_luma_minus8
bitArray
.
readUnsignedExpGolombCodedInt
();
// Skip bit_depth_chroma_minus8
bitArray
.
readUnsignedExpGolombCodedInt
();
// Skip qpprime_y_zero_transform_bypass_flag
bitArray
.
skipBits
(
1
);
boolean
seqScalingMatrixPresentFlag
=
bitArray
.
readBit
();
if
(
seqScalingMatrixPresentFlag
)
{
int
limit
=
(
chromaFormatIdc
!=
3
)
?
8
:
12
;
for
(
int
i
=
0
;
i
<
limit
;
i
++)
{
boolean
seqScalingListPresentFlag
=
bitArray
.
readBit
();
if
(
seqScalingListPresentFlag
)
{
skipScalingList
(
bitArray
,
i
<
6
?
16
:
64
);
}
}
}
}
// Skip log2_max_frame_num_minus4
bitArray
.
readUnsignedExpGolombCodedInt
();
long
picOrderCntType
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
picOrderCntType
==
0
)
{
// Skip log2_max_pic_order_cnt_lsb_minus4
bitArray
.
readUnsignedExpGolombCodedInt
();
}
else
if
(
picOrderCntType
==
1
)
{
// Skip delta_pic_order_always_zero_flag
bitArray
.
skipBits
(
1
);
// Skip offset_for_non_ref_pic
bitArray
.
readSignedExpGolombCodedInt
();
// Skip offset_for_top_to_bottom_field
bitArray
.
readSignedExpGolombCodedInt
();
long
numRefFramesInPicOrderCntCycle
=
bitArray
.
readUnsignedExpGolombCodedInt
();
for
(
int
i
=
0
;
i
<
numRefFramesInPicOrderCntCycle
;
i
++)
{
// Skip offset_for_ref_frame[i]
bitArray
.
readUnsignedExpGolombCodedInt
();
}
}
// Skip max_num_ref_frames
bitArray
.
readUnsignedExpGolombCodedInt
();
// Skip gaps_in_frame_num_value_allowed_flag
bitArray
.
skipBits
(
1
);
int
picWidthInMbs
=
bitArray
.
readUnsignedExpGolombCodedInt
()
+
1
;
int
picHeightInMapUnits
=
bitArray
.
readUnsignedExpGolombCodedInt
()
+
1
;
boolean
frameMbsOnlyFlag
=
bitArray
.
readBit
();
int
frameHeightInMbs
=
(
2
-
(
frameMbsOnlyFlag
?
1
:
0
))
*
picHeightInMapUnits
;
if
(!
frameMbsOnlyFlag
)
{
// Skip mb_adaptive_frame_field_flag
bitArray
.
skipBits
(
1
);
}
// Skip direct_8x8_inference_flag
bitArray
.
skipBits
(
1
);
int
frameWidth
=
picWidthInMbs
*
16
;
int
frameHeight
=
frameHeightInMbs
*
16
;
boolean
frameCroppingFlag
=
bitArray
.
readBit
();
if
(
frameCroppingFlag
)
{
int
frameCropLeftOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropRightOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropTopOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropBottomOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
cropUnitX
,
cropUnitY
;
if
(
chromaFormatIdc
==
0
)
{
cropUnitX
=
1
;
cropUnitY
=
2
-
(
frameMbsOnlyFlag
?
1
:
0
);
}
else
{
int
subWidthC
=
(
chromaFormatIdc
==
3
)
?
1
:
2
;
int
subHeightC
=
(
chromaFormatIdc
==
1
)
?
2
:
1
;
cropUnitX
=
subWidthC
;
cropUnitY
=
subHeightC
*
(
2
-
(
frameMbsOnlyFlag
?
1
:
0
));
}
frameWidth
-=
(
frameCropLeftOffset
+
frameCropRightOffset
)
*
cropUnitX
;
frameHeight
-=
(
frameCropTopOffset
+
frameCropBottomOffset
)
*
cropUnitY
;
}
// Set the format.
setMediaFormat
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
frameWidth
,
frameHeight
,
initializationData
));
}
private
void
skipScalingList
(
BitArray
bitArray
,
int
size
)
{
int
lastScale
=
8
;
int
nextScale
=
8
;
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
if
(
nextScale
!=
0
)
{
int
deltaScale
=
bitArray
.
readSignedExpGolombCodedInt
();
nextScale
=
(
lastScale
+
deltaScale
+
256
)
%
256
;
}
lastScale
=
(
nextScale
==
0
)
?
lastScale
:
nextScale
;
}
}
/**
* Replaces occurrences of [0, 0, 3] with [0, 0].
* <p>
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
*/
private
byte
[]
unescapeStream
(
byte
[]
data
,
int
offset
,
int
limit
)
{
int
position
=
offset
;
List
<
Integer
>
escapePositions
=
new
ArrayList
<
Integer
>();
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
escapePositions
.
add
(
position
);
position
+=
3
;
}
}
int
escapeCount
=
escapePositions
.
size
();
int
escapedPosition
=
offset
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
byte
[]
unescapedData
=
new
byte
[
limit
-
offset
-
escapeCount
];
for
(
int
i
=
0
;
i
<
escapeCount
;
i
++)
{
int
nextEscapePosition
=
escapePositions
.
get
(
i
);
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
copyLength
);
escapedPosition
+=
copyLength
+
3
;
unescapedPosition
+=
copyLength
+
2
;
}
int
remainingLength
=
unescapedData
.
length
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
remainingLength
);
return
unescapedData
;
}
private
int
findNextUnescapeIndex
(
byte
[]
bytes
,
int
offset
,
int
limit
)
{
for
(
int
i
=
offset
;
i
<
limit
-
2
;
i
++)
{
if
(
bytes
[
i
]
==
0x00
&&
bytes
[
i
+
1
]
==
0x00
&&
bytes
[
i
+
2
]
==
0x03
)
{
return
i
;
}
}
return
limit
;
}
}
/**
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
*
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
* a sample with an earlier timestamp won't be added to it.
*/
private
class
SeiReader
extends
SampleQueue
implements
Comparator
<
Sample
>
{
// SEI data, used for Closed Captions.
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
private
final
BitArray
seiBuffer
;
private
final
TreeSet
<
Sample
>
internalQueue
;
public
SeiReader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
setMediaFormat
(
MediaFormat
.
createEia608Format
());
seiBuffer
=
new
BitArray
();
internalQueue
=
new
TreeSet
<
Sample
>(
this
);
}
@SuppressLint
(
"InlinedApi"
)
public
void
read
(
byte
[]
data
,
int
size
,
long
pesTimeUs
)
{
seiBuffer
.
reset
(
data
,
size
);
while
(
seiBuffer
.
bytesLeft
()
>
0
)
{
int
seiStart
=
seiBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_SEI
,
0
);
if
(
seiStart
==
seiBuffer
.
bytesLeft
())
{
return
;
}
seiBuffer
.
skipBytes
(
seiStart
+
4
);
int
ccDataSize
=
Eia608Parser
.
parseHeader
(
seiBuffer
);
if
(
ccDataSize
>
0
)
{
addSample
(
Sample
.
TYPE_MISC
,
seiBuffer
,
ccDataSize
,
pesTimeUs
,
true
);
}
}
}
@Override
public
int
compare
(
Sample
first
,
Sample
second
)
{
// Note - We don't expect samples to have identical timestamps.
return
first
.
timeUs
<=
second
.
timeUs
?
-
1
:
1
;
}
@Override
protected
synchronized
Sample
internalPeekSample
()
{
return
internalQueue
.
isEmpty
()
?
null
:
internalQueue
.
first
();
}
@Override
protected
synchronized
Sample
internalPollSample
()
{
return
internalQueue
.
pollFirst
();
}
@Override
protected
synchronized
void
internalQueueSample
(
Sample
sample
)
{
internalQueue
.
add
(
sample
);
}
}
/**
* Parses a continuous ADTS byte stream and extracts individual frames.
*/
private
class
AdtsReader
extends
PesPayloadReader
{
private
final
BitArray
adtsBuffer
;
private
long
timeUs
;
private
long
frameDurationUs
;
public
AdtsReader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
adtsBuffer
=
new
BitArray
();
}
@Override
public
void
read
(
BitArray
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
{
timeUs
=
pesTimeUs
+
(
frameDurationUs
*
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
);
boolean
hasCRC
=
!
adtsBuffer
.
readBit
();
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
));
frameDurationUs
=
(
C
.
MICROS_PER_SECOND
*
1024L
)
/
mediaFormat
.
sampleRate
;
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
)
{
// Skip CRC.
adtsBuffer
.
skipBytes
(
2
);
frameSize
-=
9
;
}
else
{
frameSize
-=
7
;
}
if
(
frameSize
>
adtsBuffer
.
bytesLeft
())
{
adtsBuffer
.
setByteOffset
(
adtsStartOffset
);
adtsBuffer
.
clearReadData
();
return
false
;
}
addSample
(
Sample
.
TYPE_AUDIO
,
adtsBuffer
,
frameSize
,
timeUs
,
true
);
return
true
;
}
@Override
public
void
release
()
{
super
.
release
();
adtsBuffer
.
reset
();
}
}
/**
* Parses ID3 data and extracts individual text information frames.
*/
private
class
Id3Reader
extends
PesPayloadReader
{
public
Id3Reader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
setMediaFormat
(
MediaFormat
.
createId3Format
());
}
@SuppressLint
(
"InlinedApi"
)
@Override
public
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
)
{
addSample
(
Sample
.
TYPE_MISC
,
pesBuffer
,
pesPayloadSize
,
pesTimeUs
,
true
);
}
}
/**
* A pool from which the extractor can obtain sample objects for internal use.
*
* TODO: Over time the average size of a sample in the video pool will become larger, as the
* proportion of samples in the pool that have at some point held a keyframe grows. Currently
* this leads to inefficient memory usage, since samples large enough to hold keyframes end up
* being used to hold non-keyframes. We need to fix this.
*/
public
static
class
SamplePool
{
private
static
final
int
[]
DEFAULT_SAMPLE_SIZES
;
static
{
DEFAULT_SAMPLE_SIZES
=
new
int
[
Sample
.
TYPE_COUNT
];
DEFAULT_SAMPLE_SIZES
[
Sample
.
TYPE_VIDEO
]
=
10
*
1024
;
DEFAULT_SAMPLE_SIZES
[
Sample
.
TYPE_AUDIO
]
=
512
;
DEFAULT_SAMPLE_SIZES
[
Sample
.
TYPE_MISC
]
=
512
;
}
private
final
Sample
[]
pools
;
public
SamplePool
()
{
pools
=
new
Sample
[
Sample
.
TYPE_COUNT
];
}
/* package */
synchronized
Sample
get
(
int
type
)
{
if
(
pools
[
type
]
==
null
)
{
return
new
Sample
(
type
,
DEFAULT_SAMPLE_SIZES
[
type
]);
}
Sample
sample
=
pools
[
type
];
pools
[
type
]
=
sample
.
nextInPool
;
sample
.
nextInPool
=
null
;
return
sample
;
}
/* package */
synchronized
void
recycle
(
Sample
sample
)
{
sample
.
reset
();
sample
.
nextInPool
=
pools
[
sample
.
type
];
pools
[
sample
.
type
]
=
sample
;
}
}
/**
* An internal variant of {@link SampleHolder} for internal pooling and buffering.
*/
private
static
class
Sample
{
public
static
final
int
TYPE_VIDEO
=
0
;
public
static
final
int
TYPE_AUDIO
=
1
;
public
static
final
int
TYPE_MISC
=
2
;
public
static
final
int
TYPE_COUNT
=
3
;
public
final
int
type
;
public
Sample
nextInPool
;
public
byte
[]
data
;
public
boolean
isKeyframe
;
public
int
size
;
public
long
timeUs
;
public
Sample
(
int
type
,
int
length
)
{
this
.
type
=
type
;
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
()
{
isKeyframe
=
false
;
size
=
0
;
timeUs
=
0
;
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
android.util.Pair
;
import
java.util.Collections
;
/**
* Parses a continuous ADTS byte stream and extracts individual frames.
*/
/* package */
class
AdtsReader
extends
PesPayloadReader
{
private
static
final
int
STATE_FINDING_SYNC
=
0
;
private
static
final
int
STATE_READING_HEADER
=
1
;
private
static
final
int
STATE_READING_SAMPLE
=
2
;
private
static
final
int
HEADER_SIZE
=
5
;
private
static
final
int
CRC_SIZE
=
2
;
private
final
ParsableBitArray
adtsScratch
;
private
int
state
;
private
int
bytesRead
;
// Used to find the header.
private
boolean
lastByteWasFF
;
private
boolean
hasCrc
;
// Parsed from the header.
private
long
frameDurationUs
;
private
int
sampleSize
;
// Used when reading the samples.
private
long
timeUs
;
public
AdtsReader
(
BufferPool
bufferPool
)
{
super
(
bufferPool
);
adtsScratch
=
new
ParsableBitArray
(
new
byte
[
HEADER_SIZE
+
CRC_SIZE
]);
state
=
STATE_FINDING_SYNC
;
}
@Override
public
void
consume
(
ParsableByteArray
data
,
long
pesTimeUs
,
boolean
startOfPacket
)
{
if
(
startOfPacket
)
{
timeUs
=
pesTimeUs
;
}
while
(
data
.
bytesLeft
()
>
0
)
{
switch
(
state
)
{
case
STATE_FINDING_SYNC:
if
(
skipToNextSync
(
data
))
{
bytesRead
=
0
;
state
=
STATE_READING_HEADER
;
}
break
;
case
STATE_READING_HEADER:
int
targetLength
=
hasCrc
?
HEADER_SIZE
+
CRC_SIZE
:
HEADER_SIZE
;
if
(
continueRead
(
data
,
adtsScratch
.
getData
(),
targetLength
))
{
parseHeader
();
startSample
(
timeUs
);
bytesRead
=
0
;
state
=
STATE_READING_SAMPLE
;
}
break
;
case
STATE_READING_SAMPLE:
int
bytesToRead
=
Math
.
min
(
data
.
bytesLeft
(),
sampleSize
-
bytesRead
);
appendData
(
data
,
bytesToRead
);
bytesRead
+=
bytesToRead
;
if
(
bytesRead
==
sampleSize
)
{
commitSample
(
true
);
timeUs
+=
frameDurationUs
;
bytesRead
=
0
;
state
=
STATE_FINDING_SYNC
;
}
break
;
}
}
}
@Override
public
void
packetFinished
()
{
// Do nothing.
}
/**
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed
* that the data should be written into {@code target} starting from an offset of zero.
*
* @param source The source from which to read.
* @param target The target into which data is to be read.
* @param targetLength The target length of the read.
* @return Whether the target length was reached.
*/
private
boolean
continueRead
(
ParsableByteArray
source
,
byte
[]
target
,
int
targetLength
)
{
int
bytesToRead
=
Math
.
min
(
source
.
bytesLeft
(),
targetLength
-
bytesRead
);
source
.
readBytes
(
target
,
bytesRead
,
bytesToRead
);
bytesRead
+=
bytesToRead
;
return
bytesRead
==
targetLength
;
}
/**
* Locates the next sync word, advancing the position to the byte that immediately follows it.
* If a sync word was not located, the position is advanced to the limit.
*
* @param pesBuffer The buffer whose position should be advanced.
* @return True if a sync word position was found. False otherwise.
*/
private
boolean
skipToNextSync
(
ParsableByteArray
pesBuffer
)
{
byte
[]
adtsData
=
pesBuffer
.
data
;
int
startOffset
=
pesBuffer
.
getPosition
();
int
endOffset
=
pesBuffer
.
limit
();
for
(
int
i
=
startOffset
;
i
<
endOffset
;
i
++)
{
boolean
byteIsFF
=
(
adtsData
[
i
]
&
0xFF
)
==
0xFF
;
boolean
found
=
lastByteWasFF
&&
!
byteIsFF
&&
(
adtsData
[
i
]
&
0xF0
)
==
0xF0
;
lastByteWasFF
=
byteIsFF
;
if
(
found
)
{
hasCrc
=
(
adtsData
[
i
]
&
0x1
)
==
0
;
pesBuffer
.
setPosition
(
i
+
1
);
return
true
;
}
}
pesBuffer
.
setPosition
(
endOffset
);
return
false
;
}
/**
* Parses the sample header.
*/
private
void
parseHeader
()
{
adtsScratch
.
setPosition
(
0
);
if
(!
hasMediaFormat
())
{
int
audioObjectType
=
adtsScratch
.
readBits
(
2
)
+
1
;
int
sampleRateIndex
=
adtsScratch
.
readBits
(
4
);
adtsScratch
.
skipBits
(
1
);
int
channelConfig
=
adtsScratch
.
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
));
frameDurationUs
=
(
C
.
MICROS_PER_SECOND
*
1024L
)
/
mediaFormat
.
sampleRate
;
setMediaFormat
(
mediaFormat
);
}
else
{
adtsScratch
.
skipBits
(
10
);
}
adtsScratch
.
skipBits
(
4
);
sampleSize
=
adtsScratch
.
readBits
(
13
)
-
2
/* the sync word */
-
HEADER_SIZE
;
if
(
hasCrc
)
{
sampleSize
-=
CRC_SIZE
;
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.mp4.Mp4Util
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* Parses a continuous H264 byte stream and extracts individual frames.
*/
/* package */
class
H264Reader
extends
PesPayloadReader
{
private
static
final
int
NAL_UNIT_TYPE_IDR
=
5
;
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
private
static
final
int
NAL_UNIT_TYPE_PPS
=
8
;
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
private
final
SeiReader
seiReader
;
private
final
boolean
[]
prefixFlags
;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
sei
;
private
boolean
isKeyframe
;
public
H264Reader
(
BufferPool
bufferPool
,
SeiReader
seiReader
)
{
super
(
bufferPool
);
this
.
seiReader
=
seiReader
;
prefixFlags
=
new
boolean
[
3
];
sps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SPS
,
128
);
pps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_PPS
,
128
);
sei
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SEI
,
128
);
}
@Override
public
void
consume
(
ParsableByteArray
data
,
long
pesTimeUs
,
boolean
startOfPacket
)
{
while
(
data
.
bytesLeft
()
>
0
)
{
int
offset
=
data
.
getPosition
();
int
limit
=
data
.
limit
();
byte
[]
dataArray
=
data
.
data
;
// Append the data to the buffer.
appendData
(
data
,
data
.
bytesLeft
());
// Scan the appended data, processing NAL units as they are encountered
while
(
offset
<
limit
)
{
int
nextNalUnitOffset
=
Mp4Util
.
findNalUnit
(
dataArray
,
offset
,
limit
,
prefixFlags
);
if
(
nextNalUnitOffset
<
limit
)
{
// We've seen the start of a NAL unit.
// This is the length to the start of the unit. It may be negative if the NAL unit
// actually started in previously consumed data.
int
lengthToNalUnit
=
nextNalUnitOffset
-
offset
;
if
(
lengthToNalUnit
>
0
)
{
feedNalUnitTargetBuffersData
(
dataArray
,
offset
,
nextNalUnitOffset
);
}
int
nalUnitType
=
Mp4Util
.
getNalUnitType
(
dataArray
,
nextNalUnitOffset
);
int
nalUnitOffsetInData
=
nextNalUnitOffset
-
limit
;
if
(
nalUnitType
==
NAL_UNIT_TYPE_AUD
)
{
if
(
writingSample
())
{
if
(
isKeyframe
&&
!
hasMediaFormat
()
&&
sps
.
isCompleted
()
&&
pps
.
isCompleted
())
{
parseMediaFormat
(
sps
,
pps
);
}
commitSample
(
isKeyframe
,
nalUnitOffsetInData
);
}
startSample
(
pesTimeUs
,
nalUnitOffsetInData
);
isKeyframe
=
false
;
}
else
if
(
nalUnitType
==
NAL_UNIT_TYPE_IDR
)
{
isKeyframe
=
true
;
}
// If the length to the start of the unit is negative then we wrote too many bytes to the
// NAL buffers. Discard the excess bytes when notifying that the unit has ended.
feedNalUnitTargetEnd
(
pesTimeUs
,
lengthToNalUnit
<
0
?
-
lengthToNalUnit
:
0
);
// Notify the start of the next NAL unit.
feedNalUnitTargetBuffersStart
(
nalUnitType
);
// Continue scanning the data.
offset
=
nextNalUnitOffset
+
4
;
}
else
{
feedNalUnitTargetBuffersData
(
dataArray
,
offset
,
limit
);
offset
=
limit
;
}
}
}
}
@Override
public
void
packetFinished
()
{
// Do nothing.
}
private
void
feedNalUnitTargetBuffersStart
(
int
nalUnitType
)
{
if
(!
hasMediaFormat
())
{
sps
.
startNalUnit
(
nalUnitType
);
pps
.
startNalUnit
(
nalUnitType
);
}
sei
.
startNalUnit
(
nalUnitType
);
}
private
void
feedNalUnitTargetBuffersData
(
byte
[]
dataArray
,
int
offset
,
int
limit
)
{
if
(!
hasMediaFormat
())
{
sps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
pps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
}
sei
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
}
private
void
feedNalUnitTargetEnd
(
long
pesTimeUs
,
int
discardPadding
)
{
sps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
if
(
sei
.
endNalUnit
(
discardPadding
))
{
seiReader
.
read
(
sei
.
nalData
,
0
,
pesTimeUs
);
}
}
private
void
parseMediaFormat
(
NalUnitTargetBuffer
sps
,
NalUnitTargetBuffer
pps
)
{
byte
[]
spsData
=
new
byte
[
sps
.
nalLength
];
byte
[]
ppsData
=
new
byte
[
pps
.
nalLength
];
System
.
arraycopy
(
sps
.
nalData
,
0
,
spsData
,
0
,
sps
.
nalLength
);
System
.
arraycopy
(
pps
.
nalData
,
0
,
ppsData
,
0
,
pps
.
nalLength
);
List
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>();
initializationData
.
add
(
spsData
);
initializationData
.
add
(
ppsData
);
// Unescape and then parse the SPS unit.
byte
[]
unescapedSps
=
unescapeStream
(
spsData
,
0
,
spsData
.
length
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
unescapedSps
);
bitArray
.
skipBits
(
32
);
// NAL header
int
profileIdc
=
bitArray
.
readBits
(
8
);
bitArray
.
skipBits
(
16
);
// constraint bits (6), reserved (2) and level_idc (8)
bitArray
.
readUnsignedExpGolombCodedInt
();
// seq_parameter_set_id
int
chromaFormatIdc
=
1
;
// Default is 4:2:0
if
(
profileIdc
==
100
||
profileIdc
==
110
||
profileIdc
==
122
||
profileIdc
==
244
||
profileIdc
==
44
||
profileIdc
==
83
||
profileIdc
==
86
||
profileIdc
==
118
||
profileIdc
==
128
||
profileIdc
==
138
)
{
chromaFormatIdc
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
chromaFormatIdc
==
3
)
{
bitArray
.
skipBits
(
1
);
// separate_colour_plane_flag
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// bit_depth_luma_minus8
bitArray
.
readUnsignedExpGolombCodedInt
();
// bit_depth_chroma_minus8
bitArray
.
skipBits
(
1
);
// qpprime_y_zero_transform_bypass_flag
boolean
seqScalingMatrixPresentFlag
=
bitArray
.
readBit
();
if
(
seqScalingMatrixPresentFlag
)
{
int
limit
=
(
chromaFormatIdc
!=
3
)
?
8
:
12
;
for
(
int
i
=
0
;
i
<
limit
;
i
++)
{
boolean
seqScalingListPresentFlag
=
bitArray
.
readBit
();
if
(
seqScalingListPresentFlag
)
{
skipScalingList
(
bitArray
,
i
<
6
?
16
:
64
);
}
}
}
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_max_frame_num_minus4
long
picOrderCntType
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
picOrderCntType
==
0
)
{
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_max_pic_order_cnt_lsb_minus4
}
else
if
(
picOrderCntType
==
1
)
{
bitArray
.
skipBits
(
1
);
// delta_pic_order_always_zero_flag
bitArray
.
readSignedExpGolombCodedInt
();
// offset_for_non_ref_pic
bitArray
.
readSignedExpGolombCodedInt
();
// offset_for_top_to_bottom_field
long
numRefFramesInPicOrderCntCycle
=
bitArray
.
readUnsignedExpGolombCodedInt
();
for
(
int
i
=
0
;
i
<
numRefFramesInPicOrderCntCycle
;
i
++)
{
bitArray
.
readUnsignedExpGolombCodedInt
();
// offset_for_ref_frame[i]
}
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// max_num_ref_frames
bitArray
.
skipBits
(
1
);
// gaps_in_frame_num_value_allowed_flag
int
picWidthInMbs
=
bitArray
.
readUnsignedExpGolombCodedInt
()
+
1
;
int
picHeightInMapUnits
=
bitArray
.
readUnsignedExpGolombCodedInt
()
+
1
;
boolean
frameMbsOnlyFlag
=
bitArray
.
readBit
();
int
frameHeightInMbs
=
(
2
-
(
frameMbsOnlyFlag
?
1
:
0
))
*
picHeightInMapUnits
;
if
(!
frameMbsOnlyFlag
)
{
bitArray
.
skipBits
(
1
);
// mb_adaptive_frame_field_flag
}
bitArray
.
skipBits
(
1
);
// direct_8x8_inference_flag
int
frameWidth
=
picWidthInMbs
*
16
;
int
frameHeight
=
frameHeightInMbs
*
16
;
boolean
frameCroppingFlag
=
bitArray
.
readBit
();
if
(
frameCroppingFlag
)
{
int
frameCropLeftOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropRightOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropTopOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
frameCropBottomOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
cropUnitX
,
cropUnitY
;
if
(
chromaFormatIdc
==
0
)
{
cropUnitX
=
1
;
cropUnitY
=
2
-
(
frameMbsOnlyFlag
?
1
:
0
);
}
else
{
int
subWidthC
=
(
chromaFormatIdc
==
3
)
?
1
:
2
;
int
subHeightC
=
(
chromaFormatIdc
==
1
)
?
2
:
1
;
cropUnitX
=
subWidthC
;
cropUnitY
=
subHeightC
*
(
2
-
(
frameMbsOnlyFlag
?
1
:
0
));
}
frameWidth
-=
(
frameCropLeftOffset
+
frameCropRightOffset
)
*
cropUnitX
;
frameHeight
-=
(
frameCropTopOffset
+
frameCropBottomOffset
)
*
cropUnitY
;
}
// Set the format.
setMediaFormat
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
frameWidth
,
frameHeight
,
initializationData
));
}
private
void
skipScalingList
(
ParsableBitArray
bitArray
,
int
size
)
{
int
lastScale
=
8
;
int
nextScale
=
8
;
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
if
(
nextScale
!=
0
)
{
int
deltaScale
=
bitArray
.
readSignedExpGolombCodedInt
();
nextScale
=
(
lastScale
+
deltaScale
+
256
)
%
256
;
}
lastScale
=
(
nextScale
==
0
)
?
lastScale
:
nextScale
;
}
}
/**
* Replaces occurrences of [0, 0, 3] with [0, 0].
* <p>
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
*/
private
byte
[]
unescapeStream
(
byte
[]
data
,
int
offset
,
int
limit
)
{
int
position
=
offset
;
List
<
Integer
>
escapePositions
=
new
ArrayList
<
Integer
>();
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
escapePositions
.
add
(
position
);
position
+=
3
;
}
}
int
escapeCount
=
escapePositions
.
size
();
int
escapedPosition
=
offset
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
byte
[]
unescapedData
=
new
byte
[
limit
-
offset
-
escapeCount
];
for
(
int
i
=
0
;
i
<
escapeCount
;
i
++)
{
int
nextEscapePosition
=
escapePositions
.
get
(
i
);
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
copyLength
);
escapedPosition
+=
copyLength
+
3
;
unescapedPosition
+=
copyLength
+
2
;
}
int
remainingLength
=
unescapedData
.
length
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
unescapedData
,
unescapedPosition
,
remainingLength
);
return
unescapedData
;
}
private
int
findNextUnescapeIndex
(
byte
[]
bytes
,
int
offset
,
int
limit
)
{
for
(
int
i
=
offset
;
i
<
limit
-
2
;
i
++)
{
if
(
bytes
[
i
]
==
0x00
&&
bytes
[
i
+
1
]
==
0x00
&&
bytes
[
i
+
2
]
==
0x03
)
{
return
i
;
}
}
return
limit
;
}
/**
* A buffer that fills itself with data corresponding to a specific NAL unit, as it is
* encountered in the stream.
*/
private
static
final
class
NalUnitTargetBuffer
{
private
final
int
targetType
;
private
boolean
isFilling
;
private
boolean
isCompleted
;
public
byte
[]
nalData
;
public
int
nalLength
;
public
NalUnitTargetBuffer
(
int
targetType
,
int
initialCapacity
)
{
this
.
targetType
=
targetType
;
// Initialize data, writing the known NAL prefix into the first four bytes.
nalData
=
new
byte
[
4
+
initialCapacity
];
nalData
[
2
]
=
1
;
nalData
[
3
]
=
(
byte
)
targetType
;
}
public
boolean
isCompleted
()
{
return
isCompleted
;
}
/**
* Invoked to indicate that a NAL unit has started.
*
* @param type The type of the NAL unit.
*/
public
void
startNalUnit
(
int
type
)
{
Assertions
.
checkState
(!
isFilling
);
isFilling
=
type
==
targetType
;
if
(
isFilling
)
{
// Length is initially the length of the NAL prefix.
nalLength
=
4
;
isCompleted
=
false
;
}
}
/**
* Invoked to pass stream data. The data passed should not include 4 byte NAL unit prefixes.
*
* @param data Holds the data being passed.
* @param offset The offset of the data in {@code data}.
* @param limit The limit (exclusive) of the data in {@code data}.
*/
public
void
appendToNalUnit
(
byte
[]
data
,
int
offset
,
int
limit
)
{
if
(!
isFilling
)
{
return
;
}
int
readLength
=
limit
-
offset
;
if
(
nalData
.
length
<
nalLength
+
readLength
)
{
nalData
=
Arrays
.
copyOf
(
nalData
,
(
nalLength
+
readLength
)
*
2
);
}
System
.
arraycopy
(
data
,
offset
,
nalData
,
nalLength
,
readLength
);
nalLength
+=
readLength
;
}
/**
* Invoked to indicate that a NAL unit has ended.
*
* @param discardPadding The number of excess bytes that were passed to
* {@link #appendToNalUnit(byte[], int, int)}, which should be discarded.
* @return True if the ended NAL unit is of the target type. False otherwise.
*/
public
boolean
endNalUnit
(
int
discardPadding
)
{
if
(!
isFilling
)
{
return
false
;
}
nalLength
-=
discardPadding
;
isFilling
=
false
;
isCompleted
=
true
;
return
true
;
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
/**
* Parses ID3 data and extracts individual text information frames.
*/
/* package */
class
Id3Reader
extends
PesPayloadReader
{
public
Id3Reader
(
BufferPool
bufferPool
)
{
super
(
bufferPool
);
setMediaFormat
(
MediaFormat
.
createId3Format
());
}
@Override
public
void
consume
(
ParsableByteArray
data
,
long
pesTimeUs
,
boolean
startOfPacket
)
{
if
(
startOfPacket
)
{
startSample
(
pesTimeUs
);
}
if
(
writingSample
())
{
appendData
(
data
,
data
.
bytesLeft
());
}
}
@Override
public
void
packetFinished
()
{
commitSample
(
true
);
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/PesPayloadReader.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
/**
* Extracts individual samples from continuous byte stream, preserving original order.
*/
/* package */
abstract
class
PesPayloadReader
extends
SampleQueue
{
protected
PesPayloadReader
(
BufferPool
bufferPool
)
{
super
(
bufferPool
);
}
/**
* Consumes (possibly partial) payload data.
*
* @param data The payload data to consume.
* @param pesTimeUs The timestamp associated with the payload.
* @param startOfPacket True if this is the first time this method is being called for the
* current packet. False otherwise.
*/
public
abstract
void
consume
(
ParsableByteArray
data
,
long
pesTimeUs
,
boolean
startOfPacket
);
/**
* Invoked once all of the payload data for a packet has been passed to
* {@link #consume(ParsableByteArray, long, boolean)}. The next call to
* {@link #consume(ParsableByteArray, long, boolean)} will have {@code startOfPacket == true}.
*/
public
abstract
void
packetFinished
();
}
library/src/main/java/com/google/android/exoplayer/hls/parser/RollingSampleBuffer.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.nio.ByteBuffer
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
/**
* A rolling buffer of sample data and corresponding sample information.
*/
/* package */
final
class
RollingSampleBuffer
{
private
final
BufferPool
fragmentPool
;
private
final
int
fragmentLength
;
private
final
InfoQueue
infoQueue
;
private
final
ConcurrentLinkedQueue
<
byte
[]>
dataQueue
;
private
final
long
[]
dataOffsetHolder
;
// Accessed only by the consuming thread.
private
long
totalBytesDropped
;
// Accessed only by the loading thread.
private
long
totalBytesWritten
;
private
byte
[]
lastFragment
;
private
int
lastFragmentOffset
;
private
long
pendingSampleTimeUs
;
private
long
pendingSampleOffset
;
public
RollingSampleBuffer
(
BufferPool
bufferPool
)
{
this
.
fragmentPool
=
bufferPool
;
fragmentLength
=
bufferPool
.
bufferLength
;
infoQueue
=
new
InfoQueue
();
dataQueue
=
new
ConcurrentLinkedQueue
<
byte
[]>();
dataOffsetHolder
=
new
long
[
1
];
}
public
void
release
()
{
while
(!
dataQueue
.
isEmpty
())
{
fragmentPool
.
releaseDirect
(
dataQueue
.
remove
());
}
}
// Called by the consuming thread.
/**
* Fills {@code holder} with information about the current sample, but does not write its data.
* <p>
* The fields set are {SampleHolder#size}, {SampleHolder#timeUs} and {SampleHolder#flags}.
*
* @param holder The holder into which the current sample information should be written.
* @return True if the holder was filled. False if there is no current sample.
*/
public
boolean
peekSample
(
SampleHolder
holder
)
{
return
infoQueue
.
peekSample
(
holder
,
dataOffsetHolder
);
}
/**
* Skips the current sample.
*/
public
void
skipSample
()
{
long
nextOffset
=
infoQueue
.
moveToNextSample
();
dropFragmentsTo
(
nextOffset
);
}
/**
* Reads the current sample, advancing the read index to the next sample.
*
* @param holder The holder into which the current sample should be written.
*/
public
void
readSample
(
SampleHolder
holder
)
{
// Write the sample information into the holder.
infoQueue
.
peekSample
(
holder
,
dataOffsetHolder
);
// Write the sample data into the holder.
if
(
holder
.
data
==
null
||
holder
.
data
.
capacity
()
<
holder
.
size
)
{
holder
.
replaceBuffer
(
holder
.
size
);
}
if
(
holder
.
data
!=
null
)
{
readData
(
dataOffsetHolder
[
0
],
holder
.
data
,
holder
.
size
);
}
// Advance the read head.
long
nextOffset
=
infoQueue
.
moveToNextSample
();
dropFragmentsTo
(
nextOffset
);
}
/**
* Reads data from the front of the rolling buffer.
*
* @param absolutePosition The absolute position from which data should be read.
* @param target The buffer into which data should be written.
* @param length The number of bytes to read.
*/
private
void
readData
(
long
absolutePosition
,
ByteBuffer
target
,
int
length
)
{
int
remaining
=
length
;
while
(
remaining
>
0
)
{
dropFragmentsTo
(
absolutePosition
);
int
positionInFragment
=
(
int
)
(
absolutePosition
-
totalBytesDropped
);
int
toCopy
=
Math
.
min
(
remaining
,
fragmentLength
-
positionInFragment
);
target
.
put
(
dataQueue
.
peek
(),
positionInFragment
,
toCopy
);
absolutePosition
+=
toCopy
;
remaining
-=
toCopy
;
}
}
/**
* Discard any fragments that hold data prior to the specified absolute position, returning
* them to the pool.
*
* @param absolutePosition The absolute position up to which fragments can be discarded.
*/
private
void
dropFragmentsTo
(
long
absolutePosition
)
{
int
relativePosition
=
(
int
)
(
absolutePosition
-
totalBytesDropped
);
int
fragmentIndex
=
relativePosition
/
fragmentLength
;
for
(
int
i
=
0
;
i
<
fragmentIndex
;
i
++)
{
fragmentPool
.
releaseDirect
(
dataQueue
.
remove
());
totalBytesDropped
+=
fragmentLength
;
}
}
// Called by the loading thread.
/**
* Indicates the start point for the next sample.
*
* @param sampleTimeUs The sample timestamp.
* @param offset The offset of the sample's data, relative to the total number of bytes written
* to the buffer. Must be negative or zero.
*/
public
void
startSample
(
long
sampleTimeUs
,
int
offset
)
{
Assertions
.
checkState
(
offset
<=
0
);
pendingSampleTimeUs
=
sampleTimeUs
;
pendingSampleOffset
=
totalBytesWritten
+
offset
;
}
/**
* Appends data to the rolling buffer.
*
* @param buffer A buffer containing the data to append.
* @param length The length of the data to append.
*/
public
void
appendData
(
ParsableByteArray
buffer
,
int
length
)
{
int
remainingWriteLength
=
length
;
while
(
remainingWriteLength
>
0
)
{
if
(
dataQueue
.
isEmpty
()
||
lastFragmentOffset
==
fragmentLength
)
{
lastFragmentOffset
=
0
;
lastFragment
=
fragmentPool
.
allocateDirect
();
dataQueue
.
add
(
lastFragment
);
}
int
thisWriteLength
=
Math
.
min
(
remainingWriteLength
,
fragmentLength
-
lastFragmentOffset
);
buffer
.
readBytes
(
lastFragment
,
lastFragmentOffset
,
thisWriteLength
);
lastFragmentOffset
+=
thisWriteLength
;
remainingWriteLength
-=
thisWriteLength
;
}
totalBytesWritten
+=
length
;
}
/**
* Indicates the end point for the current sample, making it available for consumption.
*
* @param isKeyframe True if the sample being committed is a keyframe. False otherwise.
* @param offset The offset of the first byte after the end of the sample's data, relative to
* the total number of bytes written to the buffer. Must be negative or zero.
*/
public
void
commitSample
(
boolean
isKeyframe
,
int
offset
)
{
Assertions
.
checkState
(
offset
<=
0
);
int
sampleSize
=
(
int
)
(
totalBytesWritten
+
offset
-
pendingSampleOffset
);
infoQueue
.
commitSample
(
pendingSampleTimeUs
,
pendingSampleOffset
,
sampleSize
,
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
);
}
/**
* Holds information about the samples in the rolling buffer.
*/
private
static
class
InfoQueue
{
private
static
final
int
SAMPLE_CAPACITY_INCREMENT
=
1000
;
private
int
capacity
;
private
long
[]
offsets
;
private
int
[]
sizes
;
private
int
[]
flags
;
private
long
[]
timesUs
;
private
int
queueSize
;
private
int
readIndex
;
private
int
writeIndex
;
public
InfoQueue
()
{
capacity
=
SAMPLE_CAPACITY_INCREMENT
;
offsets
=
new
long
[
capacity
];
timesUs
=
new
long
[
capacity
];
flags
=
new
int
[
capacity
];
sizes
=
new
int
[
capacity
];
}
// Called by the consuming thread.
/**
* Fills {@code holder} with information about the current sample, but does not write its data.
* The first entry in {@code offsetHolder} is filled with the absolute position of the sample's
* data in the rolling buffer.
* <p>
* The fields set are {SampleHolder#size}, {SampleHolder#timeUs}, {SampleHolder#flags} and
* {@code offsetHolder[0]}.
*
* @param holder The holder into which the current sample information should be written.
* @param offsetHolder The holder into which the absolute position of the sample's data should
* be written.
* @return True if the holders were filled. False if there is no current sample.
*/
public
synchronized
boolean
peekSample
(
SampleHolder
holder
,
long
[]
offsetHolder
)
{
if
(
queueSize
==
0
)
{
return
false
;
}
holder
.
timeUs
=
timesUs
[
readIndex
];
holder
.
size
=
sizes
[
readIndex
];
holder
.
flags
=
flags
[
readIndex
];
offsetHolder
[
0
]
=
offsets
[
readIndex
];
return
true
;
}
/**
* Advances the read index to the next sample.
*
* @return The absolute position of the first byte in the rolling buffer that may still be
* required after advancing the index. Data prior to this position can be dropped.
*/
public
synchronized
long
moveToNextSample
()
{
queueSize
--;
int
lastReadIndex
=
readIndex
++;
if
(
readIndex
==
capacity
)
{
// Wrap around.
readIndex
=
0
;
}
return
queueSize
>
0
?
offsets
[
readIndex
]
:
(
sizes
[
lastReadIndex
]
+
offsets
[
lastReadIndex
]);
}
// Called by the loading thread.
public
synchronized
void
commitSample
(
long
timeUs
,
long
offset
,
int
size
,
int
sampleFlags
)
{
timesUs
[
writeIndex
]
=
timeUs
;
offsets
[
writeIndex
]
=
offset
;
sizes
[
writeIndex
]
=
size
;
flags
[
writeIndex
]
=
sampleFlags
;
// Increment the write index.
queueSize
++;
if
(
queueSize
==
capacity
)
{
// Increase the capacity.
int
newCapacity
=
capacity
+
SAMPLE_CAPACITY_INCREMENT
;
long
[]
newOffsets
=
new
long
[
newCapacity
];
long
[]
newTimesUs
=
new
long
[
newCapacity
];
int
[]
newFlags
=
new
int
[
newCapacity
];
int
[]
newSizes
=
new
int
[
newCapacity
];
int
beforeWrap
=
capacity
-
readIndex
;
System
.
arraycopy
(
offsets
,
readIndex
,
newOffsets
,
0
,
beforeWrap
);
System
.
arraycopy
(
timesUs
,
readIndex
,
newTimesUs
,
0
,
beforeWrap
);
System
.
arraycopy
(
flags
,
readIndex
,
newFlags
,
0
,
beforeWrap
);
System
.
arraycopy
(
sizes
,
readIndex
,
newSizes
,
0
,
beforeWrap
);
int
afterWrap
=
readIndex
;
System
.
arraycopy
(
offsets
,
0
,
newOffsets
,
beforeWrap
,
afterWrap
);
System
.
arraycopy
(
timesUs
,
0
,
newTimesUs
,
beforeWrap
,
afterWrap
);
System
.
arraycopy
(
flags
,
0
,
newFlags
,
beforeWrap
,
afterWrap
);
System
.
arraycopy
(
sizes
,
0
,
newSizes
,
beforeWrap
,
afterWrap
);
offsets
=
newOffsets
;
timesUs
=
newTimesUs
;
flags
=
newFlags
;
sizes
=
newSizes
;
readIndex
=
0
;
writeIndex
=
capacity
;
queueSize
=
capacity
;
capacity
=
newCapacity
;
}
else
{
writeIndex
++;
if
(
writeIndex
==
capacity
)
{
// Wrap around.
writeIndex
=
0
;
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
/**
* Wraps a {@link RollingSampleBuffer}, adding higher level functionality such as enforcing that
* the first sample returned from the queue is a keyframe, allowing splicing to another queue, and
* so on.
*/
/* package */
abstract
class
SampleQueue
{
private
final
RollingSampleBuffer
rollingBuffer
;
private
final
SampleHolder
sampleInfoHolder
;
// Accessed only by the consuming thread.
private
boolean
needKeyframe
;
private
long
lastReadTimeUs
;
private
long
spliceOutTimeUs
;
// Accessed only by the loading thread.
private
boolean
writingSample
;
// Accessed by both the loading and consuming threads.
private
volatile
MediaFormat
mediaFormat
;
private
volatile
long
largestParsedTimestampUs
;
protected
SampleQueue
(
BufferPool
bufferPool
)
{
rollingBuffer
=
new
RollingSampleBuffer
(
bufferPool
);
sampleInfoHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_DISABLED
);
needKeyframe
=
true
;
lastReadTimeUs
=
Long
.
MIN_VALUE
;
spliceOutTimeUs
=
Long
.
MIN_VALUE
;
largestParsedTimestampUs
=
Long
.
MIN_VALUE
;
}
public
void
release
()
{
rollingBuffer
.
release
();
}
// Called by the consuming thread.
public
long
getLargestParsedTimestampUs
()
{
return
largestParsedTimestampUs
;
}
public
boolean
hasMediaFormat
()
{
return
mediaFormat
!=
null
;
}
public
MediaFormat
getMediaFormat
()
{
return
mediaFormat
;
}
public
boolean
isEmpty
()
{
return
!
advanceToEligibleSample
();
}
/**
* Removes the next sample from the head of the queue, writing it into the provided holder.
* <p>
* The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples
* queued prior to the first keyframe are discarded.
*
* @param holder A {@link SampleHolder} into which the sample should be read.
* @return True if a sample was read. False otherwise.
*/
public
boolean
getSample
(
SampleHolder
holder
)
{
boolean
foundEligibleSample
=
advanceToEligibleSample
();
if
(!
foundEligibleSample
)
{
return
false
;
}
// Write the sample into the holder.
rollingBuffer
.
readSample
(
holder
);
needKeyframe
=
false
;
lastReadTimeUs
=
holder
.
timeUs
;
return
true
;
}
/**
* Discards samples from the queue up to the specified time.
*
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public
void
discardUntil
(
long
timeUs
)
{
while
(
rollingBuffer
.
peekSample
(
sampleInfoHolder
)
&&
sampleInfoHolder
.
timeUs
<
timeUs
)
{
rollingBuffer
.
skipSample
();
// We're discarding one or more samples. A subsequent read will need to start at a keyframe.
needKeyframe
=
true
;
}
lastReadTimeUs
=
Long
.
MIN_VALUE
;
}
/**
* Attempts to configure a splice from this queue to the next.
*
* @param nextQueue The queue being spliced to.
* @return Whether the splice was configured successfully.
*/
public
boolean
configureSpliceTo
(
SampleQueue
nextQueue
)
{
if
(
spliceOutTimeUs
!=
Long
.
MIN_VALUE
)
{
// We've already configured the splice.
return
true
;
}
long
firstPossibleSpliceTime
;
if
(
rollingBuffer
.
peekSample
(
sampleInfoHolder
))
{
firstPossibleSpliceTime
=
sampleInfoHolder
.
timeUs
;
}
else
{
firstPossibleSpliceTime
=
lastReadTimeUs
+
1
;
}
RollingSampleBuffer
nextRollingBuffer
=
nextQueue
.
rollingBuffer
;
while
(
nextRollingBuffer
.
peekSample
(
sampleInfoHolder
)
&&
(
sampleInfoHolder
.
timeUs
<
firstPossibleSpliceTime
||
(
sampleInfoHolder
.
flags
&
C
.
SAMPLE_FLAG_SYNC
)
==
0
))
{
// Discard samples from the next queue for as long as they are before the earliest possible
// splice time, or not keyframes.
nextRollingBuffer
.
skipSample
();
}
if
(
nextRollingBuffer
.
peekSample
(
sampleInfoHolder
))
{
// We've found a keyframe in the next queue that can serve as the splice point. Set the
// splice point now.
spliceOutTimeUs
=
sampleInfoHolder
.
timeUs
;
return
true
;
}
return
false
;
}
/**
* Advances the underlying buffer to the next sample that is eligible to be returned.
*
* @boolean True if an eligible sample was found. False otherwise, in which case the underlying
* buffer has been emptied.
*/
private
boolean
advanceToEligibleSample
()
{
boolean
haveNext
=
rollingBuffer
.
peekSample
(
sampleInfoHolder
);
if
(
needKeyframe
)
{
while
(
haveNext
&&
(
sampleInfoHolder
.
flags
&
C
.
SAMPLE_FLAG_SYNC
)
==
0
)
{
rollingBuffer
.
skipSample
();
haveNext
=
rollingBuffer
.
peekSample
(
sampleInfoHolder
);
}
}
if
(!
haveNext
)
{
return
false
;
}
if
(
spliceOutTimeUs
!=
Long
.
MIN_VALUE
&&
sampleInfoHolder
.
timeUs
>=
spliceOutTimeUs
)
{
return
false
;
}
return
true
;
}
// Called by the loading thread.
protected
boolean
writingSample
()
{
return
writingSample
;
}
protected
void
setMediaFormat
(
MediaFormat
mediaFormat
)
{
this
.
mediaFormat
=
mediaFormat
;
}
protected
void
startSample
(
long
sampleTimeUs
)
{
startSample
(
sampleTimeUs
,
0
);
}
protected
void
startSample
(
long
sampleTimeUs
,
int
offset
)
{
writingSample
=
true
;
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sampleTimeUs
);
rollingBuffer
.
startSample
(
sampleTimeUs
,
offset
);
}
protected
void
appendData
(
ParsableByteArray
buffer
,
int
length
)
{
rollingBuffer
.
appendData
(
buffer
,
length
);
}
protected
void
commitSample
(
boolean
isKeyframe
)
{
commitSample
(
isKeyframe
,
0
);
}
protected
void
commitSample
(
boolean
isKeyframe
,
int
offset
)
{
rollingBuffer
.
commitSample
(
isKeyframe
,
offset
);
writingSample
=
false
;
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.text.eia608.Eia608Parser
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
/**
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
*
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
* a sample with an earlier timestamp won't be added to it.
*/
/* package */
class
SeiReader
extends
SampleQueue
{
private
final
ParsableByteArray
seiBuffer
;
public
SeiReader
(
BufferPool
bufferPool
)
{
super
(
bufferPool
);
setMediaFormat
(
MediaFormat
.
createEia608Format
());
seiBuffer
=
new
ParsableByteArray
();
}
public
void
read
(
byte
[]
data
,
int
position
,
long
pesTimeUs
)
{
seiBuffer
.
reset
(
data
,
data
.
length
);
seiBuffer
.
setPosition
(
position
+
4
);
int
ccDataSize
=
Eia608Parser
.
parseHeader
(
seiBuffer
);
if
(
ccDataSize
>
0
)
{
startSample
(
pesTimeUs
);
appendData
(
seiBuffer
,
ccDataSize
);
commitSample
(
true
);
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
parser
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
android.util.Log
;
import
android.util.SparseArray
;
import
java.io.IOException
;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
*/
public
final
class
TsExtractor
{
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
TS_STREAM_TYPE_ID3
=
0x15
;
private
static
final
int
TS_STREAM_TYPE_EIA608
=
0x100
;
// 0xFF + 1
private
static
final
long
MAX_PTS
=
0x1FFFFFFFF
L
;
private
final
ParsableByteArray
tsPacketBuffer
;
private
final
SparseArray
<
SampleQueue
>
sampleQueues
;
// Indexed by streamType
private
final
SparseArray
<
TsPayloadReader
>
tsPayloadReaders
;
// Indexed by pid
private
final
BufferPool
bufferPool
;
private
final
boolean
shouldSpliceIn
;
private
final
long
firstSampleTimestamp
;
private
final
ParsableBitArray
tsScratch
;
// Accessed only by the consuming thread.
private
boolean
spliceConfigured
;
// Accessed only by the loading thread.
private
int
tsPacketBytesRead
;
private
long
timestampOffsetUs
;
private
long
lastPts
;
// Accessed by both the loading and consuming threads.
private
volatile
boolean
prepared
;
public
TsExtractor
(
long
firstSampleTimestamp
,
boolean
shouldSpliceIn
,
BufferPool
bufferPool
)
{
this
.
firstSampleTimestamp
=
firstSampleTimestamp
;
this
.
shouldSpliceIn
=
shouldSpliceIn
;
this
.
bufferPool
=
bufferPool
;
tsScratch
=
new
ParsableBitArray
(
new
byte
[
3
]);
tsPacketBuffer
=
new
ParsableByteArray
(
TS_PACKET_SIZE
);
sampleQueues
=
new
SparseArray
<
SampleQueue
>();
tsPayloadReaders
=
new
SparseArray
<
TsPayloadReader
>();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
lastPts
=
Long
.
MIN_VALUE
;
}
/**
* 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
sampleQueues
.
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
sampleQueues
.
valueAt
(
track
).
getMediaFormat
();
}
/**
* Whether the extractor is prepared.
*
* @return True if the extractor is prepared. False otherwise.
*/
public
boolean
isPrepared
()
{
return
prepared
;
}
/**
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
* <p>
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
*/
public
void
release
()
{
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
sampleQueues
.
valueAt
(
i
).
release
();
}
}
/**
* Attempts to configure a splice from this extractor to the next.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
*
* @param nextExtractor The extractor being spliced to.
*/
public
void
configureSpliceTo
(
TsExtractor
nextExtractor
)
{
Assertions
.
checkState
(
prepared
);
if
(
spliceConfigured
||
!
nextExtractor
.
shouldSpliceIn
||
!
nextExtractor
.
isPrepared
())
{
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
// the next extractor isn't ready to be spliced in.
return
;
}
boolean
spliceConfigured
=
true
;
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
spliceConfigured
&=
sampleQueues
.
valueAt
(
i
).
configureSpliceTo
(
nextExtractor
.
sampleQueues
.
valueAt
(
i
));
}
this
.
spliceConfigured
=
spliceConfigured
;
return
;
}
/**
* Gets the largest timestamp of any sample parsed by the extractor.
*
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
*/
public
long
getLargestSampleTimestamp
()
{
long
largestParsedTimestampUs
=
Long
.
MIN_VALUE
;
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sampleQueues
.
valueAt
(
i
).
getLargestParsedTimestampUs
());
}
return
largestParsedTimestampUs
;
}
/**
* Gets the next sample for the specified track.
*
* @param track The track from which to read.
* @param holder A {@link SampleHolder} into which the sample should be read.
* @return True if a sample was read. False otherwise.
*/
public
boolean
getSample
(
int
track
,
SampleHolder
holder
)
{
Assertions
.
checkState
(
prepared
);
return
sampleQueues
.
valueAt
(
track
).
getSample
(
holder
);
}
/**
* Discards samples for the specified track up to the specified time.
*
* @param track The track from which samples should be discarded.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public
void
discardUntil
(
int
track
,
long
timeUs
)
{
Assertions
.
checkState
(
prepared
);
sampleQueues
.
valueAt
(
track
).
discardUntil
(
timeUs
);
}
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
* specified track.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for the specified track. False otherwise.
*/
public
boolean
hasSamples
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
!
sampleQueues
.
valueAt
(
track
).
isEmpty
();
}
private
boolean
checkPrepared
()
{
int
pesPayloadReaderCount
=
sampleQueues
.
size
();
if
(
pesPayloadReaderCount
==
0
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
pesPayloadReaderCount
;
i
++)
{
if
(!
sampleQueues
.
valueAt
(
i
).
hasMediaFormat
())
{
return
false
;
}
}
return
true
;
}
/**
* Reads up to a single TS packet.
*
* @param dataSource The {@link DataSource} from which to read.
* @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source.
*/
public
int
read
(
DataSource
dataSource
)
throws
IOException
{
int
bytesRead
=
dataSource
.
read
(
tsPacketBuffer
.
data
,
tsPacketBytesRead
,
TS_PACKET_SIZE
-
tsPacketBytesRead
);
if
(
bytesRead
==
-
1
)
{
return
-
1
;
}
tsPacketBytesRead
+=
bytesRead
;
if
(
tsPacketBytesRead
<
TS_PACKET_SIZE
)
{
// We haven't read the whole packet yet.
return
bytesRead
;
}
// Reset before reading the packet.
tsPacketBytesRead
=
0
;
tsPacketBuffer
.
setPosition
(
0
);
tsPacketBuffer
.
setLimit
(
TS_PACKET_SIZE
);
int
syncByte
=
tsPacketBuffer
.
readUnsignedByte
();
if
(
syncByte
!=
TS_SYNC_BYTE
)
{
return
bytesRead
;
}
tsPacketBuffer
.
readBytes
(
tsScratch
,
3
);
tsScratch
.
skipBits
(
1
);
// transport_error_indicator
boolean
payloadUnitStartIndicator
=
tsScratch
.
readBit
();
tsScratch
.
skipBits
(
1
);
// transport_priority
int
pid
=
tsScratch
.
readBits
(
13
);
tsScratch
.
skipBits
(
2
);
// transport_scrambling_control
boolean
adaptationFieldExists
=
tsScratch
.
readBit
();
boolean
payloadExists
=
tsScratch
.
readBit
();
// Last 4 bits of scratch are skipped: continuity_counter
// Skip the adaptation field.
if
(
adaptationFieldExists
)
{
int
adaptationFieldLength
=
tsPacketBuffer
.
readUnsignedByte
();
tsPacketBuffer
.
skip
(
adaptationFieldLength
);
}
// Read the payload.
if
(
payloadExists
)
{
TsPayloadReader
payloadReader
=
tsPayloadReaders
.
get
(
pid
);
if
(
payloadReader
!=
null
)
{
payloadReader
.
consume
(
tsPacketBuffer
,
payloadUnitStartIndicator
);
}
}
if
(!
prepared
)
{
prepared
=
checkPrepared
();
}
return
bytesRead
;
}
/**
* Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound.
*
* @param pts The raw PTS value.
* @return The corresponding time in microseconds.
*/
/* package */
long
ptsToTimeUs
(
long
pts
)
{
if
(
lastPts
!=
Long
.
MIN_VALUE
)
{
// The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1),
// and we need to snap to the one closest to lastPts.
long
closestWrapCount
=
(
lastPts
+
(
MAX_PTS
/
2
))
/
MAX_PTS
;
long
ptsWrapBelow
=
pts
+
(
MAX_PTS
*
(
closestWrapCount
-
1
));
long
ptsWrapAbove
=
pts
+
(
MAX_PTS
*
closestWrapCount
);
pts
=
Math
.
abs
(
ptsWrapBelow
-
lastPts
)
<
Math
.
abs
(
ptsWrapAbove
-
lastPts
)
?
ptsWrapBelow
:
ptsWrapAbove
;
}
// Calculate the corresponding timestamp.
long
timeUs
=
(
pts
*
C
.
MICROS_PER_SECOND
)
/
90000
;
// If we haven't done the initial timestamp adjustment, do it now.
if
(
lastPts
==
Long
.
MIN_VALUE
)
{
timestampOffsetUs
=
firstSampleTimestamp
-
timeUs
;
}
// Record the adjusted PTS to adjust for wraparound next time.
lastPts
=
pts
;
return
timeUs
+
timestampOffsetUs
;
}
/**
* Parses TS packet payload data.
*/
private
abstract
static
class
TsPayloadReader
{
public
abstract
void
consume
(
ParsableByteArray
data
,
boolean
payloadUnitStartIndicator
);
}
/**
* Parses Program Association Table data.
*/
private
class
PatReader
extends
TsPayloadReader
{
private
final
ParsableBitArray
patScratch
;
public
PatReader
()
{
patScratch
=
new
ParsableBitArray
(
new
byte
[
4
]);
}
@Override
public
void
consume
(
ParsableByteArray
data
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
int
pointerField
=
data
.
readUnsignedByte
();
data
.
skip
(
pointerField
);
}
data
.
readBytes
(
patScratch
,
3
);
patScratch
.
skipBits
(
12
);
// table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int
sectionLength
=
patScratch
.
readBits
(
12
);
// transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8)
data
.
skip
(
5
);
int
programCount
=
(
sectionLength
-
9
)
/
4
;
for
(
int
i
=
0
;
i
<
programCount
;
i
++)
{
data
.
readBytes
(
patScratch
,
4
);
patScratch
.
skipBits
(
19
);
// program_number (16), reserved (3)
int
pid
=
patScratch
.
readBits
(
13
);
tsPayloadReaders
.
put
(
pid
,
new
PmtReader
());
}
// Skip CRC_32.
}
}
/**
* Parses Program Map Table.
*/
private
class
PmtReader
extends
TsPayloadReader
{
private
final
ParsableBitArray
pmtScratch
;
public
PmtReader
()
{
pmtScratch
=
new
ParsableBitArray
(
new
byte
[
5
]);
}
@Override
public
void
consume
(
ParsableByteArray
data
,
boolean
payloadUnitStartIndicator
)
{
// Skip pointer.
if
(
payloadUnitStartIndicator
)
{
int
pointerField
=
data
.
readUnsignedByte
();
data
.
skip
(
pointerField
);
}
data
.
readBytes
(
pmtScratch
,
3
);
pmtScratch
.
skipBits
(
12
);
// table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int
sectionLength
=
pmtScratch
.
readBits
(
12
);
// program_number (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8), reserved (3), PCR_PID (13)
// Skip the rest of the PMT header.
data
.
skip
(
7
);
data
.
readBytes
(
pmtScratch
,
2
);
pmtScratch
.
skipBits
(
4
);
int
programInfoLength
=
pmtScratch
.
readBits
(
12
);
// Skip the descriptors.
data
.
skip
(
programInfoLength
);
int
entriesSize
=
sectionLength
-
9
/* Size of the rest of the fields before descriptors */
-
programInfoLength
-
4
/* CRC size */
;
while
(
entriesSize
>
0
)
{
data
.
readBytes
(
pmtScratch
,
5
);
int
streamType
=
pmtScratch
.
readBits
(
8
);
pmtScratch
.
skipBits
(
3
);
// reserved
int
elementaryPid
=
pmtScratch
.
readBits
(
13
);
pmtScratch
.
skipBits
(
4
);
// reserved
int
esInfoLength
=
pmtScratch
.
readBits
(
12
);
// Skip the descriptors.
data
.
skip
(
esInfoLength
);
entriesSize
-=
esInfoLength
+
5
;
if
(
sampleQueues
.
get
(
streamType
)
!=
null
)
{
continue
;
}
PesPayloadReader
pesPayloadReader
=
null
;
switch
(
streamType
)
{
case
TS_STREAM_TYPE_AAC:
pesPayloadReader
=
new
AdtsReader
(
bufferPool
);
break
;
case
TS_STREAM_TYPE_H264:
SeiReader
seiReader
=
new
SeiReader
(
bufferPool
);
sampleQueues
.
put
(
TS_STREAM_TYPE_EIA608
,
seiReader
);
pesPayloadReader
=
new
H264Reader
(
bufferPool
,
seiReader
);
break
;
case
TS_STREAM_TYPE_ID3:
pesPayloadReader
=
new
Id3Reader
(
bufferPool
);
break
;
}
if
(
pesPayloadReader
!=
null
)
{
sampleQueues
.
put
(
streamType
,
pesPayloadReader
);
tsPayloadReaders
.
put
(
elementaryPid
,
new
PesReader
(
pesPayloadReader
));
}
}
// Skip CRC_32.
}
}
/**
* Parses PES packet data and extracts samples.
*/
private
class
PesReader
extends
TsPayloadReader
{
private
static
final
int
STATE_FINDING_HEADER
=
0
;
private
static
final
int
STATE_READING_HEADER
=
1
;
private
static
final
int
STATE_READING_HEADER_EXTENSION
=
2
;
private
static
final
int
STATE_READING_BODY
=
3
;
private
static
final
int
HEADER_SIZE
=
9
;
private
static
final
int
MAX_HEADER_EXTENSION_SIZE
=
5
;
private
final
ParsableBitArray
pesScratch
;
private
final
PesPayloadReader
pesPayloadReader
;
private
int
state
;
private
int
bytesRead
;
private
boolean
bodyStarted
;
private
boolean
ptsFlag
;
private
int
extendedHeaderLength
;
private
int
payloadSize
;
private
long
timeUs
;
public
PesReader
(
PesPayloadReader
pesPayloadReader
)
{
this
.
pesPayloadReader
=
pesPayloadReader
;
pesScratch
=
new
ParsableBitArray
(
new
byte
[
HEADER_SIZE
]);
state
=
STATE_FINDING_HEADER
;
}
@Override
public
void
consume
(
ParsableByteArray
data
,
boolean
payloadUnitStartIndicator
)
{
if
(
payloadUnitStartIndicator
)
{
switch
(
state
)
{
case
STATE_FINDING_HEADER:
case
STATE_READING_HEADER:
// Expected.
break
;
case
STATE_READING_HEADER_EXTENSION:
Log
.
w
(
TAG
,
"Unexpected start indicator reading extended header"
);
break
;
case
STATE_READING_BODY:
// If payloadSize == -1 then the length of the previous packet was unspecified, and so
// we only know that it's finished now that we've seen the start of the next one. This
// is expected. If payloadSize != -1, then the length of the previous packet was known,
// but we didn't receive that amount of data. This is not expected.
if
(
payloadSize
!=
-
1
)
{
Log
.
w
(
TAG
,
"Unexpected start indicator: expected "
+
payloadSize
+
" more bytes"
);
}
// Either way, if the body was started, notify the reader that it has now finished.
if
(
bodyStarted
)
{
pesPayloadReader
.
packetFinished
();
}
break
;
}
setState
(
STATE_READING_HEADER
);
}
while
(
data
.
bytesLeft
()
>
0
)
{
switch
(
state
)
{
case
STATE_FINDING_HEADER:
data
.
skip
(
data
.
bytesLeft
());
break
;
case
STATE_READING_HEADER:
if
(
continueRead
(
data
,
pesScratch
.
getData
(),
HEADER_SIZE
))
{
setState
(
parseHeader
()
?
STATE_READING_HEADER_EXTENSION
:
STATE_FINDING_HEADER
);
}
break
;
case
STATE_READING_HEADER_EXTENSION:
int
readLength
=
Math
.
min
(
MAX_HEADER_EXTENSION_SIZE
,
extendedHeaderLength
);
// Read as much of the extended header as we're interested in, and skip the rest.
if
(
continueRead
(
data
,
pesScratch
.
getData
(),
readLength
)
&&
continueRead
(
data
,
null
,
extendedHeaderLength
))
{
parseHeaderExtension
();
bodyStarted
=
false
;
setState
(
STATE_READING_BODY
);
}
break
;
case
STATE_READING_BODY:
readLength
=
data
.
bytesLeft
();
int
padding
=
payloadSize
==
-
1
?
0
:
readLength
-
payloadSize
;
if
(
padding
>
0
)
{
readLength
-=
padding
;
data
.
setLimit
(
data
.
getPosition
()
+
readLength
);
}
pesPayloadReader
.
consume
(
data
,
timeUs
,
!
bodyStarted
);
bodyStarted
=
true
;
if
(
payloadSize
!=
-
1
)
{
payloadSize
-=
readLength
;
if
(
payloadSize
==
0
)
{
pesPayloadReader
.
packetFinished
();
setState
(
STATE_READING_HEADER
);
}
}
break
;
}
}
}
private
void
setState
(
int
state
)
{
this
.
state
=
state
;
bytesRead
=
0
;
}
/**
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed
* that the data should be written into {@code target} starting from an offset of zero.
*
* @param source The source from which to read.
* @param target The target into which data is to be read, or {@code null} to skip.
* @param targetLength The target length of the read.
* @return Whether the target length has been reached.
*/
private
boolean
continueRead
(
ParsableByteArray
source
,
byte
[]
target
,
int
targetLength
)
{
int
bytesToRead
=
Math
.
min
(
source
.
bytesLeft
(),
targetLength
-
bytesRead
);
if
(
bytesToRead
<=
0
)
{
return
true
;
}
else
if
(
target
==
null
)
{
source
.
skip
(
bytesToRead
);
}
else
{
source
.
readBytes
(
target
,
bytesRead
,
bytesToRead
);
}
bytesRead
+=
bytesToRead
;
return
bytesRead
==
targetLength
;
}
private
boolean
parseHeader
()
{
pesScratch
.
setPosition
(
0
);
int
startCodePrefix
=
pesScratch
.
readBits
(
24
);
if
(
startCodePrefix
!=
0x000001
)
{
Log
.
w
(
TAG
,
"Unexpected start code prefix: "
+
startCodePrefix
);
payloadSize
=
-
1
;
return
false
;
}
pesScratch
.
skipBits
(
8
);
// stream_id.
int
packetLength
=
pesScratch
.
readBits
(
16
);
// First 8 bits are skipped: '10' (2), PES_scrambling_control (2), PES_priority (1),
// data_alignment_indicator (1), copyright (1), original_or_copy (1)
pesScratch
.
skipBits
(
8
);
ptsFlag
=
pesScratch
.
readBit
();
// DTS_flag (1), ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
// additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1)
pesScratch
.
skipBits
(
7
);
extendedHeaderLength
=
pesScratch
.
readBits
(
8
);
if
(
packetLength
==
0
)
{
payloadSize
=
-
1
;
}
else
{
payloadSize
=
packetLength
+
6
/* packetLength does not include the first 6 bytes */
-
HEADER_SIZE
-
extendedHeaderLength
;
}
return
true
;
}
private
void
parseHeaderExtension
()
{
pesScratch
.
setPosition
(
0
);
timeUs
=
0
;
if
(
ptsFlag
)
{
pesScratch
.
skipBits
(
4
);
// '0010'
long
pts
=
pesScratch
.
readBitsLong
(
3
)
<<
30
;
pesScratch
.
skipBits
(
1
);
// marker_bit
pts
|=
pesScratch
.
readBitsLong
(
15
)
<<
15
;
pesScratch
.
skipBits
(
1
);
// marker_bit
pts
|=
pesScratch
.
readBitsLong
(
15
);
pesScratch
.
skipBits
(
1
);
// marker_bit
timeUs
=
ptsToTimeUs
(
pts
);
}
}
}
}
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
View file @
8f0d576f
...
@@ -16,8 +16,8 @@
...
@@ -16,8 +16,8 @@
package
com
.
google
.
android
.
exoplayer
.
metadata
;
package
com
.
google
.
android
.
exoplayer
.
metadata
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.util.BitArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.UnsupportedEncodingException
;
import
java.io.UnsupportedEncodingException
;
import
java.util.Collections
;
import
java.util.Collections
;
...
@@ -37,30 +37,28 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
...
@@ -37,30 +37,28 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
@Override
@Override
public
Map
<
String
,
Object
>
parse
(
byte
[]
data
,
int
size
)
public
Map
<
String
,
Object
>
parse
(
byte
[]
data
,
int
size
)
throws
UnsupportedEncodingException
,
ParserException
{
throws
UnsupportedEncodingException
,
ParserException
{
BitArray
id3Buffer
=
new
BitArray
(
data
,
size
);
int
id3Size
=
parseId3Header
(
id3Buffer
);
Map
<
String
,
Object
>
metadata
=
new
HashMap
<
String
,
Object
>();
Map
<
String
,
Object
>
metadata
=
new
HashMap
<
String
,
Object
>();
ParsableByteArray
id3Data
=
new
ParsableByteArray
(
data
,
size
);
int
id3Size
=
parseId3Header
(
id3Data
);
while
(
id3Size
>
0
)
{
while
(
id3Size
>
0
)
{
int
frameId0
=
id3Buffer
.
readUnsignedByte
();
int
frameId0
=
id3Data
.
readUnsignedByte
();
int
frameId1
=
id3Buffer
.
readUnsignedByte
();
int
frameId1
=
id3Data
.
readUnsignedByte
();
int
frameId2
=
id3Buffer
.
readUnsignedByte
();
int
frameId2
=
id3Data
.
readUnsignedByte
();
int
frameId3
=
id3Buffer
.
readUnsignedByte
();
int
frameId3
=
id3Data
.
readUnsignedByte
();
int
frameSize
=
id3Data
.
readSynchSafeInt
();
int
frameSize
=
id3Buffer
.
readSynchSafeInt
();
if
(
frameSize
<=
1
)
{
if
(
frameSize
<=
1
)
{
break
;
break
;
}
}
id3Buffer
.
skipBytes
(
2
);
// Skip frame flags.
// Skip frame flags.
id3Data
.
skip
(
2
);
// Check Frame ID == TXXX.
// Check Frame ID == TXXX.
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
if
(
frameId0
==
'T'
&&
frameId1
==
'X'
&&
frameId2
==
'X'
&&
frameId3
==
'X'
)
{
int
encoding
=
id3
Buffer
.
readUnsignedByte
();
int
encoding
=
id3
Data
.
readUnsignedByte
();
String
charset
=
getCharsetName
(
encoding
);
String
charset
=
getCharsetName
(
encoding
);
byte
[]
frame
=
new
byte
[
frameSize
-
1
];
byte
[]
frame
=
new
byte
[
frameSize
-
1
];
id3
Buffer
.
readBytes
(
frame
,
0
,
frameSize
-
1
);
id3
Data
.
readBytes
(
frame
,
0
,
frameSize
-
1
);
int
firstZeroIndex
=
indexOf
(
frame
,
0
,
(
byte
)
0
);
int
firstZeroIndex
=
indexOf
(
frame
,
0
,
(
byte
)
0
);
String
description
=
new
String
(
frame
,
0
,
firstZeroIndex
,
charset
);
String
description
=
new
String
(
frame
,
0
,
firstZeroIndex
,
charset
);
...
@@ -72,7 +70,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
...
@@ -72,7 +70,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
}
else
{
}
else
{
String
type
=
String
.
format
(
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
String
type
=
String
.
format
(
"%c%c%c%c"
,
frameId0
,
frameId1
,
frameId2
,
frameId3
);
byte
[]
frame
=
new
byte
[
frameSize
];
byte
[]
frame
=
new
byte
[
frameSize
];
id3
Buffer
.
readBytes
(
frame
,
0
,
frameSize
);
id3
Data
.
readBytes
(
frame
,
0
,
frameSize
);
metadata
.
put
(
type
,
frame
);
metadata
.
put
(
type
,
frame
);
}
}
...
@@ -101,12 +99,13 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
...
@@ -101,12 +99,13 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
}
}
/**
/**
* Parses ID3 header.
* Parses an ID3 header.
* @param id3Buffer A {@link BitArray} with raw ID3 data.
*
* @return The size of data that contains ID3 frames without header and footer.
* @param id3Buffer A {@link ParsableByteArray} from which data should be read.
* @return The size of ID3 frames in bytes, excluding the header and footer.
* @throws ParserException If ID3 file identifier != "ID3".
* @throws ParserException If ID3 file identifier != "ID3".
*/
*/
private
static
int
parseId3Header
(
Bit
Array
id3Buffer
)
throws
ParserException
{
private
static
int
parseId3Header
(
ParsableByte
Array
id3Buffer
)
throws
ParserException
{
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id1
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id2
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
int
id3
=
id3Buffer
.
readUnsignedByte
();
...
@@ -114,7 +113,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
...
@@ -114,7 +113,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
throw
new
ParserException
(
String
.
format
(
throw
new
ParserException
(
String
.
format
(
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
"Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\"."
,
id1
,
id2
,
id3
));
}
}
id3Buffer
.
skip
Bytes
(
2
);
// Skip version.
id3Buffer
.
skip
(
2
);
// Skip version.
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
flags
=
id3Buffer
.
readUnsignedByte
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
int
id3Size
=
id3Buffer
.
readSynchSafeInt
();
...
@@ -123,7 +122,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
...
@@ -123,7 +122,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
if
((
flags
&
0x2
)
!=
0
)
{
if
((
flags
&
0x2
)
!=
0
)
{
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
int
extendedHeaderSize
=
id3Buffer
.
readSynchSafeInt
();
if
(
extendedHeaderSize
>
4
)
{
if
(
extendedHeaderSize
>
4
)
{
id3Buffer
.
skip
Bytes
(
extendedHeaderSize
-
4
);
id3Buffer
.
skip
(
extendedHeaderSize
-
4
);
}
}
id3Size
-=
extendedHeaderSize
;
id3Size
-=
extendedHeaderSize
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
View file @
8f0d576f
...
@@ -24,8 +24,6 @@ import com.google.android.exoplayer.util.MimeTypes;
...
@@ -24,8 +24,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
com.google.android.exoplayer.util.Util
;
import
android.annotation.SuppressLint
;
import
android.media.MediaExtractor
;
import
android.util.Pair
;
import
android.util.Pair
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
...
@@ -37,8 +35,8 @@ public final class CommonMp4AtomParsers {
...
@@ -37,8 +35,8 @@ public final class CommonMp4AtomParsers {
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_CHANNEL_COUNTS
=
new
int
[]
{
2
,
1
,
2
,
3
,
3
,
4
,
4
,
5
};
private
static
final
int
[]
AC3_CHANNEL_COUNTS
=
new
int
[]
{
2
,
1
,
2
,
3
,
3
,
4
,
4
,
5
};
/** Nominal bit
-
rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
/** Nominal bitrates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_BIT
_
RATES
=
new
int
[]
{
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
private
static
final
int
[]
AC3_BITRATES
=
new
int
[]
{
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
192
,
224
,
256
,
320
,
384
,
448
,
512
,
576
,
640
};
192
,
224
,
256
,
320
,
384
,
448
,
512
,
576
,
640
};
/**
/**
...
@@ -81,7 +79,6 @@ public final class CommonMp4AtomParsers {
...
@@ -81,7 +79,6 @@ public final class CommonMp4AtomParsers {
* @param stblAtom stbl (sample table) atom to parse.
* @param stblAtom stbl (sample table) atom to parse.
* @return Sample table described by the stbl atom.
* @return Sample table described by the stbl atom.
*/
*/
@SuppressLint
(
"InlinedApi"
)
public
static
Mp4TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
)
{
public
static
Mp4TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
)
{
// Array of sample sizes.
// Array of sample sizes.
ParsableByteArray
stsz
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsz
).
data
;
ParsableByteArray
stsz
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsz
).
data
;
...
@@ -174,9 +171,9 @@ public final class CommonMp4AtomParsers {
...
@@ -174,9 +171,9 @@ public final class CommonMp4AtomParsers {
timestamps
[
i
]
=
timestampTimeUnits
+
timestampOffset
;
timestamps
[
i
]
=
timestampTimeUnits
+
timestampOffset
;
// All samples are synchronization samples if the stss is not present.
// All samples are synchronization samples if the stss is not present.
flags
[
i
]
=
stss
==
null
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
flags
[
i
]
=
stss
==
null
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
if
(
i
==
nextSynchronizationSampleIndex
)
{
if
(
i
==
nextSynchronizationSampleIndex
)
{
flags
[
i
]
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
flags
[
i
]
=
C
.
SAMPLE_FLAG_SYNC
;
remainingSynchronizationSamples
--;
remainingSynchronizationSamples
--;
if
(
remainingSynchronizationSamples
>
0
)
{
if
(
remainingSynchronizationSamples
>
0
)
{
nextSynchronizationSampleIndex
=
stss
.
readUnsignedIntToInt
()
-
1
;
nextSynchronizationSampleIndex
=
stss
.
readUnsignedIntToInt
()
-
1
;
...
@@ -639,8 +636,8 @@ public final class CommonMp4AtomParsers {
...
@@ -639,8 +636,8 @@ public final class CommonMp4AtomParsers {
channelCount
++;
channelCount
++;
}
}
// Map bit_rate_code onto a bit
-
rate in kbit/s.
// Map bit_rate_code onto a bitrate in kbit/s.
int
bitrate
=
AC3_BIT
_
RATES
[((
nextByte
&
0x03
)
<<
3
)
+
(
parent
.
readUnsignedByte
()
>>
5
)];
int
bitrate
=
AC3_BITRATES
[((
nextByte
&
0x03
)
<<
3
)
+
(
parent
.
readUnsignedByte
()
>>
5
)];
return
new
Ac3Format
(
channelCount
,
sampleRate
,
bitrate
);
return
new
Ac3Format
(
channelCount
,
sampleRate
,
bitrate
);
}
}
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Mp4TrackSampleTable.java
View file @
8f0d576f
...
@@ -15,11 +15,10 @@
...
@@ -15,11 +15,10 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
import
com.google.android.exoplayer.util.Util
;
import
android.media.MediaExtractor
;
/** Sample table for a track in an MP4 file. */
/** Sample table for a track in an MP4 file. */
public
final
class
Mp4TrackSampleTable
{
public
final
class
Mp4TrackSampleTable
{
...
@@ -59,7 +58,7 @@ public final class Mp4TrackSampleTable {
...
@@ -59,7 +58,7 @@ public final class Mp4TrackSampleTable {
public
int
getIndexOfEarlierOrEqualSynchronizationSample
(
long
timeUs
)
{
public
int
getIndexOfEarlierOrEqualSynchronizationSample
(
long
timeUs
)
{
int
startIndex
=
Util
.
binarySearchFloor
(
timestampsUs
,
timeUs
,
true
,
false
);
int
startIndex
=
Util
.
binarySearchFloor
(
timestampsUs
,
timeUs
,
true
,
false
);
for
(
int
i
=
startIndex
;
i
>=
0
;
i
--)
{
for
(
int
i
=
startIndex
;
i
>=
0
;
i
--)
{
if
(
timestampsUs
[
i
]
<=
timeUs
&&
(
flags
[
i
]
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
if
(
timestampsUs
[
i
]
<=
timeUs
&&
(
flags
[
i
]
&
C
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
return
i
;
return
i
;
}
}
}
}
...
@@ -77,7 +76,7 @@ public final class Mp4TrackSampleTable {
...
@@ -77,7 +76,7 @@ public final class Mp4TrackSampleTable {
public
int
getIndexOfLaterOrEqualSynchronizationSample
(
long
timeUs
)
{
public
int
getIndexOfLaterOrEqualSynchronizationSample
(
long
timeUs
)
{
int
startIndex
=
Util
.
binarySearchCeil
(
timestampsUs
,
timeUs
,
true
,
false
);
int
startIndex
=
Util
.
binarySearchCeil
(
timestampsUs
,
timeUs
,
true
,
false
);
for
(
int
i
=
startIndex
;
i
<
timestampsUs
.
length
;
i
++)
{
for
(
int
i
=
startIndex
;
i
<
timestampsUs
.
length
;
i
++)
{
if
(
timestampsUs
[
i
]
>=
timeUs
&&
(
flags
[
i
]
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
if
(
timestampsUs
[
i
]
>=
timeUs
&&
(
flags
[
i
]
&
C
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
return
i
;
return
i
;
}
}
}
}
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
View file @
8f0d576f
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
mp4
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
...
@@ -99,4 +100,155 @@ public final class Mp4Util {
...
@@ -99,4 +100,155 @@ public final class Mp4Util {
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
data
,
offset
,
length
);
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
data
,
offset
,
length
);
}
}
/**
* Finds the first NAL unit in {@code data}.
* <p>
* For a NAL unit to be found, its first four bytes must be contained within the part of the
* array being searched.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @param type The type of the NAL unit to search for, or -1 for any NAL unit.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public
static
int
findNalUnit
(
byte
[]
data
,
int
startOffset
,
int
endOffset
,
int
type
)
{
return
findNalUnit
(
data
,
startOffset
,
endOffset
,
type
,
null
);
}
/**
* Like {@link #findNalUnit(byte[], int, int, int)}, but supports finding of NAL units across
* array boundaries.
* <p>
* To use this method, pass the same {@code prefixFlags} parameter to successive calls where the
* data passed represents a contiguous stream. The state maintained in this parameter allows the
* detection of NAL units where the NAL unit prefix spans array boundaries.
* <p>
* Note that when using {@code prefixFlags} the return value may be 3, 2 or 1 less than
* {@code startOffset}, to indicate a NAL unit starting 3, 2 or 1 bytes before the first byte in
* the current array.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @param type The type of the NAL unit to search for, or -1 for any NAL unit.
* @param prefixFlags A boolean array whose first three elements are used to store the state
* required to detect NAL units where the NAL unit prefix spans array boundaries. The array
* must be at least 3 elements long.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public
static
int
findNalUnit
(
byte
[]
data
,
int
startOffset
,
int
endOffset
,
int
type
,
boolean
[]
prefixFlags
)
{
int
length
=
endOffset
-
startOffset
;
Assertions
.
checkState
(
length
>=
0
);
if
(
length
==
0
)
{
return
endOffset
;
}
if
(
prefixFlags
!=
null
)
{
if
(
prefixFlags
[
0
]
&&
matchesType
(
data
,
startOffset
,
type
))
{
clearPrefixFlags
(
prefixFlags
);
return
startOffset
-
3
;
}
else
if
(
length
>
1
&&
prefixFlags
[
1
]
&&
data
[
startOffset
]
==
1
&&
matchesType
(
data
,
startOffset
+
1
,
type
))
{
clearPrefixFlags
(
prefixFlags
);
return
startOffset
-
2
;
}
else
if
(
length
>
2
&&
prefixFlags
[
2
]
&&
data
[
startOffset
]
==
0
&&
data
[
startOffset
+
1
]
==
1
&&
matchesType
(
data
,
startOffset
+
2
,
type
))
{
clearPrefixFlags
(
prefixFlags
);
return
startOffset
-
1
;
}
}
int
limit
=
endOffset
-
2
;
// We're looking for the NAL unit start code prefix 0x000001, followed by a byte that matches
// the specified type. The value of i tracks the index of the third byte in the four bytes
// being examined.
for
(
int
i
=
startOffset
+
2
;
i
<
limit
;
i
+=
3
)
{
if
((
data
[
i
]
&
0xFE
)
!=
0
)
{
// There isn't a NAL prefix here, or at the next two positions. Do nothing and let the
// loop advance the index by three.
}
else
if
(
data
[
i
-
2
]
==
0
&&
data
[
i
-
1
]
==
0
&&
data
[
i
]
==
1
&&
matchesType
(
data
,
i
+
1
,
type
))
{
return
i
-
2
;
}
else
{
// There isn't a NAL prefix here, but there might be at the next position. We should
// only skip forward by one. The loop will skip forward by three, so subtract two here.
i
-=
2
;
}
}
if
(
prefixFlags
!=
null
)
{
// True if the last three bytes in the data seen so far are {0,0,1}.
prefixFlags
[
0
]
=
length
>
2
?
(
data
[
endOffset
-
3
]
==
0
&&
data
[
endOffset
-
2
]
==
0
&&
data
[
endOffset
-
1
]
==
1
)
:
length
==
2
?
(
prefixFlags
[
2
]
&&
data
[
endOffset
-
2
]
==
0
&&
data
[
endOffset
-
1
]
==
1
)
:
(
prefixFlags
[
1
]
&&
data
[
endOffset
-
1
]
==
1
);
// True if the last three bytes in the data seen so far are {0,0}.
prefixFlags
[
1
]
=
length
>
1
?
data
[
endOffset
-
2
]
==
0
&&
data
[
endOffset
-
1
]
==
0
:
prefixFlags
[
2
]
&&
data
[
endOffset
-
1
]
==
0
;
// True if the last three bytes in the data seen so far are {0}.
prefixFlags
[
2
]
=
data
[
endOffset
-
1
]
==
0
;
}
return
endOffset
;
}
/**
* Like {@link #findNalUnit(byte[], int, int, int)} with {@code type == -1}.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public
static
int
findNalUnit
(
byte
[]
data
,
int
startOffset
,
int
endOffset
)
{
return
findNalUnit
(
data
,
startOffset
,
endOffset
,
null
);
}
/**
* Like {@link #findNalUnit(byte[], int, int, int, boolean[])} with {@code type == -1}.
*
* @param data The data to search.
* @param startOffset The offset (inclusive) in the data to start the search.
* @param endOffset The offset (exclusive) in the data to end the search.
* @param prefixFlags A boolean array of length at least 3.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/
public
static
int
findNalUnit
(
byte
[]
data
,
int
startOffset
,
int
endOffset
,
boolean
[]
prefixFlags
)
{
return
findNalUnit
(
data
,
startOffset
,
endOffset
,
-
1
,
prefixFlags
);
}
/**
* Gets the type of the NAL unit in {@code data} that starts at {@code offset}.
*
* @param data The data to search.
* @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and
* {@code data.length - 3} (exclusive).
* @return The type of the unit.
*/
public
static
int
getNalUnitType
(
byte
[]
data
,
int
offset
)
{
return
data
[
offset
+
3
]
&
0x1F
;
}
/**
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, int, boolean[])}.
*
* @param prefixFlags The flags to clear.
*/
private
static
void
clearPrefixFlags
(
boolean
[]
prefixFlags
)
{
prefixFlags
[
0
]
=
false
;
prefixFlags
[
1
]
=
false
;
prefixFlags
[
2
]
=
false
;
}
/**
* Returns true if the type at {@code offset} is equal to {@code type}, or if {@code type == -1}.
*/
private
static
boolean
matchesType
(
byte
[]
data
,
int
offset
,
int
type
)
{
return
type
==
-
1
||
(
data
[
offset
]
&
0x1F
)
==
type
;
}
}
}
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java
View file @
8f0d576f
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.text.eia608;
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.text.eia608;
/**
/**
* A Closed Caption that contains textual data associated with time indices.
* A Closed Caption that contains textual data associated with time indices.
*/
*/
/* package */
abstract
class
ClosedCaption
implements
Comparable
<
ClosedCaption
>
{
/* package */
abstract
class
ClosedCaption
{
/**
/**
* Identifies closed captions with control characters.
* Identifies closed captions with control characters.
...
@@ -33,23 +33,9 @@ package com.google.android.exoplayer.text.eia608;
...
@@ -33,23 +33,9 @@ package com.google.android.exoplayer.text.eia608;
* The type of the closed caption data.
* The type of the closed caption data.
*/
*/
public
final
int
type
;
public
final
int
type
;
/**
* Timestamp associated with the closed caption.
*/
public
final
long
timeUs
;
protected
ClosedCaption
(
int
type
,
long
timeUs
)
{
protected
ClosedCaption
(
int
type
)
{
this
.
type
=
type
;
this
.
type
=
type
;
this
.
timeUs
=
timeUs
;
}
@Override
public
int
compareTo
(
ClosedCaption
another
)
{
long
delta
=
this
.
timeUs
-
another
.
timeUs
;
if
(
delta
==
0
)
{
return
0
;
}
return
delta
>
0
?
1
:
-
1
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java
View file @
8f0d576f
...
@@ -70,8 +70,8 @@ package com.google.android.exoplayer.text.eia608;
...
@@ -70,8 +70,8 @@ package com.google.android.exoplayer.text.eia608;
public
final
byte
cc1
;
public
final
byte
cc1
;
public
final
byte
cc2
;
public
final
byte
cc2
;
protected
ClosedCaptionCtrl
(
byte
cc1
,
byte
cc2
,
long
timeUs
)
{
protected
ClosedCaptionCtrl
(
byte
cc1
,
byte
cc2
)
{
super
(
ClosedCaption
.
TYPE_CTRL
,
timeUs
);
super
(
ClosedCaption
.
TYPE_CTRL
);
this
.
cc1
=
cc1
;
this
.
cc1
=
cc1
;
this
.
cc2
=
cc2
;
this
.
cc2
=
cc2
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionList.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
text
.
eia608
;
/* package */
final
class
ClosedCaptionList
implements
Comparable
<
ClosedCaptionList
>
{
public
final
long
timeUs
;
public
final
boolean
decodeOnly
;
public
final
ClosedCaption
[]
captions
;
public
ClosedCaptionList
(
long
timeUs
,
boolean
decodeOnly
,
ClosedCaption
[]
captions
)
{
this
.
timeUs
=
timeUs
;
this
.
decodeOnly
=
decodeOnly
;
this
.
captions
=
captions
;
}
@Override
public
int
compareTo
(
ClosedCaptionList
other
)
{
long
delta
=
timeUs
-
other
.
timeUs
;
if
(
delta
==
0
)
{
return
0
;
}
return
delta
>
0
?
1
:
-
1
;
}
}
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java
View file @
8f0d576f
...
@@ -19,8 +19,8 @@ package com.google.android.exoplayer.text.eia608;
...
@@ -19,8 +19,8 @@ package com.google.android.exoplayer.text.eia608;
public
final
String
text
;
public
final
String
text
;
public
ClosedCaptionText
(
String
text
,
long
timeUs
)
{
public
ClosedCaptionText
(
String
text
)
{
super
(
ClosedCaption
.
TYPE_TEXT
,
timeUs
);
super
(
ClosedCaption
.
TYPE_TEXT
);
this
.
text
=
text
;
this
.
text
=
text
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java
View file @
8f0d576f
...
@@ -15,10 +15,12 @@
...
@@ -15,10 +15,12 @@
*/
*/
package
com
.
google
.
android
.
exoplayer
.
text
.
eia608
;
package
com
.
google
.
android
.
exoplayer
.
text
.
eia608
;
import
com.google.android.exoplayer.
util.BitArray
;
import
com.google.android.exoplayer.
SampleHolder
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.util.List
;
import
java.util.
Array
List
;
/**
/**
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
...
@@ -80,28 +82,31 @@ public class Eia608Parser {
...
@@ -80,28 +82,31 @@ public class Eia608Parser {
0xFB
// 3F: 251 'û' "Latin small letter U with circumflex"
0xFB
// 3F: 251 'û' "Latin small letter U with circumflex"
};
};
private
final
BitArray
seiBuffer
;
private
final
Parsable
BitArray
seiBuffer
;
private
final
StringBuilder
stringBuilder
;
private
final
StringBuilder
stringBuilder
;
private
final
ArrayList
<
ClosedCaption
>
captions
;
/* package */
Eia608Parser
()
{
/* package */
Eia608Parser
()
{
seiBuffer
=
new
BitArray
();
seiBuffer
=
new
Parsable
BitArray
();
stringBuilder
=
new
StringBuilder
();
stringBuilder
=
new
StringBuilder
();
captions
=
new
ArrayList
<
ClosedCaption
>();
}
}
/* package */
boolean
canParse
(
String
mimeType
)
{
/* package */
boolean
canParse
(
String
mimeType
)
{
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_EIA608
);
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_EIA608
);
}
}
/* package */
void
parse
(
byte
[]
data
,
int
size
,
long
timeUs
,
List
<
ClosedCaption
>
out
)
{
/* package */
ClosedCaptionList
parse
(
SampleHolder
sampleHolder
)
{
if
(
size
<=
0
)
{
if
(
s
ampleHolder
.
s
ize
<=
0
)
{
return
;
return
null
;
}
}
captions
.
clear
();
stringBuilder
.
setLength
(
0
);
stringBuilder
.
setLength
(
0
);
seiBuffer
.
reset
(
data
,
size
);
seiBuffer
.
reset
(
sampleHolder
.
data
.
array
()
);
seiBuffer
.
skipBits
(
3
);
// reserved + process_cc_data_flag + zero_bit
seiBuffer
.
skipBits
(
3
);
// reserved + process_cc_data_flag + zero_bit
int
ccCount
=
seiBuffer
.
readBits
(
5
);
int
ccCount
=
seiBuffer
.
readBits
(
5
);
seiBuffer
.
skipB
ytes
(
1
);
seiBuffer
.
skipB
its
(
8
);
for
(
int
i
=
0
;
i
<
ccCount
;
i
++)
{
for
(
int
i
=
0
;
i
<
ccCount
;
i
++)
{
seiBuffer
.
skipBits
(
5
);
// one_bit + reserved
seiBuffer
.
skipBits
(
5
);
// one_bit + reserved
...
@@ -134,10 +139,10 @@ public class Eia608Parser {
...
@@ -134,10 +139,10 @@ public class Eia608Parser {
// Control character.
// Control character.
if
(
ccData1
<
0x20
)
{
if
(
ccData1
<
0x20
)
{
if
(
stringBuilder
.
length
()
>
0
)
{
if
(
stringBuilder
.
length
()
>
0
)
{
out
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
(),
timeUs
));
captions
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
()
));
stringBuilder
.
setLength
(
0
);
stringBuilder
.
setLength
(
0
);
}
}
out
.
add
(
new
ClosedCaptionCtrl
(
ccData1
,
ccData2
,
timeUs
));
captions
.
add
(
new
ClosedCaptionCtrl
(
ccData1
,
ccData2
));
continue
;
continue
;
}
}
...
@@ -149,8 +154,16 @@ public class Eia608Parser {
...
@@ -149,8 +154,16 @@ public class Eia608Parser {
}
}
if
(
stringBuilder
.
length
()
>
0
)
{
if
(
stringBuilder
.
length
()
>
0
)
{
out
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
(),
timeUs
));
captions
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
()
));
}
}
if
(
captions
.
isEmpty
())
{
return
null
;
}
ClosedCaption
[]
captionArray
=
new
ClosedCaption
[
captions
.
size
()];
captions
.
toArray
(
captionArray
);
return
new
ClosedCaptionList
(
sampleHolder
.
timeUs
,
sampleHolder
.
decodeOnly
,
captionArray
);
}
}
private
static
char
getChar
(
byte
ccData
)
{
private
static
char
getChar
(
byte
ccData
)
{
...
@@ -170,7 +183,7 @@ public class Eia608Parser {
...
@@ -170,7 +183,7 @@ public class Eia608Parser {
* @param seiBuffer The buffer to read from.
* @param seiBuffer The buffer to read from.
* @return The size of closed captions data.
* @return The size of closed captions data.
*/
*/
public
static
int
parseHeader
(
Bit
Array
seiBuffer
)
{
public
static
int
parseHeader
(
ParsableByte
Array
seiBuffer
)
{
int
b
=
0
;
int
b
=
0
;
int
payloadType
=
0
;
int
payloadType
=
0
;
...
@@ -197,11 +210,11 @@ public class Eia608Parser {
...
@@ -197,11 +210,11 @@ public class Eia608Parser {
if
(
countryCode
!=
COUNTRY_CODE
)
{
if
(
countryCode
!=
COUNTRY_CODE
)
{
return
0
;
return
0
;
}
}
int
providerCode
=
seiBuffer
.
read
Bits
(
16
);
int
providerCode
=
seiBuffer
.
read
UnsignedShort
(
);
if
(
providerCode
!=
PROVIDER_CODE
)
{
if
(
providerCode
!=
PROVIDER_CODE
)
{
return
0
;
return
0
;
}
}
int
userIdentifier
=
seiBuffer
.
read
Bits
(
32
);
int
userIdentifier
=
seiBuffer
.
read
Int
(
);
if
(
userIdentifier
!=
USER_ID
)
{
if
(
userIdentifier
!=
USER_ID
)
{
return
0
;
return
0
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java
View file @
8f0d576f
...
@@ -31,8 +31,7 @@ import android.os.Looper;
...
@@ -31,8 +31,7 @@ import android.os.Looper;
import
android.os.Message
;
import
android.os.Message
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.TreeSet
;
import
java.util.List
;
/**
/**
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
...
@@ -48,6 +47,8 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -48,6 +47,8 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
// The default number of rows to display in roll-up captions mode.
// The default number of rows to display in roll-up captions mode.
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
// The maximum duration that captions are parsed ahead of the current position.
private
static
final
int
MAX_SAMPLE_READAHEAD_US
=
5000000
;
private
final
SampleSource
source
;
private
final
SampleSource
source
;
private
final
Eia608Parser
eia608Parser
;
private
final
Eia608Parser
eia608Parser
;
...
@@ -56,7 +57,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -56,7 +57,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
private
final
MediaFormatHolder
formatHolder
;
private
final
MediaFormatHolder
formatHolder
;
private
final
SampleHolder
sampleHolder
;
private
final
SampleHolder
sampleHolder
;
private
final
StringBuilder
captionStringBuilder
;
private
final
StringBuilder
captionStringBuilder
;
private
final
List
<
ClosedCaption
>
captionBuffer
;
private
final
TreeSet
<
ClosedCaptionList
>
pendingCaptionLists
;
private
int
trackIndex
;
private
int
trackIndex
;
private
long
currentPositionUs
;
private
long
currentPositionUs
;
...
@@ -85,7 +86,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -85,7 +86,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
formatHolder
=
new
MediaFormatHolder
();
formatHolder
=
new
MediaFormatHolder
();
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
captionStringBuilder
=
new
StringBuilder
();
captionStringBuilder
=
new
StringBuilder
();
captionBuffer
=
new
ArrayList
<
ClosedCaption
>();
pendingCaptionLists
=
new
TreeSet
<
ClosedCaptionList
>();
}
}
@Override
@Override
...
@@ -122,6 +123,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -122,6 +123,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
private
void
seekToInternal
(
long
positionUs
)
{
private
void
seekToInternal
(
long
positionUs
)
{
currentPositionUs
=
positionUs
;
currentPositionUs
=
positionUs
;
inputStreamEnded
=
false
;
inputStreamEnded
=
false
;
pendingCaptionLists
.
clear
();
clearPendingSample
();
clearPendingSample
();
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
setCaptionMode
(
CC_MODE_UNKNOWN
);
setCaptionMode
(
CC_MODE_UNKNOWN
);
...
@@ -138,10 +140,17 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -138,10 +140,17 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
throw
new
ExoPlaybackException
(
e
);
throw
new
ExoPlaybackException
(
e
);
}
}
if
(!
inputStreamEnded
&&
!
isSamplePending
())
{
if
(
isSamplePending
())
{
maybeParsePendingSample
();
}
int
result
=
inputStreamEnded
?
SampleSource
.
END_OF_STREAM
:
SampleSource
.
SAMPLE_READ
;
while
(!
isSamplePending
()
&&
result
==
SampleSource
.
SAMPLE_READ
)
{
try
{
try
{
int
result
=
source
.
readData
(
trackIndex
,
positionUs
,
formatHolder
,
sampleHolder
,
false
);
result
=
source
.
readData
(
trackIndex
,
positionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
maybeParsePendingSample
();
}
else
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
inputStreamEnded
=
true
;
inputStreamEnded
=
true
;
}
}
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
...
@@ -149,17 +158,18 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -149,17 +158,18 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
}
}
}
}
if
(
isSamplePending
()
&&
sampleHolder
.
timeUs
<=
currentPositionUs
)
{
while
(!
pendingCaptionLists
.
isEmpty
())
{
// Parse the pending sample.
if
(
pendingCaptionLists
.
first
().
timeUs
>
currentPositionUs
)
{
eia608Parser
.
parse
(
sampleHolder
.
data
.
array
(),
sampleHolder
.
size
,
sampleHolder
.
timeUs
,
// We're too early to render any of the pending caption lists.
captionBuffer
);
return
;
// Consume parsed captions.
}
consumeCaptionBuffer
();
// Remove and consume the next caption list.
// Update the renderer, unless the sample was marked for decoding only.
ClosedCaptionList
nextCaptionList
=
pendingCaptionLists
.
pollFirst
();
if
(!
sampleHolder
.
decodeOnly
)
{
consumeCaptionList
(
nextCaptionList
);
// Update the renderer, unless the caption list was marked for decoding only.
if
(!
nextCaptionList
.
decodeOnly
)
{
invokeRenderer
(
caption
);
invokeRenderer
(
caption
);
}
}
clearPendingSample
();
}
}
}
}
...
@@ -221,14 +231,26 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -221,14 +231,26 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
textRenderer
.
onText
(
text
);
textRenderer
.
onText
(
text
);
}
}
private
void
consumeCaptionBuffer
()
{
private
void
maybeParsePendingSample
()
{
int
captionBufferSize
=
captionBuffer
.
size
();
if
(
sampleHolder
.
timeUs
>
currentPositionUs
+
MAX_SAMPLE_READAHEAD_US
)
{
// We're too early to parse the sample.
return
;
}
ClosedCaptionList
holder
=
eia608Parser
.
parse
(
sampleHolder
);
clearPendingSample
();
if
(
holder
!=
null
)
{
pendingCaptionLists
.
add
(
holder
);
}
}
private
void
consumeCaptionList
(
ClosedCaptionList
captionList
)
{
int
captionBufferSize
=
captionList
.
captions
.
length
;
if
(
captionBufferSize
==
0
)
{
if
(
captionBufferSize
==
0
)
{
return
;
return
;
}
}
for
(
int
i
=
0
;
i
<
captionBufferSize
;
i
++)
{
for
(
int
i
=
0
;
i
<
captionBufferSize
;
i
++)
{
ClosedCaption
caption
=
caption
Buffer
.
get
(
i
)
;
ClosedCaption
caption
=
caption
List
.
captions
[
i
]
;
if
(
caption
.
type
==
ClosedCaption
.
TYPE_CTRL
)
{
if
(
caption
.
type
==
ClosedCaption
.
TYPE_CTRL
)
{
ClosedCaptionCtrl
captionCtrl
=
(
ClosedCaptionCtrl
)
caption
;
ClosedCaptionCtrl
captionCtrl
=
(
ClosedCaptionCtrl
)
caption
;
if
(
captionCtrl
.
isMiscCode
())
{
if
(
captionCtrl
.
isMiscCode
())
{
...
@@ -240,7 +262,6 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
...
@@ -240,7 +262,6 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
handleText
((
ClosedCaptionText
)
caption
);
handleText
((
ClosedCaptionText
)
caption
);
}
}
}
}
captionBuffer
.
clear
();
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
caption
=
getDisplayCaption
();
caption
=
getDisplayCaption
();
...
...
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
View file @
8f0d576f
...
@@ -96,13 +96,39 @@ public final class BufferPool implements Allocator {
...
@@ -96,13 +96,39 @@ public final class BufferPool implements Allocator {
allocatedBufferCount
+=
requiredBufferCount
-
firstNewBufferIndex
;
allocatedBufferCount
+=
requiredBufferCount
-
firstNewBufferIndex
;
for
(
int
i
=
firstNewBufferIndex
;
i
<
requiredBufferCount
;
i
++)
{
for
(
int
i
=
firstNewBufferIndex
;
i
<
requiredBufferCount
;
i
++)
{
// Use a recycled buffer if one is available. Else instantiate a new one.
// Use a recycled buffer if one is available. Else instantiate a new one.
buffers
[
i
]
=
recycledBufferCount
>
0
?
recycledBuffers
[--
recycledBufferCount
]
:
buffers
[
i
]
=
nextBuffer
();
new
byte
[
bufferLength
];
}
}
return
buffers
;
return
buffers
;
}
}
/**
/**
* Obtain a single buffer directly from the pool.
* <p>
* When the caller has finished with the buffer, it should be returned to the pool by calling
* {@link #releaseDirect(byte[])}.
*
* @return The allocated buffer.
*/
public
synchronized
byte
[]
allocateDirect
()
{
allocatedBufferCount
++;
return
nextBuffer
();
}
/**
* Return a single buffer to the pool.
*
* @param buffer The buffer being returned.
*/
public
synchronized
void
releaseDirect
(
byte
[]
buffer
)
{
// Weak sanity check that the buffer probably originated from this pool.
Assertions
.
checkArgument
(
buffer
.
length
==
bufferLength
);
allocatedBufferCount
--;
ensureRecycledBufferCapacity
(
recycledBufferCount
+
1
);
recycledBuffers
[
recycledBufferCount
++]
=
buffer
;
}
/**
* Returns the buffers belonging to an allocation to the pool.
* Returns the buffers belonging to an allocation to the pool.
*
*
* @param allocation The allocation to return.
* @param allocation The allocation to return.
...
@@ -112,14 +138,7 @@ public final class BufferPool implements Allocator {
...
@@ -112,14 +138,7 @@ public final class BufferPool implements Allocator {
allocatedBufferCount
-=
buffers
.
length
;
allocatedBufferCount
-=
buffers
.
length
;
int
newRecycledBufferCount
=
recycledBufferCount
+
buffers
.
length
;
int
newRecycledBufferCount
=
recycledBufferCount
+
buffers
.
length
;
if
(
recycledBuffers
.
length
<
newRecycledBufferCount
)
{
ensureRecycledBufferCapacity
(
newRecycledBufferCount
);
// Expand the capacity of the recycled buffers array.
byte
[][]
newRecycledBuffers
=
new
byte
[
newRecycledBufferCount
*
2
][];
if
(
recycledBufferCount
>
0
)
{
System
.
arraycopy
(
recycledBuffers
,
0
,
newRecycledBuffers
,
0
,
recycledBufferCount
);
}
recycledBuffers
=
newRecycledBuffers
;
}
System
.
arraycopy
(
buffers
,
0
,
recycledBuffers
,
recycledBufferCount
,
buffers
.
length
);
System
.
arraycopy
(
buffers
,
0
,
recycledBuffers
,
recycledBufferCount
,
buffers
.
length
);
recycledBufferCount
=
newRecycledBufferCount
;
recycledBufferCount
=
newRecycledBufferCount
;
}
}
...
@@ -128,6 +147,22 @@ public final class BufferPool implements Allocator {
...
@@ -128,6 +147,22 @@ public final class BufferPool implements Allocator {
return
(
int
)
((
size
+
bufferLength
-
1
)
/
bufferLength
);
return
(
int
)
((
size
+
bufferLength
-
1
)
/
bufferLength
);
}
}
private
byte
[]
nextBuffer
()
{
return
recycledBufferCount
>
0
?
recycledBuffers
[--
recycledBufferCount
]
:
new
byte
[
bufferLength
];
}
private
void
ensureRecycledBufferCapacity
(
int
requiredCapacity
)
{
if
(
recycledBuffers
.
length
<
requiredCapacity
)
{
// Expand the capacity of the recycled buffers array.
byte
[][]
newRecycledBuffers
=
new
byte
[
requiredCapacity
*
2
][];
if
(
recycledBufferCount
>
0
)
{
System
.
arraycopy
(
recycledBuffers
,
0
,
newRecycledBuffers
,
0
,
recycledBufferCount
);
}
recycledBuffers
=
newRecycledBuffers
;
}
}
private
class
AllocationImpl
implements
Allocation
{
private
class
AllocationImpl
implements
Allocation
{
private
byte
[][]
buffers
;
private
byte
[][]
buffers
;
...
...
library/src/main/java/com/google/android/exoplayer/util/BitArray.java
deleted
100644 → 0
View file @
ccac9fad
/*
* 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
.
util
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
java.io.IOException
;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
public
final
class
BitArray
{
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
;
public
BitArray
()
{
}
public
BitArray
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
}
/**
* Clears all data, setting the offset and limit to zero.
*/
public
void
reset
()
{
byteOffset
=
0
;
bitOffset
=
0
;
limit
=
0
;
}
/**
* Resets to wrap the specified data, setting the offset to zero.
*
* @param data The data to wrap.
* @param limit The limit to set.
*/
public
void
reset
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
byteOffset
=
0
;
bitOffset
=
0
;
}
/**
* Gets the backing byte array.
*
* @return The backing byte array.
*/
public
byte
[]
getData
()
{
return
data
;
}
/**
* 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 DataSource}.
*
* @param dataSource The {@link DataSource} from which to read.
* @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended, or -1 if no more data is available.
* @throws IOException If an error occurs reading from the source.
*/
public
int
append
(
DataSource
dataSource
,
int
length
)
throws
IOException
{
expand
(
length
);
int
bytesRead
=
dataSource
.
read
(
data
,
limit
,
length
);
if
(
bytesRead
==
-
1
)
{
return
-
1
;
}
limit
+=
bytesRead
;
return
bytesRead
;
}
/**
* Appends data from another {@link BitArray}.
*
* @param bitsArray The {@link BitArray} whose data should be appended.
* @param length The number of bytes to read and append.
*/
public
void
append
(
BitArray
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
()
{
int
value
;
if
(
bitOffset
!=
0
)
{
value
=
((
data
[
byteOffset
]
&
0xFF
)
<<
bitOffset
)
|
((
data
[
byteOffset
+
1
]
&
0xFF
)
>>>
(
8
-
bitOffset
));
}
else
{
value
=
data
[
byteOffset
];
}
byteOffset
++;
return
value
&
0xFF
;
}
/**
* Reads a single bit.
*
* @return True if the bit is set. False otherwise.
*/
public
boolean
readBit
()
{
return
readBits
(
1
)
==
1
;
}
/**
* 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
;
}
/**
* Reads an unsigned Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public
int
readUnsignedExpGolombCodedInt
()
{
return
readExpGolombCodeNum
();
}
/**
* Reads an signed Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public
int
readSignedExpGolombCodedInt
()
{
int
codeNum
=
readExpGolombCodeNum
();
return
((
codeNum
%
2
)
==
0
?
-
1
:
1
)
*
((
codeNum
+
1
)
/
2
);
}
private
int
readExpGolombCodeNum
()
{
int
leadingZeros
=
0
;
while
(!
readBit
())
{
leadingZeros
++;
}
return
(
1
<<
leadingZeros
)
-
1
+
(
leadingZeros
>
0
?
readBits
(
leadingZeros
)
:
0
);
}
/**
* Reads a Synchsafe integer.
* Synchsafe integers are integers that keep the highest bit of every byte zeroed.
* A 32 bit synchsafe integer can store 28 bits of information.
*
* @return The value of the parsed Synchsafe integer.
*/
public
int
readSynchSafeInt
()
{
int
b1
=
readUnsignedByte
();
int
b2
=
readUnsignedByte
();
int
b3
=
readUnsignedByte
();
int
b4
=
readUnsignedByte
();
return
(
b1
<<
21
)
|
(
b2
<<
14
)
|
(
b3
<<
7
)
|
b4
;
}
// 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, or -1 for any NAL unit.
* @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
==
-
1
||
(
nalUnitType
==
(
data
[
i
+
3
]
&
0x1F
))))
{
return
i
-
byteOffset
;
}
}
return
limit
-
byteOffset
;
}
}
library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java
0 → 100644
View file @
8f0d576f
/*
* 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
.
util
;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
public
final
class
ParsableBitArray
{
private
byte
[]
data
;
// 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
;
/** Creates a new instance that initially has no backing data. */
public
ParsableBitArray
()
{}
/**
* Creates a new instance that wraps an existing array.
*
* @param data The data to wrap.
*/
public
ParsableBitArray
(
byte
[]
data
)
{
this
.
data
=
data
;
}
/**
* Updates the instance to wrap {@code data}, and resets the position to zero.
*
* @param data The array to wrap.
*/
public
void
reset
(
byte
[]
data
)
{
this
.
data
=
data
;
byteOffset
=
0
;
bitOffset
=
0
;
}
/**
* Gets the backing byte array.
*
* @return The backing byte array.
*/
public
byte
[]
getData
()
{
return
data
;
}
/**
* Gets the current bit offset.
*
* @return The current bit offset.
*/
public
int
getPosition
()
{
return
byteOffset
*
8
+
bitOffset
;
}
/**
* Sets the current bit offset.
*
* @param position The position to set.
*/
public
void
setPosition
(
int
position
)
{
byteOffset
=
position
/
8
;
bitOffset
=
position
-
(
byteOffset
*
8
);
}
/**
* 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
;
}
}
/**
* Reads a single bit.
*
* @return True if the bit is set. False otherwise.
*/
public
boolean
readBit
()
{
return
readBits
(
1
)
==
1
;
}
/**
* 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
;
}
/**
* Reads an unsigned Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public
int
readUnsignedExpGolombCodedInt
()
{
return
readExpGolombCodeNum
();
}
/**
* Reads an signed Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public
int
readSignedExpGolombCodedInt
()
{
int
codeNum
=
readExpGolombCodeNum
();
return
((
codeNum
%
2
)
==
0
?
-
1
:
1
)
*
((
codeNum
+
1
)
/
2
);
}
private
int
readUnsignedByte
()
{
int
value
;
if
(
bitOffset
!=
0
)
{
value
=
((
data
[
byteOffset
]
&
0xFF
)
<<
bitOffset
)
|
((
data
[
byteOffset
+
1
]
&
0xFF
)
>>>
(
8
-
bitOffset
));
}
else
{
value
=
data
[
byteOffset
];
}
byteOffset
++;
return
value
&
0xFF
;
}
private
int
getUnsignedByte
(
int
offset
)
{
return
data
[
offset
]
&
0xFF
;
}
private
int
readExpGolombCodeNum
()
{
int
leadingZeros
=
0
;
while
(!
readBit
())
{
leadingZeros
++;
}
return
(
1
<<
leadingZeros
)
-
1
+
(
leadingZeros
>
0
?
readBits
(
leadingZeros
)
:
0
);
}
}
library/src/main/java/com/google/android/exoplayer/util/ParsableByteArray.java
View file @
8f0d576f
...
@@ -23,18 +23,69 @@ import java.nio.ByteBuffer;
...
@@ -23,18 +23,69 @@ import java.nio.ByteBuffer;
*/
*/
public
final
class
ParsableByteArray
{
public
final
class
ParsableByteArray
{
public
final
byte
[]
data
;
public
byte
[]
data
;
private
int
position
;
private
int
position
;
private
int
limit
;
/** Creates a new parsable array with {@code length} bytes. */
/** Creates a new instance that initially has no backing data. */
public
ParsableByteArray
()
{}
/** Creates a new instance with {@code length} bytes. */
public
ParsableByteArray
(
int
length
)
{
public
ParsableByteArray
(
int
length
)
{
this
.
data
=
new
byte
[
length
];
this
.
data
=
new
byte
[
length
];
limit
=
data
.
length
;
}
/**
* Creates a new instance that wraps an existing array.
*
* @param data The data to wrap.
* @param limit The limit.
*/
public
ParsableByteArray
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
}
/**
* Updates the instance to wrap {@code data}, and resets the position to zero.
*
* @param data The array to wrap.
* @param limit The limit.
*/
public
void
reset
(
byte
[]
data
,
int
limit
)
{
this
.
data
=
data
;
this
.
limit
=
limit
;
position
=
0
;
}
/**
* Sets the position and limit to zero.
*/
public
void
reset
()
{
position
=
0
;
limit
=
0
;
}
/** Returns the number of bytes yet to be read. */
public
int
bytesLeft
()
{
return
limit
-
position
;
}
}
/** Returns the number of bytes in the array. */
/** Returns the limit. */
public
int
length
()
{
public
int
limit
()
{
return
data
.
length
;
return
limit
;
}
/**
* Sets the limit.
*
* @param limit The limit to set.
*/
public
void
setLimit
(
int
limit
)
{
Assertions
.
checkArgument
(
limit
>=
0
&&
limit
<=
data
.
length
);
this
.
limit
=
limit
;
}
}
/** Returns the current offset in the array, in bytes. */
/** Returns the current offset in the array, in bytes. */
...
@@ -42,6 +93,11 @@ public final class ParsableByteArray {
...
@@ -42,6 +93,11 @@ public final class ParsableByteArray {
return
position
;
return
position
;
}
}
/** Returns the capacity of the array, which may be larger than the limit. */
public
int
capacity
()
{
return
data
==
null
?
0
:
data
.
length
;
}
/**
/**
* Sets the reading offset in the array.
* Sets the reading offset in the array.
*
*
...
@@ -51,7 +107,7 @@ public final class ParsableByteArray {
...
@@ -51,7 +107,7 @@ public final class ParsableByteArray {
*/
*/
public
void
setPosition
(
int
position
)
{
public
void
setPosition
(
int
position
)
{
// It is fine for position to be at the end of the array.
// It is fine for position to be at the end of the array.
Assertions
.
checkArgument
(
position
>=
0
&&
position
<=
data
.
length
);
Assertions
.
checkArgument
(
position
>=
0
&&
position
<=
limit
);
this
.
position
=
position
;
this
.
position
=
position
;
}
}
...
@@ -61,11 +117,27 @@ public final class ParsableByteArray {
...
@@ -61,11 +117,27 @@ public final class ParsableByteArray {
* @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the
* @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the
* array.
* array.
*/
*/
// TODO: Rename to skipBytes so that it's clearer how much data is being skipped in code where
// both ParsableBitArray and ParsableByteArray are in use.
public
void
skip
(
int
bytes
)
{
public
void
skip
(
int
bytes
)
{
setPosition
(
position
+
bytes
);
setPosition
(
position
+
bytes
);
}
}
/**
/**
* Reads the next {@code length} bytes into {@code bitArray}, and resets the position of
* {@code bitArray} to zero.
*
* @param bitArray The {@link ParsableBitArray} into which the bytes should be read.
* @param length The number of bytes to write.
*/
// TODO: It's possible to have bitArray directly index into the same array as is being wrapped
// by this instance. Decide whether it's worth doing this.
public
void
readBytes
(
ParsableBitArray
bitArray
,
int
length
)
{
readBytes
(
bitArray
.
getData
(),
0
,
length
);
bitArray
.
setPosition
(
0
);
}
/**
* Reads the next {@code length} bytes into {@code buffer} at {@code offset}.
* Reads the next {@code length} bytes into {@code buffer} at {@code offset}.
*
*
* @see System#arraycopy
* @see System#arraycopy
...
@@ -128,6 +200,22 @@ public final class ParsableByteArray {
...
@@ -128,6 +200,22 @@ public final class ParsableByteArray {
}
}
/**
/**
* Reads a Synchsafe integer.
* <p>
* Synchsafe integers keep the highest bit of every byte zeroed. A 32 bit synchsafe integer can
* store 28 bits of information.
*
* @return The parsed value.
*/
public
int
readSynchSafeInt
()
{
int
b1
=
readUnsignedByte
();
int
b2
=
readUnsignedByte
();
int
b3
=
readUnsignedByte
();
int
b4
=
readUnsignedByte
();
return
(
b1
<<
21
)
|
(
b2
<<
14
)
|
(
b3
<<
7
)
|
b4
;
}
/**
* Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero.
* Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero.
*
*
* @throws IllegalArgumentException Thrown if the top bit of the input data is set.
* @throws IllegalArgumentException Thrown if the top bit of the input data is set.
...
...
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