Merge pull request #9864 from OxygenCobalt:vorbis-comments
PiperOrigin-RevId: 424355325
This commit is contained in:
commit
b2152f1988
@ -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() {}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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}.
|
||||
|
@ -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>() {
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
};
|
||||
}
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user