Commit 3760f514 by olly Committed by Oliver Woodman

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
parent 97c633f1
...@@ -62,6 +62,11 @@ import java.util.List; ...@@ -62,6 +62,11 @@ import java.util.List;
} }
@Override @Override
public String getName() {
return "libflac";
}
@Override
public DecoderInputBuffer createInputBuffer() { public DecoderInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
} }
......
...@@ -133,6 +133,11 @@ import java.util.List; ...@@ -133,6 +133,11 @@ import java.util.List;
} }
@Override @Override
public String getName() {
return "libopus" + getLibopusVersion();
}
@Override
public DecoderInputBuffer createInputBuffer() { public DecoderInputBuffer createInputBuffer() {
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
} }
......
...@@ -151,13 +151,15 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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);
} }
}); });
} }
......
...@@ -74,6 +74,11 @@ import java.nio.ByteBuffer; ...@@ -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.
* *
......
...@@ -30,6 +30,7 @@ import com.google.android.exoplayer.audio.AudioTrack; ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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() {
......
...@@ -25,6 +25,13 @@ package com.google.android.exoplayer.extensions; ...@@ -25,6 +25,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.
* *
* @return The input buffer, which will have been cleared, or null if a buffer isn't available. * @return The input buffer, which will have been cleared, or null if a buffer isn't available.
......
...@@ -27,12 +27,20 @@ public abstract class SimpleSubtitleParser extends ...@@ -27,12 +27,20 @@ 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 @Override
public final String getName() {
return name;
}
@Override
public void setPositionUs(long timeUs) { public void setPositionUs(long timeUs) {
// Do nothing // Do nothing
} }
......
...@@ -206,6 +206,11 @@ public final class Eia608Parser implements SubtitleParser { ...@@ -206,6 +206,11 @@ public final class Eia608Parser implements SubtitleParser {
} }
@Override @Override
public String getName() {
return "Eia608Parser";
}
@Override
public void setPositionUs(long positionUs) { public void setPositionUs(long positionUs) {
playbackPositionUs = positionUs; playbackPositionUs = positionUs;
} }
......
...@@ -43,6 +43,7 @@ public final class SubripParser extends SimpleSubtitleParser { ...@@ -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();
} }
......
...@@ -87,6 +87,7 @@ public final class TtmlParser extends SimpleSubtitleParser { ...@@ -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);
......
...@@ -26,6 +26,10 @@ import com.google.android.exoplayer.text.Subtitle; ...@@ -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);
......
...@@ -40,6 +40,7 @@ public final class Mp4WebvttParser extends SimpleSubtitleParser { ...@@ -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();
} }
......
...@@ -46,6 +46,7 @@ public final class WebvttParser extends SimpleSubtitleParser { ...@@ -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();
......
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