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
5b186a2a
authored
Jun 01, 2015
by
Andrew Lewis
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add support for reading H.265 in MPEG TS.
parent
02d5cb81
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
580 additions
and
12 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/extractor/ts/SeiReader.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java
View file @
5b186a2a
...
@@ -77,7 +77,7 @@ import java.util.List;
...
@@ -77,7 +77,7 @@ import java.util.List;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
sei
;
private
final
NalUnitTargetBuffer
sei
;
private
boolean
writing
Sample
;
private
boolean
foundFirst
Sample
;
private
long
totalBytesWritten
;
private
long
totalBytesWritten
;
// Per sample state that gets reset at the start of each sample.
// Per sample state that gets reset at the start of each sample.
...
@@ -111,7 +111,7 @@ import java.util.List;
...
@@ -111,7 +111,7 @@ import java.util.List;
if
(
ifrParserBuffer
!=
null
)
{
if
(
ifrParserBuffer
!=
null
)
{
ifrParserBuffer
.
reset
();
ifrParserBuffer
.
reset
();
}
}
writing
Sample
=
false
;
foundFirst
Sample
=
false
;
totalBytesWritten
=
0
;
totalBytesWritten
=
0
;
}
}
...
@@ -146,7 +146,7 @@ import java.util.List;
...
@@ -146,7 +146,7 @@ import java.util.List;
isKeyframe
=
true
;
isKeyframe
=
true
;
break
;
break
;
case
NAL_UNIT_TYPE_AUD:
case
NAL_UNIT_TYPE_AUD:
if
(
writing
Sample
)
{
if
(
foundFirst
Sample
)
{
if
(
ifrParserBuffer
!=
null
&&
ifrParserBuffer
.
isCompleted
())
{
if
(
ifrParserBuffer
!=
null
&&
ifrParserBuffer
.
isCompleted
())
{
int
sliceType
=
ifrParserBuffer
.
getSliceType
();
int
sliceType
=
ifrParserBuffer
.
getSliceType
();
isKeyframe
|=
(
sliceType
==
FRAME_TYPE_I
||
sliceType
==
FRAME_TYPE_ALL_I
);
isKeyframe
|=
(
sliceType
==
FRAME_TYPE_I
||
sliceType
==
FRAME_TYPE_ALL_I
);
...
@@ -158,9 +158,8 @@ import java.util.List;
...
@@ -158,9 +158,8 @@ import java.util.List;
int
flags
=
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
int
flags
=
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
int
size
=
(
int
)
(
totalBytesWritten
-
samplePosition
)
-
bytesWrittenPastNalUnit
;
int
size
=
(
int
)
(
totalBytesWritten
-
samplePosition
)
-
bytesWrittenPastNalUnit
;
output
.
sampleMetadata
(
sampleTimeUs
,
flags
,
size
,
bytesWrittenPastNalUnit
,
null
);
output
.
sampleMetadata
(
sampleTimeUs
,
flags
,
size
,
bytesWrittenPastNalUnit
,
null
);
writingSample
=
false
;
}
}
writing
Sample
=
true
;
foundFirst
Sample
=
true
;
samplePosition
=
totalBytesWritten
-
bytesWrittenPastNalUnit
;
samplePosition
=
totalBytesWritten
-
bytesWrittenPastNalUnit
;
sampleTimeUs
=
pesTimeUs
;
sampleTimeUs
=
pesTimeUs
;
isKeyframe
=
false
;
isKeyframe
=
false
;
...
@@ -215,6 +214,7 @@ import java.util.List;
...
@@ -215,6 +214,7 @@ import java.util.List;
if
(
sei
.
endNalUnit
(
discardPadding
))
{
if
(
sei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
sei
.
nalData
,
sei
.
nalLength
);
int
unescapedLength
=
unescapeStream
(
sei
.
nalData
,
sei
.
nalLength
);
seiWrapper
.
reset
(
sei
.
nalData
,
unescapedLength
);
seiWrapper
.
reset
(
sei
.
nalData
,
unescapedLength
);
seiWrapper
.
setPosition
(
4
);
// NAL prefix and nal_unit() header.
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
}
}
}
}
...
@@ -385,7 +385,7 @@ import java.util.List;
...
@@ -385,7 +385,7 @@ import java.util.List;
return
unescapedLength
;
return
unescapedLength
;
}
}
private
int
findNextUnescapeIndex
(
byte
[]
bytes
,
int
offset
,
int
limit
)
{
private
static
int
findNextUnescapeIndex
(
byte
[]
bytes
,
int
offset
,
int
limit
)
{
for
(
int
i
=
offset
;
i
<
limit
-
2
;
i
++)
{
for
(
int
i
=
offset
;
i
<
limit
-
2
;
i
++)
{
if
(
bytes
[
i
]
==
0x00
&&
bytes
[
i
+
1
]
==
0x00
&&
bytes
[
i
+
2
]
==
0x03
)
{
if
(
bytes
[
i
]
==
0x00
&&
bytes
[
i
+
1
]
==
0x00
&&
bytes
[
i
+
2
]
==
0x03
)
{
return
i
;
return
i
;
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java
0 → 100644
View file @
5b186a2a
/*
* 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
.
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.Assertions
;
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
android.util.Log
;
import
java.util.Arrays
;
import
java.util.Collections
;
/**
* Parses a continuous H.265 byte stream and extracts individual frames.
*/
/* package */
class
H265Reader
extends
ElementaryStreamReader
{
private
static
final
String
TAG
=
"H265Reader"
;
// nal_unit_type values from H.265/HEVC (2014) Table 7-1.
private
static
final
int
BLA_W_LP
=
16
;
private
static
final
int
BLA_W_RADL
=
17
;
private
static
final
int
BLA_N_LP
=
18
;
private
static
final
int
IDR_W_RADL
=
19
;
private
static
final
int
IDR_N_LP
=
20
;
private
static
final
int
CRA_NUT
=
21
;
private
static
final
int
VPS_NUT
=
32
;
private
static
final
int
SPS_NUT
=
33
;
private
static
final
int
PPS_NUT
=
34
;
private
static
final
int
PREFIX_SEI_NUT
=
39
;
private
static
final
int
SUFFIX_SEI_NUT
=
40
;
// TODO: Deduplicate with H264Reader.
private
static
final
int
EXTENDED_SAR
=
0xFF
;
private
static
final
float
[]
ASPECT_RATIO_IDC_VALUES
=
new
float
[]
{
1
f
/* Unspecified. Assume square */
,
1
f
,
12
f
/
11
f
,
10
f
/
11
f
,
16
f
/
11
f
,
40
f
/
33
f
,
24
f
/
11
f
,
20
f
/
11
f
,
32
f
/
11
f
,
80
f
/
33
f
,
18
f
/
11
f
,
15
f
/
11
f
,
64
f
/
33
f
,
160
f
/
99
f
,
4
f
/
3
f
,
3
f
/
2
f
,
2
f
};
// State that should not be reset on seek.
private
boolean
hasOutputFormat
;
// State that should be reset on seek.
private
final
SeiReader
seiReader
;
private
final
boolean
[]
prefixFlags
;
private
final
NalUnitTargetBuffer
vps
;
private
final
NalUnitTargetBuffer
sps
;
private
final
NalUnitTargetBuffer
pps
;
private
final
NalUnitTargetBuffer
prefixSei
;
private
final
NalUnitTargetBuffer
suffixSei
;
// TODO: Are both needed?
private
boolean
foundFirstSample
;
private
long
totalBytesWritten
;
// Per sample state that gets reset at the start of each sample.
private
boolean
isKeyframe
;
private
long
samplePosition
;
private
long
sampleTimeUs
;
// Scratch variables to avoid allocations.
private
final
ParsableByteArray
seiWrapper
;
private
int
[]
scratchEscapePositions
;
public
H265Reader
(
TrackOutput
output
,
SeiReader
seiReader
)
{
super
(
output
);
this
.
seiReader
=
seiReader
;
prefixFlags
=
new
boolean
[
3
];
vps
=
new
NalUnitTargetBuffer
(
VPS_NUT
,
128
);
sps
=
new
NalUnitTargetBuffer
(
SPS_NUT
,
128
);
pps
=
new
NalUnitTargetBuffer
(
PPS_NUT
,
128
);
prefixSei
=
new
NalUnitTargetBuffer
(
PREFIX_SEI_NUT
,
128
);
suffixSei
=
new
NalUnitTargetBuffer
(
SUFFIX_SEI_NUT
,
128
);
seiWrapper
=
new
ParsableByteArray
();
scratchEscapePositions
=
new
int
[
10
];
}
@Override
public
void
seek
()
{
seiReader
.
seek
();
NalUnitUtil
.
clearPrefixFlags
(
prefixFlags
);
vps
.
reset
();
sps
.
reset
();
pps
.
reset
();
prefixSei
.
reset
();
suffixSei
.
reset
();
foundFirstSample
=
false
;
totalBytesWritten
=
0
;
}
@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.
totalBytesWritten
+=
data
.
bytesLeft
();
output
.
sampleData
(
data
,
data
.
bytesLeft
());
// Scan the appended data, processing NAL units as they are encountered
while
(
offset
<
limit
)
{
int
nextNalUnitOffset
=
NalUnitUtil
.
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
=
NalUnitUtil
.
getH265NalUnitType
(
dataArray
,
nextNalUnitOffset
);
int
bytesWrittenPastNalUnit
=
limit
-
nextNalUnitOffset
;
isKeyframe
|=
isRandomAccessPoint
(
nalUnitType
);
// Output sample data for VCL NAL units.
if
(
isInVcl
(
nalUnitType
))
{
if
(
foundFirstSample
)
{
if
(
isKeyframe
&&
!
hasOutputFormat
&&
vps
.
isCompleted
()
&&
sps
.
isCompleted
()
&&
pps
.
isCompleted
())
{
parseMediaFormat
(
vps
,
sps
,
pps
);
}
int
flags
=
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
int
size
=
(
int
)
(
totalBytesWritten
-
samplePosition
)
-
bytesWrittenPastNalUnit
;
output
.
sampleMetadata
(
sampleTimeUs
,
flags
,
size
,
bytesWrittenPastNalUnit
,
null
);
}
foundFirstSample
=
true
;
samplePosition
=
totalBytesWritten
-
bytesWrittenPastNalUnit
;
sampleTimeUs
=
pesTimeUs
;
isKeyframe
=
false
;
}
// 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
+
3
;
}
else
{
feedNalUnitTargetBuffersData
(
dataArray
,
offset
,
limit
);
offset
=
limit
;
}
}
}
}
@Override
public
void
packetFinished
()
{
// Do nothing.
}
private
void
feedNalUnitTargetBuffersStart
(
int
nalUnitType
)
{
if
(!
hasOutputFormat
)
{
vps
.
startNalUnit
(
nalUnitType
);
sps
.
startNalUnit
(
nalUnitType
);
pps
.
startNalUnit
(
nalUnitType
);
}
prefixSei
.
startNalUnit
(
nalUnitType
);
suffixSei
.
startNalUnit
(
nalUnitType
);
}
private
void
feedNalUnitTargetBuffersData
(
byte
[]
dataArray
,
int
offset
,
int
limit
)
{
if
(!
hasOutputFormat
)
{
vps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
sps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
pps
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
}
prefixSei
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
suffixSei
.
appendToNalUnit
(
dataArray
,
offset
,
limit
);
}
private
void
feedNalUnitTargetEnd
(
long
pesTimeUs
,
int
discardPadding
)
{
vps
.
endNalUnit
(
discardPadding
);
sps
.
endNalUnit
(
discardPadding
);
pps
.
endNalUnit
(
discardPadding
);
if
(
prefixSei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
prefixSei
.
nalData
,
prefixSei
.
nalLength
);
seiWrapper
.
reset
(
prefixSei
.
nalData
,
unescapedLength
);
// Skip the NAL prefix and type.
seiWrapper
.
skipBytes
(
5
);
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
}
if
(
suffixSei
.
endNalUnit
(
discardPadding
))
{
int
unescapedLength
=
unescapeStream
(
suffixSei
.
nalData
,
suffixSei
.
nalLength
);
seiWrapper
.
reset
(
suffixSei
.
nalData
,
unescapedLength
);
// Skip the NAL prefix and type.
seiWrapper
.
skipBytes
(
5
);
seiReader
.
consume
(
seiWrapper
,
pesTimeUs
,
true
);
}
}
private
void
parseMediaFormat
(
NalUnitTargetBuffer
vps
,
NalUnitTargetBuffer
sps
,
NalUnitTargetBuffer
pps
)
{
// Build codec-specific data.
byte
[]
csd
=
new
byte
[
vps
.
nalLength
+
sps
.
nalLength
+
pps
.
nalLength
];
System
.
arraycopy
(
vps
.
nalData
,
0
,
csd
,
0
,
vps
.
nalLength
);
System
.
arraycopy
(
sps
.
nalData
,
0
,
csd
,
vps
.
nalLength
,
sps
.
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.
unescapeStream
(
sps
.
nalData
,
sps
.
nalLength
);
ParsableBitArray
bitArray
=
new
ParsableBitArray
(
sps
.
nalData
);
bitArray
.
skipBits
(
40
+
4
);
// NAL header, sps_video_parameter_set_id
int
maxSubLayersMinus1
=
bitArray
.
readBits
(
3
);
bitArray
.
skipBits
(
1
);
// sps_temporal_id_nesting_flag
// profile_tier_level(1, sps_max_sub_layers_minus1)
bitArray
.
skipBits
(
88
);
// if (profilePresentFlag) {...}
bitArray
.
skipBits
(
8
);
// general_level_idc
int
toSkip
=
0
;
for
(
int
i
=
0
;
i
<
maxSubLayersMinus1
;
i
++)
{
if
(
bitArray
.
readBits
(
1
)
==
1
)
{
// sub_layer_profile_present_flag[i]
toSkip
+=
89
;
}
if
(
bitArray
.
readBits
(
1
)
==
1
)
{
// sub_layer_level_present_flag[i]
toSkip
+=
8
;
}
}
bitArray
.
skipBits
(
toSkip
);
if
(
maxSubLayersMinus1
>
0
)
{
bitArray
.
skipBits
(
2
*
(
8
-
maxSubLayersMinus1
));
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// sps_seq_parameter_set_id
int
chromaFormatIdc
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
chromaFormatIdc
==
3
)
{
bitArray
.
skipBits
(
1
);
// separate_colour_plane_flag
}
int
picWidthInLumaSamples
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
picHeightInLumaSamples
=
bitArray
.
readUnsignedExpGolombCodedInt
();
if
(
bitArray
.
readBit
())
{
// conformance_window_flag
int
confWinLeftOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
confWinRightOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
confWinTopOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
int
confWinBottomOffset
=
bitArray
.
readUnsignedExpGolombCodedInt
();
// H.265/HEVC (2014) Table 6-1
int
subWidthC
=
chromaFormatIdc
==
1
||
chromaFormatIdc
==
2
?
2
:
1
;
int
subHeightC
=
chromaFormatIdc
==
1
?
2
:
1
;
picWidthInLumaSamples
-=
subWidthC
*
(
confWinLeftOffset
+
confWinRightOffset
);
picHeightInLumaSamples
-=
subHeightC
*
(
confWinTopOffset
+
confWinBottomOffset
);
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// bit_depth_luma_minus8
bitArray
.
readUnsignedExpGolombCodedInt
();
// bit_depth_chroma_minus8
int
log2MaxPicOrderCntLsbMinus4
=
bitArray
.
readUnsignedExpGolombCodedInt
();
// for (i = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1; ...)
for
(
int
i
=
bitArray
.
readBit
()
?
0
:
maxSubLayersMinus1
;
i
<=
maxSubLayersMinus1
;
i
++)
{
bitArray
.
readUnsignedExpGolombCodedInt
();
// sps_max_dec_pic_buffering_minus1[i]
bitArray
.
readUnsignedExpGolombCodedInt
();
// sps_max_num_reorder_pics[i]
bitArray
.
readUnsignedExpGolombCodedInt
();
// sps_max_latency_increase_plus1[i]
}
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_min_luma_coding_block_size_minus3
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_diff_max_min_luma_coding_block_size
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_min_luma_transform_block_size_minus2
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_diff_max_min_luma_transform_block_size
bitArray
.
readUnsignedExpGolombCodedInt
();
// max_transform_hierarchy_depth_inter
bitArray
.
readUnsignedExpGolombCodedInt
();
// max_transform_hierarchy_depth_intra
// if (scaling_list_enabled_flag) { if (sps_scaling_list_data_present_flag) {...}}
if
(
bitArray
.
readBit
()
&&
bitArray
.
readBit
())
{
skipScalingList
(
bitArray
);
}
bitArray
.
skipBits
(
2
);
// amp_enabled_flag (1), sample_adaptive_offset_enabled_flag (1)
if
(
bitArray
.
readBit
())
{
// pcm_enabled_flag
// pcm_sample_bit_depth_luma_minus1 (4), pcm_sample_bit_depth_chroma_minus1 (4)
bitArray
.
skipBits
(
4
);
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_min_pcm_luma_coding_block_size_minus3
bitArray
.
readUnsignedExpGolombCodedInt
();
// log2_diff_max_min_pcm_luma_coding_block_size
bitArray
.
skipBits
(
1
);
// pcm_loop_filter_disabled_flag
}
// Skips all short term reference picture sets.
skipShortTermRefPicSets
(
bitArray
);
if
(
bitArray
.
readBit
())
{
// long_term_ref_pics_present_flag
// num_long_term_ref_pics_sps
for
(
int
i
=
0
;
i
<
bitArray
.
readUnsignedExpGolombCodedInt
();
i
++)
{
int
ltRefPicPocLsbSpsLength
=
log2MaxPicOrderCntLsbMinus4
+
4
;
// lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i]
bitArray
.
skipBits
(
ltRefPicPocLsbSpsLength
+
1
);
}
}
bitArray
.
skipBits
(
2
);
// sps_temporal_mvp_enabled_flag, strong_intra_smoothing_enabled_flag
float
pixelWidthHeightRatio
=
1
;
if
(
bitArray
.
readBit
())
{
// vui_parameters_present_flag
if
(
bitArray
.
readBit
())
{
// aspect_ratio_info_present_flag
int
aspectRatioIdc
=
bitArray
.
readBits
(
8
);
if
(
aspectRatioIdc
==
EXTENDED_SAR
)
{
int
sarWidth
=
bitArray
.
readBits
(
16
);
int
sarHeight
=
bitArray
.
readBits
(
16
);
if
(
sarWidth
!=
0
&&
sarHeight
!=
0
)
{
pixelWidthHeightRatio
=
(
float
)
sarWidth
/
sarHeight
;
}
}
else
if
(
aspectRatioIdc
<
ASPECT_RATIO_IDC_VALUES
.
length
)
{
pixelWidthHeightRatio
=
ASPECT_RATIO_IDC_VALUES
[
aspectRatioIdc
];
}
else
{
Log
.
w
(
TAG
,
"Unexpected aspect_ratio_idc value: "
+
aspectRatioIdc
);
}
}
}
output
.
format
(
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H265
,
MediaFormat
.
NO_VALUE
,
C
.
UNKNOWN_TIME_US
,
picWidthInLumaSamples
,
picHeightInLumaSamples
,
pixelWidthHeightRatio
,
Collections
.
singletonList
(
csd
)));
hasOutputFormat
=
true
;
}
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
private
void
skipScalingList
(
ParsableBitArray
bitArray
)
{
for
(
int
sizeId
=
0
;
sizeId
<
4
;
sizeId
++)
{
for
(
int
matrixId
=
0
;
matrixId
<
6
;
matrixId
+=
sizeId
==
3
?
3
:
1
)
{
if
(!
bitArray
.
readBit
())
{
// scaling_list_pred_mode_flag[sizeId][matrixId]
// scaling_list_pred_matrix_id_delta[sizeId][matrixId]
bitArray
.
readUnsignedExpGolombCodedInt
();
}
else
{
int
coefNum
=
Math
.
min
(
64
,
1
<<
(
4
+
sizeId
<<
1
));
if
(
sizeId
>
1
)
{
// scaling_list_dc_coef_minus8[sizeId − 2][matrixId]
bitArray
.
readSignedExpGolombCodedInt
();
}
for
(
int
i
=
0
;
i
<
coefNum
;
i
++)
{
bitArray
.
readSignedExpGolombCodedInt
();
// scaling_list_delta_coef
}
}
}
}
}
// 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. */
private
static
boolean
isRandomAccessPoint
(
int
nalUnitType
)
{
return
nalUnitType
==
BLA_W_LP
||
nalUnitType
==
BLA_W_RADL
||
nalUnitType
==
BLA_N_LP
||
nalUnitType
==
IDR_W_RADL
||
nalUnitType
==
IDR_N_LP
||
nalUnitType
==
CRA_NUT
;
}
/** Returns whether the NAL unit is in the video coding layer. */
private
static
boolean
isInVcl
(
int
nalUnitType
)
{
return
nalUnitType
<=
VPS_NUT
;
}
/**
* Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of
* them. See H.265/HEVC (2014) 7.3.7.
*/
private
static
void
skipShortTermRefPicSets
(
ParsableBitArray
bitArray
)
{
int
numShortTermRefPicSets
=
bitArray
.
readUnsignedExpGolombCodedInt
();
boolean
interRefPicSetPredictionFlag
=
false
;
int
numNegativePics
=
0
;
int
numPositivePics
=
0
;
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
// one, so we just keep track of that rather than storing the whole array.
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
int
previousNumDeltaPocs
=
0
;
for
(
int
stRpsIdx
=
0
;
stRpsIdx
<
numShortTermRefPicSets
;
stRpsIdx
++)
{
if
(
stRpsIdx
!=
0
)
{
interRefPicSetPredictionFlag
=
bitArray
.
readBit
();
}
if
(
interRefPicSetPredictionFlag
)
{
bitArray
.
skipBits
(
1
);
// delta_rps_sign
bitArray
.
readUnsignedExpGolombCodedInt
();
// abs_delta_rps_minus1
for
(
int
j
=
0
;
j
<=
previousNumDeltaPocs
;
j
++)
{
if
(
bitArray
.
readBit
())
{
// used_by_curr_pic_flag[j]
bitArray
.
skipBits
(
1
);
// use_delta_flag[j]
}
}
}
else
{
numNegativePics
=
bitArray
.
readUnsignedExpGolombCodedInt
();
numPositivePics
=
bitArray
.
readUnsignedExpGolombCodedInt
();
previousNumDeltaPocs
=
numNegativePics
+
numPositivePics
;
for
(
int
i
=
0
;
i
<
numNegativePics
;
i
++)
{
bitArray
.
readUnsignedExpGolombCodedInt
();
// delta_poc_s0_minus1[i]
bitArray
.
skipBits
(
1
);
// used_by_curr_pic_s0_flag[i]
}
for
(
int
i
=
0
;
i
<
numPositivePics
;
i
++)
{
bitArray
.
readUnsignedExpGolombCodedInt
();
// delta_poc_s1_minus1[i]
bitArray
.
skipBits
(
1
);
// used_by_curr_pic_s1_flag[i]
}
}
}
}
// TODO: Deduplicate with H264Reader.NalUnitTargetBuffer.
/**
* 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
;
nalData
=
new
byte
[
5
+
initialCapacity
];
nalData
[
2
]
=
1
;
}
/**
* Resets the buffer, clearing any data that it holds.
*/
public
void
reset
()
{
isFilling
=
false
;
isCompleted
=
false
;
}
/**
* True if the buffer currently holds a complete NAL unit of the target type.
*/
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
)
{
nalLength
=
3
;
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/extractor/ts/SeiReader.java
View file @
5b186a2a
...
@@ -41,9 +41,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
...
@@ -41,9 +41,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
@Override
@Override
public
void
consume
(
ParsableByteArray
seiBuffer
,
long
pesTimeUs
,
boolean
startOfPacket
)
{
public
void
consume
(
ParsableByteArray
seiBuffer
,
long
pesTimeUs
,
boolean
startOfPacket
)
{
// Skip the NAL prefix and type.
seiBuffer
.
skipBytes
(
4
);
int
b
;
int
b
;
while
(
seiBuffer
.
bytesLeft
()
>
1
/* last byte will be rbsp_trailing_bits */
)
{
while
(
seiBuffer
.
bytesLeft
()
>
1
/* last byte will be rbsp_trailing_bits */
)
{
// Parse payload type.
// Parse payload type.
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
View file @
5b186a2a
...
@@ -46,6 +46,7 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -46,6 +46,7 @@ public final class TsExtractor implements Extractor, SeekMap {
private
static
final
int
TS_STREAM_TYPE_ATSC_AC3
=
0x81
;
private
static
final
int
TS_STREAM_TYPE_ATSC_AC3
=
0x81
;
private
static
final
int
TS_STREAM_TYPE_ATSC_E_AC3
=
0x87
;
private
static
final
int
TS_STREAM_TYPE_ATSC_E_AC3
=
0x87
;
private
static
final
int
TS_STREAM_TYPE_H264
=
0x1B
;
private
static
final
int
TS_STREAM_TYPE_H264
=
0x1B
;
private
static
final
int
TS_STREAM_TYPE_H265
=
0x24
;
private
static
final
int
TS_STREAM_TYPE_ID3
=
0x15
;
private
static
final
int
TS_STREAM_TYPE_ID3
=
0x15
;
private
static
final
int
TS_STREAM_TYPE_EIA608
=
0x100
;
// 0xFF + 1
private
static
final
int
TS_STREAM_TYPE_EIA608
=
0x100
;
// 0xFF + 1
...
@@ -361,9 +362,12 @@ public final class TsExtractor implements Extractor, SeekMap {
...
@@ -361,9 +362,12 @@ public final class TsExtractor implements Extractor, SeekMap {
pesPayloadReader
=
new
Ac3Reader
(
output
.
track
(
streamType
));
pesPayloadReader
=
new
Ac3Reader
(
output
.
track
(
streamType
));
break
;
break
;
case
TS_STREAM_TYPE_H264:
case
TS_STREAM_TYPE_H264:
SeiReader
seiReader
=
new
SeiReader
(
output
.
track
(
TS_STREAM_TYPE_EIA608
));
pesPayloadReader
=
new
H264Reader
(
output
.
track
(
TS_STREAM_TYPE_H264
),
pesPayloadReader
=
new
H264Reader
(
output
.
track
(
TS_STREAM_TYPE_H264
),
seiReader
,
new
SeiReader
(
output
.
track
(
TS_STREAM_TYPE_EIA608
)),
idrKeyframesOnly
);
idrKeyframesOnly
);
break
;
case
TS_STREAM_TYPE_H265:
pesPayloadReader
=
new
H265Reader
(
output
.
track
(
TS_STREAM_TYPE_H265
),
new
SeiReader
(
output
.
track
(
TS_STREAM_TYPE_EIA608
)));
break
;
break
;
case
TS_STREAM_TYPE_ID3:
case
TS_STREAM_TYPE_ID3:
pesPayloadReader
=
id3Reader
;
pesPayloadReader
=
id3Reader
;
...
...
library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java
View file @
5b186a2a
...
@@ -65,6 +65,18 @@ public final class NalUnitUtil {
...
@@ -65,6 +65,18 @@ public final class NalUnitUtil {
}
}
/**
/**
* Gets the type of the H.265 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
getH265NalUnitType
(
byte
[]
data
,
int
offset
)
{
return
(
data
[
offset
+
3
]
&
0x7E
)
>>
1
;
}
/**
* Finds the first NAL unit in {@code data}.
* Finds the first NAL unit in {@code data}.
* <p>
* <p>
* If {@code prefixFlags} is null then the first four bytes of a NAL unit must be entirely
* If {@code prefixFlags} is null then the first four bytes of a NAL unit must be entirely
...
...
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