Commit 0005f41f by ojw28

Merge pull request #138 from google/dev

dev -> dev-hls
parents 7b5c4d70 cb068459
...@@ -175,13 +175,13 @@ public class DashVodRendererBuilder implements RendererBuilder, ...@@ -175,13 +175,13 @@ public class DashVodRendererBuilder implements RendererBuilder,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
mainHandler, player, 50); mainHandler, player, 50);
// Build the audio renderer. // Build the audio renderer.
final String[] audioTrackNames; final String[] audioTrackNames;
final MultiTrackChunkSource audioChunkSource; final MultiTrackChunkSource audioChunkSource;
final MediaCodecAudioTrackRenderer audioRenderer; final TrackRenderer audioRenderer;
if (audioRepresentationsList.isEmpty()) { if (audioRepresentationsList.isEmpty()) {
audioTrackNames = null; audioTrackNames = null;
audioChunkSource = null; audioChunkSource = null;
......
...@@ -48,8 +48,8 @@ public class DefaultRendererBuilder implements RendererBuilder { ...@@ -48,8 +48,8 @@ public class DefaultRendererBuilder implements RendererBuilder {
// Build the video and audio renderers. // Build the video and audio renderers.
FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2); FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
player.getMainHandler(), player, 50); player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player); null, true, player.getMainHandler(), player);
......
...@@ -163,7 +163,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -163,7 +163,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
mainHandler, player, 50); mainHandler, player, 50);
// Build the audio renderer. // Build the audio renderer.
......
...@@ -68,8 +68,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { ...@@ -68,8 +68,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
public AudioTrackInitializationException(int audioTrackState, int sampleRate, public AudioTrackInitializationException(int audioTrackState, int sampleRate,
int channelConfig, int bufferSize) { int channelConfig, int bufferSize) {
super("AudioTrack init failed: " + audioTrackState + ", Config(" + sampleRate + ", " + super("AudioTrack init failed: " + audioTrackState + ", Config(" + sampleRate + ", "
channelConfig + ", " + bufferSize + ")"); + channelConfig + ", " + bufferSize + ")");
this.audioTrackState = audioTrackState; this.audioTrackState = audioTrackState;
} }
...@@ -538,8 +538,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { ...@@ -538,8 +538,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
// Compute the audio track latency, excluding the latency due to the buffer (leaving // Compute the audio track latency, excluding the latency due to the buffer (leaving
// latency due to the mixer and audio hardware driver). // latency due to the mixer and audio hardware driver).
audioTrackLatencyUs = audioTrackLatencyUs =
(Integer) audioTrackGetLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L - (Integer) audioTrackGetLatencyMethod.invoke(audioTrack, (Object[]) null) * 1000L
framesToDurationUs(bufferSize / frameSize); - framesToDurationUs(bufferSize / frameSize);
// Sanity check that the latency is non-negative. // Sanity check that the latency is non-negative.
audioTrackLatencyUs = Math.max(audioTrackLatencyUs, 0); audioTrackLatencyUs = Math.max(audioTrackLatencyUs, 0);
// Sanity check that the latency isn't too large. // Sanity check that the latency isn't too large.
...@@ -612,19 +612,19 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { ...@@ -612,19 +612,19 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
if (temporaryBufferSize == 0) { if (temporaryBufferSize == 0) {
// This is the first time we've seen this {@code buffer}. // This is the first time we've seen this {@code buffer}.
// Note: presentationTimeUs corresponds to the end of the sample, not the start. // Note: presentationTimeUs corresponds to the end of the sample, not the start.
long bufferStartTime = bufferInfo.presentationTimeUs - long bufferStartTime = bufferInfo.presentationTimeUs
framesToDurationUs(bufferInfo.size / frameSize); - framesToDurationUs(bufferInfo.size / frameSize);
if (audioTrackStartMediaTimeState == START_NOT_SET) { if (audioTrackStartMediaTimeState == START_NOT_SET) {
audioTrackStartMediaTimeUs = Math.max(0, bufferStartTime); audioTrackStartMediaTimeUs = Math.max(0, bufferStartTime);
audioTrackStartMediaTimeState = START_IN_SYNC; audioTrackStartMediaTimeState = START_IN_SYNC;
} else { } else {
// Sanity check that bufferStartTime is consistent with the expected value. // Sanity check that bufferStartTime is consistent with the expected value.
long expectedBufferStartTime = audioTrackStartMediaTimeUs + long expectedBufferStartTime = audioTrackStartMediaTimeUs
framesToDurationUs(submittedBytes / frameSize); + framesToDurationUs(submittedBytes / frameSize);
if (audioTrackStartMediaTimeState == START_IN_SYNC if (audioTrackStartMediaTimeState == START_IN_SYNC
&& Math.abs(expectedBufferStartTime - bufferStartTime) > 200000) { && Math.abs(expectedBufferStartTime - bufferStartTime) > 200000) {
Log.e(TAG, "Discontinuity detected [expected " + expectedBufferStartTime + ", got " + Log.e(TAG, "Discontinuity detected [expected " + expectedBufferStartTime + ", got "
bufferStartTime + "]"); + bufferStartTime + "]");
audioTrackStartMediaTimeState = START_NEED_SYNC; audioTrackStartMediaTimeState = START_NEED_SYNC;
} }
if (audioTrackStartMediaTimeState == START_NEED_SYNC) { if (audioTrackStartMediaTimeState == START_NEED_SYNC) {
...@@ -679,7 +679,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { ...@@ -679,7 +679,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
} }
@TargetApi(21) @TargetApi(21)
private int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) { private static int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING); return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING);
} }
...@@ -703,8 +703,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { ...@@ -703,8 +703,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
} }
private int getPendingFrameCount() { private int getPendingFrameCount() {
return audioTrack == null ? return audioTrack == null
0 : (int) (submittedBytes / frameSize - getPlaybackHeadPosition()); ? 0 : (int) (submittedBytes / frameSize - getPlaybackHeadPosition());
} }
@Override @Override
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer.util.Util; ...@@ -21,6 +21,7 @@ import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.CryptoException; import android.media.MediaCodec.CryptoException;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaExtractor; import android.media.MediaExtractor;
...@@ -70,10 +71,24 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { ...@@ -70,10 +71,24 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
*/ */
public final String decoderName; public final String decoderName;
/**
* An optional developer-readable diagnostic information string. May be null.
*/
public final String diagnosticInfo;
public DecoderInitializationException(String decoderName, MediaFormat mediaFormat, public DecoderInitializationException(String decoderName, MediaFormat mediaFormat,
Exception cause) { Throwable cause) {
super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause); super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause);
this.decoderName = decoderName; this.decoderName = decoderName;
this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
}
@TargetApi(21)
private static String getDiagnosticInfoV21(Throwable cause) {
if (cause instanceof CodecException) {
return ((CodecException) cause).getDiagnosticInfo();
}
return null;
} }
} }
...@@ -235,6 +250,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { ...@@ -235,6 +250,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codec.configure(x, null, crypto, 0); codec.configure(x, null, crypto, 0);
} }
@SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException { protected final void maybeInitCodec() throws ExoPlaybackException {
if (!shouldInitCodec()) { if (!shouldInitCodec()) {
return; return;
...@@ -694,6 +710,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { ...@@ -694,6 +710,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @return True if it may be possible to drain more output data. False otherwise. * @return True if it may be possible to drain more output data. False otherwise.
* @throws ExoPlaybackException If an error occurs draining the output buffer. * @throws ExoPlaybackException If an error occurs draining the output buffer.
*/ */
@SuppressWarnings("deprecation")
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
if (outputStreamEnded) { if (outputStreamEnded) {
......
...@@ -64,7 +64,10 @@ public class MediaCodecUtil { ...@@ -64,7 +64,10 @@ public class MediaCodecUtil {
/** /**
* Returns the best decoder and its capabilities for the given mimeType. If there's no decoder * Returns the best decoder and its capabilities for the given mimeType. If there's no decoder
* returns null. * returns null.
*
* TODO: We need to use the new object based MediaCodecList API.
*/ */
@SuppressWarnings("deprecation")
private static synchronized Pair<MediaCodecInfo, CodecCapabilities> getMediaCodecInfo( private static synchronized Pair<MediaCodecInfo, CodecCapabilities> getMediaCodecInfo(
String mimeType) { String mimeType) {
Pair<MediaCodecInfo, CodecCapabilities> result = codecs.get(mimeType); Pair<MediaCodecInfo, CodecCapabilities> result = codecs.get(mimeType);
......
...@@ -75,6 +75,34 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -75,6 +75,34 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
} }
/**
* An interface for fine-grained adjustment of frame release times.
*/
public interface FrameReleaseTimeHelper {
/**
* Enables the helper.
*/
void enable();
/**
* Disables the helper.
*/
void disable();
/**
* Called to make a fine-grained adjustment to a frame release time.
*
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
* the same time base as {@link System#nanoTime()}.
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
* {@link System#nanoTime()}.
*/
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs);
}
// TODO: Use MediaFormat constants if these get exposed through the API. See [redacted]. // TODO: Use MediaFormat constants if these get exposed through the API. See [redacted].
private static final String KEY_CROP_LEFT = "crop-left"; private static final String KEY_CROP_LEFT = "crop-left";
private static final String KEY_CROP_RIGHT = "crop-right"; private static final String KEY_CROP_RIGHT = "crop-right";
...@@ -88,6 +116,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -88,6 +116,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
*/ */
public static final int MSG_SET_SURFACE = 1; public static final int MSG_SET_SURFACE = 1;
private final FrameReleaseTimeHelper frameReleaseTimeHelper;
private final EventListener eventListener; private final EventListener eventListener;
private final long allowedJoiningTimeUs; private final long allowedJoiningTimeUs;
private final int videoScalingMode; private final int videoScalingMode;
...@@ -162,7 +191,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -162,7 +191,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs) { boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs) {
this(source, drmSessionManager, playClearSamplesWithoutKeys, videoScalingMode, this(source, drmSessionManager, playClearSamplesWithoutKeys, videoScalingMode,
allowedJoiningTimeMs, null, null, -1); allowedJoiningTimeMs, null, null, null, -1);
} }
/** /**
...@@ -180,8 +209,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -180,8 +209,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode, public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode,
long allowedJoiningTimeMs, Handler eventHandler, EventListener eventListener, long allowedJoiningTimeMs, Handler eventHandler, EventListener eventListener,
int maxDroppedFrameCountToNotify) { int maxDroppedFrameCountToNotify) {
this(source, null, true, videoScalingMode, allowedJoiningTimeMs, eventHandler, eventListener, this(source, null, true, videoScalingMode, allowedJoiningTimeMs, null, eventHandler,
maxDroppedFrameCountToNotify); eventListener, maxDroppedFrameCountToNotify);
} }
/** /**
...@@ -197,6 +226,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -197,6 +226,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
* {@link MediaCodec#setVideoScalingMode(int)}. * {@link MediaCodec#setVideoScalingMode(int)}.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback. * can attempt to seamlessly join an ongoing playback.
* @param frameReleaseTimeHelper An optional helper to make fine-grained adjustments to frame
* release times. May be null.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
...@@ -205,10 +236,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -205,10 +236,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
*/ */
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs, boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs,
Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) { FrameReleaseTimeHelper frameReleaseTimeHelper, Handler eventHandler,
EventListener eventListener, int maxDroppedFrameCountToNotify) {
super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener); super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener);
this.videoScalingMode = videoScalingMode; this.videoScalingMode = videoScalingMode;
this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000; this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000;
this.frameReleaseTimeHelper = frameReleaseTimeHelper;
this.eventListener = eventListener; this.eventListener = eventListener;
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
joiningDeadlineUs = -1; joiningDeadlineUs = -1;
...@@ -232,6 +265,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -232,6 +265,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
if (joining && allowedJoiningTimeUs > 0) { if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs; joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
} }
if (frameReleaseTimeHelper != null) {
frameReleaseTimeHelper.enable();
}
} }
@Override @Override
...@@ -283,6 +319,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -283,6 +319,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
lastReportedWidth = -1; lastReportedWidth = -1;
lastReportedHeight = -1; lastReportedHeight = -1;
lastReportedPixelWidthHeightRatio = -1; lastReportedPixelWidthHeightRatio = -1;
if (frameReleaseTimeHelper != null) {
frameReleaseTimeHelper.disable();
}
super.onDisabled(); super.onDisabled();
} }
...@@ -362,8 +401,24 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -362,8 +401,24 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
return true; return true;
} }
long elapsedSinceStartOfLoop = SystemClock.elapsedRealtime() * 1000 - elapsedRealtimeUs; // Compute how many microseconds it is until the buffer's presentation time.
long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoop; long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;
// Compute the buffer's desired release time in nanoseconds.
long systemTimeNs = System.nanoTime();
long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);
// Apply a timestamp adjustment, if there is one.
long adjustedReleaseTimeNs;
if (frameReleaseTimeHelper != null) {
adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(
bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
} else {
adjustedReleaseTimeNs = unadjustedFrameReleaseTimeNs;
}
if (earlyUs < -30000) { if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame. // We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex); dropOutputBuffer(codec, bufferIndex);
...@@ -383,7 +438,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -383,7 +438,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// Let the underlying framework time the release. // Let the underlying framework time the release.
if (earlyUs < 50000) { if (earlyUs < 50000) {
renderOutputBufferTimedV21(codec, bufferIndex, System.nanoTime() + (earlyUs * 1000L)); renderOutputBufferTimedV21(codec, bufferIndex, adjustedReleaseTimeNs);
return true; return true;
} }
} else { } else {
...@@ -436,10 +491,10 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -436,10 +491,10 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
} }
@TargetApi(21) @TargetApi(21)
private void renderOutputBufferTimedV21(MediaCodec codec, int bufferIndex, long nanoTime) { private void renderOutputBufferTimedV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) {
maybeNotifyVideoSizeChanged(); maybeNotifyVideoSizeChanged();
TraceUtil.beginSection("releaseOutputBufferTimed"); TraceUtil.beginSection("releaseOutputBufferTimed");
codec.releaseOutputBuffer(bufferIndex, nanoTime); codec.releaseOutputBuffer(bufferIndex, releaseTimeNs);
TraceUtil.endSection(); TraceUtil.endSection();
codecCounters.renderedOutputBufferCount++; codecCounters.renderedOutputBufferCount++;
maybeNotifyDrawnToSurface(); maybeNotifyDrawnToSurface();
......
...@@ -47,6 +47,8 @@ public class MediaFormat { ...@@ -47,6 +47,8 @@ public class MediaFormat {
public final int channelCount; public final int channelCount;
public final int sampleRate; public final int sampleRate;
public final int bitrate;
private int maxWidth; private int maxWidth;
private int maxHeight; private int maxHeight;
...@@ -70,13 +72,19 @@ public class MediaFormat { ...@@ -70,13 +72,19 @@ public class MediaFormat {
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width, public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width,
int height, float pixelWidthHeightRatio, List<byte[]> initializationData) { int height, float pixelWidthHeightRatio, List<byte[]> initializationData) {
return new MediaFormat(mimeType, maxInputSize, width, height, pixelWidthHeightRatio, NO_VALUE, return new MediaFormat(mimeType, maxInputSize, width, height, pixelWidthHeightRatio, NO_VALUE,
NO_VALUE, initializationData); NO_VALUE, NO_VALUE, initializationData);
} }
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount, public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
int sampleRate, List<byte[]> initializationData) { int sampleRate, List<byte[]> initializationData) {
return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, channelCount, return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, channelCount,
sampleRate, initializationData); sampleRate, NO_VALUE, initializationData);
}
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
int sampleRate, int bitrate, List<byte[]> initializationData) {
return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, channelCount,
sampleRate, bitrate, initializationData);
} }
public static MediaFormat createId3Format() { public static MediaFormat createId3Format() {
...@@ -93,6 +101,7 @@ public class MediaFormat { ...@@ -93,6 +101,7 @@ public class MediaFormat {
height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
bitrate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_BIT_RATE);
pixelWidthHeightRatio = getOptionalFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); pixelWidthHeightRatio = getOptionalFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
initializationData = new ArrayList<byte[]>(); initializationData = new ArrayList<byte[]>();
for (int i = 0; format.containsKey("csd-" + i); i++) { for (int i = 0; format.containsKey("csd-" + i); i++) {
...@@ -107,7 +116,7 @@ public class MediaFormat { ...@@ -107,7 +116,7 @@ public class MediaFormat {
} }
private MediaFormat(String mimeType, int maxInputSize, int width, int height, private MediaFormat(String mimeType, int maxInputSize, int width, int height,
float pixelWidthHeightRatio, int channelCount, int sampleRate, float pixelWidthHeightRatio, int channelCount, int sampleRate, int bitrate,
List<byte[]> initializationData) { List<byte[]> initializationData) {
this.mimeType = mimeType; this.mimeType = mimeType;
this.maxInputSize = maxInputSize; this.maxInputSize = maxInputSize;
...@@ -116,6 +125,7 @@ public class MediaFormat { ...@@ -116,6 +125,7 @@ public class MediaFormat {
this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount; this.channelCount = channelCount;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.bitrate = bitrate;
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList() this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
: initializationData; : initializationData;
maxWidth = NO_VALUE; maxWidth = NO_VALUE;
...@@ -151,6 +161,7 @@ public class MediaFormat { ...@@ -151,6 +161,7 @@ public class MediaFormat {
result = 31 * result + maxHeight; result = 31 * result + maxHeight;
result = 31 * result + channelCount; result = 31 * result + channelCount;
result = 31 * result + sampleRate; result = 31 * result + sampleRate;
result = 31 * result + bitrate;
for (int i = 0; i < initializationData.size(); i++) { for (int i = 0; i < initializationData.size(); i++) {
result = 31 * result + Arrays.hashCode(initializationData.get(i)); result = 31 * result + Arrays.hashCode(initializationData.get(i));
} }
...@@ -186,6 +197,7 @@ public class MediaFormat { ...@@ -186,6 +197,7 @@ public class MediaFormat {
|| (!ignoreMaxDimensions && (maxWidth != other.maxWidth || maxHeight != other.maxHeight)) || (!ignoreMaxDimensions && (maxWidth != other.maxWidth || maxHeight != other.maxHeight))
|| channelCount != other.channelCount || sampleRate != other.sampleRate || channelCount != other.channelCount || sampleRate != other.sampleRate
|| !Util.areEqual(mimeType, other.mimeType) || !Util.areEqual(mimeType, other.mimeType)
|| bitrate != other.bitrate
|| initializationData.size() != other.initializationData.size()) { || initializationData.size() != other.initializationData.size()) {
return false; return false;
} }
...@@ -200,8 +212,8 @@ public class MediaFormat { ...@@ -200,8 +212,8 @@ public class MediaFormat {
@Override @Override
public String toString() { public String toString() {
return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", "
+ pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + maxWidth + ", " + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + bitrate + ", "
+ maxHeight + ")"; + maxWidth + ", " + maxHeight + ")";
} }
/** /**
...@@ -217,6 +229,7 @@ public class MediaFormat { ...@@ -217,6 +229,7 @@ public class MediaFormat {
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_BIT_RATE, bitrate);
maybeSetFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio); maybeSetFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
for (int i = 0; i < initializationData.size(); i++) { for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
......
...@@ -24,6 +24,10 @@ import java.util.ArrayList; ...@@ -24,6 +24,10 @@ import java.util.ArrayList;
public static final int TYPE_esds = 0x65736473; public static final int TYPE_esds = 0x65736473;
public static final int TYPE_mdat = 0x6D646174; public static final int TYPE_mdat = 0x6D646174;
public static final int TYPE_mp4a = 0x6D703461; public static final int TYPE_mp4a = 0x6D703461;
public static final int TYPE_ac_3 = 0x61632D33; // ac-3
public static final int TYPE_dac3 = 0x64616333;
public static final int TYPE_ec_3 = 0x65632D33; // ec-3
public static final int TYPE_dec3 = 0x64656333;
public static final int TYPE_tfdt = 0x74666474; public static final int TYPE_tfdt = 0x74666474;
public static final int TYPE_tfhd = 0x74666864; public static final int TYPE_tfhd = 0x74666864;
public static final int TYPE_trex = 0x74726578; public static final int TYPE_trex = 0x74726578;
......
...@@ -65,6 +65,11 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -65,6 +65,11 @@ public final class FragmentedMp4Extractor implements Extractor {
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
192, 224, 256, 320, 384, 448, 512, 576, 640};
// Parser states // Parser states
private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_HEADER = 0;
...@@ -512,11 +517,12 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -512,11 +517,12 @@ public final class FragmentedMp4Extractor implements Extractor {
parseAvcFromParent(stsd, childStartPosition, childAtomSize); parseAvcFromParent(stsd, childStartPosition, childAtomSize);
mediaFormat = avc.first; mediaFormat = avc.first;
trackEncryptionBoxes[i] = avc.second; trackEncryptionBoxes[i] = avc.second;
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca) { } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
Pair<MediaFormat, TrackEncryptionBox> mp4a = || childAtomType == Atom.TYPE_ac_3) {
parseMp4aFromParent(stsd, childStartPosition, childAtomSize); Pair<MediaFormat, TrackEncryptionBox> audioSampleEntry =
mediaFormat = mp4a.first; parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
trackEncryptionBoxes[i] = mp4a.second; mediaFormat = audioSampleEntry.first;
trackEncryptionBoxes[i] = audioSampleEntry.second;
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
} }
...@@ -556,15 +562,15 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -556,15 +562,15 @@ public final class FragmentedMp4Extractor implements Extractor {
return Pair.create(format, trackEncryptionBox); return Pair.create(format, trackEncryptionBox);
} }
private static Pair<MediaFormat, TrackEncryptionBox> parseMp4aFromParent(ParsableByteArray parent, private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
int position, int size) { ParsableByteArray parent, int atomType, int position, int size) {
parent.setPosition(position + ATOM_HEADER_SIZE); parent.setPosition(position + ATOM_HEADER_SIZE);
// Start of the mp4a atom (defined in 14496-14)
parent.skip(16); parent.skip(16);
int channelCount = parent.readUnsignedShort(); int channelCount = parent.readUnsignedShort();
int sampleSize = parent.readUnsignedShort(); int sampleSize = parent.readUnsignedShort();
parent.skip(4); parent.skip(4);
int sampleRate = parent.readUnsignedFixedPoint1616(); int sampleRate = parent.readUnsignedFixedPoint1616();
int bitrate = MediaFormat.NO_VALUE;
byte[] initializationData = null; byte[] initializationData = null;
TrackEncryptionBox trackEncryptionBox = null; TrackEncryptionBox trackEncryptionBox = null;
...@@ -574,25 +580,97 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -574,25 +580,97 @@ public final class FragmentedMp4Extractor implements Extractor {
int childStartPosition = parent.getPosition(); int childStartPosition = parent.getPosition();
int childAtomSize = parent.readInt(); int childAtomSize = parent.readInt();
int childAtomType = parent.readInt(); int childAtomType = parent.readInt();
if (childAtomType == Atom.TYPE_esds) { if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
initializationData = parseEsdsFromParent(parent, childStartPosition); if (childAtomType == Atom.TYPE_esds) {
// TODO: Do we really need to do this? See [redacted] initializationData = parseEsdsFromParent(parent, childStartPosition);
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data. // TODO: Do we really need to do this? See [redacted]
Pair<Integer, Integer> audioSpecificConfig = // Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); Pair<Integer, Integer> audioSpecificConfig =
sampleRate = audioSpecificConfig.first; CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
channelCount = audioSpecificConfig.second; sampleRate = audioSpecificConfig.first;
} else if (childAtomType == Atom.TYPE_sinf) { channelCount = audioSpecificConfig.second;
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize); } else if (childAtomType == Atom.TYPE_sinf) {
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
}
} else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
Ac3Format ac3Format =
parseAc3SpecificBoxFromParent(parent, childStartPosition);
if (ac3Format != null) {
sampleRate = ac3Format.sampleRate;
channelCount = ac3Format.channelCount;
bitrate = ac3Format.bitrate;
}
// TODO: Add support for encrypted AC-3.
trackEncryptionBox = null;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
trackEncryptionBox = null;
} }
childPosition += childAtomSize; childPosition += childAtomSize;
} }
MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", sampleSize, channelCount, String mimeType;
sampleRate, Collections.singletonList(initializationData)); if (atomType == Atom.TYPE_ac_3) {
mimeType = MimeTypes.AUDIO_AC3;
} else if (atomType == Atom.TYPE_ec_3) {
mimeType = MimeTypes.AUDIO_EC3;
} else {
mimeType = MimeTypes.AUDIO_AAC;
}
MediaFormat format = MediaFormat.createAudioFormat(
mimeType, sampleSize, channelCount, sampleRate, bitrate,
initializationData == null ? null : Collections.singletonList(initializationData));
return Pair.create(format, trackEncryptionBox); return Pair.create(format, trackEncryptionBox);
} }
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + ATOM_HEADER_SIZE);
// fscod (sample rate code)
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
int sampleRate;
switch (fscod) {
case 0:
sampleRate = 48000;
break;
case 1:
sampleRate = 44100;
break;
case 2:
sampleRate = 32000;
break;
default:
// TODO: The decoder should not use this stream.
return null;
}
int nextByte = parent.readUnsignedByte();
// Map acmod (audio coding mode) onto a channel count.
int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
// lfeon (low frequency effects on)
if ((nextByte & 0x04) != 0) {
channelCount++;
}
// Map bit_rate_code onto a bit-rate in kbit/s.
int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
return new Ac3Format(channelCount, sampleRate, bitrate);
}
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent.setPosition(position + ATOM_HEADER_SIZE);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return 0;
}
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) { private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
parent.setPosition(position + ATOM_HEADER_SIZE + 4); parent.setPosition(position + ATOM_HEADER_SIZE + 4);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15) // Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
...@@ -1182,4 +1260,19 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -1182,4 +1260,19 @@ public final class FragmentedMp4Extractor implements Extractor {
return result; return result;
} }
/** Represents the format for AC-3 audio. */
private static final class Ac3Format {
public final int channelCount;
public final int sampleRate;
public final int bitrate;
public Ac3Format(int channelCount, int sampleRate, int bitrate) {
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.bitrate = bitrate;
}
}
} }
...@@ -113,7 +113,7 @@ public final class CaptionStyleCompat { ...@@ -113,7 +113,7 @@ public final class CaptionStyleCompat {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
return createFromCaptionStyleV21(captionStyle); return createFromCaptionStyleV21(captionStyle);
} else { } else {
// Note - Any caller must be on at least API level 19 of greater (because CaptionStyle did // Note - Any caller must be on at least API level 19 or greater (because CaptionStyle did
// not exist in earlier API levels). // not exist in earlier API levels).
return createFromCaptionStyleV19(captionStyle); return createFromCaptionStyleV19(captionStyle);
} }
......
...@@ -171,7 +171,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -171,7 +171,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
loader = new Loader("manifestLoader"); loader = new Loader("manifestLoader");
} }
if (!loader.isLoading()) { if (!loader.isLoading()) {
currentLoadable = new ManifestLoadable(userAgent); currentLoadable = new ManifestLoadable();
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
} }
} }
...@@ -221,7 +221,7 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -221,7 +221,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
this.callbackLooper = callbackLooper; this.callbackLooper = callbackLooper;
this.wrappedCallback = wrappedCallback; this.wrappedCallback = wrappedCallback;
singleUseLoader = new Loader("manifestLoader:single"); singleUseLoader = new Loader("manifestLoader:single");
singleUseLoadable = new ManifestLoadable(userAgent); singleUseLoadable = new ManifestLoadable();
} }
public void startLoading() { public void startLoading() {
...@@ -269,15 +269,9 @@ public class ManifestFetcher<T> implements Loader.Callback { ...@@ -269,15 +269,9 @@ public class ManifestFetcher<T> implements Loader.Callback {
private static final int TIMEOUT_MILLIS = 10000; private static final int TIMEOUT_MILLIS = 10000;
private final String userAgent;
/* package */ volatile T result; /* package */ volatile T result;
private volatile boolean isCanceled; private volatile boolean isCanceled;
public ManifestLoadable(String userAgent) {
this.userAgent = userAgent;
}
@Override @Override
public void cancelLoad() { public void cancelLoad() {
// We don't actually cancel anything, but we need to record the cancellation so that // We don't actually cancel anything, but we need to record the cancellation so that
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment