Commit 632ccc6c by Oliver Woodman

Simplify passthrough playback rate calculation.

This change also fixes pre-M DTS HD passthrough playback on NVIDIA Shield.
parent b8e7e107
...@@ -160,8 +160,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener ...@@ -160,8 +160,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
} }
@Override @Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) { public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
printInternalError("audioTrackUnderrun [" + audioTrackBufferSizeMs + ", " printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
+ elapsedSinceLastFeedMs + "]", null); + elapsedSinceLastFeedMs + "]", null);
} }
......
...@@ -105,7 +105,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -105,7 +105,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
void onRendererInitializationError(Exception e); void onRendererInitializationError(Exception e);
void onAudioTrackInitializationError(AudioTrack.InitializationException e); void onAudioTrackInitializationError(AudioTrack.InitializationException e);
void onAudioTrackWriteError(AudioTrack.WriteException e); void onAudioTrackWriteError(AudioTrack.WriteException e);
void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs); void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
void onDecoderInitializationError(DecoderInitializationException e); void onDecoderInitializationError(DecoderInitializationException e);
void onCryptoError(CryptoException e); void onCryptoError(CryptoException e);
void onLoadError(int sourceId, IOException e); void onLoadError(int sourceId, IOException e);
...@@ -483,9 +483,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi ...@@ -483,9 +483,9 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
} }
@Override @Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) { public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
if (internalErrorListener != null) { if (internalErrorListener != null) {
internalErrorListener.onAudioTrackUnderrun(audioTrackBufferSizeMs, elapsedSinceLastFeedMs); internalErrorListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
} }
} }
......
...@@ -59,10 +59,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem ...@@ -59,10 +59,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
/** /**
* Invoked when an {@link AudioTrack} underrun occurs. * Invoked when an {@link AudioTrack} underrun occurs.
* *
* @param audioTrackBufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds. * @param bufferSize The size of the {@link AudioTrack}'s buffer, in bytes.
* @param bufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds, if it is
* configured for PCM output. -1 if it is configured for passthrough output, as the buffered
* media can have a variable bitrate so the duration may be unknown.
* @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data. * @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data.
*/ */
void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs); void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
} }
...@@ -355,7 +358,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem ...@@ -355,7 +358,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
audioTrackHasData = audioTrack.hasPendingData(); audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) { if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
notifyAudioTrackUnderrun(audioTrack.getBufferSizeUs() / 1000, elapsedSinceLastFeedMs); long bufferSizeUs = audioTrack.getBufferSizeUs();
long bufferSizeMs = bufferSizeUs == C.UNKNOWN_TIME_US ? -1 : bufferSizeUs / 1000;
notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs);
} }
} }
...@@ -425,13 +430,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem ...@@ -425,13 +430,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
} }
} }
private void notifyAudioTrackUnderrun(final long audioTrackBufferSizeMs, private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) { final long elapsedSinceLastFeedMs) {
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.onAudioTrackUnderrun(audioTrackBufferSizeMs, elapsedSinceLastFeedMs); eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
} }
}); });
} }
......
...@@ -156,11 +156,6 @@ public final class AudioTrack { ...@@ -156,11 +156,6 @@ public final class AudioTrack {
*/ */
private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND; private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND;
/**
* Value for {@link #passthroughBitrate} before the bitrate has been calculated.
*/
private static final int UNKNOWN_BITRATE = 0;
private static final int START_NOT_SET = 0; private static final int START_NOT_SET = 0;
private static final int START_IN_SYNC = 1; private static final int START_IN_SYNC = 1;
private static final int START_NEED_SYNC = 2; private static final int START_NEED_SYNC = 2;
...@@ -201,7 +196,8 @@ public final class AudioTrack { ...@@ -201,7 +196,8 @@ public final class AudioTrack {
private int sampleRate; private int sampleRate;
private int channelConfig; private int channelConfig;
private int encoding; private int encoding;
private int frameSize; private boolean passthrough;
private int pcmFrameSize;
private int minBufferSize; private int minBufferSize;
private int bufferSize; private int bufferSize;
private long bufferSizeUs; private long bufferSizeUs;
...@@ -214,7 +210,9 @@ public final class AudioTrack { ...@@ -214,7 +210,9 @@ public final class AudioTrack {
private long lastTimestampSampleTimeUs; private long lastTimestampSampleTimeUs;
private Method getLatencyMethod; private Method getLatencyMethod;
private long submittedBytes; private long submittedPcmBytes;
private long submittedEncodedFrames;
private int framesPerEncodedSample;
private int startMediaTimeState; private int startMediaTimeState;
private long startMediaTimeUs; private long startMediaTimeUs;
private long resumeSystemTimeUs; private long resumeSystemTimeUs;
...@@ -226,11 +224,6 @@ public final class AudioTrack { ...@@ -226,11 +224,6 @@ public final class AudioTrack {
private int temporaryBufferSize; private int temporaryBufferSize;
/** /**
* Bitrate measured in kilobits per second, if using passthrough.
*/
private int passthroughBitrate;
/**
* Creates an audio track with default audio capabilities (no encoded audio passthrough support). * Creates an audio track with default audio capabilities (no encoded audio passthrough support).
*/ */
public AudioTrack() { public AudioTrack() {
...@@ -344,7 +337,7 @@ public final class AudioTrack { ...@@ -344,7 +337,7 @@ public final class AudioTrack {
* Configures (or reconfigures) the audio track to play back media in {@code format}. * Configures (or reconfigures) the audio track to play back media in {@code format}.
* *
* @param format Specifies the channel count and sample rate to play back. * @param format Specifies the channel count and sample rate to play back.
* @param passthrough Whether to playback using a passthrough encoding. * @param passthrough Whether to play back using a passthrough encoding.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a
* size inferred from the format. * size inferred from the format.
*/ */
...@@ -391,25 +384,29 @@ public final class AudioTrack { ...@@ -391,25 +384,29 @@ public final class AudioTrack {
reset(); reset();
this.encoding = encoding; this.encoding = encoding;
this.passthrough = passthrough;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channelConfig = channelConfig; this.channelConfig = channelConfig;
passthroughBitrate = UNKNOWN_BITRATE; pcmFrameSize = 2 * channelCount; // 2 bytes per 16 bit sample * number of channels.
frameSize = 2 * channelCount; // 2 bytes per 16 bit sample * number of channels.
minBufferSize = android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding); minBufferSize = android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding);
Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE); Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE);
if (specifiedBufferSize != 0) { if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize; bufferSize = specifiedBufferSize;
} else if (passthrough) {
// TODO: Set the minimum buffer size correctly for encoded output when getMinBufferSize takes
// the encoding into account. [Internal: b/25181305]
bufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR * 2;
} else { } else {
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * frameSize; int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
int maxAppBufferSize = (int) Math.max(minBufferSize, int maxAppBufferSize = (int) Math.max(minBufferSize,
durationUsToFrames(MAX_BUFFER_DURATION_US) * frameSize); durationUsToFrames(MAX_BUFFER_DURATION_US) * pcmFrameSize);
bufferSize = multipliedBufferSize < minAppBufferSize ? minAppBufferSize bufferSize = multipliedBufferSize < minAppBufferSize ? minAppBufferSize
: multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize : multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize
: multipliedBufferSize; : multipliedBufferSize;
} }
bufferSizeUs = framesToDurationUs(bytesToFrames(bufferSize)); bufferSizeUs = passthrough ? C.UNKNOWN_TIME_US
: framesToDurationUs(pcmBytesToFrames(bufferSize));
} }
/** /**
...@@ -473,13 +470,26 @@ public final class AudioTrack { ...@@ -473,13 +470,26 @@ public final class AudioTrack {
} }
/** /**
* Returns the size of this {@link AudioTrack}'s buffer in microseconds, given its current * Returns the size of this {@link AudioTrack}'s buffer in bytes.
* configuration. * <p>
* The value returned from this method may change as a result of calling one of the
* {@link #configure} methods.
*
* @return The size of the buffer in bytes.
*/
public int getBufferSize() {
return bufferSize;
}
/**
* Returns the size of the buffer in microseconds for PCM {@link AudioTrack}s, or
* {@link C#UNKNOWN_TIME_US} for passthrough {@link AudioTrack}s.
* <p> * <p>
* The duration returned from this method may change as a result of calling one of the * The value returned from this method may change as a result of calling one of the
* {@link #configure} methods. * {@link #configure} methods.
* *
* @return The size of the buffer in microseconds. * @return The size of the buffer in microseconds for PCM {@link AudioTrack}s, or
* {@link C#UNKNOWN_TIME_US} for passthrough {@link AudioTrack}s.
*/ */
public long getBufferSizeUs() { public long getBufferSizeUs() {
return bufferSizeUs; return bufferSizeUs;
...@@ -544,26 +554,23 @@ public final class AudioTrack { ...@@ -544,26 +554,23 @@ public final class AudioTrack {
int result = 0; int result = 0;
if (temporaryBufferSize == 0) { if (temporaryBufferSize == 0) {
if (passthroughBitrate == UNKNOWN_BITRATE) {
if (isAc3Passthrough()) {
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate);
} else if (isDtsPassthrough()) {
int unscaledBitrate = size * 8 * sampleRate;
int divisor = 1000 * 512;
passthroughBitrate = (unscaledBitrate + divisor / 2) / divisor;
}
}
// This is the first time we've seen this {@code buffer}. // This is the first time we've seen this {@code buffer}.
temporaryBufferSize = size;
buffer.position(offset);
if (passthrough && framesPerEncodedSample == 0) {
// If this is the first encoded sample, calculate the sample size in frames.
framesPerEncodedSample = getFramesPerEncodedSample(encoding, buffer);
}
long frames = passthrough ? framesPerEncodedSample : pcmBytesToFrames(size);
long bufferDurationUs = framesToDurationUs(frames);
// 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 = presentationTimeUs - framesToDurationUs(bytesToFrames(size)); long bufferStartTime = presentationTimeUs - bufferDurationUs;
if (startMediaTimeState == START_NOT_SET) { if (startMediaTimeState == START_NOT_SET) {
startMediaTimeUs = Math.max(0, bufferStartTime); startMediaTimeUs = Math.max(0, bufferStartTime);
startMediaTimeState = START_IN_SYNC; startMediaTimeState = 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 = startMediaTimeUs long expectedBufferStartTime = startMediaTimeUs + framesToDurationUs(getSubmittedFrames());
+ framesToDurationUs(bytesToFrames(submittedBytes));
if (startMediaTimeState == START_IN_SYNC if (startMediaTimeState == 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 "
...@@ -578,11 +585,6 @@ public final class AudioTrack { ...@@ -578,11 +585,6 @@ public final class AudioTrack {
result |= RESULT_POSITION_DISCONTINUITY; result |= RESULT_POSITION_DISCONTINUITY;
} }
} }
}
if (temporaryBufferSize == 0) {
temporaryBufferSize = size;
buffer.position(offset);
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) {
// Copy {@code buffer} into {@code temporaryBuffer}. // Copy {@code buffer} into {@code temporaryBuffer}.
if (temporaryBuffer == null || temporaryBuffer.length < size) { if (temporaryBuffer == null || temporaryBuffer.length < size) {
...@@ -594,10 +596,10 @@ public final class AudioTrack { ...@@ -594,10 +596,10 @@ public final class AudioTrack {
} }
int bytesWritten = 0; int bytesWritten = 0;
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) { // passthrough == false
// Work out how many bytes we can write without the risk of blocking. // Work out how many bytes we can write without the risk of blocking.
int bytesPending = int bytesPending =
(int) (submittedBytes - (audioTrackUtil.getPlaybackHeadPosition() * frameSize)); (int) (submittedPcmBytes - (audioTrackUtil.getPlaybackHeadPosition() * pcmFrameSize));
int bytesToWrite = bufferSize - bytesPending; int bytesToWrite = bufferSize - bytesPending;
if (bytesToWrite > 0) { if (bytesToWrite > 0) {
bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite); bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite);
...@@ -615,8 +617,13 @@ public final class AudioTrack { ...@@ -615,8 +617,13 @@ public final class AudioTrack {
} }
temporaryBufferSize -= bytesWritten; temporaryBufferSize -= bytesWritten;
submittedBytes += bytesWritten; if (!passthrough) {
submittedPcmBytes += bytesWritten;
}
if (temporaryBufferSize == 0) { if (temporaryBufferSize == 0) {
if (passthrough) {
submittedEncodedFrames += framesPerEncodedSample;
}
result |= RESULT_BUFFER_CONSUMED; result |= RESULT_BUFFER_CONSUMED;
} }
return result; return result;
...@@ -628,7 +635,7 @@ public final class AudioTrack { ...@@ -628,7 +635,7 @@ public final class AudioTrack {
*/ */
public void handleEndOfStream() { public void handleEndOfStream() {
if (isInitialized()) { if (isInitialized()) {
audioTrackUtil.handleEndOfStream(bytesToFrames(submittedBytes)); audioTrackUtil.handleEndOfStream(getSubmittedFrames());
} }
} }
...@@ -643,7 +650,7 @@ public final class AudioTrack { ...@@ -643,7 +650,7 @@ public final class AudioTrack {
*/ */
public boolean hasPendingData() { public boolean hasPendingData() {
return isInitialized() return isInitialized()
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition() && (getSubmittedFrames() > audioTrackUtil.getPlaybackHeadPosition()
|| overrideHasPendingData()); || overrideHasPendingData());
} }
...@@ -694,7 +701,9 @@ public final class AudioTrack { ...@@ -694,7 +701,9 @@ public final class AudioTrack {
*/ */
public void reset() { public void reset() {
if (isInitialized()) { if (isInitialized()) {
submittedBytes = 0; submittedPcmBytes = 0;
submittedEncodedFrames = 0;
framesPerEncodedSample = 0;
temporaryBufferSize = 0; temporaryBufferSize = 0;
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
latencyUs = 0; latencyUs = 0;
...@@ -818,7 +827,7 @@ public final class AudioTrack { ...@@ -818,7 +827,7 @@ public final class AudioTrack {
audioTimestampSet = false; audioTimestampSet = false;
} }
} }
if (getLatencyMethod != null) { if (getLatencyMethod != null && !passthrough) {
try { try {
// 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).
...@@ -865,13 +874,8 @@ public final class AudioTrack { ...@@ -865,13 +874,8 @@ public final class AudioTrack {
throw new InitializationException(state, sampleRate, channelConfig, bufferSize); throw new InitializationException(state, sampleRate, channelConfig, bufferSize);
} }
private long bytesToFrames(long byteCount) { private long pcmBytesToFrames(long byteCount) {
if (isPassthrough()) { return byteCount / pcmFrameSize;
return passthroughBitrate == UNKNOWN_BITRATE
? 0L : byteCount * 8 * sampleRate / (1000 * passthroughBitrate);
} else {
return byteCount / frameSize;
}
} }
private long framesToDurationUs(long frameCount) { private long framesToDurationUs(long frameCount) {
...@@ -882,6 +886,10 @@ public final class AudioTrack { ...@@ -882,6 +886,10 @@ public final class AudioTrack {
return (durationUs * sampleRate) / C.MICROS_PER_SECOND; return (durationUs * sampleRate) / C.MICROS_PER_SECOND;
} }
private long getSubmittedFrames() {
return passthrough ? submittedEncodedFrames : pcmBytesToFrames(submittedPcmBytes);
}
private void resetSyncParams() { private void resetSyncParams() {
smoothedPlayheadOffsetUs = 0; smoothedPlayheadOffsetUs = 0;
playheadOffsetCount = 0; playheadOffsetCount = 0;
...@@ -891,24 +899,12 @@ public final class AudioTrack { ...@@ -891,24 +899,12 @@ public final class AudioTrack {
lastTimestampSampleTimeUs = 0; lastTimestampSampleTimeUs = 0;
} }
private boolean isPassthrough() {
return isAc3Passthrough() || isDtsPassthrough();
}
private boolean isAc3Passthrough() {
return encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3;
}
private boolean isDtsPassthrough() {
return encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD;
}
/** /**
* Returns whether to work around problems with passthrough audio tracks. * Returns whether to work around problems with passthrough audio tracks.
* See [Internal: b/18899620, b/19187573, b/21145353]. * See [Internal: b/18899620, b/19187573, b/21145353].
*/ */
private boolean needsPassthroughWorkarounds() { private boolean needsPassthroughWorkarounds() {
return Util.SDK_INT < 23 && isAc3Passthrough(); return Util.SDK_INT < 23 && (encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3);
} }
/** /**
...@@ -938,6 +934,21 @@ public final class AudioTrack { ...@@ -938,6 +934,21 @@ public final class AudioTrack {
} }
} }
private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) {
if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) {
// Calculate the sample size in frames as per ETSI TS 102 114 F.3.2.1.
int nblks = ((buffer.get(buffer.position() + 4) & 0x01) << 6)
| ((buffer.get(buffer.position() + 5) & 0xFC) >> 2);
return (nblks + 1) * 32;
} else if (encoding == C.ENCODING_AC3) {
return Ac3Util.getAc3SamplesPerSyncframe();
} else if (encoding == C.ENCODING_E_AC3) {
return Ac3Util.parseEac3SamplesPerSyncframe(buffer);
} else {
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
}
}
/** /**
* Wraps an {@link android.media.AudioTrack} to expose useful utility methods. * Wraps an {@link android.media.AudioTrack} to expose useful utility methods.
*/ */
......
...@@ -17,19 +17,41 @@ package com.google.android.exoplayer.util; ...@@ -17,19 +17,41 @@ package com.google.android.exoplayer.util;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import java.nio.ByteBuffer;
/** /**
* Utility methods for parsing AC-3 headers. * Utility methods for parsing AC-3 headers.
*/ */
public final class Ac3Util { public final class Ac3Util {
/** Sample rates, indexed by fscod. */ /**
* The number of new samples per (E-)AC-3 audio block.
*/
private static final int SAMPLES_PER_AUDIO_BLOCK = 256;
/**
* Each syncframe has 6 blocks that provide 256 new samples. See ETSI TS 102 366 subsection 4.1.
*/
private static final int AC3_SAMPLES_PER_SYNCFRAME = 6 * SAMPLES_PER_AUDIO_BLOCK;
/**
* Number of audio blocks per E-AC-3 sync frame, indexed by numblkscod.
*/
private static final int[] AUDIO_BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6};
/**
* Sample rates, indexed by fscod.
*/
private static final int[] SAMPLE_RATES = new int[] {48000, 44100, 32000}; private static final int[] SAMPLE_RATES = new int[] {48000, 44100, 32000};
/** Channel counts, indexed by acmod. */ /**
* Channel counts, indexed by acmod.
*/
private static final int[] CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5}; private static final int[] CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/** Nominal bitrates in kbps, indexed by bit_rate_code. */ /**
* Nominal bitrates in kbps, indexed by bit_rate_code.
*/
private static final int[] BITRATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, private static final int[] BITRATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192,
224, 256, 320, 384, 448, 512, 576, 640}; 224, 256, 320, 384, 448, 512, 576, 640};
/** 16-bit words per sync frame, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) */ /**
* 16-bit words per sync frame, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.)
*/
private static final int[] FRMSIZECOD_TO_FRAME_SIZE_44_1 = new int[] {69, 87, 104, 121, 139, 174, private static final int[] FRMSIZECOD_TO_FRAME_SIZE_44_1 = new int[] {69, 87, 104, 121, 139, 174,
208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393}; 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393};
...@@ -161,6 +183,31 @@ public final class Ac3Util { ...@@ -161,6 +183,31 @@ public final class Ac3Util {
return (unscaledBitrate + divisor / 2) / divisor; return (unscaledBitrate + divisor / 2) / divisor;
} }
/**
* Returns the number of samples per AC-3 syncframe.
*/
public static int getAc3SamplesPerSyncframe() {
return AC3_SAMPLES_PER_SYNCFRAME;
}
/**
* Returns the number of samples per syncframe for the E-AC-3 frame in {@code buffer}.
*
* @param buffer The frame to parse.
* @return The number of samples per syncframe.
*/
public static int parseEac3SamplesPerSyncframe(ByteBuffer buffer) {
// See ETSI TS 102 366 subsection E.1.2.2.
int audioBlocks;
if (((buffer.get(buffer.position() + 4) & 0xC0) >> 6) == 0x03) { // fscod
audioBlocks = 6;
} else {
int numblkscod = (buffer.get(buffer.position() + 4) & 0x30) >> 4;
audioBlocks = AUDIO_BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod] * 256;
}
return audioBlocks * SAMPLES_PER_AUDIO_BLOCK;
}
private Ac3Util() { private Ac3Util() {
// Prevent instantiation. // Prevent instantiation.
} }
......
...@@ -105,9 +105,9 @@ public final class LogcatLogger implements ExoPlayer.Listener, ...@@ -105,9 +105,9 @@ public final class LogcatLogger implements ExoPlayer.Listener,
} }
@Override @Override
public void onAudioTrackUnderrun(long audioTrackBufferSizeMs, long elapsedSinceLastFeedMs) { public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
Log.e(tag, "Audio track underrun (" + audioTrackBufferSizeMs + ", " + elapsedSinceLastFeedMs Log.e(tag, "Audio track underrun (" + bufferSize + ", " + bufferSizeMs + ", "
+ ")"); + elapsedSinceLastFeedMs + ")");
} }
@Override @Override
......
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