Support anamorphic video content.
This commit is contained in:
parent
6c3ae7f175
commit
ec90eac301
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user