Support anamorphic video content.

This commit is contained in:
Oliver Woodman 2014-09-11 16:34:35 +01:00
parent 6c3ae7f175
commit ec90eac301
9 changed files with 95 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]>();
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<byte[]> initializationData) {
private MediaFormat(String mimeType, int maxInputSize, int width, int height,
float pixelWidthHeightRatio, int channelCount, int sampleRate,
List<byte[]> 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.<byte[]>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;
}
}

View File

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

View File

@ -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<byte[]> 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;