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:
olly 2016-03-15 02:42:11 -07:00 committed by Oliver Woodman
parent ba88091c7b
commit a1a48abe92
4 changed files with 74 additions and 28 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);