parent
8db1331021
commit
d3995eaa7a
@ -76,8 +76,10 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
|
||||
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + pixelWidthHeightRatio + "]");
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {
|
||||
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + unappliedRotationDegrees
|
||||
+ ", " + pixelWidthHeightRatio + "]");
|
||||
}
|
||||
|
||||
// DemoPlayer.InfoListener
|
||||
|
@ -341,7 +341,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, float pixelWidthAspectRatio) {
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthAspectRatio) {
|
||||
shutterView.setVisibility(View.GONE);
|
||||
videoFrame.setAspectRatio(
|
||||
height == 0 ? 1 : (width * pixelWidthAspectRatio) / height);
|
||||
|
@ -89,7 +89,8 @@ 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, float pixelWidthHeightRatio);
|
||||
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -449,9 +450,10 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.onVideoSizeChanged(width, height, pixelWidthHeightRatio);
|
||||
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
|
||||
public void testMaxVideoDimensions() {
|
||||
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
|
||||
null, null, null);
|
||||
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, null);
|
||||
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, 1, null);
|
||||
format = chunkSource.getWithMaxVideoDimensions(format);
|
||||
|
||||
assertEquals(WIDE_WIDTH, format.maxWidth);
|
||||
@ -121,7 +121,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
|
||||
Representation.newInstance(0, 0, null, 0, WIDE_VIDEO, segmentBase2);
|
||||
|
||||
DashChunkSource chunkSource = new DashChunkSource(null, null, representation1, representation2);
|
||||
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, null);
|
||||
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 1, 1, 1, 1, 1, null);
|
||||
format = chunkSource.getWithMaxVideoDimensions(format);
|
||||
|
||||
assertEquals(WIDE_WIDTH, format.maxWidth);
|
||||
|
@ -77,8 +77,9 @@ public final class Mp4ExtractorTest extends TestCase {
|
||||
+ "000000000000000000000000000003");
|
||||
|
||||
/** String of hexadecimal bytes containing a tkhd payload with an unknown duration. */
|
||||
private static final byte[] TKHD_PAYLOAD =
|
||||
getByteArray("0000000000000000000000000000000000000000FFFFFFFF");
|
||||
private static final byte[] TKHD_PAYLOAD = getByteArray(
|
||||
"00000007D1F0C7BFD1F0C7BF0000000000000000FFFFFFFF00000000000000000000000000000000000100"
|
||||
+ "0000000000000000000000000000010000000000000000000000000000400000000780000004380000");
|
||||
|
||||
/** Video frame timestamps in time units. */
|
||||
private static final int[] SAMPLE_TIMESTAMPS = {0, 2, 3, 5, 6, 7};
|
||||
@ -87,7 +88,7 @@ public final class Mp4ExtractorTest extends TestCase {
|
||||
/** Indices of key-frames. */
|
||||
private static final boolean[] SAMPLE_IS_SYNC = {true, false, false, false, true, true};
|
||||
/** Indices of video frame chunk offsets. */
|
||||
private static final int[] CHUNK_OFFSETS = {1080, 2000, 3000, 4000};
|
||||
private static final int[] CHUNK_OFFSETS = {1200, 2120, 3120, 4120};
|
||||
/** Numbers of video frames in each chunk. */
|
||||
private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1};
|
||||
/** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */
|
||||
@ -399,7 +400,7 @@ public final class Mp4ExtractorTest extends TestCase {
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco())))))),
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1048 : 1038, !mp4vFormat)));
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1168 : 1158, !mp4vFormat)));
|
||||
}
|
||||
|
||||
/** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */
|
||||
@ -435,7 +436,7 @@ public final class Mp4ExtractorTest extends TestCase {
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco())))))),
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 992 : 982, !mp4vFormat)));
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1112 : 1102, !mp4vFormat)));
|
||||
}
|
||||
|
||||
private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) {
|
||||
|
@ -302,6 +302,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
|
||||
int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
|
||||
int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
|
||||
int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
|
||||
int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees");
|
||||
int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
|
||||
int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
|
||||
ArrayList<byte[]> initializationData = new ArrayList<>();
|
||||
@ -314,9 +315,9 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
|
||||
}
|
||||
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
|
||||
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, MediaFormat.NO_VALUE,
|
||||
channelCount, sampleRate, language, initializationData, MediaFormat.NO_VALUE,
|
||||
MediaFormat.NO_VALUE);
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, rotationDegrees,
|
||||
MediaFormat.NO_VALUE, channelCount, sampleRate, language, initializationData,
|
||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
|
@ -26,6 +26,7 @@ import android.media.MediaCrypto;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@ -59,11 +60,19 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
||||
*
|
||||
* @param width The video width in pixels.
|
||||
* @param height The video height in pixels.
|
||||
* @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise
|
||||
* rotation in degrees that the application should apply for the video for it to be rendered
|
||||
* in the correct orientation. This value will always be zero on API levels 21 and above,
|
||||
* since the renderer will apply all necessary rotations internally. On earlier API levels
|
||||
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
|
||||
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
|
||||
* rotated videos can safely ignore this parameter.
|
||||
* @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, float pixelWidthHeightRatio);
|
||||
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio);
|
||||
|
||||
/**
|
||||
* Invoked when a frame is rendered to a surface for the first time following that surface
|
||||
@ -129,12 +138,15 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
||||
private long droppedFrameAccumulationStartTimeMs;
|
||||
private int droppedFrameCount;
|
||||
|
||||
private int pendingRotationDegrees;
|
||||
private float pendingPixelWidthHeightRatio;
|
||||
private int currentWidth;
|
||||
private int currentHeight;
|
||||
private int currentUnappliedRotationDegrees;
|
||||
private float currentPixelWidthHeightRatio;
|
||||
private float pendingPixelWidthHeightRatio;
|
||||
private int lastReportedWidth;
|
||||
private int lastReportedHeight;
|
||||
private int lastReportedUnappliedRotationDegrees;
|
||||
private float lastReportedPixelWidthHeightRatio;
|
||||
|
||||
/**
|
||||
@ -374,6 +386,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
||||
super.onInputFormatChanged(holder);
|
||||
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1
|
||||
: holder.format.pixelWidthHeightRatio;
|
||||
pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0
|
||||
: holder.format.rotationDegrees;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -395,6 +409,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
||||
? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
|
||||
: outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT);
|
||||
currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio;
|
||||
if (Util.SDK_INT >= 21) {
|
||||
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
|
||||
// Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need
|
||||
// to flip the width, height and pixel aspect ratio to reflect the rotation that was applied.
|
||||
if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) {
|
||||
int rotatedHeight = currentWidth;
|
||||
currentWidth = currentHeight;
|
||||
currentHeight = rotatedHeight;
|
||||
currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio;
|
||||
}
|
||||
} else {
|
||||
// On API level 20 and below the decoder does not apply the rotation.
|
||||
currentUnappliedRotationDegrees = pendingRotationDegrees;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -520,22 +548,26 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
||||
private void maybeNotifyVideoSizeChanged() {
|
||||
if (eventHandler == null || eventListener == null
|
||||
|| (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight
|
||||
&& lastReportedUnappliedRotationDegrees == currentUnappliedRotationDegrees
|
||||
&& lastReportedPixelWidthHeightRatio == currentPixelWidthHeightRatio)) {
|
||||
return;
|
||||
}
|
||||
// Make final copies to ensure the runnable reports the correct values.
|
||||
final int currentWidth = this.currentWidth;
|
||||
final int currentHeight = this.currentHeight;
|
||||
final int currentUnappliedRotationDegrees = this.currentUnappliedRotationDegrees;
|
||||
final float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio;
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio);
|
||||
eventListener.onVideoSizeChanged(currentWidth, currentHeight,
|
||||
currentUnappliedRotationDegrees, currentPixelWidthHeightRatio);
|
||||
}
|
||||
});
|
||||
// Update the last reported values.
|
||||
lastReportedWidth = currentWidth;
|
||||
lastReportedHeight = currentHeight;
|
||||
lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees;
|
||||
lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ public final class MediaFormat {
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
public final int rotationDegrees;
|
||||
public final float pixelWidthHeightRatio;
|
||||
|
||||
public final int channelCount;
|
||||
@ -64,19 +65,21 @@ public final class MediaFormat {
|
||||
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width,
|
||||
int height, List<byte[]> initializationData) {
|
||||
return createVideoFormat(
|
||||
mimeType, maxInputSize, C.UNKNOWN_TIME_US, width, height, initializationData);
|
||||
mimeType, maxInputSize, C.UNKNOWN_TIME_US, width, height, NO_VALUE, initializationData);
|
||||
}
|
||||
|
||||
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, long durationUs,
|
||||
int width, int height, List<byte[]> initializationData) {
|
||||
int width, int height, int rotationDegrees, List<byte[]> initializationData) {
|
||||
return createVideoFormat(
|
||||
mimeType, maxInputSize, durationUs, width, height, 1, initializationData);
|
||||
mimeType, maxInputSize, durationUs, width, height, rotationDegrees, NO_VALUE,
|
||||
initializationData);
|
||||
}
|
||||
|
||||
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, long durationUs,
|
||||
int width, int height, float pixelWidthHeightRatio, List<byte[]> initializationData) {
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, pixelWidthHeightRatio,
|
||||
NO_VALUE, NO_VALUE, null, initializationData, NO_VALUE, NO_VALUE);
|
||||
int width, int height, int rotationDegrees, float pixelWidthHeightRatio,
|
||||
List<byte[]> initializationData) {
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, rotationDegrees,
|
||||
pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, initializationData, NO_VALUE, NO_VALUE);
|
||||
}
|
||||
|
||||
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
|
||||
@ -88,7 +91,7 @@ public final class MediaFormat {
|
||||
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, long durationUs,
|
||||
int channelCount, int sampleRate, List<byte[]> initializationData) {
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
channelCount, sampleRate, null, initializationData, NO_VALUE, NO_VALUE);
|
||||
NO_VALUE, channelCount, sampleRate, null, initializationData, NO_VALUE, NO_VALUE);
|
||||
}
|
||||
|
||||
public static MediaFormat createTextFormat(String mimeType, String language) {
|
||||
@ -97,7 +100,7 @@ public final class MediaFormat {
|
||||
|
||||
public static MediaFormat createTextFormat(String mimeType, String language, long durationUs) {
|
||||
return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, language, null, NO_VALUE, NO_VALUE);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, language, null, NO_VALUE, NO_VALUE);
|
||||
}
|
||||
|
||||
public static MediaFormat createFormatForMimeType(String mimeType) {
|
||||
@ -106,17 +109,19 @@ public final class MediaFormat {
|
||||
|
||||
public static MediaFormat createFormatForMimeType(String mimeType, long durationUs) {
|
||||
return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, null, null, NO_VALUE, NO_VALUE);
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, null, null, NO_VALUE, NO_VALUE);
|
||||
}
|
||||
|
||||
/* package */ MediaFormat(String mimeType, int maxInputSize, long durationUs, int width,
|
||||
int height, float pixelWidthHeightRatio, int channelCount, int sampleRate, String language,
|
||||
List<byte[]> initializationData, int maxWidth, int maxHeight) {
|
||||
int height, int rotationDegrees, float pixelWidthHeightRatio, int channelCount,
|
||||
int sampleRate, String language, List<byte[]> initializationData, int maxWidth,
|
||||
int maxHeight) {
|
||||
this.mimeType = mimeType;
|
||||
this.maxInputSize = maxInputSize;
|
||||
this.durationUs = durationUs;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.rotationDegrees = rotationDegrees;
|
||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||
this.channelCount = channelCount;
|
||||
this.sampleRate = sampleRate;
|
||||
@ -128,8 +133,9 @@ public final class MediaFormat {
|
||||
}
|
||||
|
||||
public MediaFormat copyWithMaxVideoDimension(int maxWidth, int maxHeight) {
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, pixelWidthHeightRatio,
|
||||
channelCount, sampleRate, language, initializationData, maxWidth, maxHeight);
|
||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, rotationDegrees,
|
||||
pixelWidthHeightRatio, channelCount, sampleRate, language, initializationData, maxWidth,
|
||||
maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,6 +151,7 @@ public final class MediaFormat {
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width);
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
|
||||
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
|
||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
|
||||
@ -163,8 +170,8 @@ public final class MediaFormat {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", "
|
||||
+ pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + language + ", "
|
||||
+ durationUs + ", " + maxWidth + ", " + maxHeight + ")";
|
||||
+ rotationDegrees + ", " + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate
|
||||
+ ", " + language + ", " + durationUs + ", " + maxWidth + ", " + maxHeight + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -175,6 +182,7 @@ public final class MediaFormat {
|
||||
result = 31 * result + maxInputSize;
|
||||
result = 31 * result + width;
|
||||
result = 31 * result + height;
|
||||
result = 31 * result + rotationDegrees;
|
||||
result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
|
||||
result = 31 * result + (int) durationUs;
|
||||
result = 31 * result + maxWidth;
|
||||
@ -213,6 +221,7 @@ public final class MediaFormat {
|
||||
|
||||
private boolean equalsInternal(MediaFormat other, boolean ignoreMaxDimensions) {
|
||||
if (maxInputSize != other.maxInputSize || width != other.width || height != other.height
|
||||
|| rotationDegrees != other.rotationDegrees
|
||||
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|
||||
|| (!ignoreMaxDimensions && (maxWidth != other.maxWidth || maxHeight != other.maxHeight))
|
||||
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|
||||
|
@ -50,9 +50,8 @@ import java.util.List;
|
||||
return null;
|
||||
}
|
||||
|
||||
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
|
||||
int id = header.first;
|
||||
long duration = header.second;
|
||||
TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
|
||||
long duration = tkhdData.duration;
|
||||
long movieTimescale = parseMvhd(mvhd.data);
|
||||
long durationUs;
|
||||
if (duration == -1) {
|
||||
@ -64,10 +63,10 @@ import java.util.List;
|
||||
.getContainerAtomOfType(Atom.TYPE_stbl);
|
||||
|
||||
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
||||
StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs,
|
||||
mdhdData.second);
|
||||
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs,
|
||||
tkhdData.rotationDegrees, mdhdData.second);
|
||||
return stsdData.mediaFormat == null ? null
|
||||
: new Track(id, trackType, mdhdData.first, durationUs, stsdData.mediaFormat,
|
||||
: new Track(tkhdData.id, trackType, mdhdData.first, durationUs, stsdData.mediaFormat,
|
||||
stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength);
|
||||
}
|
||||
|
||||
@ -268,19 +267,17 @@ import java.util.List;
|
||||
/**
|
||||
* Parses a tkhd atom (defined in 14496-12).
|
||||
*
|
||||
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
|
||||
* the movie header box). The duration is set to -1 if the duration is unspecified.
|
||||
* @return An object containing the parsed data.
|
||||
*/
|
||||
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
|
||||
private static TkhdData parseTkhd(ParsableByteArray tkhd) {
|
||||
tkhd.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = tkhd.readInt();
|
||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||
|
||||
tkhd.skipBytes(version == 0 ? 8 : 16);
|
||||
|
||||
int trackId = tkhd.readInt();
|
||||
tkhd.skipBytes(4);
|
||||
|
||||
tkhd.skipBytes(4);
|
||||
boolean durationUnknown = true;
|
||||
int durationPosition = tkhd.getPosition();
|
||||
int durationByteCount = version == 0 ? 4 : 8;
|
||||
@ -298,7 +295,27 @@ import java.util.List;
|
||||
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
return Pair.create(trackId, duration);
|
||||
tkhd.skipBytes(16);
|
||||
int a00 = tkhd.readInt();
|
||||
int a01 = tkhd.readInt();
|
||||
tkhd.skipBytes(4);
|
||||
int a10 = tkhd.readInt();
|
||||
int a11 = tkhd.readInt();
|
||||
|
||||
int rotationDegrees;
|
||||
int fixedOne = 65536;
|
||||
if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) {
|
||||
rotationDegrees = 90;
|
||||
} else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) {
|
||||
rotationDegrees = 270;
|
||||
} else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) {
|
||||
rotationDegrees = 180;
|
||||
} else {
|
||||
// Only 0, 90, 180 and 270 are supported. Treat anything else as 0.
|
||||
rotationDegrees = 0;
|
||||
}
|
||||
|
||||
return new TkhdData(trackId, duration, rotationDegrees);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,11 +350,16 @@ import java.util.List;
|
||||
return Pair.create(timescale, language);
|
||||
}
|
||||
|
||||
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs,
|
||||
/**
|
||||
* Parses a stsd atom (defined in 14496-12).
|
||||
*
|
||||
* @return An object containing the parsed data.
|
||||
*/
|
||||
private static StsdData parseStsd(ParsableByteArray stsd, long durationUs, int rotationDegrees,
|
||||
String language) {
|
||||
stsd.setPosition(Atom.FULL_HEADER_SIZE);
|
||||
int numberOfEntries = stsd.readInt();
|
||||
StsdDataHolder holder = new StsdDataHolder(numberOfEntries);
|
||||
StsdData out = new StsdData(numberOfEntries);
|
||||
for (int i = 0; i < numberOfEntries; i++) {
|
||||
int childStartPosition = stsd.getPosition();
|
||||
int childAtomSize = stsd.readInt();
|
||||
@ -347,25 +369,26 @@ import java.util.List;
|
||||
|| childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v
|
||||
|| childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1
|
||||
|| childAtomType == Atom.TYPE_s263) {
|
||||
parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, holder, i);
|
||||
parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, durationUs, rotationDegrees,
|
||||
out, i);
|
||||
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|
||||
|| childAtomType == Atom.TYPE_ac_3) {
|
||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs,
|
||||
holder, i);
|
||||
out, i);
|
||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, language,
|
||||
out.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, language,
|
||||
durationUs);
|
||||
} else if (childAtomType == Atom.TYPE_tx3g) {
|
||||
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, language,
|
||||
out.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, language,
|
||||
durationUs);
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
}
|
||||
return holder;
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void parseVideoSampleEntry(ParsableByteArray parent, int position, int size,
|
||||
long durationUs, StsdDataHolder out, int entryIndex) {
|
||||
long durationUs, int rotationDegrees, StsdData out, int entryIndex) {
|
||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
||||
|
||||
parent.skipBytes(24);
|
||||
@ -428,7 +451,7 @@ import java.util.List;
|
||||
}
|
||||
|
||||
out.mediaFormat = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE, durationUs,
|
||||
width, height, pixelWidthHeightRatio, initializationData);
|
||||
width, height, rotationDegrees, pixelWidthHeightRatio, initializationData);
|
||||
}
|
||||
|
||||
private static AvcCData parseAvcCFromParent(ParsableByteArray parent, int position) {
|
||||
@ -556,7 +579,7 @@ import java.util.List;
|
||||
}
|
||||
|
||||
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
|
||||
int size, long durationUs, StsdDataHolder out, int entryIndex) {
|
||||
int size, long durationUs, StsdData out, int entryIndex) {
|
||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
||||
parent.skipBytes(16);
|
||||
int channelCount = parent.readUnsignedShort();
|
||||
@ -702,23 +725,43 @@ import java.util.List;
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds data parsed from a tkhd atom.
|
||||
*/
|
||||
private static final class TkhdData {
|
||||
|
||||
private final int id;
|
||||
private final long duration;
|
||||
private final int rotationDegrees;
|
||||
|
||||
public TkhdData(int id, long duration, int rotationDegrees) {
|
||||
this.id = id;
|
||||
this.duration = duration;
|
||||
this.rotationDegrees = rotationDegrees;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds data parsed from an stsd atom and its children.
|
||||
*/
|
||||
private static final class StsdDataHolder {
|
||||
private static final class StsdData {
|
||||
|
||||
public final TrackEncryptionBox[] trackEncryptionBoxes;
|
||||
|
||||
public MediaFormat mediaFormat;
|
||||
public int nalUnitLengthFieldLength;
|
||||
|
||||
public StsdDataHolder(int numberOfEntries) {
|
||||
public StsdData(int numberOfEntries) {
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
||||
nalUnitLengthFieldLength = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds data parsed from an AvcC atom.
|
||||
*/
|
||||
private static final class AvcCData {
|
||||
|
||||
public final List<byte[]> initializationData;
|
||||
|
@ -211,7 +211,7 @@ import java.util.List;
|
||||
|
||||
// Construct and output the format.
|
||||
output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
||||
C.UNKNOWN_TIME_US, parsedSpsData.width, parsedSpsData.height,
|
||||
C.UNKNOWN_TIME_US, parsedSpsData.width, parsedSpsData.height, 0,
|
||||
parsedSpsData.pixelWidthAspectRatio, initializationData));
|
||||
hasOutputFormat = true;
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ import java.util.Collections;
|
||||
}
|
||||
|
||||
output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE,
|
||||
C.UNKNOWN_TIME_US, picWidthInLumaSamples, picHeightInLumaSamples, pixelWidthHeightRatio,
|
||||
C.UNKNOWN_TIME_US, picWidthInLumaSamples, picHeightInLumaSamples, 0, pixelWidthHeightRatio,
|
||||
Collections.singletonList(csd)));
|
||||
hasOutputFormat = true;
|
||||
}
|
||||
|
@ -1127,7 +1127,7 @@ public final class WebmExtractor implements Extractor {
|
||||
sampleRate, initializationData);
|
||||
} else if (MimeTypes.isVideo(mimeType)) {
|
||||
return MediaFormat.createVideoFormat(mimeType, maxInputSize, durationUs, pixelWidth,
|
||||
pixelHeight, initializationData);
|
||||
pixelHeight, 0, initializationData);
|
||||
} else {
|
||||
throw new ParserException("Unexpected MIME type.");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user