Merge pull request #9864 from OxygenCobalt:vorbis-comments

PiperOrigin-RevId: 424355325
This commit is contained in:
Andrew Lewis 2022-01-28 08:42:33 +00:00
commit b2152f1988
23 changed files with 262 additions and 94 deletions

View File

@ -25,10 +25,9 @@ import androidx.media3.extractor.VorbisUtil.CommentHeader;
import androidx.media3.extractor.flac.FlacConstants;
import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.id3.Id3Decoder;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
@ -170,9 +169,12 @@ public final class FlacMetadataReader {
metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithVorbisComments(vorbisComments);
} else if (type == FlacConstants.METADATA_TYPE_PICTURE) {
PictureFrame pictureFrame = readPictureMetadataBlock(input, length);
ParsableByteArray pictureBlock = new ParsableByteArray(length);
input.readFully(pictureBlock.getData(), 0, length);
pictureBlock.skipBytes(FlacConstants.METADATA_BLOCK_HEADER_SIZE);
PictureFrame pictureFrame = PictureFrame.fromPictureBlock(pictureBlock);
metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithPictureFrames(Collections.singletonList(pictureFrame));
flacStreamMetadata.copyWithPictureFrames(ImmutableList.of(pictureFrame));
} else {
input.skipFully(length);
}
@ -270,28 +272,5 @@ public final class FlacMetadataReader {
return Arrays.asList(commentHeader.comments);
}
private static PictureFrame readPictureMetadataBlock(ExtractorInput input, int length)
throws IOException {
ParsableByteArray scratch = new ParsableByteArray(length);
input.readFully(scratch.getData(), 0, length);
scratch.skipBytes(FlacConstants.METADATA_BLOCK_HEADER_SIZE);
int pictureType = scratch.readInt();
int mimeTypeLength = scratch.readInt();
String mimeType = scratch.readString(mimeTypeLength, Charsets.US_ASCII);
int descriptionLength = scratch.readInt();
String description = scratch.readString(descriptionLength);
int width = scratch.readInt();
int height = scratch.readInt();
int depth = scratch.readInt();
int colors = scratch.readInt();
int pictureDataLength = scratch.readInt();
byte[] pictureData = new byte[pictureDataLength];
scratch.readBytes(pictureData, 0, pictureDataLength);
return new PictureFrame(
pictureType, mimeType, description, width, height, depth, colors, pictureData);
}
private FlacMetadataReader() {}
}

View File

@ -15,17 +15,17 @@
*/
package androidx.media3.extractor;
import static androidx.media3.extractor.VorbisUtil.parseVorbisComments;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.flac.VorbisComment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -62,8 +62,6 @@ public final class FlacStreamMetadata {
/** Indicates that a value is not in the corresponding lookup table. */
public static final int NOT_IN_LOOKUP_TABLE = -1;
/** Separator between the field name of a Vorbis comment and the corresponding value. */
private static final String SEPARATOR = "=";
/** Minimum number of samples per block. */
public final int minBlockSizeSamples;
@ -151,7 +149,7 @@ public final class FlacStreamMetadata {
bitsPerSample,
totalSamples,
/* seekTable= */ null,
buildMetadata(vorbisComments, pictureFrames));
concatenateVorbisMetadata(vorbisComments, pictureFrames));
}
private FlacStreamMetadata(
@ -276,8 +274,7 @@ public final class FlacStreamMetadata {
public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) {
@Nullable
Metadata appendedMetadata =
getMetadataCopyWithAppendedEntriesFrom(
buildMetadata(vorbisComments, Collections.emptyList()));
getMetadataCopyWithAppendedEntriesFrom(parseVorbisComments(vorbisComments));
return new FlacStreamMetadata(
minBlockSizeSamples,
maxBlockSizeSamples,
@ -294,9 +291,7 @@ public final class FlacStreamMetadata {
/** Returns a copy of {@code this} with the given picture frames added to the metadata. */
public FlacStreamMetadata copyWithPictureFrames(List<PictureFrame> pictureFrames) {
@Nullable
Metadata appendedMetadata =
getMetadataCopyWithAppendedEntriesFrom(
buildMetadata(Collections.emptyList(), pictureFrames));
Metadata appendedMetadata = getMetadataCopyWithAppendedEntriesFrom(new Metadata(pictureFrames));
return new FlacStreamMetadata(
minBlockSizeSamples,
maxBlockSizeSamples,
@ -310,6 +305,20 @@ public final class FlacStreamMetadata {
appendedMetadata);
}
/**
* Returns a new {@link Metadata} instance created from {@code vorbisComments} and {@code
* pictureFrames}.
*/
@Nullable
private static Metadata concatenateVorbisMetadata(
List<String> vorbisComments, List<PictureFrame> pictureFrames) {
@Nullable Metadata parsedVorbisComments = parseVorbisComments(vorbisComments);
if (parsedVorbisComments == null && pictureFrames.isEmpty()) {
return null;
}
return new Metadata(pictureFrames).copyWithAppendedEntriesFrom(parsedVorbisComments);
}
private static int getSampleRateLookupKey(int sampleRate) {
switch (sampleRate) {
case 88200:
@ -355,27 +364,4 @@ public final class FlacStreamMetadata {
return NOT_IN_LOOKUP_TABLE;
}
}
@Nullable
private static Metadata buildMetadata(
List<String> vorbisComments, List<PictureFrame> pictureFrames) {
if (vorbisComments.isEmpty() && pictureFrames.isEmpty()) {
return null;
}
ArrayList<Metadata.Entry> metadataEntries = new ArrayList<>();
for (int i = 0; i < vorbisComments.size(); i++) {
String vorbisComment = vorbisComments.get(i);
String[] keyAndValue = Util.splitAtFirst(vorbisComment, SEPARATOR);
if (keyAndValue.length != 2) {
Log.w(TAG, "Failed to parse Vorbis comment: " + vorbisComment);
} else {
VorbisComment entry = new VorbisComment(keyAndValue[0], keyAndValue[1]);
metadataEntries.add(entry);
}
}
metadataEntries.addAll(pictureFrames);
return metadataEntries.isEmpty() ? null : new Metadata(metadataEntries);
}
}

View File

@ -15,12 +15,21 @@
*/
package androidx.media3.extractor;
import android.util.Base64;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.Metadata.Entry;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.vorbis.VorbisComment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** Utility methods for parsing Vorbis streams. */
@UnstableApi
@ -250,6 +259,45 @@ public final class VorbisUtil {
return new CommentHeader(vendor, comments, length);
}
/**
* Builds a {@link Metadata} instance from a list of Vorbis Comments.
*
* <p>METADATA_BLOCK_PICTURE comments will be transformed into {@link PictureFrame} entries. All
* others will be transformed into {@link VorbisComment} entries.
*
* @param vorbisComments The raw input of comments, as a key-value pair KEY=VAL.
* @return The fully parsed Metadata instance. Null if no vorbis comments could be parsed.
*/
@Nullable
public static Metadata parseVorbisComments(List<String> vorbisComments) {
List<Entry> metadataEntries = new ArrayList<>();
for (int i = 0; i < vorbisComments.size(); i++) {
String vorbisComment = vorbisComments.get(i);
String[] keyAndValue = Util.splitAtFirst(vorbisComment, "=");
if (keyAndValue.length != 2) {
Log.w(TAG, "Failed to parse Vorbis comment: " + vorbisComment);
continue;
}
if (keyAndValue[0].equals("METADATA_BLOCK_PICTURE")) {
// This tag is a special cover art tag, outlined by
// https://wiki.xiph.org/index.php/VorbisComment#Cover_art.
// Decode it from Base64 and transform it into a PictureFrame.
try {
byte[] decoded = Base64.decode(keyAndValue[1], Base64.DEFAULT);
metadataEntries.add(PictureFrame.fromPictureBlock(new ParsableByteArray(decoded)));
} catch (RuntimeException e) {
Log.w(TAG, "Failed to parse vorbis picture", e);
}
} else {
VorbisComment entry = new VorbisComment(keyAndValue[0], keyAndValue[1]);
metadataEntries.add(entry);
}
}
return metadataEntries.isEmpty() ? null : new Metadata(metadataEntries);
}
/**
* Verifies whether the next bytes in {@code header} are a Vorbis header of the given {@code
* headerType}.

View File

@ -22,10 +22,12 @@ import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Charsets;
import java.util.Arrays;
/** A picture parsed from a FLAC file. */
/** A picture parsed from a Vorbis Comment or a FLAC picture block. */
@UnstableApi
public final class PictureFrame implements Metadata.Entry {
@ -136,6 +138,35 @@ public final class PictureFrame implements Metadata.Entry {
return 0;
}
/**
* Parses a {@code METADATA_BLOCK_PICTURE} into a {@code PictureFrame} instance.
*
* <p>{@code pictureBlock} may be read directly from a <a
* href="https://xiph.org/flac/format.html#metadata_block_picture">FLAC file</a>, or decoded from
* the base64 content of a <a
* href="https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE">Vorbis Comment</a>.
*
* @param pictureBlock The data of the {@code METADATA_BLOCK_PICTURE}, not including any headers.
* @return A {@code PictureFrame} parsed from {@code pictureBlock}.
*/
public static PictureFrame fromPictureBlock(ParsableByteArray pictureBlock) {
int pictureType = pictureBlock.readInt();
int mimeTypeLength = pictureBlock.readInt();
String mimeType = pictureBlock.readString(mimeTypeLength, Charsets.US_ASCII);
int descriptionLength = pictureBlock.readInt();
String description = pictureBlock.readString(descriptionLength);
int width = pictureBlock.readInt();
int height = pictureBlock.readInt();
int depth = pictureBlock.readInt();
int colors = pictureBlock.readInt();
int pictureDataLength = pictureBlock.readInt();
byte[] pictureData = new byte[pictureDataLength];
pictureBlock.readBytes(pictureData, 0, pictureDataLength);
return new PictureFrame(
pictureType, mimeType, description, width, height, depth, colors, pictureData);
}
public static final Parcelable.Creator<PictureFrame> CREATOR =
new Parcelable.Creator<PictureFrame>() {

View File

@ -24,9 +24,10 @@ import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi;
/** A vorbis comment. */
/** @deprecated Use {@link androidx.media3.extractor.metadata.vorbis.VorbisComment} instead. */
@Deprecated
@UnstableApi
public final class VorbisComment implements Metadata.Entry {
public class VorbisComment implements Metadata.Entry {
/** The key. */
public final String key;
@ -43,7 +44,7 @@ public final class VorbisComment implements Metadata.Entry {
this.value = value;
}
/* package */ VorbisComment(Parcel in) {
protected VorbisComment(Parcel in) {
this.key = castNonNull(in.readString());
this.value = castNonNull(in.readString());
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2019 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 androidx.media3.extractor.metadata.vorbis;
import android.os.Parcel;
import androidx.media3.common.util.UnstableApi;
/** A vorbis comment, extracted from a FLAC or Ogg file. */
@SuppressWarnings("deprecation") // Extending deprecated type for backwards compatibility.
@UnstableApi
public final class VorbisComment extends androidx.media3.extractor.metadata.flac.VorbisComment {
/**
* @param key The key.
* @param value The value.
*/
public VorbisComment(String key, String value) {
super(key, value);
}
/* package */ VorbisComment(Parcel in) {
super(in);
}
public static final Creator<VorbisComment> CREATOR =
new Creator<VorbisComment>() {
@Override
public VorbisComment createFromParcel(Parcel in) {
return new VorbisComment(in);
}
@Override
public VorbisComment[] newArray(int size) {
return new VorbisComment[size];
}
};
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 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.
*/
@NonNullApi
package androidx.media3.extractor.metadata.vorbis;
import androidx.media3.common.util.NonNullApi;

View File

@ -15,12 +15,18 @@
*/
package androidx.media3.extractor.ogg;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.OpusUtil;
import androidx.media3.extractor.VorbisUtil;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@ -28,26 +34,13 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
/** {@link StreamReader} to extract Opus data out of Ogg byte stream. */
/* package */ final class OpusReader extends StreamReader {
private static final int OPUS_CODE = 0x4f707573;
private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'};
private boolean headerRead;
private static final byte[] OPUS_ID_HEADER_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'};
private static final byte[] OPUS_COMMENT_HEADER_SIGNATURE = {
'O', 'p', 'u', 's', 'T', 'a', 'g', 's'
};
public static boolean verifyBitstreamType(ParsableByteArray data) {
if (data.bytesLeft() < OPUS_SIGNATURE.length) {
return false;
}
byte[] header = new byte[OPUS_SIGNATURE.length];
data.readBytes(header, 0, OPUS_SIGNATURE.length);
return Arrays.equals(header, OPUS_SIGNATURE);
}
@Override
protected void reset(boolean headerData) {
super.reset(headerData);
if (headerData) {
headerRead = false;
}
return peekPacketStartsWith(data, OPUS_ID_HEADER_SIGNATURE);
}
@Override
@ -57,11 +50,16 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@Override
@EnsuresNonNullIf(expression = "#3.format", result = false)
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) {
if (!headerRead) {
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData)
throws ParserException {
if (peekPacketStartsWith(packet, OPUS_ID_HEADER_SIGNATURE)) {
byte[] headerBytes = Arrays.copyOf(packet.getData(), packet.limit());
int channelCount = OpusUtil.getChannelCount(headerBytes);
List<byte[]> initializationData = OpusUtil.buildInitializationData(headerBytes);
// The ID header must come at the start of the file:
// https://datatracker.ietf.org/doc/html/rfc7845#section-3
checkState(setupData.format == null);
setupData.format =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_OPUS)
@ -69,13 +67,33 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
.setSampleRate(OpusUtil.SAMPLE_RATE)
.setInitializationData(initializationData)
.build();
headerRead = true;
return true;
} else if (peekPacketStartsWith(packet, OPUS_COMMENT_HEADER_SIGNATURE)) {
// The comment header must come immediately after the ID header, so the format will already
// be populated: https://datatracker.ietf.org/doc/html/rfc7845#section-3
checkStateNotNull(setupData.format);
packet.skipBytes(OPUS_COMMENT_HEADER_SIGNATURE.length);
VorbisUtil.CommentHeader commentHeader =
VorbisUtil.readVorbisCommentHeader(
packet, /* hasMetadataHeader= */ false, /* hasFramingBit= */ false);
@Nullable
Metadata vorbisMetadata =
VorbisUtil.parseVorbisComments(ImmutableList.copyOf(commentHeader.comments));
if (vorbisMetadata == null) {
return true;
}
setupData.format =
setupData
.format
.buildUpon()
.setMetadata(vorbisMetadata.copyWithAppendedEntriesFrom(setupData.format.metadata))
.build();
return true;
} else {
checkNotNull(setupData.format); // Has been set when the header was read.
boolean headerPacket = packet.readInt() == OPUS_CODE;
packet.setPosition(0);
return headerPacket;
// The ID header must come at the start of the file, so the format must already be populated:
// https://datatracker.ietf.org/doc/html/rfc7845#section-3
checkStateNotNull(setupData.format);
return false;
}
}
@ -114,4 +132,22 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
}
return (long) frames * length;
}
/**
* Returns true if the given {@link ParsableByteArray} starts with {@code expectedPrefix}. Does
* not change the {@link ParsableByteArray#getPosition() position} of {@code packet}.
*
* @param packet The packet data.
* @return True if the packet starts with {@code expectedPrefix}, false if not.
*/
private static boolean peekPacketStartsWith(ParsableByteArray packet, byte[] expectedPrefix) {
if (packet.bytesLeft() < expectedPrefix.length) {
return false;
}
int startPosition = packet.getPosition();
byte[] header = new byte[expectedPrefix.length];
packet.readBytes(header, 0, expectedPrefix.length);
packet.setPosition(startPosition);
return Arrays.equals(header, expectedPrefix);
}
}

View File

@ -21,11 +21,13 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.VorbisUtil;
import androidx.media3.extractor.VorbisUtil.Mode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -111,6 +113,10 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
codecInitializationData.add(idHeader.data);
codecInitializationData.add(vorbisSetup.setupHeaderData);
@Nullable
Metadata metadata =
VorbisUtil.parseVorbisComments(ImmutableList.copyOf(vorbisSetup.commentHeader.comments));
setupData.format =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_VORBIS)
@ -119,6 +125,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
.setChannelCount(idHeader.channels)
.setSampleRate(idHeader.sampleRate)
.setInitializationData(codecInitializationData)
.setMetadata(metadata)
.build();
return true;
}

View File

@ -24,7 +24,7 @@ import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.FlacMetadataReader.FlacStreamMetadataHolder;
import androidx.media3.extractor.flac.FlacConstants;
import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.flac.VorbisComment;
import androidx.media3.extractor.metadata.vorbis.VorbisComment;
import androidx.media3.test.utils.FakeExtractorInput;
import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider;

View File

@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.Metadata;
import androidx.media3.extractor.flac.FlacConstants;
import androidx.media3.extractor.metadata.flac.VorbisComment;
import androidx.media3.extractor.metadata.vorbis.VorbisComment;
import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.extractor.metadata.flac;
package androidx.media3.extractor.metadata.vorbis;
import static com.google.common.truth.Truth.assertThat;

View File

@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus
channelCount = 2
sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData:
data = length 19, hash BFE794DB
data = length 8, hash CA22068C

View File

@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus
channelCount = 2
sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData:
data = length 19, hash BFE794DB
data = length 8, hash CA22068C

View File

@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus
channelCount = 2
sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData:
data = length 19, hash BFE794DB
data = length 8, hash CA22068C

View File

@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus
channelCount = 2
sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData:
data = length 19, hash BFE794DB
data = length 8, hash CA22068C

View File

@ -10,6 +10,7 @@ track 0:
sampleMimeType = audio/opus
channelCount = 2
sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData:
data = length 19, hash BFE794DB
data = length 8, hash CA22068C

View File

@ -26,7 +26,6 @@ import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.extractor.metadata.dvbsi.AppInfoTable;
import androidx.media3.extractor.metadata.emsg.EventMessage;
import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.flac.VorbisComment;
import androidx.media3.extractor.metadata.icy.IcyHeaders;
import androidx.media3.extractor.metadata.icy.IcyInfo;
import androidx.media3.extractor.metadata.id3.Id3Frame;
@ -35,6 +34,7 @@ import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata;
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry;
import androidx.media3.extractor.metadata.scte35.SpliceCommand;
import androidx.media3.extractor.metadata.vorbis.VorbisComment;
import androidx.media3.test.utils.CapturingRenderersFactory;
import androidx.media3.test.utils.Dumper;
import com.google.common.collect.ImmutableList;