Flesh out ExoPlayer extensions.

1. AudioDecoderTrackRenderer now reports decoder initialization
   and AudioTrack underruns.
2. AudioDecoderTrackRenderer can now render more than one output
   buffer per call to doSomeWork, to be consistent with
   MediaCodecAudioTrackRenderer. This may also prevent audio
   underruns in the case that audio is fragmented into many small
   buffers.
3. AudioDecoderTrackRenderer now has an overridable method that
   receives the audio session id, to be consistent with
   MediaCodecAudioTrackRenderer.
4. Fix unsafe event notification in LibvpxVideoTrackRenderer.
5. Vpx and AudioDecoder extensions now increment the CodecCounter
   inputBufferCount field correctly.
6. Decoders now have names :).
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=122250009
This commit is contained in:
olly 2016-05-13 04:59:12 -07:00 committed by Oliver Woodman
parent 97c633f128
commit 3760f514a9
13 changed files with 139 additions and 27 deletions

View File

@ -61,6 +61,11 @@ import java.util.List;
maxOutputBufferSize = streamInfo.maxDecodedFrameSize(); maxOutputBufferSize = streamInfo.maxDecodedFrameSize();
} }
@Override
public String getName() {
return "libflac";
}
@Override @Override
public DecoderInputBuffer createInputBuffer() { public DecoderInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);

View File

@ -132,6 +132,11 @@ import java.util.List;
setInitialInputBufferSize(initialInputBufferSize); setInitialInputBufferSize(initialInputBufferSize);
} }
@Override
public String getName() {
return "libopus" + getLibopusVersion();
}
@Override @Override
public DecoderInputBuffer createInputBuffer() { public DecoderInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);

View File

@ -151,13 +151,15 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
try { try {
if (decoder == null) { if (decoder == null) {
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
long startElapsedRealtimeMs = SystemClock.elapsedRealtime(); long codecInitializingTimestamp = SystemClock.elapsedRealtime();
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE); decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
notifyDecoderInitialized(startElapsedRealtimeMs, SystemClock.elapsedRealtime()); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
notifyDecoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} }
while (processOutputBuffer(positionUs)) {} while (drainOutputBuffer(positionUs)) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
} catch (VpxDecoderException e) { } catch (VpxDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
@ -165,8 +167,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
codecCounters.ensureUpdated(); codecCounters.ensureUpdated();
} }
private boolean processOutputBuffer(long positionUs) private boolean drainOutputBuffer(long positionUs) throws VpxDecoderException {
throws VpxDecoderException {
if (outputStreamEnded) { if (outputStreamEnded) {
return false; return false;
} }
@ -226,7 +227,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
private void renderBuffer() { private void renderBuffer() {
codecCounters.renderedOutputBufferCount++; codecCounters.renderedOutputBufferCount++;
notifyIfVideoSizeChanged(outputBuffer); notifyIfVideoSizeChanged(outputBuffer.width, outputBuffer.height);
if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_RGB && surface != null) { if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_RGB && surface != null) {
renderRgbFrame(outputBuffer, scaleToFit); renderRgbFrame(outputBuffer, scaleToFit);
if (!drawnToSurface) { if (!drawnToSurface) {
@ -280,9 +281,13 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
} }
if (inputBuffer.isEndOfStream()) { if (inputBuffer.isEndOfStream()) {
inputStreamEnded = true; inputStreamEnded = true;
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
return false;
} }
inputBuffer.flip(); inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer);
codecCounters.inputBufferCount++;
inputBuffer = null; inputBuffer = null;
return true; return true;
} }
@ -398,16 +403,16 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
} }
} }
private void notifyIfVideoSizeChanged(final VpxOutputBuffer outputBuffer) { private void notifyIfVideoSizeChanged(final int width, final int height) {
if (previousWidth == -1 || previousHeight == -1 if (previousWidth == -1 || previousHeight == -1 || previousWidth != width
|| previousWidth != outputBuffer.width || previousHeight != outputBuffer.height) { || previousHeight != height) {
previousWidth = outputBuffer.width; previousWidth = width;
previousHeight = outputBuffer.height; previousHeight = height;
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
eventListener.onVideoSizeChanged(outputBuffer.width, outputBuffer.height, 0, 1); eventListener.onVideoSizeChanged(width, height, 0, 1);
} }
}); });
} }
@ -441,14 +446,14 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
} }
} }
private void notifyDecoderInitialized( private void notifyDecoderInitialized(final String decoderName,
final long startElapsedRealtimeMs, final long finishElapsedRealtimeMs) { final long initializedTimestamp, final long initializationDuration) {
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
eventListener.onDecoderInitialized("libvpx" + getLibvpxVersion(), eventListener.onDecoderInitialized(decoderName, initializedTimestamp,
finishElapsedRealtimeMs, finishElapsedRealtimeMs - startElapsedRealtimeMs); initializationDuration);
} }
}); });
} }

View File

@ -74,6 +74,11 @@ import java.nio.ByteBuffer;
setInitialInputBufferSize(initialInputBufferSize); setInitialInputBufferSize(initialInputBufferSize);
} }
@Override
public String getName() {
return "libvpx" + getLibvpxVersion();
}
/** /**
* Sets the output mode for frames rendered by the decoder. * Sets the output mode for frames rendered by the decoder.
* *

View File

@ -30,6 +30,7 @@ import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
/** /**
* Decodes and renders audio using a {@link SimpleDecoder}. * Decodes and renders audio using a {@link SimpleDecoder}.
@ -63,6 +64,9 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private int audioSessionId; private int audioSessionId;
private boolean audioTrackHasData;
private long lastFeedElapsedRealtimeMs;
public AudioDecoderTrackRenderer() { public AudioDecoderTrackRenderer() {
this(null, null); this(null, null);
} }
@ -101,16 +105,20 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) { if (decoder == null) {
try { try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
decoder = createDecoder(inputFormat); decoder = createDecoder(inputFormat);
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
notifyDecoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
codecCounters.codecInitCount++;
} catch (AudioDecoderException e) { } catch (AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
codecCounters.codecInitCount++;
} }
// Rendering loop. // Rendering loop.
try { try {
renderBuffer(); while (drainOutputBuffer()) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) { } catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e); notifyAudioTrackInitializationError(e);
@ -145,16 +153,16 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
null, null, null); null, null, null);
} }
private void renderBuffer() throws AudioDecoderException, AudioTrack.InitializationException, private boolean drainOutputBuffer() throws AudioDecoderException,
AudioTrack.WriteException { AudioTrack.InitializationException, AudioTrack.WriteException {
if (outputStreamEnded) { if (outputStreamEnded) {
return; return false;
} }
if (outputBuffer == null) { if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer(); outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) { if (outputBuffer == null) {
return; return false;
} }
} }
@ -163,7 +171,7 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
audioTrack.handleEndOfStream(); audioTrack.handleEndOfStream();
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
return; return false;
} }
if (!audioTrack.isInitialized()) { if (!audioTrack.isInitialized()) {
@ -174,14 +182,26 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
audioTrack.initialize(audioSessionId); audioTrack.initialize(audioSessionId);
} else { } else {
audioSessionId = audioTrack.initialize(); audioSessionId = audioTrack.initialize();
onAudioSessionId(audioSessionId);
} }
audioTrackHasData = false;
if (getState() == TrackRenderer.STATE_STARTED) { if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play(); audioTrack.play();
} }
} else {
// Check for AudioTrack underrun.
boolean audioTrackHadData = audioTrackHasData;
audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeUs = audioTrack.getBufferSizeUs();
long bufferSizeMs = bufferSizeUs == C.UNSET_TIME_US ? -1 : bufferSizeUs / 1000;
notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs);
}
} }
int handleBufferResult; int handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timestampUs);
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timestampUs); lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
// If we are out of sync, allow currentPositionUs to jump backwards. // If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
@ -193,7 +213,10 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
codecCounters.renderedOutputBufferCount++; codecCounters.renderedOutputBufferCount++;
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
return true;
} }
return false;
} }
private boolean feedInputBuffer() throws AudioDecoderException { private boolean feedInputBuffer() throws AudioDecoderException {
@ -218,10 +241,13 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
} }
if (inputBuffer.isEndOfStream()) { if (inputBuffer.isEndOfStream()) {
inputStreamEnded = true; inputStreamEnded = true;
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
return false;
} }
inputBuffer.flip(); inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer);
codecCounters.inputBufferCount++;
inputBuffer = null; inputBuffer = null;
return true; return true;
} }
@ -269,6 +295,19 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
} }
} }
/**
* Invoked when the audio session id becomes known. Once the id is known it will not change
* (and hence this method will not be invoked again) unless the renderer is disabled and then
* subsequently re-enabled.
* <p>
* The default implementation is a no-op.
*
* @param audioSessionId The audio session id.
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
}
@Override @Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException { protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
notifyAudioCodecCounters(); notifyAudioCodecCounters();
@ -320,6 +359,19 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
} }
} }
private void notifyDecoderInitialized(final String decoderName,
final long initializedTimestamp, final long initializationDuration) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderInitialized(decoderName, initializedTimestamp,
initializationDuration);
}
});
}
}
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {
@ -342,6 +394,18 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
} }
} }
private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
private void notifyAudioCodecCounters() { private void notifyAudioCodecCounters() {
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {

View File

@ -24,6 +24,13 @@ package com.google.android.exoplayer.extensions;
*/ */
public interface Decoder<I, O, E extends Exception> { public interface Decoder<I, O, E extends Exception> {
/**
* Returns the name of the decoder.
*
* @return The name of the decoder.
*/
String getName();
/** /**
* Dequeues the next input buffer to be filled and queued to the decoder. * Dequeues the next input buffer to be filled and queued to the decoder.
* *

View File

@ -27,11 +27,19 @@ public abstract class SimpleSubtitleParser extends
SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> implements SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> implements
SubtitleParser { SubtitleParser {
protected SimpleSubtitleParser() { private final String name;
protected SimpleSubtitleParser(String name) {
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
this.name = name;
setInitialInputBufferSize(1024); setInitialInputBufferSize(1024);
} }
@Override
public final String getName() {
return name;
}
@Override @Override
public void setPositionUs(long timeUs) { public void setPositionUs(long timeUs) {
// Do nothing // Do nothing

View File

@ -205,6 +205,11 @@ public final class Eia608Parser implements SubtitleParser {
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
} }
@Override
public String getName() {
return "Eia608Parser";
}
@Override @Override
public void setPositionUs(long positionUs) { public void setPositionUs(long positionUs) {
playbackPositionUs = positionUs; playbackPositionUs = positionUs;

View File

@ -43,6 +43,7 @@ public final class SubripParser extends SimpleSubtitleParser {
private final StringBuilder textBuilder; private final StringBuilder textBuilder;
public SubripParser() { public SubripParser() {
super("SubripParser");
textBuilder = new StringBuilder(); textBuilder = new StringBuilder();
} }

View File

@ -87,6 +87,7 @@ public final class TtmlParser extends SimpleSubtitleParser {
private final XmlPullParserFactory xmlParserFactory; private final XmlPullParserFactory xmlParserFactory;
public TtmlParser() { public TtmlParser() {
super("TtmlParser");
try { try {
xmlParserFactory = XmlPullParserFactory.newInstance(); xmlParserFactory = XmlPullParserFactory.newInstance();
xmlParserFactory.setNamespaceAware(true); xmlParserFactory.setNamespaceAware(true);

View File

@ -26,6 +26,10 @@ import com.google.android.exoplayer.text.Subtitle;
*/ */
public final class Tx3gParser extends SimpleSubtitleParser { public final class Tx3gParser extends SimpleSubtitleParser {
public Tx3gParser() {
super("Tx3gParser");
}
@Override @Override
protected Subtitle decode(byte[] bytes, int length) { protected Subtitle decode(byte[] bytes, int length) {
String cueText = new String(bytes, 0, length); String cueText = new String(bytes, 0, length);

View File

@ -40,6 +40,7 @@ public final class Mp4WebvttParser extends SimpleSubtitleParser {
private final WebvttCue.Builder builder; private final WebvttCue.Builder builder;
public Mp4WebvttParser() { public Mp4WebvttParser() {
super("Mp4WebvttParser");
sampleData = new ParsableByteArray(); sampleData = new ParsableByteArray();
builder = new WebvttCue.Builder(); builder = new WebvttCue.Builder();
} }

View File

@ -46,6 +46,7 @@ public final class WebvttParser extends SimpleSubtitleParser {
private final List<WebvttCssStyle> definedStyles; private final List<WebvttCssStyle> definedStyles;
public WebvttParser() { public WebvttParser() {
super("WebvttParser");
cueParser = new WebvttCueParser(); cueParser = new WebvttCueParser();
parsableWebvttData = new ParsableByteArray(); parsableWebvttData = new ParsableByteArray();
webvttCueBuilder = new WebvttCue.Builder(); webvttCueBuilder = new WebvttCue.Builder();