Commit fe7e5b81 by Andrew Lewis

Merge pull request #9864 from OxygenCobalt:vorbis-comments

PiperOrigin-RevId: 424355325
parents 651fa0db c2d0649e
Showing with 257 additions and 94 deletions
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
* Extractors: * Extractors:
* Fix inconsistency with spec in H.265 SPS nal units parsing * Fix inconsistency with spec in H.265 SPS nal units parsing
([#9719](https://github.com/google/ExoPlayer/issues/9719)). ([#9719](https://github.com/google/ExoPlayer/issues/9719)).
* Parse Vorbis Comments (including `METADATA_BLOCK_PICTURE`) in Ogg Opus
and Vorbis files.
* Text: * Text:
* Add a `MediaItem.SubtitleConfiguration#id` field which is propagated to * Add a `MediaItem.SubtitleConfiguration#id` field which is propagated to
the `Format#id` field of the subtitle track created from the the `Format#id` field of the subtitle track created from the
......
...@@ -24,10 +24,9 @@ import com.google.android.exoplayer2.metadata.flac.PictureFrame; ...@@ -24,10 +24,9 @@ import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
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;
/** /**
...@@ -168,9 +167,12 @@ public final class FlacMetadataReader { ...@@ -168,9 +167,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);
} }
...@@ -268,28 +270,5 @@ public final class FlacMetadataReader { ...@@ -268,28 +270,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,13 +15,13 @@ ...@@ -15,13 +15,13 @@
*/ */
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import static com.google.android.exoplayer2.extractor.VorbisUtil.parseVorbisComments;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.flac.PictureFrame; import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.flac.VorbisComment;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -60,8 +60,6 @@ public final class FlacStreamMetadata { ...@@ -60,8 +60,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;
...@@ -149,7 +147,7 @@ public final class FlacStreamMetadata { ...@@ -149,7 +147,7 @@ public final class FlacStreamMetadata {
bitsPerSample, bitsPerSample,
totalSamples, totalSamples,
/* seekTable= */ null, /* seekTable= */ null,
buildMetadata(vorbisComments, pictureFrames)); concatenateVorbisMetadata(vorbisComments, pictureFrames));
} }
private FlacStreamMetadata( private FlacStreamMetadata(
...@@ -274,8 +272,7 @@ public final class FlacStreamMetadata { ...@@ -274,8 +272,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,
...@@ -292,9 +289,7 @@ public final class FlacStreamMetadata { ...@@ -292,9 +289,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,
...@@ -308,6 +303,20 @@ public final class FlacStreamMetadata { ...@@ -308,6 +303,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:
...@@ -353,27 +362,4 @@ public final class FlacStreamMetadata { ...@@ -353,27 +362,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,11 +15,20 @@ ...@@ -15,11 +15,20 @@
*/ */
package com.google.android.exoplayer2.extractor; package com.google.android.exoplayer2.extractor;
import android.util.Base64;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.Metadata.Entry;
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
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. */
public final class VorbisUtil { public final class VorbisUtil {
...@@ -249,6 +258,45 @@ public final class VorbisUtil { ...@@ -249,6 +258,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}.
* *
......
...@@ -15,12 +15,18 @@ ...@@ -15,12 +15,18 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.audio.OpusUtil;
import com.google.android.exoplayer2.extractor.VorbisUtil;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
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);
}
} }
...@@ -24,8 +24,10 @@ import com.google.android.exoplayer2.Format; ...@@ -24,8 +24,10 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.VorbisUtil; import com.google.android.exoplayer2.extractor.VorbisUtil;
import com.google.android.exoplayer2.extractor.VorbisUtil.Mode; import com.google.android.exoplayer2.extractor.VorbisUtil.Mode;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
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;
} }
......
...@@ -22,9 +22,11 @@ import android.os.Parcelable; ...@@ -22,9 +22,11 @@ import android.os.Parcelable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.ParsableByteArray;
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. */
public final class PictureFrame implements Metadata.Entry { public final class PictureFrame implements Metadata.Entry {
/** The type of the picture. */ /** The type of the picture. */
...@@ -134,6 +136,35 @@ public final class PictureFrame implements Metadata.Entry { ...@@ -134,6 +136,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>() {
......
...@@ -23,8 +23,9 @@ import androidx.annotation.Nullable; ...@@ -23,8 +23,9 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
/** A vorbis comment. */ /** @deprecated Use {@link com.google.android.exoplayer2.metadata.vorbis.VorbisComment} instead. */
public final class VorbisComment implements Metadata.Entry { @Deprecated
public class VorbisComment implements Metadata.Entry {
/** The key. */ /** The key. */
public final String key; public final String key;
...@@ -41,7 +42,7 @@ public final class VorbisComment implements Metadata.Entry { ...@@ -41,7 +42,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 com.google.android.exoplayer2.metadata.vorbis;
import android.os.Parcel;
/** A vorbis comment, extracted from a FLAC or Ogg file. */
@SuppressWarnings("deprecation") // Extending deprecated type for backwards compatibility.
public final class VorbisComment extends com.google.android.exoplayer2.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 com.google.android.exoplayer2.metadata.vorbis;
import com.google.android.exoplayer2.util.NonNullApi;
...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.extractor.FlacMetadataReader.FlacStreamMeta ...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.extractor.FlacMetadataReader.FlacStreamMeta
import com.google.android.exoplayer2.extractor.flac.FlacConstants; import com.google.android.exoplayer2.extractor.flac.FlacConstants;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.flac.PictureFrame; import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.flac.VorbisComment; import com.google.android.exoplayer2.metadata.vorbis.VorbisComment;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
......
...@@ -21,7 +21,7 @@ import androidx.test.core.app.ApplicationProvider; ...@@ -21,7 +21,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.flac.FlacConstants; import com.google.android.exoplayer2.extractor.flac.FlacConstants;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.flac.VorbisComment; import com.google.android.exoplayer2.metadata.vorbis.VorbisComment;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
......
...@@ -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 com.google.android.exoplayer2.metadata.flac; package com.google.android.exoplayer2.metadata.vorbis;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.metadata.Metadata; ...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.dvbsi.AppInfoTable; import com.google.android.exoplayer2.metadata.dvbsi.AppInfoTable;
import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.flac.PictureFrame; import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.flac.VorbisComment;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders; import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.metadata.icy.IcyInfo; import com.google.android.exoplayer2.metadata.icy.IcyInfo;
import com.google.android.exoplayer2.metadata.id3.Id3Frame; import com.google.android.exoplayer2.metadata.id3.Id3Frame;
...@@ -32,6 +31,7 @@ import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata; ...@@ -32,6 +31,7 @@ import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData;
import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry; import com.google.android.exoplayer2.metadata.mp4.SmtaMetadataEntry;
import com.google.android.exoplayer2.metadata.scte35.SpliceCommand; import com.google.android.exoplayer2.metadata.scte35.SpliceCommand;
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment;
import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory;
import com.google.android.exoplayer2.testutil.Dumper; import com.google.android.exoplayer2.testutil.Dumper;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
......
...@@ -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
......
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