mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Support out-of-band HDR10+ metadata for VP9
Extract supplemental data from block additions in WebM/Matroska. Allow storing supplemental data alongside samples in the SampleQueue and write it as a separate field in DecoderInputBuffers. Handle supplemental data in the VP9 extension by propagating it to the output buffer. Handle supplemental data for HDR10+ in MediaCodecVideoRenderer by passing it to MediaCodec.setParameters, if supported by the component. PiperOrigin-RevId: 264582805
This commit is contained in:
parent
c361e3abc3
commit
f0aae7aee5
11 changed files with 230 additions and 15 deletions
|
|
@ -41,6 +41,7 @@
|
||||||
* Fix issue where player errors are thrown too early at playlist transitions
|
* Fix issue where player errors are thrown too early at playlist transitions
|
||||||
([#5407](https://github.com/google/ExoPlayer/issues/5407)).
|
([#5407](https://github.com/google/ExoPlayer/issues/5407)).
|
||||||
* Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be set.
|
* Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be set.
|
||||||
|
* Support out-of-band HDR10+ metadata for VP9 in WebM/Matroska.
|
||||||
|
|
||||||
### 2.10.4 ###
|
### 2.10.4 ###
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,10 @@ import java.nio.ByteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputBuffer.isDecodeOnly()) {
|
if (!inputBuffer.isDecodeOnly()) {
|
||||||
outputBuffer.init(inputBuffer.timeUs, outputMode);
|
@Nullable
|
||||||
|
ByteBuffer supplementalData =
|
||||||
|
inputBuffer.hasSupplementalData() ? inputBuffer.supplementalData : null;
|
||||||
|
outputBuffer.init(inputBuffer.timeUs, outputMode, supplementalData);
|
||||||
int getFrameResult = vpxGetFrame(vpxDecContext, outputBuffer);
|
int getFrameResult = vpxGetFrame(vpxDecContext, outputBuffer);
|
||||||
if (getFrameResult == 1) {
|
if (getFrameResult == 1) {
|
||||||
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,7 @@ public final class C {
|
||||||
value = {
|
value = {
|
||||||
BUFFER_FLAG_KEY_FRAME,
|
BUFFER_FLAG_KEY_FRAME,
|
||||||
BUFFER_FLAG_END_OF_STREAM,
|
BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
|
||||||
BUFFER_FLAG_LAST_SAMPLE,
|
BUFFER_FLAG_LAST_SAMPLE,
|
||||||
BUFFER_FLAG_ENCRYPTED,
|
BUFFER_FLAG_ENCRYPTED,
|
||||||
BUFFER_FLAG_DECODE_ONLY
|
BUFFER_FLAG_DECODE_ONLY
|
||||||
|
|
@ -493,6 +494,8 @@ public final class C {
|
||||||
* Flag for empty buffers that signal that the end of the stream was reached.
|
* Flag for empty buffers that signal that the end of the stream was reached.
|
||||||
*/
|
*/
|
||||||
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||||
|
/** Indicates that a buffer has supplemental data. */
|
||||||
|
public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000
|
||||||
/** Indicates that a buffer is known to contain the last media sample of the stream. */
|
/** Indicates that a buffer is known to contain the last media sample of the stream. */
|
||||||
public static final int BUFFER_FLAG_LAST_SAMPLE = 1 << 29; // 0x20000000
|
public static final int BUFFER_FLAG_LAST_SAMPLE = 1 << 29; // 0x20000000
|
||||||
/** Indicates that a buffer is (at least partially) encrypted. */
|
/** Indicates that a buffer is (at least partially) encrypted. */
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ public abstract class Buffer {
|
||||||
return getFlag(C.BUFFER_FLAG_KEY_FRAME);
|
return getFlag(C.BUFFER_FLAG_KEY_FRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether the {@link C#BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA} flag is set. */
|
||||||
|
public final boolean hasSupplementalData() {
|
||||||
|
return getFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces this buffer's flags with {@code flags}.
|
* Replaces this buffer's flags with {@code flags}.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,12 @@ public class DecoderInputBuffer extends Buffer {
|
||||||
*/
|
*/
|
||||||
public long timeUs;
|
public long timeUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplemental data related to the buffer, if {@link #hasSupplementalData()} returns true. If
|
||||||
|
* present, the buffer is populated with supplemental data from position 0 to its limit.
|
||||||
|
*/
|
||||||
|
@Nullable public ByteBuffer supplementalData;
|
||||||
|
|
||||||
@BufferReplacementMode private final int bufferReplacementMode;
|
@BufferReplacementMode private final int bufferReplacementMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -89,6 +95,16 @@ public class DecoderInputBuffer extends Buffer {
|
||||||
this.bufferReplacementMode = bufferReplacementMode;
|
this.bufferReplacementMode = bufferReplacementMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resets {@link #supplementalData} in preparation for storing {@code length} bytes. */
|
||||||
|
@EnsuresNonNull("supplementalData")
|
||||||
|
public void resetSupplementalData(int length) {
|
||||||
|
if (supplementalData == null || supplementalData.capacity() < length) {
|
||||||
|
supplementalData = ByteBuffer.allocate(length);
|
||||||
|
}
|
||||||
|
supplementalData.position(0);
|
||||||
|
supplementalData.limit(length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that {@link #data} is large enough to accommodate a write of a given length at its
|
* Ensures that {@link #data} is large enough to accommodate a write of a given length at its
|
||||||
* current position.
|
* current position.
|
||||||
|
|
@ -148,6 +164,9 @@ public class DecoderInputBuffer extends Buffer {
|
||||||
*/
|
*/
|
||||||
public final void flip() {
|
public final void flip() {
|
||||||
data.flip();
|
data.flip();
|
||||||
|
if (supplementalData != null) {
|
||||||
|
supplementalData.flip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,10 @@ public class MatroskaExtractor implements Extractor {
|
||||||
private static final int ID_BLOCK_GROUP = 0xA0;
|
private static final int ID_BLOCK_GROUP = 0xA0;
|
||||||
private static final int ID_BLOCK = 0xA1;
|
private static final int ID_BLOCK = 0xA1;
|
||||||
private static final int ID_BLOCK_DURATION = 0x9B;
|
private static final int ID_BLOCK_DURATION = 0x9B;
|
||||||
|
private static final int ID_BLOCK_ADDITIONS = 0x75A1;
|
||||||
|
private static final int ID_BLOCK_MORE = 0xA6;
|
||||||
|
private static final int ID_BLOCK_ADD_ID = 0xEE;
|
||||||
|
private static final int ID_BLOCK_ADDITIONAL = 0xA5;
|
||||||
private static final int ID_REFERENCE_BLOCK = 0xFB;
|
private static final int ID_REFERENCE_BLOCK = 0xFB;
|
||||||
private static final int ID_TRACKS = 0x1654AE6B;
|
private static final int ID_TRACKS = 0x1654AE6B;
|
||||||
private static final int ID_TRACK_ENTRY = 0xAE;
|
private static final int ID_TRACK_ENTRY = 0xAE;
|
||||||
|
|
@ -157,6 +161,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
private static final int ID_FLAG_DEFAULT = 0x88;
|
private static final int ID_FLAG_DEFAULT = 0x88;
|
||||||
private static final int ID_FLAG_FORCED = 0x55AA;
|
private static final int ID_FLAG_FORCED = 0x55AA;
|
||||||
private static final int ID_DEFAULT_DURATION = 0x23E383;
|
private static final int ID_DEFAULT_DURATION = 0x23E383;
|
||||||
|
private static final int ID_MAX_BLOCK_ADDITION_ID = 0x55EE;
|
||||||
private static final int ID_NAME = 0x536E;
|
private static final int ID_NAME = 0x536E;
|
||||||
private static final int ID_CODEC_ID = 0x86;
|
private static final int ID_CODEC_ID = 0x86;
|
||||||
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
||||||
|
|
@ -215,6 +220,12 @@ public class MatroskaExtractor implements Extractor {
|
||||||
private static final int ID_LUMNINANCE_MAX = 0x55D9;
|
private static final int ID_LUMNINANCE_MAX = 0x55D9;
|
||||||
private static final int ID_LUMNINANCE_MIN = 0x55DA;
|
private static final int ID_LUMNINANCE_MIN = 0x55DA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockAddID value for ITU T.35 metadata in a VP9 track. See also
|
||||||
|
* https://www.webmproject.org/docs/container/.
|
||||||
|
*/
|
||||||
|
private static final int BLOCK_ADD_ID_VP9_ITU_T_35 = 4;
|
||||||
|
|
||||||
private static final int LACING_NONE = 0;
|
private static final int LACING_NONE = 0;
|
||||||
private static final int LACING_XIPH = 1;
|
private static final int LACING_XIPH = 1;
|
||||||
private static final int LACING_FIXED_SIZE = 2;
|
private static final int LACING_FIXED_SIZE = 2;
|
||||||
|
|
@ -323,6 +334,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
private final ParsableByteArray subtitleSample;
|
private final ParsableByteArray subtitleSample;
|
||||||
private final ParsableByteArray encryptionInitializationVector;
|
private final ParsableByteArray encryptionInitializationVector;
|
||||||
private final ParsableByteArray encryptionSubsampleData;
|
private final ParsableByteArray encryptionSubsampleData;
|
||||||
|
private final ParsableByteArray blockAddData;
|
||||||
private ByteBuffer encryptionSubsampleDataBuffer;
|
private ByteBuffer encryptionSubsampleDataBuffer;
|
||||||
|
|
||||||
private long segmentContentSize;
|
private long segmentContentSize;
|
||||||
|
|
@ -361,6 +373,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
private int blockTrackNumberLength;
|
private int blockTrackNumberLength;
|
||||||
@C.BufferFlags
|
@C.BufferFlags
|
||||||
private int blockFlags;
|
private int blockFlags;
|
||||||
|
private int blockAddId;
|
||||||
|
|
||||||
// Sample reading state.
|
// Sample reading state.
|
||||||
private int sampleBytesRead;
|
private int sampleBytesRead;
|
||||||
|
|
@ -401,6 +414,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
subtitleSample = new ParsableByteArray();
|
subtitleSample = new ParsableByteArray();
|
||||||
encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE);
|
encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE);
|
||||||
encryptionSubsampleData = new ParsableByteArray();
|
encryptionSubsampleData = new ParsableByteArray();
|
||||||
|
blockAddData = new ParsableByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -479,6 +493,8 @@ public class MatroskaExtractor implements Extractor {
|
||||||
case ID_CUE_POINT:
|
case ID_CUE_POINT:
|
||||||
case ID_CUE_TRACK_POSITIONS:
|
case ID_CUE_TRACK_POSITIONS:
|
||||||
case ID_BLOCK_GROUP:
|
case ID_BLOCK_GROUP:
|
||||||
|
case ID_BLOCK_ADDITIONS:
|
||||||
|
case ID_BLOCK_MORE:
|
||||||
case ID_PROJECTION:
|
case ID_PROJECTION:
|
||||||
case ID_COLOUR:
|
case ID_COLOUR:
|
||||||
case ID_MASTERING_METADATA:
|
case ID_MASTERING_METADATA:
|
||||||
|
|
@ -499,6 +515,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
case ID_FLAG_DEFAULT:
|
case ID_FLAG_DEFAULT:
|
||||||
case ID_FLAG_FORCED:
|
case ID_FLAG_FORCED:
|
||||||
case ID_DEFAULT_DURATION:
|
case ID_DEFAULT_DURATION:
|
||||||
|
case ID_MAX_BLOCK_ADDITION_ID:
|
||||||
case ID_CODEC_DELAY:
|
case ID_CODEC_DELAY:
|
||||||
case ID_SEEK_PRE_ROLL:
|
case ID_SEEK_PRE_ROLL:
|
||||||
case ID_CHANNELS:
|
case ID_CHANNELS:
|
||||||
|
|
@ -518,6 +535,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
case ID_MAX_CLL:
|
case ID_MAX_CLL:
|
||||||
case ID_MAX_FALL:
|
case ID_MAX_FALL:
|
||||||
case ID_PROJECTION_TYPE:
|
case ID_PROJECTION_TYPE:
|
||||||
|
case ID_BLOCK_ADD_ID:
|
||||||
return EbmlProcessor.ELEMENT_TYPE_UNSIGNED_INT;
|
return EbmlProcessor.ELEMENT_TYPE_UNSIGNED_INT;
|
||||||
case ID_DOC_TYPE:
|
case ID_DOC_TYPE:
|
||||||
case ID_NAME:
|
case ID_NAME:
|
||||||
|
|
@ -531,6 +549,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
case ID_BLOCK:
|
case ID_BLOCK:
|
||||||
case ID_CODEC_PRIVATE:
|
case ID_CODEC_PRIVATE:
|
||||||
case ID_PROJECTION_PRIVATE:
|
case ID_PROJECTION_PRIVATE:
|
||||||
|
case ID_BLOCK_ADDITIONAL:
|
||||||
return EbmlProcessor.ELEMENT_TYPE_BINARY;
|
return EbmlProcessor.ELEMENT_TYPE_BINARY;
|
||||||
case ID_DURATION:
|
case ID_DURATION:
|
||||||
case ID_SAMPLING_FREQUENCY:
|
case ID_SAMPLING_FREQUENCY:
|
||||||
|
|
@ -760,6 +779,9 @@ public class MatroskaExtractor implements Extractor {
|
||||||
case ID_DEFAULT_DURATION:
|
case ID_DEFAULT_DURATION:
|
||||||
currentTrack.defaultSampleDurationNs = (int) value;
|
currentTrack.defaultSampleDurationNs = (int) value;
|
||||||
break;
|
break;
|
||||||
|
case ID_MAX_BLOCK_ADDITION_ID:
|
||||||
|
currentTrack.maxBlockAdditionId = (int) value;
|
||||||
|
break;
|
||||||
case ID_CODEC_DELAY:
|
case ID_CODEC_DELAY:
|
||||||
currentTrack.codecDelayNs = value;
|
currentTrack.codecDelayNs = value;
|
||||||
break;
|
break;
|
||||||
|
|
@ -914,6 +936,9 @@ public class MatroskaExtractor implements Extractor {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ID_BLOCK_ADD_ID:
|
||||||
|
blockAddId = (int) value;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1171,12 +1196,30 @@ public class MatroskaExtractor implements Extractor {
|
||||||
writeSampleData(input, track, blockLacingSampleSizes[0]);
|
writeSampleData(input, track, blockLacingSampleSizes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ID_BLOCK_ADDITIONAL:
|
||||||
|
if (blockState != BLOCK_STATE_DATA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleBlockAdditionalData(tracks.get(blockTrackNumber), blockAddId, input, contentSize);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ParserException("Unexpected id: " + id);
|
throw new ParserException("Unexpected id: " + id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handleBlockAdditionalData(
|
||||||
|
Track track, int blockAddId, ExtractorInput input, int contentSize)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
if (blockAddId == BLOCK_ADD_ID_VP9_ITU_T_35 && CODEC_ID_VP9.equals(track.codecId)) {
|
||||||
|
blockAddData.reset(contentSize);
|
||||||
|
input.readFully(blockAddData.data, 0, contentSize);
|
||||||
|
} else {
|
||||||
|
// Unhandled block additional data.
|
||||||
|
input.skipFully(contentSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void commitSampleToOutput(Track track, long timeUs) {
|
private void commitSampleToOutput(Track track, long timeUs) {
|
||||||
if (track.trueHdSampleRechunker != null) {
|
if (track.trueHdSampleRechunker != null) {
|
||||||
track.trueHdSampleRechunker.sampleMetadata(track, timeUs);
|
track.trueHdSampleRechunker.sampleMetadata(track, timeUs);
|
||||||
|
|
@ -1196,6 +1239,12 @@ public class MatroskaExtractor implements Extractor {
|
||||||
SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR,
|
SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR,
|
||||||
SSA_TIMECODE_EMPTY);
|
SSA_TIMECODE_EMPTY);
|
||||||
}
|
}
|
||||||
|
if ((blockFlags & C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA) != 0) {
|
||||||
|
// Append supplemental data.
|
||||||
|
int size = blockAddData.limit();
|
||||||
|
track.output.sampleData(blockAddData, size);
|
||||||
|
sampleBytesWritten += size;
|
||||||
|
}
|
||||||
track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData);
|
track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData);
|
||||||
}
|
}
|
||||||
sampleRead = true;
|
sampleRead = true;
|
||||||
|
|
@ -1328,6 +1377,21 @@ public class MatroskaExtractor implements Extractor {
|
||||||
// If the sample has header stripping, prepare to read/output the stripped bytes first.
|
// If the sample has header stripping, prepare to read/output the stripped bytes first.
|
||||||
sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length);
|
sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (track.maxBlockAdditionId > 0) {
|
||||||
|
blockFlags |= C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA;
|
||||||
|
blockAddData.reset();
|
||||||
|
// If there is supplemental data, the structure of the sample data is:
|
||||||
|
// sample size (4 bytes) || sample data || supplemental data
|
||||||
|
scratch.reset(/* limit= */ 4);
|
||||||
|
scratch.data[0] = (byte) ((size >> 24) & 0xFF);
|
||||||
|
scratch.data[1] = (byte) ((size >> 16) & 0xFF);
|
||||||
|
scratch.data[2] = (byte) ((size >> 8) & 0xFF);
|
||||||
|
scratch.data[3] = (byte) (size & 0xFF);
|
||||||
|
output.sampleData(scratch, 4);
|
||||||
|
sampleBytesWritten += 4;
|
||||||
|
}
|
||||||
|
|
||||||
sampleEncodingHandled = true;
|
sampleEncodingHandled = true;
|
||||||
}
|
}
|
||||||
size += sampleStrippedBytes.limit();
|
size += sampleStrippedBytes.limit();
|
||||||
|
|
@ -1713,6 +1777,7 @@ public class MatroskaExtractor implements Extractor {
|
||||||
public int number;
|
public int number;
|
||||||
public int type;
|
public int type;
|
||||||
public int defaultSampleDurationNs;
|
public int defaultSampleDurationNs;
|
||||||
|
public int maxBlockAdditionId;
|
||||||
public boolean hasContentEncryption;
|
public boolean hasContentEncryption;
|
||||||
public byte[] sampleStrippedBytes;
|
public byte[] sampleStrippedBytes;
|
||||||
public TrackOutput.CryptoData cryptoData;
|
public TrackOutput.CryptoData cryptoData;
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,18 @@ public final class MediaCodecInfo {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether the codec handles HDR10+ out-of-band metadata. */
|
||||||
|
public boolean isHdr10PlusOutOfBandMetadataSupported() {
|
||||||
|
if (Util.SDK_INT >= 29 && MimeTypes.VIDEO_VP9.equals(mimeType)) {
|
||||||
|
for (CodecProfileLevel capabilities : getProfileLevels()) {
|
||||||
|
if (capabilities.profile == CodecProfileLevel.VP9Profile2HDR10Plus) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether it may be possible to adapt to playing a different format when the codec is
|
* Returns whether it may be possible to adapt to playing a different format when the codec is
|
||||||
* configured to play media in the specified {@code format}. For adaptation to succeed, the codec
|
* configured to play media in the specified {@code format}. For adaptation to succeed, the codec
|
||||||
|
|
|
||||||
|
|
@ -1140,6 +1140,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
Math.max(largestQueuedPresentationTimeUs, presentationTimeUs);
|
Math.max(largestQueuedPresentationTimeUs, presentationTimeUs);
|
||||||
|
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
|
if (buffer.hasSupplementalData()) {
|
||||||
|
handleInputBufferSupplementalData(buffer);
|
||||||
|
}
|
||||||
onQueueInputBuffer(buffer);
|
onQueueInputBuffer(buffer);
|
||||||
|
|
||||||
if (bufferEncrypted) {
|
if (bufferEncrypted) {
|
||||||
|
|
@ -1297,10 +1300,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles supplemental data associated with an input buffer.
|
||||||
|
*
|
||||||
|
* <p>The default implementation is a no-op.
|
||||||
|
*
|
||||||
|
* @param buffer The input buffer that is about to be queued.
|
||||||
|
* @throws ExoPlaybackException Thrown if an error occurs handling supplemental data.
|
||||||
|
*/
|
||||||
|
protected void handleInputBufferSupplementalData(DecoderInputBuffer buffer)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called immediately before an input buffer is queued into the codec.
|
* Called immediately before an input buffer is queued into the codec.
|
||||||
* <p>
|
*
|
||||||
* The default implementation is a no-op.
|
* <p>The default implementation is a no-op.
|
||||||
*
|
*
|
||||||
* @param buffer The buffer to be queued.
|
* @param buffer The buffer to be queued.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -393,13 +393,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||||
}
|
}
|
||||||
if (!buffer.isFlagsOnly()) {
|
if (!buffer.isFlagsOnly()) {
|
||||||
// Read encryption data if the sample is encrypted.
|
readToBuffer(buffer, extrasHolder);
|
||||||
if (buffer.isEncrypted()) {
|
|
||||||
readEncryptionData(buffer, extrasHolder);
|
|
||||||
}
|
|
||||||
// Write the sample data into the holder.
|
|
||||||
buffer.ensureSpaceForWrite(extrasHolder.size);
|
|
||||||
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return C.RESULT_BUFFER_READ;
|
return C.RESULT_BUFFER_READ;
|
||||||
|
|
@ -410,12 +404,48 @@ public class SampleQueue implements TrackOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data from the rolling buffer to populate a decoder input buffer.
|
||||||
|
*
|
||||||
|
* @param buffer The buffer to populate.
|
||||||
|
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||||
|
*/
|
||||||
|
private void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
|
||||||
|
// Read encryption data if the sample is encrypted.
|
||||||
|
if (buffer.isEncrypted()) {
|
||||||
|
readEncryptionData(buffer, extrasHolder);
|
||||||
|
}
|
||||||
|
// Read sample data, extracting supplemental data into a separate buffer if needed.
|
||||||
|
if (buffer.hasSupplementalData()) {
|
||||||
|
// If there is supplemental data, the sample data is prefixed by its size.
|
||||||
|
scratch.reset(4);
|
||||||
|
readData(extrasHolder.offset, scratch.data, 4);
|
||||||
|
int sampleSize = scratch.readUnsignedIntToInt();
|
||||||
|
extrasHolder.offset += 4;
|
||||||
|
extrasHolder.size -= 4;
|
||||||
|
|
||||||
|
// Write the sample data.
|
||||||
|
buffer.ensureSpaceForWrite(sampleSize);
|
||||||
|
readData(extrasHolder.offset, buffer.data, sampleSize);
|
||||||
|
extrasHolder.offset += sampleSize;
|
||||||
|
extrasHolder.size -= sampleSize;
|
||||||
|
|
||||||
|
// Write the remaining data as supplemental data.
|
||||||
|
buffer.resetSupplementalData(extrasHolder.size);
|
||||||
|
readData(extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
|
||||||
|
} else {
|
||||||
|
// Write the sample data.
|
||||||
|
buffer.ensureSpaceForWrite(extrasHolder.size);
|
||||||
|
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads encryption data for the current sample.
|
* Reads encryption data for the current sample.
|
||||||
* <p>
|
*
|
||||||
* The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and
|
* <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
|
||||||
* {@link SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The
|
* SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
|
||||||
* same value is added to {@link SampleExtrasHolder#offset}.
|
* value is added to {@link SampleExtrasHolder#offset}.
|
||||||
*
|
*
|
||||||
* @param buffer The buffer into which the encryption data should be written.
|
* @param buffer The buffer into which the encryption data should be written.
|
||||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||||
import android.media.MediaCrypto;
|
import android.media.MediaCrypto;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
|
@ -123,6 +124,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
private CodecMaxValues codecMaxValues;
|
private CodecMaxValues codecMaxValues;
|
||||||
private boolean codecNeedsSetOutputSurfaceWorkaround;
|
private boolean codecNeedsSetOutputSurfaceWorkaround;
|
||||||
|
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
||||||
|
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
private Surface dummySurface;
|
private Surface dummySurface;
|
||||||
|
|
@ -683,6 +685,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
long initializationDurationMs) {
|
long initializationDurationMs) {
|
||||||
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
|
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
|
||||||
codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name);
|
codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name);
|
||||||
|
codecHandlesHdr10PlusOutOfBandMetadata =
|
||||||
|
Assertions.checkNotNull(getCodecInfo()).isHdr10PlusOutOfBandMetadataSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -727,6 +731,37 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
processOutputFormat(codec, width, height);
|
processOutputFormat(codec, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleInputBufferSupplementalData(DecoderInputBuffer buffer)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
if (!codecHandlesHdr10PlusOutOfBandMetadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ByteBuffer data = Assertions.checkNotNull(buffer.supplementalData);
|
||||||
|
if (data.remaining() >= 7) {
|
||||||
|
// Check for HDR10+ out-of-band metadata. See User_data_registered_itu_t_t35 in ST 2094-40.
|
||||||
|
byte ituTT35CountryCode = data.get();
|
||||||
|
int ituTT35TerminalProviderCode = data.getShort();
|
||||||
|
int ituTT35TerminalProviderOrientedCode = data.getShort();
|
||||||
|
byte applicationIdentifier = data.get();
|
||||||
|
byte applicationVersion = data.get();
|
||||||
|
data.position(0);
|
||||||
|
if (ituTT35CountryCode == (byte) 0xB5
|
||||||
|
&& ituTT35TerminalProviderCode == 0x003C
|
||||||
|
&& ituTT35TerminalProviderOrientedCode == 0x0001
|
||||||
|
&& applicationIdentifier == 4
|
||||||
|
&& applicationVersion == 0) {
|
||||||
|
// The metadata size may vary so allocate a new array every time. This is not too
|
||||||
|
// inefficient because the metadata is only a few tens of bytes.
|
||||||
|
byte[] hdr10PlusInfo = new byte[data.remaining()];
|
||||||
|
data.get(hdr10PlusInfo);
|
||||||
|
data.position(0);
|
||||||
|
// If codecHandlesHdr10PlusOutOfBandMetadata is true, this is an API 29 or later build.
|
||||||
|
setHdr10PlusInfoV29(getCodec(), hdr10PlusInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean processOutputBuffer(
|
protected boolean processOutputBuffer(
|
||||||
long positionUs,
|
long positionUs,
|
||||||
|
|
@ -1153,6 +1188,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
return earlyUs < -500000;
|
return earlyUs < -500000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(29)
|
||||||
|
private static void setHdr10PlusInfoV29(MediaCodec codec, byte[] hdr10PlusInfo) {
|
||||||
|
Bundle codecParameters = new Bundle();
|
||||||
|
codecParameters.putByteArray(MediaCodec.PARAMETER_KEY_HDR10_PLUS_INFO, hdr10PlusInfo);
|
||||||
|
codec.setParameters(codecParameters);
|
||||||
|
}
|
||||||
|
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) {
|
private static void setOutputSurfaceV23(MediaCodec codec, Surface surface) {
|
||||||
codec.setOutputSurface(surface);
|
codec.setOutputSurface(surface);
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,35 @@ public abstract class VideoDecoderOutputBuffer extends OutputBuffer {
|
||||||
@Nullable public int[] yuvStrides;
|
@Nullable public int[] yuvStrides;
|
||||||
public int colorspace;
|
public int colorspace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplemental data related to the output frame, if {@link #hasSupplementalData()} returns true.
|
||||||
|
* If present, the buffer is populated with supplemental data from position 0 to its limit.
|
||||||
|
*/
|
||||||
|
@Nullable public ByteBuffer supplementalData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the buffer.
|
* Initializes the buffer.
|
||||||
*
|
*
|
||||||
* @param timeUs The presentation timestamp for the buffer, in microseconds.
|
* @param timeUs The presentation timestamp for the buffer, in microseconds.
|
||||||
* @param mode The output mode. One of {@link C#VIDEO_OUTPUT_MODE_NONE}, {@link
|
* @param mode The output mode. One of {@link C#VIDEO_OUTPUT_MODE_NONE}, {@link
|
||||||
* C#VIDEO_OUTPUT_MODE_YUV} and {@link C#VIDEO_OUTPUT_MODE_SURFACE_YUV}.
|
* C#VIDEO_OUTPUT_MODE_YUV} and {@link C#VIDEO_OUTPUT_MODE_SURFACE_YUV}.
|
||||||
|
* @param supplementalData Supplemental data associated with the frame, or {@code null} if not
|
||||||
|
* present. It is safe to reuse the provided buffer after this method returns.
|
||||||
*/
|
*/
|
||||||
public void init(long timeUs, @C.VideoOutputMode int mode) {
|
public void init(
|
||||||
|
long timeUs, @C.VideoOutputMode int mode, @Nullable ByteBuffer supplementalData) {
|
||||||
this.timeUs = timeUs;
|
this.timeUs = timeUs;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
if (supplementalData != null) {
|
||||||
|
int size = supplementalData.limit();
|
||||||
|
if (this.supplementalData == null || this.supplementalData.capacity() < size) {
|
||||||
|
this.supplementalData = ByteBuffer.allocate(size);
|
||||||
|
}
|
||||||
|
this.supplementalData.position(0);
|
||||||
|
this.supplementalData.put(supplementalData);
|
||||||
|
this.supplementalData.flip();
|
||||||
|
supplementalData.position(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue