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
0c577ce2
authored
Jun 26, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge branch 'tresvecesseis-dev' into dev
parents
7fb5b865
c06f844e
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
163 additions
and
255 deletions
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java
library/src/main/java/com/google/android/exoplayer/extractor/mp3/MpegAudioHeader.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
0c577ce2
...
...
@@ -43,11 +43,8 @@ import java.util.Locale;
}
public
static
final
Sample
[]
YOUTUBE_DASH_MP4
=
new
Sample
[]
{
new
Sample
(
"Google Glass"
,
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+
"as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
+
"ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7."
+
"8506521BFC350652163895D4C26DEE124209AA9E&key=ik0"
,
PlayerActivity
.
TYPE_DASH
),
new
Sample
(
"XXXXXXXXX"
,
"http://178.33.229.111/live/mp4:Videolina/playlist.m3u8"
,
PlayerActivity
.
TYPE_HLS
),
new
Sample
(
"Google Play"
,
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+
"as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
...
...
library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java
View file @
0c577ce2
...
...
@@ -43,7 +43,7 @@ public final class Mp3Extractor implements Extractor {
/** Mask that includes the audio header values that must match between frames. */
private
static
final
int
HEADER_MASK
=
0xFFFE0C00
;
private
static
final
int
ID3_TAG
=
Util
.
getIntegerCodeForString
(
"ID3"
);
p
rivate
static
final
String
[]
MIME_TYPE_BY_LAYER
=
p
ublic
static
final
String
[]
MIME_TYPE_BY_LAYER
=
new
String
[]
{
MimeTypes
.
AUDIO_MPEG_L1
,
MimeTypes
.
AUDIO_MPEG_L2
,
MimeTypes
.
AUDIO_MPEG
};
private
static
final
int
XING_HEADER
=
Util
.
getIntegerCodeForString
(
"Xing"
);
private
static
final
int
INFO_HEADER
=
Util
.
getIntegerCodeForString
(
"Info"
);
...
...
@@ -55,7 +55,7 @@ public final class Mp3Extractor implements Extractor {
* 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame.
* The next power of two size is 4 KiB.
*/
p
rivate
static
final
int
MAX_FRAME_SIZE_BYTES
=
4096
;
p
ublic
static
final
int
MAX_FRAME_SIZE_BYTES
=
4096
;
private
final
BufferingInput
inputBuffer
;
private
final
ParsableByteArray
scratch
;
...
...
library/src/main/java/com/google/android/exoplayer/extractor/mp3/MpegAudioHeader.java
View file @
0c577ce2
...
...
@@ -16,7 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp3
;
/** Parsed MPEG audio frame header. */
/* package */
final
class
MpegAudioHeader
{
public
final
class
MpegAudioHeader
{
private
static
final
int
[]
SAMPLING_RATE_V1
=
{
44100
,
48000
,
32000
};
private
static
final
int
[]
BITRATE_V1_L1
=
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java
View file @
0c577ce2
...
...
@@ -130,7 +130,7 @@ import java.util.Collections;
* 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 advance
d.
* @param pesBuffer The buffer
in which to search for the sync wor
d.
* @return True if a sync word position was found. False otherwise.
*/
private
boolean
skipToNextSync
(
ParsableByteArray
pesBuffer
)
{
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java
View file @
0c577ce2
...
...
@@ -18,276 +18,187 @@ package com.google.android.exoplayer.extractor.ts;
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.extractor.mp3.Mp3Extractor
;
import
com.google.android.exoplayer.extractor.mp3.MpegAudioHeader
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
android.util.Pair
;
import
java.util.Collections
;
/**
* Parses a continuous MPEG Audio byte stream and extracts individual
* frames.
*/
* Parses a continuous MPEG Audio byte stream and extracts individual frames.
*/
/* package */
public
class
MpaReader
extends
ElementaryStreamReader
{
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
=
4
;
private
static
final
int
CRC_SIZE
=
2
;
private
final
ParsableBitArray
mpaScratch
;
private
int
state
;
private
int
bytesRead
;
// Used to find the header.
private
boolean
hasCrc
;
// Used when parsing the header.
private
boolean
hasOutputFormat
;
private
long
frameDurationUs
;
private
int
sampleSize
;
// Used when reading the samples.
private
long
timeUs
;
//
/**
* sampling rates in hertz:
*
* @index MPEG Version ID
* @index sampling rate index
*/
private
static
final
int
[][]
MPA_SAMPLING_RATES
=
new
int
[][]
{
{
11025
,
12000
,
8000
},
// MPEG 2.5
{
0
,
0
,
0
},
// reserved
{
22050
,
24000
,
16000
},
// MPEG 2
{
44100
,
48000
,
32000
}
// MPEG 1
};
private
static
final
int
STATE_FINDING_HEADER
=
0
;
private
static
final
int
STATE_READING_HEADER
=
1
;
private
static
final
int
STATE_READING_FRAME
=
2
;
/**
* bitrates:
*
* @index LSF
* @index Layer
* @index bitrate index
*/
private
static
final
int
HEADER_SIZE
=
4
;
private
static
final
int
[][][]
MPA_BITRATES
=
new
int
[][][]
{
{
// MPEG 1
// Layer1
{
0
,
32
,
64
,
96
,
128
,
160
,
192
,
224
,
256
,
288
,
320
,
352
,
384
,
416
,
448
},
// Layer2
{
0
,
32
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
192
,
224
,
256
,
320
,
384
},
// Layer3
{
0
,
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
192
,
224
,
256
,
320
}
},
{
// MPEG 2, 2.5
// Layer1
{
0
,
32
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
144
,
160
,
176
,
192
,
224
,
256
},
// Layer2
{
0
,
8
,
16
,
24
,
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
144
,
160
},
// Layer3
{
0
,
8
,
16
,
24
,
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
144
,
160
}
}
};
private
final
ParsableByteArray
headerScratch
;
/**
* Samples per Frame:
*
* @index LSF
* @index Layer
*/
private
int
state
;
private
int
bytesRead
;
private
static
final
int
[][]
MPA_SAMPLES_PER_FRAME
=
new
int
[][]
{
{
// MPEG 1
384
,
// Layer1
1152
,
// Layer2
1152
// Layer3
},
{
// MPEG 2, 2.5
384
,
// Layer1
1152
,
// Layer2
576
// Layer3
}
};
// Used to find the header.
private
boolean
lastByteWasFF
;
/**
* Coefficients (samples per frame / 8):
*
* @index = LSF
* @index = Layer
*/
// Used when parsing the header.
private
boolean
hasOutputFormat
;
private
long
frameDurationUs
;
private
int
sampleSize
;
private
static
final
int
[][]
MPA_COEFFICIENTS
=
new
int
[][]
{
{
// MPEG 1
12
,
// Layer1
144
,
// Layer2
144
// Layer3
},
{
// MPEG 2, 2.5
12
,
// Layer1
144
,
// Layer2
72
// Layer3
}
};
// Used when reading the samples.
private
long
timeUs
;
/**
* slot size per layer:
*
* @index = Layer
*/
public
MpaReader
(
TrackOutput
output
)
{
super
(
output
);
state
=
STATE_FINDING_HEADER
;
// The first byte of an MPEG Audio frame header is always 0xFF.
headerScratch
=
new
ParsableByteArray
(
4
);
headerScratch
.
data
[
0
]
=
(
byte
)
0xFF
;
}
private
static
final
int
[]
MPA_SLOT_SIZE
=
new
int
[]
{
4
,
// Layer1
1
,
// Layer2
1
// Layer3
};
@Override
public
void
seek
()
{
state
=
STATE_FINDING_HEADER
;
bytesRead
=
0
;
lastByteWasFF
=
false
;
}
public
MpaReader
(
TrackOutput
output
)
{
super
(
output
);
mpaScratch
=
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
;
}
@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
,
mpaScratch
.
getData
(),
targetLength
))
{
parseHeader
();
bytesRead
=
targetLength
;
state
=
STATE_READING_SAMPLE
;
}
break
;
case
STATE_READING_SAMPLE:
int
bytesToRead
=
Math
.
min
(
data
.
bytesLeft
(),
sampleSize
-
bytesRead
);
output
.
sampleData
(
data
,
bytesToRead
);
bytesRead
+=
bytesToRead
;
if
(
bytesRead
==
sampleSize
)
{
output
.
sampleMetadata
(
timeUs
,
C
.
SAMPLE_FLAG_SYNC
,
sampleSize
,
0
,
null
);
timeUs
+=
frameDurationUs
;
bytesRead
=
0
;
state
=
STATE_FINDING_SYNC
;
}
break
;
}
}
while
(
data
.
bytesLeft
()
>
0
)
{
switch
(
state
)
{
case
STATE_FINDING_HEADER:
if
(
findHeader
(
data
))
{
state
=
STATE_READING_HEADER
;
}
break
;
case
STATE_READING_HEADER:
if
(
readHeaderRemainder
(
data
))
{
state
=
STATE_READING_FRAME
;
}
break
;
case
STATE_READING_FRAME:
if
(
readFrame
(
data
))
{
state
=
STATE_FINDING_HEADER
;
}
break
;
}
}
@Override
public
void
packetFinished
()
{
// Do nothing.
}
@Override
public
void
packetFinished
()
{
// Do nothing.
}
/**
* Attempts to locate the start of the next frame header.
* <p>
* If a frame header is located then true is returned. The first two bytes of the header will have
* been written into {@link #headerScratch}, and the position of the source will have been
* advanced to the byte that immediately follows these two bytes.
* <p>
* If a frame header is not located then the position of the source will have been advanced to the
* limit, and the method should be called again with the next source to continue the search.
*
* @param source The source from which to read.
* @return True if the frame header was located. False otherwise.
*/
private
boolean
findHeader
(
ParsableByteArray
source
)
{
byte
[]
mpaData
=
source
.
data
;
int
startOffset
=
source
.
getPosition
();
int
endOffset
=
source
.
limit
();
for
(
int
i
=
startOffset
;
i
<
endOffset
;
i
++)
{
boolean
byteIsFF
=
(
mpaData
[
i
]
&
0xFF
)
==
0xFF
;
boolean
found
=
lastByteWasFF
&&
(
mpaData
[
i
]
&
0xF0
)
==
0xF0
;
lastByteWasFF
=
byteIsFF
;
if
(
found
)
{
source
.
setPosition
(
i
+
1
);
// Reset lastByteWasFF for next time.
lastByteWasFF
=
false
;
headerScratch
.
data
[
0
]
=
(
byte
)
0xFF
;
headerScratch
.
data
[
1
]
=
mpaData
[
i
];
bytesRead
=
2
;
return
true
;
}
}
/**
* 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
;
source
.
setPosition
(
endOffset
);
return
false
;
}
/**
* Attempts to read the remaining two bytes of the frame header.
* <p>
* If a frame header is read in full then true is returned. The media format will have been output
* if this has not previously occurred, the four header bytes will have been output as sample
* data, and the position of the source will have been advanced to the byte that immediately
* follows the header.
* <p>
* If a frame header is not read in full then the position of the source will have been advanced
* to the limit, and the method should be called again with the next source to continue the read.
*
* @param source The source from which to read.
* @return True if the frame header was read in full. False otherwise.
*/
private
boolean
readHeaderRemainder
(
ParsableByteArray
source
)
{
int
bytesToRead
=
Math
.
min
(
source
.
bytesLeft
(),
HEADER_SIZE
-
bytesRead
);
source
.
readBytes
(
headerScratch
.
data
,
bytesRead
,
bytesToRead
);
bytesRead
+=
bytesToRead
;
if
(
bytesRead
<
HEADER_SIZE
)
{
return
false
;
}
/**
* 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
[]
mpaData
=
pesBuffer
.
data
;
int
startOffset
=
pesBuffer
.
getPosition
();
int
endOffset
=
pesBuffer
.
limit
();
for
(
int
i
=
startOffset
;
i
<
endOffset
-
1
;
i
++)
{
int
syncBits
=
((
mpaData
[
i
]
&
0xFF
)
<<
8
)
|
(
mpaData
[
i
+
1
]
&
0xFF
);
if
((
syncBits
&
0xFFF0
)
==
0xFFF0
)
{
hasCrc
=
(
mpaData
[
i
+
1
]
&
0x1
)
==
0
;
pesBuffer
.
setPosition
(
i
);
return
true
;
}
}
pesBuffer
.
setPosition
(
endOffset
);
return
false
;
if
(!
hasOutputFormat
)
{
headerScratch
.
setPosition
(
0
);
int
headerInt
=
headerScratch
.
readInt
();
MpegAudioHeader
synchronizedHeader
=
new
MpegAudioHeader
();
MpegAudioHeader
.
populateHeader
(
headerInt
,
synchronizedHeader
);
MediaFormat
mediaFormat
=
MediaFormat
.
createAudioFormat
(
Mp3Extractor
.
MIME_TYPE_BY_LAYER
[
synchronizedHeader
.
layerIndex
],
Mp3Extractor
.
MAX_FRAME_SIZE_BYTES
,
C
.
UNKNOWN_TIME_US
,
synchronizedHeader
.
channels
,
synchronizedHeader
.
sampleRate
,
Collections
.<
byte
[]>
emptyList
());
output
.
format
(
mediaFormat
);
hasOutputFormat
=
true
;
frameDurationUs
=
(
C
.
MICROS_PER_SECOND
*
synchronizedHeader
.
samplesPerFrame
)
/
mediaFormat
.
sampleRate
;
sampleSize
=
synchronizedHeader
.
frameSize
;
}
/**
* Calculates MPEG Audio frame size
*
* @param layer The MPEG layer
* @param LSF Low Sample rate Format (MPEG 2)
* @param bitrate The bitrate in bits per second
* @param samplesPerSec The sampling rate in hertz
* @param -paddingSize
* @return Frame size in bytes
*/
private
static
int
CalcMpaFrameSize
(
int
layer
,
int
LSF
,
int
bitrate
,
int
samplesPerSec
,
int
paddingSize
)
{
return
(
int
)(
Math
.
floor
(
MPA_COEFFICIENTS
[
LSF
][
layer
]
*
bitrate
/
samplesPerSec
)
+
paddingSize
)
*
MPA_SLOT_SIZE
[
layer
];
headerScratch
.
setPosition
(
0
);
output
.
sampleData
(
headerScratch
,
HEADER_SIZE
);
return
true
;
}
/**
* Attempts to read the remainder of the frame.
* <p>
* If a frame is read in full then true is returned. The frame will have been output, and the
* position of the source will have been advanced to the byte that immediately follows the end of
* the frame.
* <p>
* If a frame is not read in full then the position of the source will have been advanced to the
* limit, and the method should be called again with the next source to continue the read.
*
* @param source The source from which to read.
* @return True if the frame was read in full. False otherwise.
*/
private
boolean
readFrame
(
ParsableByteArray
source
)
{
int
bytesToRead
=
Math
.
min
(
source
.
bytesLeft
(),
sampleSize
-
bytesRead
);
output
.
sampleData
(
source
,
bytesToRead
);
bytesRead
+=
bytesToRead
;
if
(
bytesRead
<
sampleSize
)
{
return
false
;
}
/**
* Parses the sample header.
*/
private
void
parseHeader
()
{
int
headerLength
=
hasCrc
?
HEADER_SIZE
+
CRC_SIZE
:
HEADER_SIZE
;
output
.
sampleMetadata
(
timeUs
,
C
.
SAMPLE_FLAG_SYNC
,
sampleSize
,
0
,
null
);
timeUs
+=
frameDurationUs
;
bytesRead
=
0
;
return
true
;
}
if
(!
hasOutputFormat
)
{
mpaScratch
.
setPosition
(
0
);
mpaScratch
.
skipBits
(
12
);
int
isLSF
=
(!
mpaScratch
.
readBit
())
?
1
:
0
;
int
layer
=
mpaScratch
.
readBits
(
2
)
^
3
;
mpaScratch
.
skipBits
(
1
);
int
audioObjectType
=
32
+
layer
;
int
bitRate
=
MPA_BITRATES
[
isLSF
][
layer
][
mpaScratch
.
readBits
(
4
)];
int
sampleRate
=
MPA_SAMPLING_RATES
[
3
-
isLSF
][
mpaScratch
.
readBits
(
2
)];
int
sampleRateIndex
=
CodecSpecificDataUtil
.
getSampleRateIndex
(
sampleRate
);
int
paddingBit
=
(
mpaScratch
.
readBit
())
?
1
:
0
;
mpaScratch
.
skipBits
(
1
);
int
channelConfig
=
mpaScratch
.
readBits
(
2
)
==
3
?
1
:
2
;
byte
[]
audioSpecificConfig
=
CodecSpecificDataUtil
.
buildAudioSpecificConfig
(
audioObjectType
,
sampleRateIndex
,
channelConfig
);
Pair
<
Integer
,
Integer
>
audioParams
=
CodecSpecificDataUtil
.
parseAudioSpecificConfig
(
audioSpecificConfig
);
// need to investigate how to detect if the mpeg decoder supports Layers other than Layer III
MediaFormat
mediaFormat
=
MediaFormat
.
createAudioFormat
(
/*isLSF == 1 ?*/
MimeTypes
.
AUDIO_MPEG
/* : MimeTypes.AUDIO_MP1L2*/
,
MediaFormat
.
NO_VALUE
,
audioParams
.
second
,
audioParams
.
first
,
Collections
.
singletonList
(
audioSpecificConfig
));
output
.
format
(
mediaFormat
);
hasOutputFormat
=
true
;
frameDurationUs
=
(
C
.
MICROS_PER_SECOND
*
MPA_SAMPLES_PER_FRAME
[
isLSF
][
layer
])
/
mediaFormat
.
sampleRate
;
sampleSize
=
CalcMpaFrameSize
(
layer
,
isLSF
,
bitRate
*
1000
,
sampleRate
,
paddingBit
);
}
mpaScratch
.
setPosition
(
0
);
ParsableByteArray
header
=
new
ParsableByteArray
(
mpaScratch
.
getData
(),
headerLength
);
output
.
sampleData
(
header
,
headerLength
);
}
}
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