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
24e897d3
authored
Jun 05, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Further H264/H265 code deduping + fix NAL unescaping.
parent
ae466cc5
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
107 additions
and
111 deletions
library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java
library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java
library/src/test/java/com/google/android/exoplayer/util/NalUnitUtilTest.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java
View file @
24e897d3
...
@@ -66,7 +66,6 @@ import java.util.List;
...
@@ -66,7 +66,6 @@ import java.util.List;
// Scratch variables to avoid allocations.
// Scratch variables to avoid allocations.
private
final
ParsableByteArray
seiWrapper
;
private
final
ParsableByteArray
seiWrapper
;
private
int
[]
scratchEscapePositions
;
public
H264Reader
(
TrackOutput
output
,
SeiReader
seiReader
,
boolean
idrKeyframesOnly
)
{
public
H264Reader
(
TrackOutput
output
,
SeiReader
seiReader
,
boolean
idrKeyframesOnly
)
{
super
(
output
);
super
(
output
);
...
@@ -77,7 +76,6 @@ import java.util.List;
...
@@ -77,7 +76,6 @@ import java.util.List;
pps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_PPS
,
128
);
pps
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_PPS
,
128
);
sei
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SEI
,
128
);
sei
=
new
NalUnitTargetBuffer
(
NAL_UNIT_TYPE_SEI
,
128
);
seiWrapper
=
new
ParsableByteArray
();
seiWrapper
=
new
ParsableByteArray
();
scratchEscapePositions
=
new
int
[
10
];
}
}
@Override
@Override
...
@@ -191,7 +189,7 @@ import java.util.List;
...
@@ -191,7 +189,7 @@ import java.util.List;
sps
.
endNalUnit
(
discardPadding
);
sps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
if
(
sei
.
endNalUnit
(
discardPadding
))
{
if
(
sei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
sei
.
nalData
,
sei
.
nalLength
);
int
unescapedLength
=
NalUnitUtil
.
unescapeStream
(
sei
.
nalData
,
sei
.
nalLength
);
seiWrapper
.
reset
(
sei
.
nalData
,
unescapedLength
);
seiWrapper
.
reset
(
sei
.
nalData
,
unescapedLength
);
seiWrapper
.
setPosition
(
4
);
// NAL prefix and nal_unit() header.
seiWrapper
.
setPosition
(
4
);
// NAL prefix and nal_unit() header.
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
...
@@ -208,7 +206,7 @@ import java.util.List;
...
@@ -208,7 +206,7 @@ import java.util.List;
initializationData
.
add
(
ppsData
);
initializationData
.
add
(
ppsData
);
// Unescape and then parse the SPS unit.
// Unescape and then parse the SPS unit.
unescapeStream
(
sps
.
nalData
,
sps
.
nalLength
);
NalUnitUtil
.
unescapeStream
(
sps
.
nalData
,
sps
.
nalLength
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
sps
.
nalData
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
sps
.
nalData
);
bitArray
.
skipBits
(
32
);
// NAL header
bitArray
.
skipBits
(
32
);
// NAL header
int
profileIdc
=
bitArray
.
readBits
(
8
);
int
profileIdc
=
bitArray
.
readBits
(
8
);
...
@@ -323,57 +321,6 @@ import java.util.List;
...
@@ -323,57 +321,6 @@ import java.util.List;
}
}
/**
/**
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
* <p>
* See ISO/IEC 14496-10:2005(E) page 36 for more information.
*
* @param data The data to unescape.
* @param limit The limit (exclusive) of the data to unescape.
* @return The length of the unescaped data.
*/
private
int
unescapeStream
(
byte
[]
data
,
int
limit
)
{
int
position
=
0
;
int
scratchEscapeCount
=
0
;
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
if
(
scratchEscapePositions
.
length
<=
scratchEscapeCount
)
{
// Grow scratchEscapePositions to hold a larger number of positions.
scratchEscapePositions
=
Arrays
.
copyOf
(
scratchEscapePositions
,
scratchEscapePositions
.
length
*
2
);
}
scratchEscapePositions
[
scratchEscapeCount
++]
=
position
;
position
+=
3
;
}
}
int
unescapedLength
=
limit
-
scratchEscapeCount
;
int
escapedPosition
=
0
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
for
(
int
i
=
0
;
i
<
scratchEscapeCount
;
i
++)
{
int
nextEscapePosition
=
scratchEscapePositions
[
i
];
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
copyLength
);
escapedPosition
+=
copyLength
+
3
;
unescapedPosition
+=
copyLength
+
2
;
}
int
remainingLength
=
unescapedLength
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
remainingLength
);
return
unescapedLength
;
}
private
static
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 specifically for IFR units that can be used to parse the IFR's slice type.
* A buffer specifically for IFR units that can be used to parse the IFR's slice type.
*/
*/
private
static
final
class
IfrParserBuffer
{
private
static
final
class
IfrParserBuffer
{
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java
View file @
24e897d3
...
@@ -25,7 +25,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
...
@@ -25,7 +25,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
import
android.util.Log
;
import
android.util.Log
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Collections
;
/**
/**
...
@@ -71,7 +70,6 @@ import java.util.Collections;
...
@@ -71,7 +70,6 @@ import java.util.Collections;
// Scratch variables to avoid allocations.
// Scratch variables to avoid allocations.
private
final
ParsableByteArray
seiWrapper
;
private
final
ParsableByteArray
seiWrapper
;
private
int
[]
scratchEscapePositions
;
public
H265Reader
(
TrackOutput
output
,
SeiReader
seiReader
)
{
public
H265Reader
(
TrackOutput
output
,
SeiReader
seiReader
)
{
super
(
output
);
super
(
output
);
...
@@ -83,7 +81,6 @@ import java.util.Collections;
...
@@ -83,7 +81,6 @@ import java.util.Collections;
prefixSei
=
new
NalUnitTargetBuffer
(
PREFIX_SEI_NUT
,
128
);
prefixSei
=
new
NalUnitTargetBuffer
(
PREFIX_SEI_NUT
,
128
);
suffixSei
=
new
NalUnitTargetBuffer
(
SUFFIX_SEI_NUT
,
128
);
suffixSei
=
new
NalUnitTargetBuffer
(
SUFFIX_SEI_NUT
,
128
);
seiWrapper
=
new
ParsableByteArray
();
seiWrapper
=
new
ParsableByteArray
();
scratchEscapePositions
=
new
int
[
10
];
}
}
@Override
@Override
...
@@ -189,7 +186,7 @@ import java.util.Collections;
...
@@ -189,7 +186,7 @@ import java.util.Collections;
sps
.
endNalUnit
(
discardPadding
);
sps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
if
(
prefixSei
.
endNalUnit
(
discardPadding
))
{
if
(
prefixSei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
prefixSei
.
nalData
,
prefixSei
.
nalLength
);
int
unescapedLength
=
NalUnitUtil
.
unescapeStream
(
prefixSei
.
nalData
,
prefixSei
.
nalLength
);
seiWrapper
.
reset
(
prefixSei
.
nalData
,
unescapedLength
);
seiWrapper
.
reset
(
prefixSei
.
nalData
,
unescapedLength
);
// Skip the NAL prefix and type.
// Skip the NAL prefix and type.
...
@@ -197,7 +194,7 @@ import java.util.Collections;
...
@@ -197,7 +194,7 @@ import java.util.Collections;
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
}
}
if
(
suffixSei
.
endNalUnit
(
discardPadding
))
{
if
(
suffixSei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
suffixSei
.
nalData
,
suffixSei
.
nalLength
);
int
unescapedLength
=
NalUnitUtil
.
unescapeStream
(
suffixSei
.
nalData
,
suffixSei
.
nalLength
);
seiWrapper
.
reset
(
suffixSei
.
nalData
,
unescapedLength
);
seiWrapper
.
reset
(
suffixSei
.
nalData
,
unescapedLength
);
// Skip the NAL prefix and type.
// Skip the NAL prefix and type.
...
@@ -215,7 +212,7 @@ import java.util.Collections;
...
@@ -215,7 +212,7 @@ import java.util.Collections;
System
.
arraycopy
(
pps
.
nalData
,
0
,
csd
,
vps
.
nalLength
+
sps
.
nalLength
,
pps
.
nalLength
);
System
.
arraycopy
(
pps
.
nalData
,
0
,
csd
,
vps
.
nalLength
+
sps
.
nalLength
,
pps
.
nalLength
);
// Unescape and then parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
// Unescape and then parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
unescapeStream
(
sps
.
nalData
,
sps
.
nalLength
);
NalUnitUtil
.
unescapeStream
(
sps
.
nalData
,
sps
.
nalLength
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
sps
.
nalData
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
sps
.
nalData
);
bitArray
.
skipBits
(
40
+
4
);
// NAL header, sps_video_parameter_set_id
bitArray
.
skipBits
(
40
+
4
);
// NAL header, sps_video_parameter_set_id
int
maxSubLayersMinus1
=
bitArray
.
readBits
(
3
);
int
maxSubLayersMinus1
=
bitArray
.
readBits
(
3
);
...
@@ -339,56 +336,6 @@ import java.util.Collections;
...
@@ -339,56 +336,6 @@ import java.util.Collections;
}
}
}
}
// TODO: Deduplicate with H264Reader.
/**
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
*
* @param data The data to unescape.
* @param limit The limit (exclusive) of the data to unescape.
* @return The length of the unescaped data.
*/
private
int
unescapeStream
(
byte
[]
data
,
int
limit
)
{
int
position
=
0
;
int
scratchEscapeCount
=
0
;
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
if
(
scratchEscapePositions
.
length
<=
scratchEscapeCount
)
{
// Grow scratchEscapePositions to hold a larger number of positions.
scratchEscapePositions
=
Arrays
.
copyOf
(
scratchEscapePositions
,
scratchEscapePositions
.
length
*
2
);
}
scratchEscapePositions
[
scratchEscapeCount
++]
=
position
;
position
+=
3
;
}
}
int
unescapedLength
=
limit
-
scratchEscapeCount
;
int
escapedPosition
=
0
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
for
(
int
i
=
0
;
i
<
scratchEscapeCount
;
i
++)
{
int
nextEscapePosition
=
scratchEscapePositions
[
i
];
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
copyLength
);
escapedPosition
+=
copyLength
+
3
;
unescapedPosition
+=
copyLength
+
2
;
}
int
remainingLength
=
unescapedLength
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
remainingLength
);
return
unescapedLength
;
}
private
static
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
;
}
/** Returns whether the NAL unit is a random access point. */
/** Returns whether the NAL unit is a random access point. */
private
static
boolean
isRandomAccessPoint
(
int
nalUnitType
)
{
private
static
boolean
isRandomAccessPoint
(
int
nalUnitType
)
{
return
nalUnitType
==
BLA_W_LP
||
nalUnitType
==
BLA_W_RADL
||
nalUnitType
==
BLA_N_LP
return
nalUnitType
==
BLA_W_LP
||
nalUnitType
==
BLA_W_RADL
||
nalUnitType
==
BLA_N_LP
...
...
library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java
View file @
24e897d3
...
@@ -16,6 +16,7 @@
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer
.
util
;
package
com
.
google
.
android
.
exoplayer
.
util
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
/**
/**
* Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
* Utility methods for handling H.264/AVC and H.265/HEVC NAL units.
...
@@ -48,6 +49,61 @@ public final class NalUnitUtil {
...
@@ -48,6 +49,61 @@ public final class NalUnitUtil {
2
f
2
f
};
};
private
static
final
Object
scratchEscapePositionsLock
=
new
Object
();
/**
* Temporary store for positions of escape codes in {@link #unescapeStream(byte[], int)}. Guarded
* by {@link #scratchEscapePositionsLock}.
*/
private
static
int
[]
scratchEscapePositions
=
new
int
[
10
];
/**
* Unescapes {@code data} up to the specified limit, replacing occurrences of [0, 0, 3] with
* [0, 0]. The unescaped data is returned in-place, with the return value indicating its length.
* <p>
* Executions of this method are mutually exclusive, so it should not be called with very large
* buffers.
*
* @param data The data to unescape.
* @param limit The limit (exclusive) of the data to unescape.
* @return The length of the unescaped data.
*/
public
static
int
unescapeStream
(
byte
[]
data
,
int
limit
)
{
synchronized
(
scratchEscapePositionsLock
)
{
int
position
=
0
;
int
scratchEscapeCount
=
0
;
while
(
position
<
limit
)
{
position
=
findNextUnescapeIndex
(
data
,
position
,
limit
);
if
(
position
<
limit
)
{
if
(
scratchEscapePositions
.
length
<=
scratchEscapeCount
)
{
// Grow scratchEscapePositions to hold a larger number of positions.
scratchEscapePositions
=
Arrays
.
copyOf
(
scratchEscapePositions
,
scratchEscapePositions
.
length
*
2
);
}
scratchEscapePositions
[
scratchEscapeCount
++]
=
position
;
position
+=
3
;
}
}
int
unescapedLength
=
limit
-
scratchEscapeCount
;
int
escapedPosition
=
0
;
// The position being read from.
int
unescapedPosition
=
0
;
// The position being written to.
for
(
int
i
=
0
;
i
<
scratchEscapeCount
;
i
++)
{
int
nextEscapePosition
=
scratchEscapePositions
[
i
];
int
copyLength
=
nextEscapePosition
-
escapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
copyLength
);
unescapedPosition
+=
copyLength
;
data
[
unescapedPosition
++]
=
0
;
data
[
unescapedPosition
++]
=
0
;
escapedPosition
+=
copyLength
+
3
;
}
int
remainingLength
=
unescapedLength
-
unescapedPosition
;
System
.
arraycopy
(
data
,
escapedPosition
,
data
,
unescapedPosition
,
remainingLength
);
return
unescapedLength
;
}
}
/**
/**
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
* {@code size} bytes preceding the buffer's position.
* {@code size} bytes preceding the buffer's position.
...
@@ -189,6 +245,15 @@ public final class NalUnitUtil {
...
@@ -189,6 +245,15 @@ public final class NalUnitUtil {
prefixFlags
[
2
]
=
false
;
prefixFlags
[
2
]
=
false
;
}
}
private
static
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
;
}
/**
/**
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* assumed that the top bit will always be set to zero.
* assumed that the top bit will always be set to zero.
...
...
library/src/test/java/com/google/android/exoplayer/util/NalUnitUtilTest.java
View file @
24e897d3
...
@@ -110,6 +110,18 @@ public class NalUnitUtilTest extends TestCase {
...
@@ -110,6 +110,18 @@ public class NalUnitUtilTest extends TestCase {
assertPrefixFlagsCleared
(
prefixFlags
);
assertPrefixFlagsCleared
(
prefixFlags
);
}
}
public
void
testUnescapeDoesNotModifyBuffersWithoutStartCodes
()
{
assertUnescapeDoesNotModify
(
""
);
assertUnescapeDoesNotModify
(
"0000"
);
assertUnescapeDoesNotModify
(
"172BF38A3C"
);
assertUnescapeDoesNotModify
(
"000004"
);
}
public
void
testUnescapeModifiesBuffersWithStartCodes
()
{
assertUnescapeMatchesExpected
(
"00000301"
,
"000001"
);
assertUnescapeMatchesExpected
(
"0000030200000300"
,
"000002000000"
);
}
private
static
byte
[]
buildTestData
()
{
private
static
byte
[]
buildTestData
()
{
byte
[]
data
=
new
byte
[
20
];
byte
[]
data
=
new
byte
[
20
];
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
{
...
@@ -130,4 +142,29 @@ public class NalUnitUtilTest extends TestCase {
...
@@ -130,4 +142,29 @@ public class NalUnitUtilTest extends TestCase {
assertEquals
(
false
,
flags
[
0
]
||
flags
[
1
]
||
flags
[
2
]);
assertEquals
(
false
,
flags
[
0
]
||
flags
[
1
]
||
flags
[
2
]);
}
}
private
static
void
assertUnescapeDoesNotModify
(
String
input
)
{
assertUnescapeMatchesExpected
(
input
,
input
);
}
private
static
void
assertUnescapeMatchesExpected
(
String
input
,
String
expectedOutput
)
{
byte
[]
bitstream
=
getByteArrayForHexString
(
input
);
byte
[]
expectedOutputBitstream
=
getByteArrayForHexString
(
expectedOutput
);
int
count
=
NalUnitUtil
.
unescapeStream
(
bitstream
,
bitstream
.
length
);
assertEquals
(
expectedOutputBitstream
.
length
,
count
);
byte
[]
outputBitstream
=
new
byte
[
count
];
System
.
arraycopy
(
bitstream
,
0
,
outputBitstream
,
0
,
count
);
assertTrue
(
Arrays
.
equals
(
expectedOutputBitstream
,
outputBitstream
));
}
private
static
byte
[]
getByteArrayForHexString
(
String
hexString
)
{
int
length
=
hexString
.
length
();
Assertions
.
checkArgument
(
length
%
2
==
0
);
byte
[]
result
=
new
byte
[
length
/
2
];
for
(
int
i
=
0
;
i
<
result
.
length
;
i
++)
{
result
[
i
]
=
(
byte
)
((
Character
.
digit
(
hexString
.
charAt
(
i
*
2
),
16
)
<<
4
)
+
Character
.
digit
(
hexString
.
charAt
(
i
*
2
+
1
),
16
));
}
return
result
;
}
}
}
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