Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ public final class WavUtil {
/** Four character code for "ds64". */
public static final int DS64_FOURCC = 0x64733634;

/** Four character code for "LIST". */
public static final int LIST_FOURCC = 0x4c495354;

/** Four character code for "INFO". */
public static final int INFO_FOURCC = 0x494e464f;

/** Four character code for "ID3 ". */
public static final int ID3_FOURCC = 0x49443320;

/** Four character code for "id3 ". */
public static final int ID3_LOWER_FOURCC = 0x69643320;

/** Four character code for "rgad". */
public static final int RGAD_FOURCC = 0x72676164;

/** WAVE type value for integer PCM audio data. */
public static final int TYPE_PCM = 0x0001;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
private @Mp4Extractor.Flags int mp4Flags;
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
private @Mp3Extractor.Flags int mp3Flags;
private @WavExtractor.Flags int wavFlags;
private @TsExtractor.Mode int tsMode;
private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
// TODO (b/261183220): Initialize tsSubtitleFormats in constructor once shrinking bug is fixed.
Expand Down Expand Up @@ -322,6 +323,19 @@ public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor.
return this;
}

/**
* Sets flags for {@link WavExtractor} instances created by the factory.
*
* @see WavExtractor#WavExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
@CanIgnoreReturnValue
public synchronized DefaultExtractorsFactory setWavExtractorFlags(@WavExtractor.Flags int flags) {
wavFlags = flags;
return this;
}

/**
* Sets the mode for {@link TsExtractor} instances created by the factory.
*
Expand Down Expand Up @@ -581,7 +595,10 @@ private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extract
tsTimestampSearchBytes));
break;
case FileTypes.WAV:
extractors.add(new WavExtractor());
extractors.add(
new WavExtractor(
wavFlags
| (disableArtworkMetadata ? WavExtractor.FLAG_DISABLE_ARTWORK_METADATA : 0)));
break;
case FileTypes.JPEG:
extractors.add(new JpegExtractor(jpegFlags));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.extractor.mp3;
package androidx.media3.extractor.metadata;

import static java.lang.annotation.ElementType.TYPE_USE;

Expand All @@ -27,9 +27,11 @@
import java.lang.annotation.Target;
import java.util.Objects;

/** Representation of the ReplayGain data stored in a LAME Xing or Info frame. */
/**
* Representation of the ReplayGain data stored in a LAME Xing or Info frame, or a RIFF RGAD chunk.
*/
@UnstableApi
public final class Mp3InfoReplayGain implements Metadata.Entry {
public final class ReplayGainInfo implements Metadata.Entry {

/** A gain field can store one gain adjustment with name and originator metadata. */
public static final class GainField {
Expand Down Expand Up @@ -202,7 +204,7 @@ public int hashCode() {
*/
@Nullable public GainField field2;

private Mp3InfoReplayGain(float peak, @Nullable GainField field1, @Nullable GainField field2) {
private ReplayGainInfo(float peak, @Nullable GainField field1, @Nullable GainField field2) {
this.peak = peak;
this.field1 = field1;
this.field2 = field2;
Expand All @@ -214,13 +216,13 @@ private Mp3InfoReplayGain(float peak, @Nullable GainField field1, @Nullable Gain
* <p>Returns null if the representation is invalid or should be ignored.
*/
@Nullable
public static Mp3InfoReplayGain parse(float peak, int field1, int field2) {
public static ReplayGainInfo parse(float peak, int field1, int field2) {
GainField parsedField1 = GainField.parse(field1);
GainField parsedField2 = GainField.parse(field2);
if (peak <= 0 && parsedField1 == null && parsedField2 == null) {
return null;
}
return new Mp3InfoReplayGain(peak, parsedField1, parsedField2);
return new ReplayGainInfo(peak, parsedField1, parsedField2);
}

@Override
Expand All @@ -236,10 +238,10 @@ public String toString() {

@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof Mp3InfoReplayGain)) {
if (!(o instanceof ReplayGainInfo)) {
return false;
}
Mp3InfoReplayGain that = (Mp3InfoReplayGain) o;
ReplayGainInfo that = (ReplayGainInfo) o;
return Float.compare(peak, that.peak) == 0
&& Objects.equals(field1, that.field1)
&& Objects.equals(field2, that.field2);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2016 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.riff;

import static com.google.common.base.Preconditions.checkArgument;

import androidx.annotation.Nullable;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;

/** The data from one or more chunks in the RIFF INFO list with the same fourcc. */
@UnstableApi
public final class RiffInfoChunk implements Metadata.Entry {

/** The fourcc converted to a String. */
public final String id;

/** The text values of these chunks. Will always have at least one element. */
public final ImmutableList<String> values;

public RiffInfoChunk(String id, List<String> values) {
checkArgument(!values.isEmpty());

this.id = id;
this.values = ImmutableList.copyOf(values);
}

/**
* Uses the first element in {@link #values} to set the relevant field in {@link MediaMetadata}
* (as determined by {@link #id}).
*/
@Override
public void populateMediaMetadata(MediaMetadata.Builder builder) {
switch (id) {
case "IART":
builder.setArtist(values.get(0));
break;
case "ICRD":
try {
int year = Integer.parseInt(values.get(0).substring(0, 4));
int month = Integer.parseInt(values.get(0).substring(5, 7));
int day = Integer.parseInt(values.get(0).substring(8, 10));
builder.setRecordingYear(year);
builder.setRecordingMonth(month);
builder.setRecordingDay(day);
} catch (NumberFormatException | IndexOutOfBoundsException e) {
// Do nothing, invalid input.
}
break;
case "IGNR":
case "GENR":
builder.setGenre(values.get(0));
break;
case "IPRD":
case "IALB":
builder.setAlbumTitle(values.get(0));
break;
case "ICOM":
case "IMUS":
builder.setComposer(values.get(0));
break;
case "IWRI":
builder.setWriter(values.get(0));
break;
case "ISBJ":
builder.setDescription(values.get(0));
break;
case "INAM":
case "TITL":
builder.setTitle(values.get(0));
break;
case "IYER":
case "YEAR":
try {
builder.setReleaseYear(Integer.parseInt(values.get(0)));
} catch (NumberFormatException e) {
// Do nothing, invalid input.
}
break;
case "IFRM":
try {
builder.setTotalTrackCount(Integer.parseInt(values.get(0)));
} catch (NumberFormatException e) {
// Do nothing, invalid input.
}
break;
case "ITRK":
case "TRCK":
case "IPRT":
try {
builder.setTrackNumber(Integer.parseInt(values.get(0)));
} catch (NumberFormatException e) {
// Do nothing, invalid input.
}
break;
default:
break;
}
}

@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
RiffInfoChunk other = (RiffInfoChunk) obj;
return Objects.equals(id, other.id) && values.equals(other.values);
}

@Override
public int hashCode() {
int result = 17;
result = 31 * result + id.hashCode();
result = 31 * result + values.hashCode();
return result;
}

@Override
public String toString() {
return id + ": values=" + values;
}
}
Original file line number Diff line number Diff line change
@@ -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.riff;

import androidx.media3.common.util.NonNullApi;
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.MpegAudioUtil;
import androidx.media3.extractor.metadata.ReplayGainInfo;

/** Representation of a LAME Xing or Info frame. */
/* package */ final class XingFrame {
Expand All @@ -37,7 +38,7 @@
public final long dataSize;

/** ReplayGain data. Only present if this frame is an Info or the LAME variant of a Xing frame. */
@Nullable public final Mp3InfoReplayGain replayGain;
@Nullable public final ReplayGainInfo replayGain;

/**
* The number of samples to skip at the start of the stream, or {@link C#LENGTH_UNSET} if not
Expand All @@ -62,7 +63,7 @@ private XingFrame(
long frameCount,
long dataSize,
@Nullable long[] tableOfContents,
@Nullable Mp3InfoReplayGain replayGain,
@Nullable ReplayGainInfo replayGain,
int encoderDelay,
int encoderPadding) {
this.header = new MpegAudioUtil.Header(header);
Expand Down Expand Up @@ -104,7 +105,7 @@ public static XingFrame parse(MpegAudioUtil.Header mpegAudioHeader, ParsableByte
frame.skipBytes(4); // Quality indicator
}

@Nullable Mp3InfoReplayGain replayGain;
@Nullable ReplayGainInfo replayGain;
int encoderDelay;
int encoderPadding;
// Skip: version string (9), revision & VBR method (1), lowpass filter (1).
Expand All @@ -117,7 +118,7 @@ public static XingFrame parse(MpegAudioUtil.Header mpegAudioHeader, ParsableByte
float peak = frame.readFloat();
int field1 = frame.readUnsignedShort();
int field2 = frame.readUnsignedShort();
replayGain = Mp3InfoReplayGain.parse(peak, field1, field2);
replayGain = ReplayGainInfo.parse(peak, field1, field2);

frame.skipBytes(bytesToSkipAfterReplayGain);
int encoderDelayAndPadding = frame.readUnsignedInt24();
Expand Down
Loading