Commit 93b5b947 by olly Committed by kim-vde

Support decode-only metadata buffers

PiperOrigin-RevId: 319798613
parent 7474547e
......@@ -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);
......
/*
* 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);
}
......
/*
* 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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment