diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java index 722e21c45e..84ea1b0f3e 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java @@ -73,8 +73,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener } @Override - public void onVideoSizeChanged(int width, int height) { - Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]"); + public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) { + Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + pixelWidthHeightRatio + "]"); } // DemoPlayer.InfoListener diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java index 366118877f..48ed2f5ded 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java @@ -260,9 +260,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba } @Override - public void onVideoSizeChanged(int width, int height) { + public void onVideoSizeChanged(int width, int height, float pixelWidthAspectRatio) { shutterView.setVisibility(View.GONE); - surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height); + surfaceView.setVideoWidthHeightRatio( + height == 0 ? 1 : (width * pixelWidthAspectRatio) / height); } // User controls diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java index 211d08dcd7..914c9f9798 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java @@ -93,7 +93,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi public interface Listener { void onStateChanged(boolean playWhenReady, int playbackState); void onError(Exception e); - void onVideoSizeChanged(int width, int height); + void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio); } /** @@ -377,9 +377,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi } @Override - public void onVideoSizeChanged(int width, int height) { + public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) { for (Listener listener : listeners) { - listener.onVideoSizeChanged(width, height); + listener.onVideoSizeChanged(width, height, pixelWidthHeightRatio); } } diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java index 26f8c739a4..73d2605c94 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java @@ -231,8 +231,9 @@ public class SimplePlayerActivity extends Activity implements SurfaceHolder.Call // MediaCodecVideoTrackRenderer.Listener @Override - public void onVideoSizeChanged(int width, int height) { - surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height); + public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) { + surfaceView.setVideoWidthHeightRatio( + height == 0 ? 1 : (pixelWidthHeightRatio * width) / height); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index a23f5edf49..4124a22c27 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -79,18 +79,19 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } /** - * Value of {@link #sourceState} when the source is not ready. + * Value returned by {@link #getSourceState()} when the source is not ready. */ protected static final int SOURCE_STATE_NOT_READY = 0; /** - * Value of {@link #sourceState} when the source is ready and we're able to read from it. + * Value returned by {@link #getSourceState()} when the source is ready and we're able to read + * from it. */ protected static final int SOURCE_STATE_READY = 1; /** - * Value of {@link #sourceState} when the source is ready but we might not be able to read from - * it. We transition to this state when an attempt to read a sample fails despite the source - * reporting that samples are available. This can occur when the next sample to be provided by - * the source is for another renderer. + * Value returned by {@link #getSourceState()} when the source is ready but we might not be able + * to read from it. We transition to this state when an attempt to read a sample fails despite the + * source reporting that samples are available. This can occur when the next sample to be provided + * by the source is for another renderer. */ protected static final int SOURCE_STATE_READY_READ_MAY_FAIL = 2; @@ -620,7 +621,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { * @param formatHolder Holds the new format. * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. */ - private void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException { + protected void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException { MediaFormat oldFormat = format; format = formatHolder.format; drmInitData = formatHolder.drmInitData; diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 08661ad557..22d529514e 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -58,8 +58,11 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { * * @param width The video width in pixels. * @param height The video height in pixels. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case + * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. */ - void onVideoSizeChanged(int width, int height); + void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio); /** * Invoked when a frame is rendered to a surface for the first time following that surface @@ -98,8 +101,10 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { private int currentWidth; private int currentHeight; + private float currentPixelWidthHeightRatio; private int lastReportedWidth; private int lastReportedHeight; + private float lastReportedPixelWidthHeightRatio; /** * @param source The upstream source from which the renderer obtains samples. @@ -208,8 +213,10 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { joiningDeadlineUs = -1; currentWidth = -1; currentHeight = -1; + currentPixelWidthHeightRatio = -1; lastReportedWidth = -1; lastReportedHeight = -1; + lastReportedPixelWidthHeightRatio = -1; } @Override @@ -272,8 +279,10 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { super.onDisabled(); currentWidth = -1; currentHeight = -1; + currentPixelWidthHeightRatio = -1; lastReportedWidth = -1; lastReportedHeight = -1; + lastReportedPixelWidthHeightRatio = -1; } @Override @@ -315,6 +324,14 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { codec.setVideoScalingMode(videoScalingMode); } + @Override + protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { + super.onInputFormatChanged(holder); + // TODO: Ideally this would be read in onOutputFormatChanged, but there doesn't seem + // to be a way to pass a custom key/value pair value through to the output format. + currentPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio; + } + @Override protected void onOutputFormatChanged(android.media.MediaFormat format) { boolean hasCrop = format.containsKey(KEY_CROP_RIGHT) && format.containsKey(KEY_CROP_LEFT) @@ -394,10 +411,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { } private void renderOutputBuffer(MediaCodec codec, int bufferIndex) { - if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight) { + if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight + || lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { lastReportedWidth = currentWidth; lastReportedHeight = currentHeight; - notifyVideoSizeChanged(currentWidth, currentHeight); + lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + notifyVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio); } TraceUtil.beginSection("renderVideoBuffer"); codec.releaseOutputBuffer(bufferIndex, true); @@ -409,12 +428,13 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { } } - private void notifyVideoSizeChanged(final int width, final int height) { + private void notifyVideoSizeChanged(final int width, final int height, + final float pixelWidthHeightRatio) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { @Override public void run() { - eventListener.onVideoSizeChanged(width, height); + eventListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio); } }); } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java index d703a72a84..24af7ba1a0 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java @@ -31,6 +31,9 @@ import java.util.List; */ public class MediaFormat { + private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO = + "com.google.android.videos.pixelWidthHeightRatio"; + public static final int NO_VALUE = -1; public final String mimeType; @@ -38,6 +41,7 @@ public class MediaFormat { public final int width; public final int height; + public final float pixelWidthHeightRatio; public final int channelCount; public final int sampleRate; @@ -59,14 +63,19 @@ public class MediaFormat { public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width, int height, List initializationData) { - return new MediaFormat(mimeType, maxInputSize, width, height, NO_VALUE, NO_VALUE, - initializationData); + return createVideoFormat(mimeType, maxInputSize, width, height, 1, initializationData); + } + + public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width, + int height, float pixelWidthHeightRatio, List initializationData) { + return new MediaFormat(mimeType, maxInputSize, width, height, pixelWidthHeightRatio, NO_VALUE, + NO_VALUE, initializationData); } public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount, int sampleRate, List initializationData) { - return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, channelCount, sampleRate, - initializationData); + return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, channelCount, + sampleRate, initializationData); } @TargetApi(16) @@ -78,6 +87,7 @@ public class MediaFormat { height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); + pixelWidthHeightRatio = getOptionalFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); initializationData = new ArrayList(); for (int i = 0; format.containsKey("csd-" + i); i++) { ByteBuffer buffer = format.getByteBuffer("csd-" + i); @@ -90,12 +100,14 @@ public class MediaFormat { maxHeight = NO_VALUE; } - private MediaFormat(String mimeType, int maxInputSize, int width, int height, int channelCount, - int sampleRate, List initializationData) { + private MediaFormat(String mimeType, int maxInputSize, int width, int height, + float pixelWidthHeightRatio, int channelCount, int sampleRate, + List initializationData) { this.mimeType = mimeType; this.maxInputSize = maxInputSize; this.width = width; this.height = height; + this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.channelCount = channelCount; this.sampleRate = sampleRate; this.initializationData = initializationData == null ? Collections.emptyList() @@ -128,6 +140,7 @@ public class MediaFormat { result = 31 * result + maxInputSize; result = 31 * result + width; result = 31 * result + height; + result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio); result = 31 * result + maxWidth; result = 31 * result + maxHeight; result = 31 * result + channelCount; @@ -163,6 +176,7 @@ public class MediaFormat { private boolean equalsInternal(MediaFormat other, boolean ignoreMaxDimensions) { if (maxInputSize != other.maxInputSize || width != other.width || height != other.height + || pixelWidthHeightRatio != other.pixelWidthHeightRatio || (!ignoreMaxDimensions && (maxWidth != other.maxWidth || maxHeight != other.maxHeight)) || channelCount != other.channelCount || sampleRate != other.sampleRate || !Util.areEqual(mimeType, other.mimeType) @@ -179,8 +193,9 @@ public class MediaFormat { @Override public String toString() { - return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " + - channelCount + ", " + sampleRate + ", " + maxWidth + ", " + maxHeight + ")"; + return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " + + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + maxWidth + ", " + + maxHeight + ")"; } /** @@ -196,6 +211,7 @@ public class MediaFormat { maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); + maybeSetFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio); for (int i = 0; i < initializationData.size(); i++) { format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); } @@ -221,9 +237,21 @@ public class MediaFormat { } @TargetApi(16) - private static final int getOptionalIntegerV16(android.media.MediaFormat format, - String key) { + private static final void maybeSetFloatV16(android.media.MediaFormat format, String key, + float value) { + if (value != NO_VALUE) { + format.setFloat(key, value); + } + } + + @TargetApi(16) + private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) { return format.containsKey(key) ? format.getInteger(key) : NO_VALUE; } + @TargetApi(16) + private static final float getOptionalFloatV16(android.media.MediaFormat format, String key) { + return format.containsKey(key) ? format.getFloat(key) : NO_VALUE; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java index fbdccc2d67..32eb1937ae 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/mp4/Atom.java @@ -53,6 +53,7 @@ import java.util.ArrayList; public static final int TYPE_saiz = 0x7361697A; public static final int TYPE_uuid = 0x75756964; public static final int TYPE_senc = 0x73656E63; + public static final int TYPE_pasp = 0x70617370; public final int type; diff --git a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java index 93c5053b6d..8ede9e93b2 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java @@ -106,6 +106,7 @@ public final class FragmentedMp4Extractor implements Extractor { parsedAtoms.add(Atom.TYPE_saiz); parsedAtoms.add(Atom.TYPE_uuid); parsedAtoms.add(Atom.TYPE_senc); + parsedAtoms.add(Atom.TYPE_pasp); PARSED_ATOMS = Collections.unmodifiableSet(parsedAtoms); } @@ -529,6 +530,7 @@ public final class FragmentedMp4Extractor implements Extractor { parent.skip(24); int width = parent.readUnsignedShort(); int height = parent.readUnsignedShort(); + float pixelWidthHeightRatio = 1; parent.skip(50); List initializationData = null; @@ -543,12 +545,14 @@ public final class FragmentedMp4Extractor implements Extractor { initializationData = parseAvcCFromParent(parent, childStartPosition); } else if (childAtomType == Atom.TYPE_sinf) { trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize); + } else if (childAtomType == Atom.TYPE_pasp) { + pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); } childPosition += childAtomSize; } MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, - width, height, initializationData); + width, height, pixelWidthHeightRatio, initializationData); return Pair.create(format, trackEncryptionBox); } @@ -643,6 +647,13 @@ public final class FragmentedMp4Extractor implements Extractor { return trackEncryptionBox; } + private static float parsePaspFromParent(ParsableByteArray parent, int position) { + parent.setPosition(position + ATOM_HEADER_SIZE); + int hSpacing = parent.readUnsignedIntToInt(); + int vSpacing = parent.readUnsignedIntToInt(); + return (float) hSpacing / vSpacing; + } + private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, int size) { int childPosition = position + ATOM_HEADER_SIZE;