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
f1e59f80
authored
Feb 17, 2022
by
olly
Committed by
Ian Baker
Feb 18, 2022
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Libopus Support For WebM DiscardPadding
PiperOrigin-RevId: 429364728
parent
cf85d1bd
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
162 additions
and
14 deletions
libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/LibopusAudioRenderer.java
libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java
libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java
libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java
libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/LibopusAudioRenderer.java
View file @
f1e59f80
...
...
@@ -115,6 +115,7 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
format
.
initializationData
,
cryptoConfig
,
outputFloat
);
decoder
.
experimentalSetDiscardPaddingEnabled
(
experimentalGetDiscardPaddingEnabled
());
TraceUtil
.
endSection
();
return
decoder
;
...
...
@@ -126,4 +127,14 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
int
pcmEncoding
=
decoder
.
outputFloat
?
C
.
ENCODING_PCM_FLOAT
:
C
.
ENCODING_PCM_16BIT
;
return
Util
.
getPcmFormat
(
pcmEncoding
,
decoder
.
channelCount
,
OpusDecoder
.
SAMPLE_RATE
);
}
/**
* Returns true if support for padding removal from the end of decoder output buffer should be
* enabled.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*/
protected
boolean
experimentalGetDiscardPaddingEnabled
()
{
return
false
;
}
}
libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java
View file @
f1e59f80
...
...
@@ -56,6 +56,7 @@ public final class OpusDecoder
private
final
int
preSkipSamples
;
private
final
int
seekPreRollSamples
;
private
final
long
nativeDecoderContext
;
private
boolean
experimentalDiscardPaddingEnabled
;
private
int
skipSamples
;
...
...
@@ -145,6 +146,16 @@ public final class OpusDecoder
}
}
/**
* Sets whether discard padding is enabled. When enabled, discard padding samples (provided as
* supplemental data on the input buffer) will be removed from the end of the decoder output.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*/
public
void
experimentalSetDiscardPaddingEnabled
(
boolean
enabled
)
{
this
.
experimentalDiscardPaddingEnabled
=
enabled
;
}
@Override
public
String
getName
()
{
return
"libopus"
+
OpusLibrary
.
getVersion
();
...
...
@@ -224,6 +235,14 @@ public final class OpusDecoder
skipSamples
=
0
;
outputData
.
position
(
skipBytes
);
}
}
else
if
(
experimentalDiscardPaddingEnabled
&&
inputBuffer
.
hasSupplementalData
())
{
int
discardPaddingSamples
=
getDiscardPaddingSamples
(
inputBuffer
.
supplementalData
);
if
(
discardPaddingSamples
>
0
)
{
int
discardBytes
=
samplesToBytes
(
discardPaddingSamples
,
channelCount
,
outputFloat
);
if
(
result
>=
discardBytes
)
{
outputData
.
limit
(
result
-
discardBytes
);
}
}
}
return
null
;
}
...
...
@@ -281,6 +300,25 @@ public final class OpusDecoder
return
DEFAULT_SEEK_PRE_ROLL_SAMPLES
;
}
/**
* Returns the number of discard padding samples specified by the supplemental data attached to an
* input buffer.
*
* @param supplementalData Supplemental data related to the an input buffer.
* @return The number of discard padding samples to remove from the decoder output.
*/
@VisibleForTesting
/* package */
static
int
getDiscardPaddingSamples
(
@Nullable
ByteBuffer
supplementalData
)
{
if
(
supplementalData
==
null
||
supplementalData
.
remaining
()
!=
8
)
{
return
0
;
}
long
discardPaddingNs
=
supplementalData
.
order
(
ByteOrder
.
LITTLE_ENDIAN
).
getLong
();
if
(
discardPaddingNs
<
0
)
{
return
0
;
}
return
(
int
)
((
discardPaddingNs
*
SAMPLE_RATE
)
/
C
.
NANOS_PER_SECOND
);
}
/** Returns number of bytes to represent {@code samples}. */
private
static
int
samplesToBytes
(
int
samples
,
int
channelCount
,
boolean
outputFloat
)
{
int
bytesPerChannel
=
outputFloat
?
4
:
2
;
...
...
libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java
View file @
f1e59f80
...
...
@@ -52,6 +52,8 @@ public final class OpusDecoderTest {
private
static
final
int
DEFAULT_SEEK_PRE_ROLL_SAMPLES
=
3840
;
private
static
final
int
DISCARD_PADDING_NANOS
=
166667
;
private
static
final
ImmutableList
<
byte
[]>
HEADER_ONLY_INITIALIZATION_DATA
=
ImmutableList
.
of
(
HEADER
);
...
...
@@ -103,6 +105,20 @@ public final class OpusDecoderTest {
}
@Test
public
void
getDiscardPaddingSamples_positiveSampleLength_returnSampleLength
()
{
int
discardPaddingSamples
=
OpusDecoder
.
getDiscardPaddingSamples
(
createSupplementalData
(
DISCARD_PADDING_NANOS
));
assertThat
(
discardPaddingSamples
).
isEqualTo
(
nanosecondsToSampleCount
(
DISCARD_PADDING_NANOS
));
}
@Test
public
void
getDiscardPaddingSamples_negativeSampleLength_returnZero
()
{
int
discardPaddingSamples
=
OpusDecoder
.
getDiscardPaddingSamples
(
createSupplementalData
(-
DISCARD_PADDING_NANOS
));
assertThat
(
discardPaddingSamples
).
isEqualTo
(
0
);
}
@Test
public
void
decode_removesPreSkipFromOutput
()
throws
OpusDecoderException
{
OpusDecoder
decoder
=
new
OpusDecoder
(
...
...
@@ -120,6 +136,49 @@ public final class OpusDecoderTest {
.
isEqualTo
(
DECODED_DATA_SIZE
-
nanosecondsToBytes
(
PRE_SKIP_NANOS
));
}
@Test
public
void
decode_whenDiscardPaddingDisabled_returnsDiscardPadding
()
throws
OpusDecoderException
{
OpusDecoder
decoder
=
new
OpusDecoder
(
/* numInputBuffers= */
0
,
/* numOutputBuffers= */
0
,
/* initialInputBufferSize= */
0
,
createInitializationData
(
/* preSkipNanos= */
0
),
/* cryptoConfig= */
null
,
/* outputFloat= */
false
);
DecoderInputBuffer
input
=
createInputBuffer
(
decoder
,
ENCODED_DATA
,
/* supplementalData= */
buildNativeOrderByteArray
(
DISCARD_PADDING_NANOS
));
SimpleDecoderOutputBuffer
output
=
decoder
.
createOutputBuffer
();
assertThat
(
decoder
.
decode
(
input
,
output
,
false
)).
isNull
();
assertThat
(
output
.
data
.
remaining
()).
isEqualTo
(
DECODED_DATA_SIZE
);
}
@Test
public
void
decode_whenDiscardPaddingEnabled_removesDiscardPadding
()
throws
OpusDecoderException
{
OpusDecoder
decoder
=
new
OpusDecoder
(
/* numInputBuffers= */
0
,
/* numOutputBuffers= */
0
,
/* initialInputBufferSize= */
0
,
createInitializationData
(
/* preSkipNanos= */
0
),
/* cryptoConfig= */
null
,
/* outputFloat= */
false
);
decoder
.
experimentalSetDiscardPaddingEnabled
(
true
);
DecoderInputBuffer
input
=
createInputBuffer
(
decoder
,
ENCODED_DATA
,
/* supplementalData= */
buildNativeOrderByteArray
(
DISCARD_PADDING_NANOS
));
SimpleDecoderOutputBuffer
output
=
decoder
.
createOutputBuffer
();
assertThat
(
decoder
.
decode
(
input
,
output
,
false
)).
isNull
();
assertThat
(
output
.
data
.
limit
())
.
isEqualTo
(
DECODED_DATA_SIZE
-
nanosecondsToBytes
(
DISCARD_PADDING_NANOS
));
}
private
static
long
sampleCountToNanoseconds
(
long
sampleCount
)
{
return
(
sampleCount
*
C
.
NANOS_PER_SECOND
)
/
OpusDecoder
.
SAMPLE_RATE
;
}
...
...
@@ -141,6 +200,10 @@ public final class OpusDecoderTest {
return
ImmutableList
.
of
(
HEADER
,
preSkip
,
CUSTOM_SEEK_PRE_ROLL_BYTES
);
}
private
static
ByteBuffer
createSupplementalData
(
long
value
)
{
return
ByteBuffer
.
allocate
(
8
).
order
(
ByteOrder
.
LITTLE_ENDIAN
).
putLong
(
value
).
rewind
();
}
private
static
DecoderInputBuffer
createInputBuffer
(
OpusDecoder
decoder
,
byte
[]
data
,
@Nullable
byte
[]
supplementalData
)
{
DecoderInputBuffer
input
=
decoder
.
createInputBuffer
();
...
...
libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java
View file @
f1e59f80
...
...
@@ -193,6 +193,7 @@ public class MatroskaExtractor implements Extractor {
private
static
final
int
ID_CODEC_PRIVATE
=
0x63A2
;
private
static
final
int
ID_CODEC_DELAY
=
0x56AA
;
private
static
final
int
ID_SEEK_PRE_ROLL
=
0x56BB
;
private
static
final
int
ID_DISCARD_PADDING
=
0x75A2
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
...
...
@@ -391,7 +392,7 @@ public class MatroskaExtractor implements Extractor {
private
final
ParsableByteArray
subtitleSample
;
private
final
ParsableByteArray
encryptionInitializationVector
;
private
final
ParsableByteArray
encryptionSubsampleData
;
private
final
ParsableByteArray
blockAddition
alData
;
private
final
ParsableByteArray
supplement
alData
;
private
@MonotonicNonNull
ByteBuffer
encryptionSubsampleDataBuffer
;
private
long
segmentContentSize
;
...
...
@@ -434,6 +435,7 @@ public class MatroskaExtractor implements Extractor {
private
@C
.
BufferFlags
int
blockFlags
;
private
int
blockAdditionalId
;
private
boolean
blockHasReferenceBlock
;
private
long
blockGroupDiscardPaddingNs
;
// Sample writing state.
private
int
sampleBytesRead
;
...
...
@@ -472,7 +474,7 @@ public class MatroskaExtractor implements Extractor {
subtitleSample
=
new
ParsableByteArray
();
encryptionInitializationVector
=
new
ParsableByteArray
(
ENCRYPTION_IV_SIZE
);
encryptionSubsampleData
=
new
ParsableByteArray
();
blockAddition
alData
=
new
ParsableByteArray
();
supplement
alData
=
new
ParsableByteArray
();
blockSampleSizes
=
new
int
[
1
];
}
...
...
@@ -579,6 +581,7 @@ public class MatroskaExtractor implements Extractor {
case
ID_BLOCK_ADD_ID_TYPE:
case
ID_CODEC_DELAY:
case
ID_SEEK_PRE_ROLL:
case
ID_DISCARD_PADDING:
case
ID_CHANNELS:
case
ID_AUDIO_BIT_DEPTH:
case
ID_CONTENT_ENCODING_ORDER:
...
...
@@ -690,6 +693,7 @@ public class MatroskaExtractor implements Extractor {
break
;
case
ID_BLOCK_GROUP:
blockHasReferenceBlock
=
false
;
blockGroupDiscardPaddingNs
=
0L
;
break
;
case
ID_CONTENT_ENCODING:
// TODO: check and fail if more than one content encoding is present.
...
...
@@ -750,13 +754,22 @@ public class MatroskaExtractor implements Extractor {
// We've skipped this block (due to incompatible track number).
return
;
}
Track
track
=
tracks
.
get
(
blockTrackNumber
);
track
.
assertOutputInitialized
();
if
(
blockGroupDiscardPaddingNs
>
0L
&&
CODEC_ID_OPUS
.
equals
(
track
.
codecId
))
{
// For Opus, attach DiscardPadding to the block group samples as supplemental data.
supplementalData
.
reset
(
ByteBuffer
.
allocate
(
8
)
.
order
(
ByteOrder
.
LITTLE_ENDIAN
)
.
putLong
(
blockGroupDiscardPaddingNs
)
.
array
());
}
// Commit sample metadata.
int
sampleOffset
=
0
;
for
(
int
i
=
0
;
i
<
blockSampleCount
;
i
++)
{
sampleOffset
+=
blockSampleSizes
[
i
];
}
Track
track
=
tracks
.
get
(
blockTrackNumber
);
track
.
assertOutputInitialized
();
for
(
int
i
=
0
;
i
<
blockSampleCount
;
i
++)
{
long
sampleTimeUs
=
blockTimeUs
+
(
i
*
track
.
defaultSampleDurationNs
)
/
1000
;
int
sampleFlags
=
blockFlags
;
...
...
@@ -888,6 +901,9 @@ public class MatroskaExtractor implements Extractor {
case
ID_SEEK_PRE_ROLL:
getCurrentTrack
(
id
).
seekPreRollNs
=
value
;
break
;
case
ID_DISCARD_PADDING:
blockGroupDiscardPaddingNs
=
value
;
break
;
case
ID_CHANNELS:
getCurrentTrack
(
id
).
channelCount
=
(
int
)
value
;
break
;
...
...
@@ -1281,7 +1297,9 @@ public class MatroskaExtractor implements Extractor {
// For SimpleBlock, we can write sample data and immediately commit the corresponding
// sample metadata.
while
(
blockSampleIndex
<
blockSampleCount
)
{
int
sampleSize
=
writeSampleData
(
input
,
track
,
blockSampleSizes
[
blockSampleIndex
]);
int
sampleSize
=
writeSampleData
(
input
,
track
,
blockSampleSizes
[
blockSampleIndex
],
/* isBlockGroup= */
false
);
long
sampleTimeUs
=
blockTimeUs
+
(
blockSampleIndex
*
track
.
defaultSampleDurationNs
)
/
1000
;
commitSampleToOutput
(
track
,
sampleTimeUs
,
blockFlags
,
sampleSize
,
/* offset= */
0
);
...
...
@@ -1296,7 +1314,8 @@ public class MatroskaExtractor implements Extractor {
// the sample data, storing the final sample sizes for when we commit the metadata.
while
(
blockSampleIndex
<
blockSampleCount
)
{
blockSampleSizes
[
blockSampleIndex
]
=
writeSampleData
(
input
,
track
,
blockSampleSizes
[
blockSampleIndex
]);
writeSampleData
(
input
,
track
,
blockSampleSizes
[
blockSampleIndex
],
/* isBlockGroup= */
true
);
blockSampleIndex
++;
}
}
...
...
@@ -1332,8 +1351,8 @@ public class MatroskaExtractor implements Extractor {
throws
IOException
{
if
(
blockAdditionalId
==
BLOCK_ADDITIONAL_ID_VP9_ITU_T_35
&&
CODEC_ID_VP9
.
equals
(
track
.
codecId
))
{
blockAddition
alData
.
reset
(
contentSize
);
input
.
readFully
(
blockAddition
alData
.
getData
(),
0
,
contentSize
);
supplement
alData
.
reset
(
contentSize
);
input
.
readFully
(
supplement
alData
.
getData
(),
0
,
contentSize
);
}
else
{
// Unhandled block additional data.
input
.
skipFully
(
contentSize
);
...
...
@@ -1405,10 +1424,10 @@ public class MatroskaExtractor implements Extractor {
flags
&=
~
C
.
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA
;
}
else
{
// Append supplemental data.
int
blockAdditionalSize
=
blockAddition
alData
.
limit
();
int
supplementalDataSize
=
supplement
alData
.
limit
();
track
.
output
.
sampleData
(
blockAdditionalData
,
blockAdditional
Size
,
TrackOutput
.
SAMPLE_DATA_PART_SUPPLEMENTAL
);
size
+=
blockAdditional
Size
;
supplementalData
,
supplementalData
Size
,
TrackOutput
.
SAMPLE_DATA_PART_SUPPLEMENTAL
);
size
+=
supplementalData
Size
;
}
}
track
.
output
.
sampleMetadata
(
timeUs
,
flags
,
size
,
offset
,
track
.
cryptoData
);
...
...
@@ -1437,11 +1456,13 @@ public class MatroskaExtractor implements Extractor {
* @param input The input from which to read sample data.
* @param track The track to output the sample to.
* @param size The size of the sample data on the input side.
* @param isBlockGroup Whether the samples are from a BlockGroup.
* @return The final size of the written sample.
* @throws IOException If an error occurs reading from the input.
*/
@RequiresNonNull
(
"#2.output"
)
private
int
writeSampleData
(
ExtractorInput
input
,
Track
track
,
int
size
)
throws
IOException
{
private
int
writeSampleData
(
ExtractorInput
input
,
Track
track
,
int
size
,
boolean
isBlockGroup
)
throws
IOException
{
if
(
CODEC_ID_SUBRIP
.
equals
(
track
.
codecId
))
{
writeSubtitleSampleData
(
input
,
SUBRIP_PREFIX
,
size
);
return
finishWriteSampleData
();
...
...
@@ -1548,9 +1569,9 @@ public class MatroskaExtractor implements Extractor {
sampleStrippedBytes
.
reset
(
track
.
sampleStrippedBytes
,
track
.
sampleStrippedBytes
.
length
);
}
if
(
track
.
maxBlockAdditionId
>
0
)
{
if
(
track
.
samplesHaveSupplementalData
(
isBlockGroup
)
)
{
blockFlags
|=
C
.
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA
;
blockAddition
alData
.
reset
(
/* limit= */
0
);
supplement
alData
.
reset
(
/* limit= */
0
);
// If there is supplemental data, the structure of the sample data is:
// encryption data (if any) || sample size (4 bytes) || sample data || supplemental data
int
sampleSize
=
size
+
sampleStrippedBytes
.
limit
()
-
sampleBytesRead
;
...
...
@@ -2337,6 +2358,21 @@ public class MatroskaExtractor implements Extractor {
}
}
/**
* Returns true if supplemental data will be attached to the samples.
*
* @param isBlockGroup Whether the samples are from a BlockGroup.
*/
private
boolean
samplesHaveSupplementalData
(
boolean
isBlockGroup
)
{
if
(
CODEC_ID_OPUS
.
equals
(
codecId
))
{
// At the end of a BlockGroup, a positive DiscardPadding value will be written out as
// supplemental data for Opus codec. Otherwise (i.e. DiscardPadding <= 0) supplemental data
// size will be 0.
return
isBlockGroup
;
}
return
maxBlockAdditionId
>
0
;
}
/** Returns the HDR Static Info as defined in CTA-861.3. */
@Nullable
private
byte
[]
getHdrStaticInfo
()
{
...
...
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