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
3e36f529
authored
Sep 29, 2015
by
joli
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
FLV Support - Added Video Reader and parsing improvements
parent
8ddc7351
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
624 additions
and
457 deletions
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagReader.java → library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagPayloadReader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/MetadataReader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/ScriptTagPayloadReader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/TagHeader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/TagReader.java → library/src/main/java/com/google/android/exoplayer/extractor/flv/TagPayloadReader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java
library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagReader.java
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
3e36f529
...
...
@@ -145,9 +145,9 @@ import java.util.Locale;
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3"
,
PlayerActivity
.
TYPE_OTHER
),
new
Sample
(
"Google Glass (WebM Video with Vorbis Audio)"
,
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm"
,
PlayerActivity
.
TYPE_OTHER
),
new
Sample
(
"FLV Sample
"
,
"http://master255.org/res/%D0%9A%D0%BB%D0%B8%D0%BF%D1%8B/B/Black%20Eyed%20Peas/black%20ey"
+
"ed%20peas-My%20Humps.flv"
,
PlayerActivity
.
TYPE_OTHER
),
new
Sample
(
"Big Buck Bunny (FLV Video)
"
,
"http://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0"
,
PlayerActivity
.
TYPE_OTHER
),
};
private
Samples
()
{}
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
View file @
3e36f529
...
...
@@ -58,9 +58,10 @@ import java.util.List;
* <li>MP3 ({@link com.google.android.exoplayer.extractor.mp3.Mp3Extractor})</li>
* <li>AAC ({@link com.google.android.exoplayer.extractor.ts.AdtsExtractor})</li>
* <li>MPEG TS ({@link com.google.android.exoplayer.extractor.ts.TsExtractor}</li>
* <li>FLV ({@link com.google.android.exoplayer.extractor.flv.FlvExtractor}</li>
* </ul>
*
* <p>Seeking in AAC
and MPEG TS
streams is not supported.
* <p>Seeking in AAC
, MPEG TS and FLV
streams is not supported.
*
* <p>To override the default extractors, pass one or more {@link Extractor} instances to the
* constructor. When reading a new stream, the first {@link Extractor} that returns {@code true}
...
...
library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagReader.java
→
library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTag
Payload
Reader.java
View file @
3e36f529
/*
* 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
.
extractor
.
flv
;
import
android.util.Log
;
import
android.util.Pair
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
...
...
@@ -14,48 +27,27 @@ import com.google.android.exoplayer.util.ParsableByteArray;
import
java.util.Collections
;
/**
*
Created by joliva on 9/27/15
.
*
Parses audio tags of from an FLV stream and extracts AAC frames
.
*/
public
class
AudioTagReader
extends
TagReader
{
private
static
final
String
TAG
=
"AudioTagReader"
;
final
class
AudioTagPayloadReader
extends
TagPayloadReader
{
// Sound format
private
static
final
int
AUDIO_FORMAT_LINEAR_PCM_PLATFORM_ENDIAN
=
0
;
private
static
final
int
AUDIO_FORMAT_ADPCM
=
1
;
private
static
final
int
AUDIO_FORMAT_MP3
=
2
;
private
static
final
int
AUDIO_FORMAT_LINEAR_PCM_LITTLE_ENDIAN
=
3
;
private
static
final
int
AUDIO_FORMAT_NELLYMOSER_16KHZ_MONO
=
4
;
private
static
final
int
AUDIO_FORMAT_NELLYMOSER_8KHZ_MONO
=
5
;
private
static
final
int
AUDIO_FORMAT_NELLYMOSER
=
6
;
private
static
final
int
AUDIO_FORMAT_G711_A_LAW
=
7
;
private
static
final
int
AUDIO_FORMAT_G711_MU_LAW
=
8
;
private
static
final
int
AUDIO_FORMAT_RESERVED
=
9
;
private
static
final
int
AUDIO_FORMAT_AAC
=
10
;
private
static
final
int
AUDIO_FORMAT_SPEEX
=
11
;
private
static
final
int
AUDIO_FORMAT_MP3_8KHZ
=
14
;
private
static
final
int
AUDIO_FORMAT_DEVICE_SPECIFIC
=
15
;
// AAC PACKET TYPE
private
static
final
int
AAC_PACKET_TYPE_SEQUENCE_HEADER
=
0
;
private
static
final
int
AAC_PACKET_TYPE_AAC_RAW
=
1
;
// SAMPLING RATES
private
static
final
int
[]
AUDIO_SAMPLING_RATE_TABLE
=
new
int
[]
{
5500
,
11000
,
22000
,
44000
};
private
int
format
;
private
int
sampleRate
;
private
int
bitsPerSample
;
private
int
channels
;
private
boolean
hasParsedAudioData
;
// State variables
private
boolean
hasParsedAudioDataHeader
;
private
boolean
hasOutputFormat
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
public
AudioTagReader
(
TrackOutput
output
)
{
public
AudioTagPayloadReader
(
TrackOutput
output
)
{
super
(
output
);
}
...
...
@@ -65,8 +57,10 @@ public class AudioTagReader extends TagReader{
}
@Override
protected
void
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
if
(!
hasParsedAudioData
)
{
protected
boolean
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
// Parse audio data header, if it was not done, to extract information
// about the audio codec and audio configuration.
if
(!
hasParsedAudioDataHeader
)
{
int
header
=
data
.
readUnsignedByte
();
int
soundFormat
=
(
header
>>
4
)
&
0x0F
;
int
sampleRateIndex
=
(
header
>>
2
)
&
0x03
;
...
...
@@ -78,42 +72,29 @@ public class AudioTagReader extends TagReader{
}
if
(!
hasOutputFormat
)
{
switch
(
soundFormat
)
{
// raw audio data. Just creates media format
case
AUDIO_FORMAT_LINEAR_PCM_LITTLE_ENDIAN:
output
.
format
(
MediaFormat
.
createAudioFormat
(
MimeTypes
.
AUDIO_RAW
,
MediaFormat
.
NO_VALUE
,
MediaFormat
.
NO_VALUE
,
MediaFormat
.
NO_VALUE
,
channels
,
AUDIO_SAMPLING_RATE_TABLE
[
sampleRateIndex
],
null
,
null
));
hasOutputFormat
=
true
;
break
;
case
AUDIO_FORMAT_AAC:
break
;
case
AUDIO_FORMAT_MP3:
case
AUDIO_FORMAT_MP3_8KHZ:
case
AUDIO_FORMAT_LINEAR_PCM_PLATFORM_ENDIAN:
default
:
throw
new
UnsupportedTrack
(
"Audio track not supported. Format: "
+
soundFormat
+
", Sample rate: "
+
sampleRateIndex
+
", bps: "
+
bitsPerSample
+
", channels: "
+
channels
);
// TODO: Adds support for MP3 and PCM
if
(
soundFormat
!=
AUDIO_FORMAT_AAC
)
{
throw
new
UnsupportedTrack
(
"Audio track not supported. Format: "
+
soundFormat
+
", Sample rate: "
+
sampleRateIndex
+
", bps: "
+
bitsPerSample
+
", channels: "
+
channels
);
}
}
this
.
format
=
soundFormat
;
this
.
sampleRate
=
AUDIO_SAMPLING_RATE_TABLE
[
sampleRateIndex
];
this
.
bitsPerSample
=
bitsPerSample
;
this
.
channels
=
channels
;
hasParsedAudioData
=
true
;
hasParsedAudioDataHeader
=
true
;
}
else
{
// Skip header if it was parsed previously.
data
.
skipBytes
(
1
);
}
// In all the cases we will be managing AAC format (otherwise an exception would be
// fired so we can just always return true
return
true
;
}
@Override
protected
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
)
{
int
packetType
=
data
.
readUnsignedByte
();
// Parse sequence header just in case it was not done before.
if
(
packetType
==
AAC_PACKET_TYPE_SEQUENCE_HEADER
&&
!
hasOutputFormat
)
{
ParsableBitArray
adtsScratch
=
new
ParsableBitArray
(
new
byte
[
data
.
bytesLeft
()]);
data
.
readBytes
(
adtsScratch
.
data
,
0
,
data
.
bytesLeft
());
...
...
@@ -134,17 +115,11 @@ public class AudioTagReader extends TagReader{
output
.
format
(
mediaFormat
);
hasOutputFormat
=
true
;
}
else
if
(
packetType
==
AAC_PACKET_TYPE_AAC_RAW
)
{
// Sample audio AAC frames
int
bytesToWrite
=
data
.
bytesLeft
();
output
.
sampleData
(
data
,
bytesToWrite
);
output
.
sampleMetadata
(
timeUs
,
C
.
SAMPLE_FLAG_SYNC
,
bytesToWrite
,
0
,
null
);
Log
.
d
(
TAG
,
"AAC TAG. Size: "
+
bytesToWrite
+
", timeUs: "
+
timeUs
);
}
}
@Override
protected
boolean
shouldParsePayload
()
{
return
(
format
==
AUDIO_FORMAT_AAC
);
}
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java
View file @
3e36f529
/*
* 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
.
extractor
.
flv
;
import
com.google.android.exoplayer.C
;
...
...
@@ -6,7 +21,6 @@ import com.google.android.exoplayer.extractor.ExtractorInput;
import
com.google.android.exoplayer.extractor.ExtractorOutput
;
import
com.google.android.exoplayer.extractor.PositionHolder
;
import
com.google.android.exoplayer.extractor.SeekMap
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
...
...
@@ -15,9 +29,9 @@ import java.io.EOFException;
import
java.io.IOException
;
/**
*
Created by joliva on 9/26/15
.
*
Facilitates the extraction of data from the FLV container format
.
*/
public
final
class
FlvExtractor
implements
Extractor
{
public
final
class
FlvExtractor
implements
Extractor
,
SeekMap
{
// Header sizes
private
static
final
int
FLV_MIN_HEADER_SIZE
=
9
;
private
static
final
int
FLV_TAG_HEADER_SIZE
=
11
;
...
...
@@ -31,8 +45,10 @@ public final class FlvExtractor implements Extractor {
private
static
final
int
TAG_TYPE_VIDEO
=
9
;
private
static
final
int
TAG_TYPE_SCRIPT_DATA
=
18
;
// FLV container identifier
private
static
final
int
FLV_TAG
=
Util
.
getIntegerCodeForString
(
"FLV"
);
// Temporary buffers
private
final
ParsableByteArray
scratch
;
private
final
ParsableByteArray
headerBuffer
;
private
final
ParsableByteArray
tagHeaderBuffer
;
...
...
@@ -40,36 +56,28 @@ public final class FlvExtractor implements Extractor {
// Extractor outputs.
private
ExtractorOutput
extractorOutput
;
private
TrackOutput
trackOutput
;
private
boolean
hasAudio
;
private
boolean
hasVideo
;
private
int
dataOffset
;
// State variables.
private
int
parserState
;
private
int
dataOffset
;
private
TagHeader
currentTagHeader
;
private
AudioTagReader
audioReader
;
private
VideoTagReader
videoReader
;
private
MetadataReader
metadataReader
;
// Tags readers
private
AudioTagPayloadReader
audioReader
;
private
VideoTagPayloadReader
videoReader
;
private
ScriptTagPayloadReader
metadataReader
;
public
FlvExtractor
()
{
scratch
=
new
ParsableByteArray
(
4
);
headerBuffer
=
new
ParsableByteArray
(
FLV_MIN_HEADER_SIZE
);
tagHeaderBuffer
=
new
ParsableByteArray
(
FLV_TAG_HEADER_SIZE
);
dataOffset
=
0
;
hasAudio
=
false
;
hasVideo
=
false
;
currentTagHeader
=
new
TagHeader
();
}
@Override
public
void
init
(
ExtractorOutput
output
)
{
this
.
extractorOutput
=
output
;
trackOutput
=
extractorOutput
.
track
(
0
);
extractorOutput
.
endTracks
();
output
.
seekMap
(
SeekMap
.
UNSEEKABLE
);
}
@Override
...
...
@@ -80,7 +88,6 @@ public final class FlvExtractor implements Extractor {
if
(
scratch
.
readUnsignedInt24
()
!=
FLV_TAG
)
{
return
false
;
}
/*
// Checking reserved flags are set to 0
input
.
peekFully
(
scratch
.
data
,
0
,
2
);
...
...
@@ -98,13 +105,10 @@ public final class FlvExtractor implements Extractor {
input
.
advancePeekPosition
(
dataOffset
);
// Checking first "previous tag size" is set to 0
input.peekFully(scratch.data, 0,
1
);
input
.
peekFully
(
scratch
.
data
,
0
,
4
);
scratch
.
setPosition
(
0
);
if (scratch.readInt() != 0) {
return false;
}
*/
return
true
;
return
scratch
.
readInt
()
==
0
;
}
@Override
...
...
@@ -117,21 +121,17 @@ public final class FlvExtractor implements Extractor {
try
{
while
(
true
)
{
switch
(
parserState
)
{
case
STATE_READING_TAG_HEADER:
if
(!
readTagHeader
(
input
))
{
return
RESULT_END_OF_INPUT
;
}
break
;
default
:
return
readSample
(
input
,
seekPosition
);
if
(
parserState
==
STATE_READING_TAG_HEADER
)
{
if
(!
readTagHeader
(
input
))
{
return
RESULT_END_OF_INPUT
;
}
}
else
{
return
readSample
(
input
);
}
}
}
catch
(
AudioTagReader
.
UnsupportedTrack
unsupportedTrack
)
{
}
catch
(
AudioTag
Payload
Reader
.
UnsupportedTrack
unsupportedTrack
)
{
unsupportedTrack
.
printStackTrace
();
return
RESULT_END_OF_INPUT
;
return
RESULT_END_OF_INPUT
;
}
}
...
...
@@ -140,23 +140,35 @@ public final class FlvExtractor implements Extractor {
dataOffset
=
0
;
}
/**
* Reads FLV container header from the provided {@link ExtractorInput}.
* @param input The {@link ExtractorInput} from which to read.
* @return True if header was read successfully. Otherwise, false.
* @throws IOException If an error occurred reading from the source.
* @throws InterruptedException If the thread was interrupted.
*/
private
boolean
readHeader
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
try
{
input
.
readFully
(
headerBuffer
.
data
,
0
,
FLV_MIN_HEADER_SIZE
);
headerBuffer
.
setPosition
(
0
);
headerBuffer
.
skipBytes
(
4
);
int
flags
=
headerBuffer
.
readUnsignedByte
();
hasAudio
=
(
flags
&
0x04
)
!=
0
;
hasVideo
=
(
flags
&
0x01
)
!=
0
;
boolean
hasAudio
=
(
flags
&
0x04
)
!=
0
;
boolean
hasVideo
=
(
flags
&
0x01
)
!=
0
;
if
(
hasAudio
)
{
audioReader
=
new
AudioTagReader
(
trackOutput
);
if
(
hasAudio
&&
audioReader
==
null
)
{
audioReader
=
new
AudioTagPayloadReader
(
extractorOutput
.
track
(
TAG_TYPE_AUDIO
));
}
if
(
hasVideo
&&
videoReader
==
null
)
{
videoReader
=
new
VideoTagPayloadReader
(
extractorOutput
.
track
(
TAG_TYPE_VIDEO
));
}
if
(
hasVideo
)
{
//videoReader = new VideoTagReader(trackOutput
);
if
(
metadataReader
==
null
)
{
metadataReader
=
new
ScriptTagPayloadReader
(
null
);
}
metadataReader
=
new
MetadataReader
(
trackOutput
);
extractorOutput
.
endTracks
();
extractorOutput
.
seekMap
(
this
);
// Store payload start position and start extended header (if there is one)
dataOffset
=
headerBuffer
.
readInt
();
input
.
skipFully
(
dataOffset
-
FLV_MIN_HEADER_SIZE
);
...
...
@@ -168,14 +180,25 @@ public final class FlvExtractor implements Extractor {
return
true
;
}
/**
* Reads a tag header from the provided {@link ExtractorInput}.
* @param input The {@link ExtractorInput} from which to read.
* @return True if tag header was read successfully. Otherwise, false.
* @throws IOException If an error occurred reading from the source.
* @throws InterruptedException If the thread was interrupted.
* @throws TagPayloadReader.UnsupportedTrack If payload of the tag is using a codec non
* supported codec.
*/
private
boolean
readTagHeader
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
,
TagReader
.
UnsupportedTrack
{
Tag
Payload
Reader
.
UnsupportedTrack
{
try
{
// skipping previous tag size field
input
.
skipFully
(
4
);
// Read the tag header from the input.
input
.
readFully
(
tagHeaderBuffer
.
data
,
0
,
FLV_TAG_HEADER_SIZE
);
tagHeaderBuffer
.
setPosition
(
0
);
// skipping previous tag size field.
int
type
=
tagHeaderBuffer
.
readUnsignedByte
();
int
dataSize
=
tagHeaderBuffer
.
readUnsignedInt24
();
long
timestamp
=
tagHeaderBuffer
.
readUnsignedInt24
();
...
...
@@ -187,8 +210,16 @@ public final class FlvExtractor implements Extractor {
currentTagHeader
.
timestamp
=
timestamp
*
1000
;
currentTagHeader
.
streamId
=
streamId
;
Assertions
.
checkState
(
dataSize
<=
Integer
.
MAX_VALUE
);
tagData
=
new
ParsableByteArray
((
int
)
dataSize
);
// Sanity checks.
Assertions
.
checkState
(
type
==
TAG_TYPE_AUDIO
||
type
==
TAG_TYPE_VIDEO
||
type
==
TAG_TYPE_SCRIPT_DATA
);
// Reuse tagData buffer to avoid lot of memory allocation (performance penalty).
if
(
tagData
==
null
||
dataSize
>
tagData
.
capacity
())
{
tagData
=
new
ParsableByteArray
(
dataSize
);
}
else
{
tagData
.
setPosition
(
0
);
}
tagData
.
setLimit
(
dataSize
);
parserState
=
STATE_READING_SAMPLE
;
}
catch
(
EOFException
eof
)
{
...
...
@@ -198,8 +229,17 @@ public final class FlvExtractor implements Extractor {
return
true
;
}
private
int
readSample
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
throws
IOException
,
InterruptedException
,
AudioTagReader
.
UnsupportedTrack
{
/**
* Reads payload of an FLV tag from the provided {@link ExtractorInput}.
* @param input The {@link ExtractorInput} from which to read.
* @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}.
* @throws IOException If an error occurred reading from the source.
* @throws InterruptedException If the thread was interrupted.
* @throws TagPayloadReader.UnsupportedTrack If payload of the tag is using a codec non
* supported codec.
*/
private
int
readSample
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
,
AudioTagPayloadReader
.
UnsupportedTrack
{
if
(
tagData
!=
null
)
{
if
(!
input
.
readFully
(
tagData
.
data
,
0
,
currentTagHeader
.
dataSize
,
true
))
{
return
RESULT_END_OF_INPUT
;
...
...
@@ -210,18 +250,19 @@ public final class FlvExtractor implements Extractor {
return
RESULT_CONTINUE
;
}
// Pass payload to the right payload reader.
if
(
currentTagHeader
.
type
==
TAG_TYPE_AUDIO
&&
audioReader
!=
null
)
{
audioReader
.
consume
(
tagData
,
currentTagHeader
.
timestamp
);
}
else
if
(
currentTagHeader
.
type
==
TAG_TYPE_VIDEO
&&
videoReader
!=
null
)
{
videoReader
.
consume
(
tagData
,
currentTagHeader
.
timestamp
);
}
else
if
(
currentTagHeader
.
type
==
TAG_TYPE_SCRIPT_DATA
&&
metadataReader
!=
null
)
{
metadataReader
.
consume
(
tagData
,
currentTagHeader
.
timestamp
);
if
(
metadataReader
.
durationUs
!=
C
.
UNKNOWN_TIME_US
)
{
if
(
metadataReader
.
getDurationUs
()
!=
C
.
UNKNOWN_TIME_US
)
{
if
(
audioReader
!=
null
)
{
audioReader
.
durationUs
=
metadataReader
.
durationUs
;
audioReader
.
setDurationUs
(
metadataReader
.
getDurationUs
())
;
}
if
(
videoReader
!=
null
)
{
videoReader
.
durationUs
=
metadataReader
.
durationUs
;
videoReader
.
setDurationUs
(
metadataReader
.
getDurationUs
())
;
}
}
}
else
{
...
...
@@ -233,4 +274,28 @@ public final class FlvExtractor implements Extractor {
return
RESULT_CONTINUE
;
}
// SeekMap implementation.
// TODO: Add seeking support
@Override
public
boolean
isSeekable
()
{
return
false
;
}
@Override
public
long
getPosition
(
long
timeUs
)
{
return
0
;
}
/**
* Defines header of a FLV tag
*/
final
class
TagHeader
{
public
int
type
;
public
int
dataSize
;
public
long
timestamp
;
public
int
streamId
;
}
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/MetadataReader.java
deleted
100644 → 0
View file @
8ddc7351
package
com
.
google
.
android
.
exoplayer
.
extractor
.
flv
;
import
android.util.Log
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* Created by joliva on 9/28/15.
*/
public
class
MetadataReader
extends
TagReader
{
private
static
final
int
METADATA_TYPE_UNKNOWN
=
-
1
;
private
static
final
int
METADATA_TYPE_NUMBER
=
0
;
private
static
final
int
METADATA_TYPE_BOOLEAN
=
1
;
private
static
final
int
METADATA_TYPE_STRING
=
2
;
private
static
final
int
METADATA_TYPE_OBJECT
=
3
;
private
static
final
int
METADATA_TYPE_MOVIE_CLIP
=
4
;
private
static
final
int
METADATA_TYPE_NULL
=
5
;
private
static
final
int
METADATA_TYPE_UNDEFINED
=
6
;
private
static
final
int
METADATA_TYPE_REFERENCE
=
7
;
private
static
final
int
METADATA_TYPE_ECMA_ARRAY
=
8
;
private
static
final
int
METADATA_TYPE_STRICT_ARRAY
=
10
;
private
static
final
int
METADATA_TYPE_DATE
=
11
;
private
static
final
int
METADATA_TYPE_LONG_STRING
=
12
;
public
long
startTime
=
C
.
UNKNOWN_TIME_US
;
public
float
frameRate
;
public
float
videoDataRate
;
public
float
audioDataRate
;
public
int
height
;
public
int
width
;
public
boolean
canSeekOnTime
;
public
String
httpHostHeader
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
public
MetadataReader
(
TrackOutput
output
)
{
super
(
output
);
}
@Override
public
void
seek
()
{
}
@Override
protected
void
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
}
@Override
protected
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
)
{
Object
messageName
=
readAMFData
(
data
,
METADATA_TYPE_UNKNOWN
);
Object
obj
=
readAMFData
(
data
,
METADATA_TYPE_UNKNOWN
);
if
(
obj
instanceof
Map
)
{
Map
<
String
,
Object
>
extractedMetadata
=
(
Map
<
String
,
Object
>)
obj
;
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
extractedMetadata
.
entrySet
())
{
if
(
entry
.
getValue
()
==
null
)
{
continue
;
}
Log
.
d
(
"Metadata"
,
"Key: "
+
entry
.
getKey
()
+
", Value: "
+
entry
.
getValue
().
toString
());
switch
(
entry
.
getKey
())
{
case
"totalduration"
:
this
.
durationUs
=
(
long
)(
C
.
MICROS_PER_SECOND
*
(
Double
)(
entry
.
getValue
()));
break
;
case
"starttime"
:
this
.
startTime
=
(
long
)(
C
.
MICROS_PER_SECOND
*
(
Double
)(
entry
.
getValue
()));
break
;
case
"videodatarate"
:
this
.
videoDataRate
=
((
Double
)
entry
.
getValue
()).
floatValue
();
break
;
case
"audiodatarate"
:
this
.
audioDataRate
=
((
Double
)
entry
.
getValue
()).
floatValue
();
break
;
case
"framerate"
:
this
.
frameRate
=
((
Double
)
entry
.
getValue
()).
floatValue
();
break
;
case
"width"
:
this
.
width
=
Math
.
round
(((
Double
)
entry
.
getValue
()).
floatValue
());
break
;
case
"height"
:
this
.
height
=
Math
.
round
(((
Double
)
entry
.
getValue
()).
floatValue
());
break
;
case
"canseekontime"
:
this
.
canSeekOnTime
=
(
boolean
)
entry
.
getValue
();
break
;
case
"httphostheader"
:
this
.
httpHostHeader
=
(
String
)
entry
.
getValue
();
break
;
default
:
break
;
}
}
}
}
@Override
protected
boolean
shouldParsePayload
()
{
return
true
;
}
private
Object
readAMFData
(
ParsableByteArray
data
,
int
type
)
{
if
(
type
==
METADATA_TYPE_UNKNOWN
)
{
type
=
data
.
readUnsignedByte
();
}
byte
[]
b
;
switch
(
type
)
{
case
METADATA_TYPE_NUMBER:
return
readAMFDouble
(
data
);
case
METADATA_TYPE_BOOLEAN:
return
readAMFBoolean
(
data
);
case
METADATA_TYPE_STRING:
return
readAMFString
(
data
);
case
METADATA_TYPE_OBJECT:
return
readAMFObject
(
data
);
case
METADATA_TYPE_ECMA_ARRAY:
return
readAMFEcmaArray
(
data
);
case
METADATA_TYPE_STRICT_ARRAY:
return
readAMFStrictArray
(
data
);
case
METADATA_TYPE_DATE:
return
readAMFDouble
(
data
);
default
:
return
null
;
}
}
private
Boolean
readAMFBoolean
(
ParsableByteArray
data
)
{
return
Boolean
.
valueOf
(
data
.
readUnsignedByte
()
==
1
);
}
private
Double
readAMFDouble
(
ParsableByteArray
data
)
{
byte
[]
b
=
new
byte
[
8
];
data
.
readBytes
(
b
,
0
,
b
.
length
);
return
ByteBuffer
.
wrap
(
b
).
getDouble
();
}
private
String
readAMFString
(
ParsableByteArray
data
)
{
int
size
=
data
.
readUnsignedShort
();
byte
[]
b
=
new
byte
[
size
];
data
.
readBytes
(
b
,
0
,
b
.
length
);
return
new
String
(
b
);
}
private
Object
readAMFStrictArray
(
ParsableByteArray
data
)
{
long
count
=
data
.
readUnsignedInt
();
ArrayList
<
Object
>
list
=
new
ArrayList
<
Object
>();
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
list
.
add
(
readAMFData
(
data
,
METADATA_TYPE_UNKNOWN
));
}
return
list
;
}
private
Object
readAMFObject
(
ParsableByteArray
data
)
{
HashMap
<
String
,
Object
>
array
=
new
HashMap
<
String
,
Object
>();
while
(
true
)
{
String
key
=
readAMFString
(
data
);
int
type
=
data
.
readUnsignedByte
();
if
(
type
==
9
)
{
// object end marker
break
;
}
array
.
put
(
key
,
readAMFData
(
data
,
type
));
}
return
array
;
}
private
Object
readAMFEcmaArray
(
ParsableByteArray
data
)
{
long
count
=
data
.
readUnsignedInt
();
HashMap
<
String
,
Object
>
array
=
new
HashMap
<
String
,
Object
>();
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
String
key
=
readAMFString
(
data
);
int
type
=
data
.
readUnsignedByte
();
array
.
put
(
key
,
readAMFData
(
data
,
type
));
}
return
array
;
}
private
Date
readAMFDate
(
ParsableByteArray
data
)
{
final
Date
date
=
new
Date
((
long
)
readAMFDouble
(
data
).
doubleValue
());
data
.
readUnsignedShort
();
return
date
;
}
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/ScriptTagPayloadReader.java
0 → 100644
View file @
3e36f529
/*
* 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
.
extractor
.
flv
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* Parses Script Data tags from an FLV stream and extracts metadata information.
*/
final
class
ScriptTagPayloadReader
extends
TagPayloadReader
{
// AMF object types
private
static
final
int
AMF_TYPE_UNKNOWN
=
-
1
;
private
static
final
int
AMF_TYPE_NUMBER
=
0
;
private
static
final
int
AMF_TYPE_BOOLEAN
=
1
;
private
static
final
int
AMF_TYPE_STRING
=
2
;
private
static
final
int
AMF_TYPE_OBJECT
=
3
;
private
static
final
int
AMF_TYPE_ECMA_ARRAY
=
8
;
private
static
final
int
AMF_TYPE_END_MARKER
=
9
;
private
static
final
int
AMF_TYPE_STRICT_ARRAY
=
10
;
private
static
final
int
AMF_TYPE_DATE
=
11
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
public
ScriptTagPayloadReader
(
TrackOutput
output
)
{
super
(
output
);
}
@Override
public
void
seek
()
{
}
@Override
protected
boolean
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
return
true
;
}
@SuppressWarnings
(
"unchecked"
)
@Override
protected
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
)
{
// Read message name (don't storing it as we are not going to give it any use)
readAMFData
(
data
,
AMF_TYPE_UNKNOWN
);
Object
obj
=
readAMFData
(
data
,
AMF_TYPE_UNKNOWN
);
if
(
obj
instanceof
Map
)
{
Map
<
String
,
Object
>
extractedMetadata
=
(
Map
<
String
,
Object
>)
obj
;
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
extractedMetadata
.
entrySet
())
{
if
(
entry
.
getValue
()
==
null
)
{
continue
;
}
switch
(
entry
.
getKey
())
{
case
"duration"
:
this
.
durationUs
=
(
long
)(
C
.
MICROS_PER_SECOND
*
(
Double
)(
entry
.
getValue
()));
break
;
default
:
break
;
}
}
}
}
private
Object
readAMFData
(
ParsableByteArray
data
,
int
type
)
{
if
(
type
==
AMF_TYPE_UNKNOWN
)
{
type
=
data
.
readUnsignedByte
();
}
switch
(
type
)
{
case
AMF_TYPE_NUMBER:
return
readAMFDouble
(
data
);
case
AMF_TYPE_BOOLEAN:
return
readAMFBoolean
(
data
);
case
AMF_TYPE_STRING:
return
readAMFString
(
data
);
case
AMF_TYPE_OBJECT:
return
readAMFObject
(
data
);
case
AMF_TYPE_ECMA_ARRAY:
return
readAMFEcmaArray
(
data
);
case
AMF_TYPE_STRICT_ARRAY:
return
readAMFStrictArray
(
data
);
case
AMF_TYPE_DATE:
return
readAMFDate
(
data
);
default
:
return
null
;
}
}
/**
* Read a boolean from an AMF encoded buffer
* @param data Buffer
* @return Boolean value read from the buffer
*/
private
Boolean
readAMFBoolean
(
ParsableByteArray
data
)
{
return
data
.
readUnsignedByte
()
==
1
;
}
/**
* Read a double number from an AMF encoded buffer
* @param data Buffer
* @return Double number read from the buffer
*/
private
Double
readAMFDouble
(
ParsableByteArray
data
)
{
byte
[]
b
=
new
byte
[
8
];
data
.
readBytes
(
b
,
0
,
b
.
length
);
return
ByteBuffer
.
wrap
(
b
).
getDouble
();
}
/**
* Read a string from an AMF encoded buffer
* @param data Buffer
* @return String read from the buffer
*/
private
String
readAMFString
(
ParsableByteArray
data
)
{
int
size
=
data
.
readUnsignedShort
();
byte
[]
b
=
new
byte
[
size
];
data
.
readBytes
(
b
,
0
,
b
.
length
);
return
new
String
(
b
);
}
/**
* Read an array from an AMF encoded buffer
* @param data Buffer
* @return Array read from the buffer
*/
private
Object
readAMFStrictArray
(
ParsableByteArray
data
)
{
long
count
=
data
.
readUnsignedInt
();
ArrayList
<
Object
>
list
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
list
.
add
(
readAMFData
(
data
,
AMF_TYPE_UNKNOWN
));
}
return
list
;
}
/**
* Read an object from an AMF encoded buffer
* @param data Buffer
* @return Object read from the buffer
*/
private
Object
readAMFObject
(
ParsableByteArray
data
)
{
HashMap
<
String
,
Object
>
array
=
new
HashMap
<>();
while
(
true
)
{
String
key
=
readAMFString
(
data
);
int
type
=
data
.
readUnsignedByte
();
if
(
type
==
AMF_TYPE_END_MARKER
)
{
break
;
}
array
.
put
(
key
,
readAMFData
(
data
,
type
));
}
return
array
;
}
/**
* Read am ecma array from an AMF encoded buffer
* @param data Buffer
* @return Ecma array read from the buffer
*/
private
Object
readAMFEcmaArray
(
ParsableByteArray
data
)
{
long
count
=
data
.
readUnsignedInt
();
HashMap
<
String
,
Object
>
array
=
new
HashMap
<>();
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
String
key
=
readAMFString
(
data
);
int
type
=
data
.
readUnsignedByte
();
array
.
put
(
key
,
readAMFData
(
data
,
type
));
}
return
array
;
}
/**
* Read a date from an AMF encoded buffer
* @param data Buffer
* @return Date read from the buffer
*/
private
Date
readAMFDate
(
ParsableByteArray
data
)
{
final
Date
date
=
new
Date
((
long
)
readAMFDouble
(
data
).
doubleValue
());
data
.
readUnsignedShort
();
return
date
;
}
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/TagHeader.java
deleted
100644 → 0
View file @
8ddc7351
package
com
.
google
.
android
.
exoplayer
.
extractor
.
flv
;
/**
* Created by joliva on 9/26/15.
*/
final
class
TagHeader
{
public
static
final
int
TAG_TYPE_AUDIO
=
8
;
public
static
final
int
TAG_TYPE_VIDEO
=
9
;
public
static
final
int
TAG_TYPE_SCRIPT_DATA
=
18
;
public
int
type
;
public
int
dataSize
;
public
long
timestamp
;
public
int
streamId
;
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/TagReader.java
→
library/src/main/java/com/google/android/exoplayer/extractor/flv/Tag
Payload
Reader.java
View file @
3e36f529
/*
* 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
.
extractor
.
flv
;
import
com.google.android.exoplayer.C
;
...
...
@@ -5,17 +20,19 @@ import com.google.android.exoplayer.extractor.TrackOutput;
import
com.google.android.exoplayer.util.ParsableByteArray
;
/**
* Extracts individual samples from FLV tags.
* Extracts individual samples from FLV tags
, preserving original order
.
*/
/* package */
abstract
class
TagReader
{
/* package */
abstract
class
Tag
Payload
Reader
{
protected
final
TrackOutput
output
;
public
long
durationUs
;
// Duration of the track
protected
long
durationUs
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
protected
TagReader
(
TrackOutput
output
)
{
protected
Tag
Payload
Reader
(
TrackOutput
output
)
{
this
.
output
=
output
;
this
.
durationUs
=
C
.
UNKNOWN_TIME_US
;
}
...
...
@@ -32,8 +49,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
/**
* Parses tag header
* @param data Buffer where the tag header is stored
* @return True if header was parsed successfully and then payload should be read;
* Otherwise, false
* @throws UnsupportedTrack
*/
protected
abstract
void
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
;
protected
abstract
boolean
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
;
/**
* Parses tag payload
...
...
@@ -43,26 +63,29 @@ import com.google.android.exoplayer.util.ParsableByteArray;
protected
abstract
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
);
/**
* Evaluate if for the current tag, payload should be parsed
* @return
*/
protected
abstract
boolean
shouldParsePayload
();
/**
* Consumes (possibly partial) payload data.
* Consumes payload data.
*
* @param data The payload data to consume.
* @param timeUs The timestamp associated with the payload.
*/
public
void
consume
(
ParsableByteArray
data
,
long
timeUs
)
throws
UnsupportedTrack
{
parseHeader
(
data
);
if
(
shouldParsePayload
())
{
if
(
parseHeader
(
data
))
{
parsePayload
(
data
,
timeUs
);
}
}
/**
* Sets duration in microseconds
* @param durationUs duration in microseconds
*/
public
void
setDurationUs
(
long
durationUs
)
{
this
.
durationUs
=
durationUs
;
}
public
long
getDurationUs
()
{
return
durationUs
;
}
/**
* Thrown when format described in the AudioTrack is not supported
*/
public
static
final
class
UnsupportedTrack
extends
Exception
{
...
...
library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java
0 → 100644
View file @
3e36f529
/*
* 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
.
extractor
.
flv
;
import
android.util.Log
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.NalUnitUtil
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* Parses video tags from an FLV stream and extracts H.264 nal units.
*/
final
class
VideoTagPayloadReader
extends
TagPayloadReader
{
private
static
final
String
TAG
=
"VideoTagPayloadReader"
;
// Video codec
private
static
final
int
VIDEO_CODEC_AVC
=
7
;
// FRAME TYPE
private
static
final
int
VIDEO_FRAME_KEYFRAME
=
1
;
private
static
final
int
VIDEO_FRAME_VIDEO_INFO
=
5
;
// PACKET TYPE
private
static
final
int
AVC_PACKET_TYPE_SEQUENCE_HEADER
=
0
;
private
static
final
int
AVC_PACKET_TYPE_AVC_NALU
=
1
;
private
static
final
int
AVC_PACKET_TYPE_AVC_END_OF_SEQUENCE
=
2
;
// Temporary arrays.
private
final
ParsableByteArray
nalStartCode
;
private
final
ParsableByteArray
nalLength
;
private
int
nalUnitsLength
;
// State variables.
private
boolean
hasOutputFormat
;
private
int
frameType
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
public
VideoTagPayloadReader
(
TrackOutput
output
)
{
super
(
output
);
nalStartCode
=
new
ParsableByteArray
(
NalUnitUtil
.
NAL_START_CODE
);
nalLength
=
new
ParsableByteArray
(
4
);
}
@Override
public
void
seek
()
{
}
@Override
protected
boolean
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
int
header
=
data
.
readUnsignedByte
();
int
frameType
=
(
header
>>
4
)
&
0x0F
;
int
videoCodec
=
(
header
&
0x0F
);
// Support just H.264 encoded content.
if
(
videoCodec
!=
VIDEO_CODEC_AVC
)
{
throw
new
UnsupportedTrack
(
"Video codec not supported. Codec: "
+
videoCodec
);
}
this
.
frameType
=
frameType
;
return
(
frameType
!=
VIDEO_FRAME_VIDEO_INFO
);
}
@Override
protected
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
)
{
int
packetType
=
data
.
readUnsignedByte
();
int
compositionTime
=
data
.
readUnsignedInt24
();
// If there is a composition time, adjust timeUs accordingly
// Note: compositionTime within AVCVIDEOPACKET is provided in milliseconds
// and timeUs is in microseconds.
if
(
compositionTime
>
0
)
{
timeUs
+=
compositionTime
*
1000
;
}
// Parse avc sequence header in case this was not done before.
if
(
packetType
==
AVC_PACKET_TYPE_SEQUENCE_HEADER
&&
!
hasOutputFormat
)
{
ParsableByteArray
videoSequence
=
new
ParsableByteArray
(
new
byte
[
data
.
bytesLeft
()]);
data
.
readBytes
(
videoSequence
.
data
,
0
,
data
.
bytesLeft
());
AvcSequenceHeaderData
avcData
;
try
{
avcData
=
parseAvcCodecPrivate
(
videoSequence
);
nalUnitsLength
=
avcData
.
nalUnitLengthFieldLength
;
}
catch
(
ParserException
e
)
{
e
.
printStackTrace
();
return
;
}
// Construct and output the format.
MediaFormat
mediaFormat
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
MediaFormat
.
NO_VALUE
,
durationUs
,
avcData
.
width
,
avcData
.
height
,
avcData
.
initializationData
,
MediaFormat
.
NO_VALUE
,
avcData
.
pixelWidthAspectRatio
);
output
.
format
(
mediaFormat
);
hasOutputFormat
=
true
;
}
else
if
(
packetType
==
AVC_PACKET_TYPE_AVC_NALU
)
{
// TODO: Deduplicate with Mp4Extractor.
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
// they're only 1 or 2 bytes long.
byte
[]
nalLengthData
=
nalLength
.
data
;
nalLengthData
[
0
]
=
0
;
nalLengthData
[
1
]
=
0
;
nalLengthData
[
2
]
=
0
;
int
nalUnitLengthFieldLength
=
nalUnitsLength
;
int
nalUnitLengthFieldLengthDiff
=
4
-
nalUnitsLength
;
// NAL units are length delimited, but the decoder requires start code delimited units.
// Loop until we've written the sample to the track output, replacing length delimiters with
// start codes as we encounter them.
int
bytesWritten
=
0
;
int
bytesToWrite
;
while
(
data
.
bytesLeft
()
>
0
)
{
// Read the NAL length so that we know where we find the next one.
data
.
readBytes
(
nalLength
.
data
,
nalUnitLengthFieldLengthDiff
,
nalUnitLengthFieldLength
);
nalLength
.
setPosition
(
0
);
bytesToWrite
=
nalLength
.
readUnsignedIntToInt
();
// First, write nal start code (replacing length field by nal delimiter codes)
nalStartCode
.
setPosition
(
0
);
output
.
sampleData
(
nalStartCode
,
4
);
bytesWritten
+=
4
;
// Then write nal unit itsef
output
.
sampleData
(
data
,
bytesToWrite
);
bytesWritten
+=
bytesToWrite
;
}
output
.
sampleMetadata
(
timeUs
,
frameType
==
VIDEO_FRAME_KEYFRAME
?
C
.
SAMPLE_FLAG_SYNC
:
0
,
bytesWritten
,
0
,
null
);
}
else
if
(
packetType
==
AVC_PACKET_TYPE_AVC_END_OF_SEQUENCE
)
{
Log
.
d
(
TAG
,
"End of seq!!!"
);
}
}
/**
* Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data.
*
* @return The AvcSequenceHeader data with all the information needed to initialize
* the video codec.
* @throws ParserException If the initialization data could not be built.
*/
private
AvcSequenceHeaderData
parseAvcCodecPrivate
(
ParsableByteArray
buffer
)
throws
ParserException
{
try
{
// TODO: Deduplicate with AtomParsers.parseAvcCFromParent.
buffer
.
setPosition
(
4
);
int
nalUnitLengthFieldLength
=
(
buffer
.
readUnsignedByte
()
&
0x03
)
+
1
;
Assertions
.
checkState
(
nalUnitLengthFieldLength
!=
3
);
List
<
byte
[]>
initializationData
=
new
ArrayList
<>();
int
numSequenceParameterSets
=
buffer
.
readUnsignedByte
()
&
0x1F
;
for
(
int
i
=
0
;
i
<
numSequenceParameterSets
;
i
++)
{
initializationData
.
add
(
NalUnitUtil
.
parseChildNalUnit
(
buffer
));
}
int
numPictureParameterSets
=
buffer
.
readUnsignedByte
();
for
(
int
j
=
0
;
j
<
numPictureParameterSets
;
j
++)
{
initializationData
.
add
(
NalUnitUtil
.
parseChildNalUnit
(
buffer
));
}
float
pixelWidthAspectRatio
=
1
;
int
width
=
MediaFormat
.
NO_VALUE
;
int
height
=
MediaFormat
.
NO_VALUE
;
if
(
numSequenceParameterSets
>
0
)
{
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
ParsableBitArray
spsDataBitArray
=
new
ParsableBitArray
(
initializationData
.
get
(
0
));
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
spsDataBitArray
.
setPosition
(
8
*
(
nalUnitLengthFieldLength
+
1
));
CodecSpecificDataUtil
.
SpsData
sps
=
CodecSpecificDataUtil
.
parseSpsNalUnit
(
spsDataBitArray
);
width
=
sps
.
width
;
height
=
sps
.
height
;
pixelWidthAspectRatio
=
sps
.
pixelWidthAspectRatio
;
}
return
new
AvcSequenceHeaderData
(
initializationData
,
nalUnitLengthFieldLength
,
width
,
height
,
pixelWidthAspectRatio
);
}
catch
(
ArrayIndexOutOfBoundsException
e
)
{
throw
new
ParserException
(
"Error parsing AVC codec private"
);
}
}
/**
* Holds data parsed from an Sequence Header video tag atom.
*/
private
static
final
class
AvcSequenceHeaderData
{
public
final
List
<
byte
[]>
initializationData
;
public
final
int
nalUnitLengthFieldLength
;
public
final
float
pixelWidthAspectRatio
;
public
final
int
width
;
public
final
int
height
;
public
AvcSequenceHeaderData
(
List
<
byte
[]>
initializationData
,
int
nalUnitLengthFieldLength
,
int
width
,
int
height
,
float
pixelWidthAspectRatio
)
{
this
.
initializationData
=
initializationData
;
this
.
nalUnitLengthFieldLength
=
nalUnitLengthFieldLength
;
this
.
pixelWidthAspectRatio
=
pixelWidthAspectRatio
;
this
.
width
=
width
;
this
.
height
=
height
;
}
}
}
library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagReader.java
deleted
100644 → 0
View file @
8ddc7351
package
com
.
google
.
android
.
exoplayer
.
extractor
.
flv
;
import
android.util.Log
;
import
android.util.Pair
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
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
java.util.Collections
;
/**
* Created by joliva on 9/27/15.
*/
public
class
VideoTagReader
extends
TagReader
{
private
static
final
String
TAG
=
"VideoTagReader"
;
// Video codec
private
static
final
int
VIDEO_CODEC_JPEG
=
1
;
private
static
final
int
VIDEO_CODEC_H263
=
2
;
private
static
final
int
VIDEO_CODEC_SCREEN_VIDEO
=
3
;
private
static
final
int
VIDEO_CODEC_VP6
=
4
;
private
static
final
int
VIDEO_CODEC_VP6_WITH_ALPHA_CHANNEL
=
5
;
private
static
final
int
VIDEO_CODEC_SCREEN_VIDEO_V2
=
6
;
private
static
final
int
VIDEO_CODEC_AVC
=
7
;
// FRAME TYPE
private
static
final
int
VIDEO_FRAME_KEYFRAME
=
1
;
private
static
final
int
VIDEO_FRAME_INTERFRAME
=
2
;
private
static
final
int
VIDEO_FRAME_DISPOSABLE_INTERFRAME
=
3
;
private
static
final
int
VIDEO_FRAME_GENERATED_KEYFRAME
=
4
;
private
static
final
int
VIDEO_FRAME_VIDEO_INFO
=
5
;
// PACKET TYPE
private
static
final
int
AVC_PACKET_TYPE_SEQUENCE_HEADER
=
0
;
private
static
final
int
AVC_PACKET_TYPE_AVC_NALU
=
1
;
private
static
final
int
AVC_PACKET_TYPE_AVC_END_OF_SEQUENCE
=
2
;
private
boolean
hasOutputFormat
;
private
int
format
;
private
int
frameType
;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
public
VideoTagReader
(
TrackOutput
output
)
{
super
(
output
);
}
@Override
public
void
seek
()
{
}
@Override
protected
void
parseHeader
(
ParsableByteArray
data
)
throws
UnsupportedTrack
{
int
header
=
data
.
readUnsignedByte
();
int
frameType
=
(
header
>>
4
)
&
0x0F
;
int
videoCodec
=
(
header
&
0x0F
);
if
(
videoCodec
!=
VIDEO_CODEC_AVC
)
{
throw
new
UnsupportedTrack
(
"Video codec not supported. Codec: "
+
videoCodec
);
}
this
.
format
=
videoCodec
;
this
.
frameType
=
frameType
;
}
@Override
protected
void
parsePayload
(
ParsableByteArray
data
,
long
timeUs
)
{
int
packetType
=
data
.
readUnsignedByte
();
int
compositionTime
=
data
.
readUnsignedInt24
();
if
(
packetType
==
AVC_PACKET_TYPE_SEQUENCE_HEADER
&&
!
hasOutputFormat
)
{
ParsableBitArray
videoSequence
=
new
ParsableBitArray
(
new
byte
[
data
.
bytesLeft
()]);
data
.
readBytes
(
videoSequence
.
data
,
0
,
data
.
bytesLeft
());
/*
// Construct and output the format.
output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, parsedSpsData.width, parsedSpsData.height,
initializationData, MediaFormat.NO_VALUE, parsedSpsData.pixelWidthAspectRatio));
*/
// output.format(mediaFormat);
hasOutputFormat
=
true
;
}
else
if
(
packetType
==
AVC_PACKET_TYPE_AVC_NALU
)
{
int
bytesToWrite
=
data
.
bytesLeft
();
output
.
sampleData
(
data
,
bytesToWrite
);
output
.
sampleMetadata
(
timeUs
,
frameType
==
VIDEO_FRAME_KEYFRAME
?
C
.
SAMPLE_FLAG_SYNC
:
0
,
bytesToWrite
,
0
,
null
);
Log
.
d
(
TAG
,
"AAC TAG. Size: "
+
bytesToWrite
+
", timeUs: "
+
timeUs
);
}
}
@Override
protected
boolean
shouldParsePayload
()
{
return
(
format
==
VIDEO_CODEC_AVC
&&
frameType
!=
VIDEO_FRAME_VIDEO_INFO
);
}
}
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