Commit b2152f19 by Andrew Lewis

Merge pull request #9864 from OxygenCobalt:vorbis-comments

PiperOrigin-RevId: 424355325
parents 2ed1deb5 c2d0649e
Showing with 257 additions and 94 deletions
...@@ -25,10 +25,9 @@ import androidx.media3.extractor.VorbisUtil.CommentHeader; ...@@ -25,10 +25,9 @@ import androidx.media3.extractor.VorbisUtil.CommentHeader;
import androidx.media3.extractor.flac.FlacConstants; import androidx.media3.extractor.flac.FlacConstants;
import androidx.media3.extractor.metadata.flac.PictureFrame; import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.id3.Id3Decoder; 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
...@@ -170,9 +169,12 @@ public final class FlacMetadataReader { ...@@ -170,9 +169,12 @@ public final class FlacMetadataReader {
metadataHolder.flacStreamMetadata = metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithVorbisComments(vorbisComments); flacStreamMetadata.copyWithVorbisComments(vorbisComments);
} else if (type == FlacConstants.METADATA_TYPE_PICTURE) { } 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 = metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithPictureFrames(Collections.singletonList(pictureFrame)); flacStreamMetadata.copyWithPictureFrames(ImmutableList.of(pictureFrame));
} else { } else {
input.skipFully(length); input.skipFully(length);
} }
...@@ -270,28 +272,5 @@ public final class FlacMetadataReader { ...@@ -270,28 +272,5 @@ public final class FlacMetadataReader {
return Arrays.asList(commentHeader.comments); 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() {} private FlacMetadataReader() {}
} }
...@@ -15,17 +15,17 @@ ...@@ -15,17 +15,17 @@
*/ */
package androidx.media3.extractor; package androidx.media3.extractor;
import static androidx.media3.extractor.VorbisUtil.parseVorbisComments;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.extractor.metadata.flac.PictureFrame; import androidx.media3.extractor.metadata.flac.PictureFrame;
import androidx.media3.extractor.metadata.flac.VorbisComment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -62,8 +62,6 @@ public final class FlacStreamMetadata { ...@@ -62,8 +62,6 @@ public final class FlacStreamMetadata {
/** Indicates that a value is not in the corresponding lookup table. */ /** Indicates that a value is not in the corresponding lookup table. */
public static final int NOT_IN_LOOKUP_TABLE = -1; 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. */ /** Minimum number of samples per block. */
public final int minBlockSizeSamples; public final int minBlockSizeSamples;
...@@ -151,7 +149,7 @@ public final class FlacStreamMetadata { ...@@ -151,7 +149,7 @@ public final class FlacStreamMetadata {
bitsPerSample, bitsPerSample,
totalSamples, totalSamples,
/* seekTable= */ null, /* seekTable= */ null,
buildMetadata(vorbisComments, pictureFrames)); concatenateVorbisMetadata(vorbisComments, pictureFrames));
} }
private FlacStreamMetadata( private FlacStreamMetadata(
...@@ -276,8 +274,7 @@ public final class FlacStreamMetadata { ...@@ -276,8 +274,7 @@ public final class FlacStreamMetadata {
public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) { public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) {
@Nullable @Nullable
Metadata appendedMetadata = Metadata appendedMetadata =
getMetadataCopyWithAppendedEntriesFrom( getMetadataCopyWithAppendedEntriesFrom(parseVorbisComments(vorbisComments));
buildMetadata(vorbisComments, Collections.emptyList()));
return new FlacStreamMetadata( return new FlacStreamMetadata(
minBlockSizeSamples, minBlockSizeSamples,
maxBlockSizeSamples, maxBlockSizeSamples,
...@@ -294,9 +291,7 @@ public final class FlacStreamMetadata { ...@@ -294,9 +291,7 @@ public final class FlacStreamMetadata {
/** Returns a copy of {@code this} with the given picture frames added to the metadata. */ /** Returns a copy of {@code this} with the given picture frames added to the metadata. */
public FlacStreamMetadata copyWithPictureFrames(List<PictureFrame> pictureFrames) { public FlacStreamMetadata copyWithPictureFrames(List<PictureFrame> pictureFrames) {
@Nullable @Nullable
Metadata appendedMetadata = Metadata appendedMetadata = getMetadataCopyWithAppendedEntriesFrom(new Metadata(pictureFrames));
getMetadataCopyWithAppendedEntriesFrom(
buildMetadata(Collections.emptyList(), pictureFrames));
return new FlacStreamMetadata( return new FlacStreamMetadata(
minBlockSizeSamples, minBlockSizeSamples,
maxBlockSizeSamples, maxBlockSizeSamples,
...@@ -310,6 +305,20 @@ public final class FlacStreamMetadata { ...@@ -310,6 +305,20 @@ public final class FlacStreamMetadata {
appendedMetadata); 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) { private static int getSampleRateLookupKey(int sampleRate) {
switch (sampleRate) { switch (sampleRate) {
case 88200: case 88200:
...@@ -355,27 +364,4 @@ public final class FlacStreamMetadata { ...@@ -355,27 +364,4 @@ public final class FlacStreamMetadata {
return NOT_IN_LOOKUP_TABLE; 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);
}
} }
...@@ -15,12 +15,21 @@ ...@@ -15,12 +15,21 @@
*/ */
package androidx.media3.extractor; package androidx.media3.extractor;
import android.util.Base64;
import androidx.annotation.Nullable;
import androidx.media3.common.Format; 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.ParserException;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; 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.Arrays;
import java.util.List;
/** Utility methods for parsing Vorbis streams. */ /** Utility methods for parsing Vorbis streams. */
@UnstableApi @UnstableApi
...@@ -251,6 +260,45 @@ public final class VorbisUtil { ...@@ -251,6 +260,45 @@ public final class VorbisUtil {
} }
/** /**
* 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 * Verifies whether the next bytes in {@code header} are a Vorbis header of the given {@code
* headerType}. * headerType}.
* *
......
...@@ -22,10 +22,12 @@ import android.os.Parcelable; ...@@ -22,10 +22,12 @@ import android.os.Parcelable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Charsets;
import java.util.Arrays; import java.util.Arrays;
/** A picture parsed from a FLAC file. */ /** A picture parsed from a Vorbis Comment or a FLAC picture block. */
@UnstableApi @UnstableApi
public final class PictureFrame implements Metadata.Entry { public final class PictureFrame implements Metadata.Entry {
...@@ -136,6 +138,35 @@ public final class PictureFrame implements Metadata.Entry { ...@@ -136,6 +138,35 @@ public final class PictureFrame implements Metadata.Entry {
return 0; 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 = public static final Parcelable.Creator<PictureFrame> CREATOR =
new Parcelable.Creator<PictureFrame>() { new Parcelable.Creator<PictureFrame>() {
......
...@@ -24,9 +24,10 @@ import androidx.media3.common.MediaMetadata; ...@@ -24,9 +24,10 @@ import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
/** A vorbis comment. */ /** @deprecated Use {@link androidx.media3.extractor.metadata.vorbis.VorbisComment} instead. */
@Deprecated
@UnstableApi @UnstableApi
public final class VorbisComment implements Metadata.Entry { public class VorbisComment implements Metadata.Entry {
/** The key. */ /** The key. */
public final String key; public final String key;
...@@ -43,7 +44,7 @@ public final class VorbisComment implements Metadata.Entry { ...@@ -43,7 +44,7 @@ public final class VorbisComment implements Metadata.Entry {
this.value = value; this.value = value;
} }
/* package */ VorbisComment(Parcel in) { protected VorbisComment(Parcel in) {
this.key = castNonNull(in.readString()); this.key = castNonNull(in.readString());
this.value = castNonNull(in.readString()); this.value = castNonNull(in.readString());
} }
......
/*
* 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];
}
};
}
/*
* 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;
...@@ -15,12 +15,18 @@ ...@@ -15,12 +15,18 @@
*/ */
package androidx.media3.extractor.ogg; 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.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.OpusUtil; import androidx.media3.extractor.OpusUtil;
import androidx.media3.extractor.VorbisUtil;
import com.google.common.collect.ImmutableList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
...@@ -28,26 +34,13 @@ 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. */ /** {@link StreamReader} to extract Opus data out of Ogg byte stream. */
/* package */ final class OpusReader extends StreamReader { /* package */ final class OpusReader extends StreamReader {
private static final int OPUS_CODE = 0x4f707573; private static final byte[] OPUS_ID_HEADER_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'};
private static final byte[] OPUS_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'
private boolean headerRead; };
public static boolean verifyBitstreamType(ParsableByteArray data) { public static boolean verifyBitstreamType(ParsableByteArray data) {
if (data.bytesLeft() < OPUS_SIGNATURE.length) { return peekPacketStartsWith(data, OPUS_ID_HEADER_SIGNATURE);
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;
}
} }
@Override @Override
...@@ -57,11 +50,16 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; ...@@ -57,11 +50,16 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@Override @Override
@EnsuresNonNullIf(expression = "#3.format", result = false) @EnsuresNonNullIf(expression = "#3.format", result = false)
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) { protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData)
if (!headerRead) { throws ParserException {
if (peekPacketStartsWith(packet, OPUS_ID_HEADER_SIGNATURE)) {
byte[] headerBytes = Arrays.copyOf(packet.getData(), packet.limit()); byte[] headerBytes = Arrays.copyOf(packet.getData(), packet.limit());
int channelCount = OpusUtil.getChannelCount(headerBytes); int channelCount = OpusUtil.getChannelCount(headerBytes);
List<byte[]> initializationData = OpusUtil.buildInitializationData(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 = setupData.format =
new Format.Builder() new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_OPUS) .setSampleMimeType(MimeTypes.AUDIO_OPUS)
...@@ -69,13 +67,33 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; ...@@ -69,13 +67,33 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
.setSampleRate(OpusUtil.SAMPLE_RATE) .setSampleRate(OpusUtil.SAMPLE_RATE)
.setInitializationData(initializationData) .setInitializationData(initializationData)
.build(); .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; return true;
} else { } else {
checkNotNull(setupData.format); // Has been set when the header was read. // The ID header must come at the start of the file, so the format must already be populated:
boolean headerPacket = packet.readInt() == OPUS_CODE; // https://datatracker.ietf.org/doc/html/rfc7845#section-3
packet.setPosition(0); checkStateNotNull(setupData.format);
return headerPacket; return false;
} }
} }
...@@ -114,4 +132,22 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; ...@@ -114,4 +132,22 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
} }
return (long) frames * length; 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);
}
} }
...@@ -21,11 +21,13 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull; ...@@ -21,11 +21,13 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException; import androidx.media3.common.ParserException;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.VorbisUtil; import androidx.media3.extractor.VorbisUtil;
import androidx.media3.extractor.VorbisUtil.Mode; import androidx.media3.extractor.VorbisUtil.Mode;
import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -111,6 +113,10 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; ...@@ -111,6 +113,10 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
codecInitializationData.add(idHeader.data); codecInitializationData.add(idHeader.data);
codecInitializationData.add(vorbisSetup.setupHeaderData); codecInitializationData.add(vorbisSetup.setupHeaderData);
@Nullable
Metadata metadata =
VorbisUtil.parseVorbisComments(ImmutableList.copyOf(vorbisSetup.commentHeader.comments));
setupData.format = setupData.format =
new Format.Builder() new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_VORBIS) .setSampleMimeType(MimeTypes.AUDIO_VORBIS)
...@@ -119,6 +125,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; ...@@ -119,6 +125,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
.setChannelCount(idHeader.channels) .setChannelCount(idHeader.channels)
.setSampleRate(idHeader.sampleRate) .setSampleRate(idHeader.sampleRate)
.setInitializationData(codecInitializationData) .setInitializationData(codecInitializationData)
.setMetadata(metadata)
.build(); .build();
return true; return true;
} }
......
...@@ -24,7 +24,7 @@ import androidx.media3.common.util.ParsableByteArray; ...@@ -24,7 +24,7 @@ import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.extractor.FlacMetadataReader.FlacStreamMetadataHolder; import androidx.media3.extractor.FlacMetadataReader.FlacStreamMetadataHolder;
import androidx.media3.extractor.flac.FlacConstants; import androidx.media3.extractor.flac.FlacConstants;
import androidx.media3.extractor.metadata.flac.PictureFrame; 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.FakeExtractorInput;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
......
...@@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.extractor.flac.FlacConstants; 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.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package androidx.media3.extractor.metadata.flac; package androidx.media3.extractor.metadata.vorbis;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
......
...@@ -13,6 +13,7 @@ track 0: ...@@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus sampleMimeType = audio/opus
channelCount = 2 channelCount = 2
sampleRate = 48000 sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData: initializationData:
data = length 19, hash BFE794DB data = length 19, hash BFE794DB
data = length 8, hash CA22068C data = length 8, hash CA22068C
......
...@@ -13,6 +13,7 @@ track 0: ...@@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus sampleMimeType = audio/opus
channelCount = 2 channelCount = 2
sampleRate = 48000 sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData: initializationData:
data = length 19, hash BFE794DB data = length 19, hash BFE794DB
data = length 8, hash CA22068C data = length 8, hash CA22068C
......
...@@ -13,6 +13,7 @@ track 0: ...@@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus sampleMimeType = audio/opus
channelCount = 2 channelCount = 2
sampleRate = 48000 sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData: initializationData:
data = length 19, hash BFE794DB data = length 19, hash BFE794DB
data = length 8, hash CA22068C data = length 8, hash CA22068C
......
...@@ -13,6 +13,7 @@ track 0: ...@@ -13,6 +13,7 @@ track 0:
sampleMimeType = audio/opus sampleMimeType = audio/opus
channelCount = 2 channelCount = 2
sampleRate = 48000 sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData: initializationData:
data = length 19, hash BFE794DB data = length 19, hash BFE794DB
data = length 8, hash CA22068C data = length 8, hash CA22068C
......
...@@ -10,6 +10,7 @@ track 0: ...@@ -10,6 +10,7 @@ track 0:
sampleMimeType = audio/opus sampleMimeType = audio/opus
channelCount = 2 channelCount = 2
sampleRate = 48000 sampleRate = 48000
metadata = entries=[VC: encoder=Lavf54.20.4]
initializationData: initializationData:
data = length 19, hash BFE794DB data = length 19, hash BFE794DB
data = length 8, hash CA22068C data = length 8, hash CA22068C
......
...@@ -26,7 +26,6 @@ import androidx.media3.exoplayer.ExoPlayer; ...@@ -26,7 +26,6 @@ import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.extractor.metadata.dvbsi.AppInfoTable; import androidx.media3.extractor.metadata.dvbsi.AppInfoTable;
import androidx.media3.extractor.metadata.emsg.EventMessage; import androidx.media3.extractor.metadata.emsg.EventMessage;
import androidx.media3.extractor.metadata.flac.PictureFrame; 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.IcyHeaders;
import androidx.media3.extractor.metadata.icy.IcyInfo; import androidx.media3.extractor.metadata.icy.IcyInfo;
import androidx.media3.extractor.metadata.id3.Id3Frame; import androidx.media3.extractor.metadata.id3.Id3Frame;
...@@ -35,6 +34,7 @@ import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata; ...@@ -35,6 +34,7 @@ import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata;
import androidx.media3.extractor.metadata.mp4.SlowMotionData; import androidx.media3.extractor.metadata.mp4.SlowMotionData;
import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry; import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry;
import androidx.media3.extractor.metadata.scte35.SpliceCommand; 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.CapturingRenderersFactory;
import androidx.media3.test.utils.Dumper; import androidx.media3.test.utils.Dumper;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
......
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