Apply gapless playback metadata for MP3/MP4 playback using MediaCodec.
Issue: #497 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117219944
This commit is contained in:
parent
ba88091c7b
commit
a1a48abe92
@ -126,6 +126,14 @@ public final class Format {
|
||||
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
|
||||
*/
|
||||
public final int sampleRate;
|
||||
/**
|
||||
* The number of samples to trim from the start of the decoded audio stream.
|
||||
*/
|
||||
public final int encoderDelay;
|
||||
/**
|
||||
* The number of samples to trim from the end of the decoded audio stream.
|
||||
*/
|
||||
public final int encoderPadding;
|
||||
|
||||
// Text specific.
|
||||
|
||||
@ -154,8 +162,8 @@ public final class Format {
|
||||
String sampleMimeType, int bitrate, int width, int height, float frameRate,
|
||||
List<byte[]> initializationData) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height,
|
||||
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
}
|
||||
|
||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
@ -168,8 +176,8 @@ public final class Format {
|
||||
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
|
||||
int rotationDegrees, float pixelWidthHeightRatio) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
|
||||
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
}
|
||||
|
||||
// Audio.
|
||||
@ -178,16 +186,23 @@ public final class Format {
|
||||
String sampleMimeType, int bitrate, int channelCount, int sampleRate,
|
||||
List<byte[]> initializationData, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, language,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
|
||||
String language) {
|
||||
return createAudioSampleFormat(id, sampleMimeType, bitrate, maxInputSize, channelCount,
|
||||
sampleRate, NO_VALUE, NO_VALUE, initializationData, language);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int channelCount, int sampleRate, int encoderDelay, int encoderPadding,
|
||||
List<byte[]> initializationData, String language) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
NO_VALUE, NO_VALUE, channelCount, sampleRate, encoderDelay, encoderPadding, language,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
}
|
||||
|
||||
// Text.
|
||||
@ -195,8 +210,8 @@ public final class Format {
|
||||
public static Format createTextContainerFormat(String id, String containerMimeType,
|
||||
String sampleMimeType, int bitrate, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, OFFSET_SAMPLE_RELATIVE, null,
|
||||
false);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
|
||||
OFFSET_SAMPLE_RELATIVE, null, false);
|
||||
}
|
||||
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
@ -207,7 +222,8 @@ public final class Format {
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
String language, long subsampleOffsetUs) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
|
||||
subsampleOffsetUs, null, false);
|
||||
}
|
||||
|
||||
// Generic.
|
||||
@ -215,19 +231,21 @@ public final class Format {
|
||||
public static Format createContainerFormat(String id, String containerMimeType,
|
||||
String sampleMimeType, int bitrate) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null,
|
||||
false);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, null, false);
|
||||
}
|
||||
|
||||
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||
null, false);
|
||||
}
|
||||
|
||||
/* package */ Format(String id, String containerMimeType, String sampleMimeType,
|
||||
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
||||
float pixelWidthHeightRatio, int channelCount, int sampleRate, String language,
|
||||
long subsampleOffsetUs, List<byte[]> initializationData, boolean requiresSecureDecryption) {
|
||||
float pixelWidthHeightRatio, int channelCount, int sampleRate, int encoderDelay,
|
||||
int encoderPadding, String language, long subsampleOffsetUs, List<byte[]> initializationData,
|
||||
boolean requiresSecureDecryption) {
|
||||
this.id = id;
|
||||
this.containerMimeType = containerMimeType;
|
||||
this.sampleMimeType = sampleMimeType;
|
||||
@ -240,6 +258,8 @@ public final class Format {
|
||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||
this.channelCount = channelCount;
|
||||
this.sampleRate = sampleRate;
|
||||
this.encoderDelay = encoderDelay;
|
||||
this.encoderPadding = encoderPadding;
|
||||
this.language = language;
|
||||
this.subsampleOffsetUs = subsampleOffsetUs;
|
||||
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
||||
@ -250,20 +270,30 @@ public final class Format {
|
||||
public Format copyWithMaxInputSize(int maxInputSize) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
|
||||
String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,6 +313,8 @@ public final class Format {
|
||||
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
|
||||
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
|
||||
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
|
||||
maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
|
||||
maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
|
||||
for (int i = 0; i < initializationData.size(); i++) {
|
||||
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
|
||||
}
|
||||
@ -322,6 +354,8 @@ public final class Format {
|
||||
result = 31 * result + height;
|
||||
result = 31 * result + channelCount;
|
||||
result = 31 * result + sampleRate;
|
||||
result = 31 * result + encoderDelay;
|
||||
result = 31 * result + encoderPadding;
|
||||
result = 31 * result + (language == null ? 0 : language.hashCode());
|
||||
hashCode = result;
|
||||
}
|
||||
@ -343,6 +377,7 @@ public final class Format {
|
||||
|| rotationDegrees != other.rotationDegrees
|
||||
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|
||||
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|
||||
|| encoderDelay != other.encoderDelay || encoderPadding != other.encoderPadding
|
||||
|| subsampleOffsetUs != other.subsampleOffsetUs
|
||||
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|
||||
|| !Util.areEqual(containerMimeType, other.containerMimeType)
|
||||
|
@ -312,6 +312,8 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
int rotationDegrees = getOptionalIntegerV16(mediaFormat, "rotation-degrees");
|
||||
int channelCount = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_CHANNEL_COUNT);
|
||||
int sampleRate = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_SAMPLE_RATE);
|
||||
int encoderDelay = getOptionalIntegerV16(mediaFormat, "encoder-delay");
|
||||
int encoderPadding = getOptionalIntegerV16(mediaFormat, "encoder-padding");
|
||||
ArrayList<byte[]> initializationData = new ArrayList<>();
|
||||
for (int i = 0; mediaFormat.containsKey("csd-" + i); i++) {
|
||||
ByteBuffer buffer = mediaFormat.getByteBuffer("csd-" + i);
|
||||
@ -322,7 +324,8 @@ public final class FrameworkSampleSource implements SampleSource {
|
||||
}
|
||||
Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE,
|
||||
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
|
||||
sampleRate, language, Format.OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
sampleRate, encoderDelay, encoderPadding, language, Format.OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
format.setFrameworkMediaFormatV16(mediaFormat);
|
||||
return format;
|
||||
}
|
||||
|
@ -119,9 +119,11 @@ public final class Mp3Extractor implements Extractor {
|
||||
if (seeker == null) {
|
||||
setupSeeker(input);
|
||||
extractorOutput.seekMap(seeker);
|
||||
int encoderDelay = gaplessInfo != null ? gaplessInfo.encoderDelay : Format.NO_VALUE;
|
||||
int encoderPadding = gaplessInfo != null ? gaplessInfo.encoderPadding : Format.NO_VALUE;
|
||||
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
|
||||
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
||||
synchronizedHeader.sampleRate, null, null));
|
||||
synchronizedHeader.sampleRate, encoderDelay, encoderPadding, null, null));
|
||||
}
|
||||
return readSample(input);
|
||||
}
|
||||
|
@ -16,9 +16,11 @@
|
||||
package com.google.android.exoplayer.extractor.mp4;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer.extractor.GaplessInfo;
|
||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||
@ -282,12 +284,12 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
long durationUs = C.UNKNOWN_TIME_US;
|
||||
List<Mp4Track> tracks = new ArrayList<>();
|
||||
long earliestSampleOffset = Long.MAX_VALUE;
|
||||
// TODO: Apply gapless information.
|
||||
// GaplessInfo gaplessInfo = null;
|
||||
// Atom.ContainerAtom udta = moov.getContainerAtomOfType(Atom.TYPE_udta);
|
||||
// if (udta != null) {
|
||||
// gaplessInfo = AtomParsers.parseUdta(udta);
|
||||
// }
|
||||
GaplessInfo gaplessInfo = null;
|
||||
Atom.ContainerAtom udta = moov.getContainerAtomOfType(Atom.TYPE_udta);
|
||||
if (udta != null) {
|
||||
gaplessInfo = AtomParsers.parseUdta(udta);
|
||||
}
|
||||
|
||||
for (int i = 0; i < moov.containerChildren.size(); i++) {
|
||||
Atom.ContainerAtom atom = moov.containerChildren.get(i);
|
||||
if (atom.type != Atom.TYPE_trak) {
|
||||
@ -311,7 +313,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
// Each sample has up to three bytes of overhead for the start code that replaces its length.
|
||||
// Allow ten source samples per output sample, like the platform extractor.
|
||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||
mp4Track.trackOutput.format(track.format.copyWithMaxInputSize(maxInputSize));
|
||||
Format format = track.format.copyWithMaxInputSize(maxInputSize);
|
||||
if (gaplessInfo != null) {
|
||||
format = format.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding);
|
||||
}
|
||||
mp4Track.trackOutput.format(format);
|
||||
|
||||
durationUs = Math.max(durationUs, track.durationUs);
|
||||
tracks.add(mp4Track);
|
||||
|
Loading…
x
Reference in New Issue
Block a user