mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
commit
0005f41fe9
12 changed files with 241 additions and 62 deletions
|
|
@ -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 {
|
||||||
// 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,
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
// 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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
* @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 {
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
* {@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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|| (!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 {
|
||||||
@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 {
|
||||||
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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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,6 +580,7 @@ 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 (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
|
||||||
if (childAtomType == Atom.TYPE_esds) {
|
if (childAtomType == Atom.TYPE_esds) {
|
||||||
initializationData = parseEsdsFromParent(parent, childStartPosition);
|
initializationData = parseEsdsFromParent(parent, childStartPosition);
|
||||||
// TODO: Do we really need to do this? See [redacted]
|
// TODO: Do we really need to do this? See [redacted]
|
||||||
|
|
@ -585,14 +592,85 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||||
} else if (childAtomType == Atom.TYPE_sinf) {
|
} else if (childAtomType == Atom.TYPE_sinf) {
|
||||||
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
|
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue