mirror of
https://github.com/samsonjs/media.git
synced 2026-06-28 05:29:33 +00:00
Support decode-only metadata buffers
PiperOrigin-RevId: 319798613
This commit is contained in:
parent
7474547e0f
commit
93b5b947db
10 changed files with 203 additions and 53 deletions
|
|
@ -31,7 +31,8 @@ public interface MetadataDecoder {
|
|||
* ByteBuffer#hasArray()} is true.
|
||||
*
|
||||
* @param inputBuffer The input buffer to decode.
|
||||
* @return The decoded metadata object, or null if the metadata could not be decoded.
|
||||
* @return The decoded metadata object, or {@code null} if the metadata could not be decoded or if
|
||||
* {@link MetadataInputBuffer#isDecodeOnly()} was set on the input buffer.
|
||||
*/
|
||||
@Nullable
|
||||
Metadata decode(MetadataInputBuffer inputBuffer);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.metadata;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A {@link MetadataDecoder} base class that validates input buffers and discards any for which
|
||||
* {@link MetadataInputBuffer#isDecodeOnly()} is {@code true}.
|
||||
*/
|
||||
public abstract class SimpleMetadataDecoder implements MetadataDecoder {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
return inputBuffer.isDecodeOnly() ? null : decode(inputBuffer, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link #decode(MetadataInputBuffer)} after input buffer validation has been
|
||||
* performed, except in the case that {@link MetadataInputBuffer#isDecodeOnly()} is {@code true}.
|
||||
*
|
||||
* @param inputBuffer The input buffer to decode.
|
||||
* @param buffer The input buffer's {@link MetadataInputBuffer#data data buffer}, for convenience.
|
||||
* Validation by {@link #decode} guarantees that {@link ByteBuffer#hasArray()}, {@link
|
||||
* ByteBuffer#position()} and {@link ByteBuffer#arrayOffset()} are {@code true}, {@code 0} and
|
||||
* {@code 0} respectively.
|
||||
* @return The decoded metadata object, or {@code null} if the metadata could not be decoded.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer);
|
||||
}
|
||||
|
|
@ -16,21 +16,19 @@
|
|||
package com.google.android.exoplayer2.metadata.emsg;
|
||||
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Decodes data encoded by {@link EventMessageEncoder}. */
|
||||
public final class EventMessageDecoder implements MetadataDecoder {
|
||||
public final class EventMessageDecoder extends SimpleMetadataDecoder {
|
||||
|
||||
@Override
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
@SuppressWarnings("ByteBufferBackingArray") // Buffer validated by SimpleMetadataDecoder.decode
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
return new Metadata(decode(new ParsableByteArray(buffer.array(), buffer.limit())));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,8 @@ package com.google.android.exoplayer2.metadata.id3;
|
|||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
|
@ -32,10 +31,8 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Decodes ID3 tags.
|
||||
*/
|
||||
public final class Id3Decoder implements MetadataDecoder {
|
||||
/** Decodes ID3 tags. */
|
||||
public final class Id3Decoder extends SimpleMetadataDecoder {
|
||||
|
||||
/**
|
||||
* A predicate for determining whether individual frames should be decoded.
|
||||
|
|
@ -98,10 +95,8 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
@SuppressWarnings("ByteBufferBackingArray") // Buffer validated by SimpleMetadataDecoder.decode
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
return decode(buffer.array(), buffer.limit());
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +113,7 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||
List<Id3Frame> id3Frames = new ArrayList<>();
|
||||
ParsableByteArray id3Data = new ParsableByteArray(data, size);
|
||||
|
||||
Id3Header id3Header = decodeHeader(id3Data);
|
||||
@Nullable Id3Header id3Header = decodeHeader(id3Data);
|
||||
if (id3Header == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -142,8 +137,14 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||
}
|
||||
|
||||
while (id3Data.bytesLeft() >= frameHeaderSize) {
|
||||
Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack,
|
||||
frameHeaderSize, framePredicate);
|
||||
@Nullable
|
||||
Id3Frame frame =
|
||||
decodeFrame(
|
||||
id3Header.majorVersion,
|
||||
id3Data,
|
||||
unsignedIntFrameSizeHack,
|
||||
frameHeaderSize,
|
||||
framePredicate);
|
||||
if (frame != null) {
|
||||
id3Frames.add(frame);
|
||||
}
|
||||
|
|
@ -660,8 +661,10 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||
ArrayList<Id3Frame> subFrames = new ArrayList<>();
|
||||
int limit = framePosition + frameSize;
|
||||
while (id3Data.getPosition() < limit) {
|
||||
Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
|
||||
frameHeaderSize, framePredicate);
|
||||
@Nullable
|
||||
Id3Frame frame =
|
||||
decodeFrame(
|
||||
majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate);
|
||||
if (frame != null) {
|
||||
subFrames.add(frame);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.metadata;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link SimpleMetadataDecoder}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SimpleMetadataDecoderTest {
|
||||
|
||||
@Test
|
||||
public void decode_nullDataInputBuffer_throwsNullPointerException() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer nullDataInputBuffer = new MetadataInputBuffer();
|
||||
nullDataInputBuffer.data = null;
|
||||
|
||||
assertThrows(NullPointerException.class, () -> decoder.decode(nullDataInputBuffer));
|
||||
assertThat(decoder.decodeWasCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_directDataInputBuffer_throwsIllegalArgumentException() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer directDataInputBuffer = new MetadataInputBuffer();
|
||||
directDataInputBuffer.data = ByteBuffer.allocateDirect(8);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> decoder.decode(directDataInputBuffer));
|
||||
assertThat(decoder.decodeWasCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_nonZeroPositionDataInputBuffer_throwsIllegalArgumentException() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer nonZeroPositionDataInputBuffer = new MetadataInputBuffer();
|
||||
nonZeroPositionDataInputBuffer.data = ByteBuffer.wrap(new byte[8]);
|
||||
nonZeroPositionDataInputBuffer.data.position(1);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> decoder.decode(nonZeroPositionDataInputBuffer));
|
||||
assertThat(decoder.decodeWasCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_nonZeroOffsetDataInputBuffer_throwsIllegalArgumentException() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer directDataInputBuffer = new MetadataInputBuffer();
|
||||
directDataInputBuffer.data = ByteBuffer.wrap(new byte[8], /* offset= */ 4, /* length= */ 4);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> decoder.decode(directDataInputBuffer));
|
||||
assertThat(decoder.decodeWasCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_decodeOnlyBuffer_notPassedToDecodeInternal() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer decodeOnlyBuffer = new MetadataInputBuffer();
|
||||
decodeOnlyBuffer.data = ByteBuffer.wrap(new byte[8]);
|
||||
decodeOnlyBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
|
||||
assertThat(decoder.decode(decodeOnlyBuffer)).isNull();
|
||||
assertThat(decoder.decodeWasCalled).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decode_returnsDecodeInternalResult() {
|
||||
TestSimpleMetadataDecoder decoder = new TestSimpleMetadataDecoder();
|
||||
MetadataInputBuffer buffer = new MetadataInputBuffer();
|
||||
buffer.data = ByteBuffer.wrap(new byte[8]);
|
||||
|
||||
assertThat(decoder.decode(buffer)).isSameInstanceAs(decoder.result);
|
||||
assertThat(decoder.decodeWasCalled).isTrue();
|
||||
}
|
||||
|
||||
private static final class TestSimpleMetadataDecoder extends SimpleMetadataDecoder {
|
||||
|
||||
public final Metadata result;
|
||||
|
||||
public boolean decodeWasCalled;
|
||||
|
||||
public TestSimpleMetadataDecoder() {
|
||||
result = new Metadata();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
decodeWasCalled = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -129,10 +129,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||
if (result == C.RESULT_BUFFER_READ) {
|
||||
if (buffer.isEndOfStream()) {
|
||||
inputStreamEnded = true;
|
||||
} else if (buffer.isDecodeOnly()) {
|
||||
// Do nothing. Note this assumes that all metadata buffers can be decoded independently.
|
||||
// If we ever need to support a metadata format where this is not the case, we'll need to
|
||||
// pass the buffer to the decoder and discard the output.
|
||||
} else {
|
||||
buffer.subsampleOffsetUs = subsampleOffsetUs;
|
||||
buffer.flip();
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@ package com.google.android.exoplayer2.metadata.dvbsi;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
|
||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||
import com.google.common.base.Charsets;
|
||||
import java.nio.ByteBuffer;
|
||||
|
|
@ -32,7 +31,7 @@ import java.util.ArrayList;
|
|||
* href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
|
||||
* DVB ETSI TS 102 809 v1.1.1 spec</a>.
|
||||
*/
|
||||
public final class AppInfoTableDecoder implements MetadataDecoder {
|
||||
public final class AppInfoTableDecoder extends SimpleMetadataDecoder {
|
||||
|
||||
/** See section 5.3.6. */
|
||||
private static final int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02;
|
||||
|
|
@ -47,10 +46,8 @@ public final class AppInfoTableDecoder implements MetadataDecoder {
|
|||
|
||||
@Override
|
||||
@Nullable
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
@SuppressWarnings("ByteBufferBackingArray") // Buffer validated by SimpleMetadataDecoder.decode
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
int tableId = buffer.get();
|
||||
return tableId == APPLICATION_INFORMATION_TABLE_ID
|
||||
? parseAit(new ParsableBitArray(buffer.array(), buffer.limit()))
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@ package com.google.android.exoplayer2.metadata.icy;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Charsets;
|
||||
import java.nio.ByteBuffer;
|
||||
|
|
@ -29,7 +28,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
/** Decodes ICY stream information. */
|
||||
public final class IcyDecoder implements MetadataDecoder {
|
||||
public final class IcyDecoder extends SimpleMetadataDecoder {
|
||||
|
||||
private static final Pattern METADATA_ELEMENT = Pattern.compile("(.+?)='(.*?)';", Pattern.DOTALL);
|
||||
private static final String STREAM_KEY_NAME = "streamtitle";
|
||||
|
|
@ -44,10 +43,7 @@ public final class IcyDecoder implements MetadataDecoder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
@Nullable String icyString = decodeToString(buffer);
|
||||
byte[] icyBytes = new byte[buffer.limit()];
|
||||
buffer.get(icyBytes);
|
||||
|
|
|
|||
|
|
@ -17,19 +17,16 @@ package com.google.android.exoplayer2.metadata.scte35;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder;
|
||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Decodes splice info sections and produces splice commands.
|
||||
*/
|
||||
public final class SpliceInfoDecoder implements MetadataDecoder {
|
||||
/** Decodes splice info sections and produces splice commands. */
|
||||
public final class SpliceInfoDecoder extends SimpleMetadataDecoder {
|
||||
|
||||
private static final int TYPE_SPLICE_NULL = 0x00;
|
||||
private static final int TYPE_SPLICE_SCHEDULE = 0x04;
|
||||
|
|
@ -48,11 +45,8 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
|
||||
Assertions.checkArgument(
|
||||
buffer.position() == 0 && buffer.hasArray() && buffer.arrayOffset() == 0);
|
||||
|
||||
@SuppressWarnings("ByteBufferBackingArray") // Buffer validated by SimpleMetadataDecoder.decode
|
||||
protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
|
||||
// Internal timestamps adjustment.
|
||||
if (timestampAdjuster == null
|
||||
|| inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) {
|
||||
|
|
|
|||
|
|
@ -361,12 +361,15 @@ public final class PlayerEmsgHandler implements Handler.Callback {
|
|||
|
||||
private void parseAndDiscardSamples() {
|
||||
while (sampleQueue.isReady(/* loadingFinished= */ false)) {
|
||||
MetadataInputBuffer inputBuffer = dequeueSample();
|
||||
@Nullable MetadataInputBuffer inputBuffer = dequeueSample();
|
||||
if (inputBuffer == null) {
|
||||
continue;
|
||||
}
|
||||
long eventTimeUs = inputBuffer.timeUs;
|
||||
Metadata metadata = decoder.decode(inputBuffer);
|
||||
@Nullable Metadata metadata = decoder.decode(inputBuffer);
|
||||
if (metadata == null) {
|
||||
continue;
|
||||
}
|
||||
EventMessage eventMessage = (EventMessage) metadata.get(0);
|
||||
if (isPlayerEmsgEvent(eventMessage.schemeIdUri, eventMessage.value)) {
|
||||
parsePlayerEmsgEvent(eventTimeUs, eventMessage);
|
||||
|
|
|
|||
Loading…
Reference in a new issue