Commit 57ee90a9 by Oliver Woodman

Clean up AudioTrack.

parent a4f1e3ce
...@@ -385,7 +385,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer ...@@ -385,7 +385,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
int result = readSource(positionUs, formatHolder, null, false); int result = readSource(positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
audioTrack.reconfigure(format.getFrameworkMediaFormatV16(), false); audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
return true; return true;
} }
return false; return false;
......
...@@ -246,7 +246,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem ...@@ -246,7 +246,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override @Override
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) { protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null; boolean passthrough = passthroughMediaFormat != null;
audioTrack.reconfigure(passthrough ? passthroughMediaFormat : outputFormat, passthrough); audioTrack.configure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
} }
/** /**
......
...@@ -36,19 +36,24 @@ import java.nio.ByteBuffer; ...@@ -36,19 +36,24 @@ import java.nio.ByteBuffer;
/** /**
* Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles * Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles
* playback position smoothing, non-blocking writes and reconfiguration. * playback position smoothing, non-blocking writes and reconfiguration.
* * <p>
* <p>If {@link #isInitialized} returns {@code false}, the instance can be {@link #initialize}d. * Before starting playback, specify the input audio format by calling one of the {@link #configure}
* After initialization, start playback by calling {@link #play}. * methods and {@link #initialize} the instance, optionally specifying an audio session.
* * <p>
* <p>Call {@link #handleBuffer} to write data for playback. * Call {@link #handleBuffer(ByteBuffer, int, int, long)} to write data to play back, and
* * {@link #handleDiscontinuity()} when a buffer is skipped. Call {@link #play()} to start playing
* <p>Call {@link #handleDiscontinuity} when a buffer is skipped. * back written data.
* * <p>
* <p>Call {@link #reconfigure} when the output format changes. * Call {@link #configure} again whenever the input format changes. If {@link #isInitialized()}
* * returns false after calling {@link #configure}, it is necessary to re-{@link #initialize} the
* <p>Call {@link #reset} to free resources. It is safe to re-{@link #initialize} the instance. * instance before writing more data.
* * <p>
* <p>Call {@link #release} when the instance will no longer be used. * The underlying framework audio track is created by {@link #initialize} and released
* asynchronously by {@link #reset} (and {@link #configure}, unless the format is unchanged).
* Reinitialization blocks until releasing the old audio track completes. It is safe to
* re-{@link #initialize} the instance after calling {@link #reset()}, without reconfiguration.
* <p>
* Call {@link #release()} when the instance will no longer be used.
*/ */
@TargetApi(16) @TargetApi(16)
public final class AudioTrack { public final class AudioTrack {
...@@ -58,7 +63,9 @@ public final class AudioTrack { ...@@ -58,7 +63,9 @@ public final class AudioTrack {
*/ */
public static final class InitializationException extends Exception { public static final class InitializationException extends Exception {
/** The state as reported by {@link android.media.AudioTrack#getState()}. */ /**
* The state as reported by {@link android.media.AudioTrack#getState()}.
*/
public final int audioTrackState; public final int audioTrackState;
public InitializationException( public InitializationException(
...@@ -75,7 +82,9 @@ public final class AudioTrack { ...@@ -75,7 +82,9 @@ public final class AudioTrack {
*/ */
public static final class WriteException extends Exception { public static final class WriteException extends Exception {
/** The value returned from {@link android.media.AudioTrack#write(byte[], int, int)}. */ /**
* The value returned from {@link android.media.AudioTrack#write(byte[], int, int)}.
*/
public final int errorCode; public final int errorCode;
public WriteException(int errorCode) { public WriteException(int errorCode) {
...@@ -97,20 +106,32 @@ public final class AudioTrack { ...@@ -97,20 +106,32 @@ public final class AudioTrack {
} }
/** Returned in the result of {@link #handleBuffer} if the buffer was discontinuous. */ /**
* Returned in the result of {@link #handleBuffer} if the buffer was discontinuous.
*/
public static final int RESULT_POSITION_DISCONTINUITY = 1; public static final int RESULT_POSITION_DISCONTINUITY = 1;
/** Returned in the result of {@link #handleBuffer} if the buffer can be released. */ /**
* Returned in the result of {@link #handleBuffer} if the buffer can be released.
*/
public static final int RESULT_BUFFER_CONSUMED = 2; public static final int RESULT_BUFFER_CONSUMED = 2;
/** Represents an unset {@link android.media.AudioTrack} session identifier. */ /**
* Represents an unset {@link android.media.AudioTrack} session identifier.
*/
public static final int SESSION_ID_NOT_SET = 0; public static final int SESSION_ID_NOT_SET = 0;
/** Returned by {@link #getCurrentPositionUs} when the position is not set. */ /**
* Returned by {@link #getCurrentPositionUs} when the position is not set.
*/
public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE;
/** A minimum length for the {@link android.media.AudioTrack} buffer, in microseconds. */ /**
* A minimum length for the {@link android.media.AudioTrack} buffer, in microseconds.
*/
private static final long MIN_BUFFER_DURATION_US = 250000; private static final long MIN_BUFFER_DURATION_US = 250000;
/** A maximum length for the {@link android.media.AudioTrack} buffer, in microseconds. */ /**
* A maximum length for the {@link android.media.AudioTrack} buffer, in microseconds.
*/
private static final long MAX_BUFFER_DURATION_US = 750000; private static final long MAX_BUFFER_DURATION_US = 750000;
/** /**
* A multiplication factor to apply to the minimum buffer size requested by the underlying * A multiplication factor to apply to the minimum buffer size requested by the underlying
...@@ -171,7 +192,9 @@ public final class AudioTrack { ...@@ -171,7 +192,9 @@ public final class AudioTrack {
private final long[] playheadOffsets; private final long[] playheadOffsets;
private final AudioTrackUtil audioTrackUtil; private final AudioTrackUtil audioTrackUtil;
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). */ /**
* Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}).
*/
private android.media.AudioTrack keepSessionIdAudioTrack; private android.media.AudioTrack keepSessionIdAudioTrack;
private android.media.AudioTrack audioTrack; private android.media.AudioTrack audioTrack;
...@@ -307,85 +330,25 @@ public final class AudioTrack { ...@@ -307,85 +330,25 @@ public final class AudioTrack {
} }
/** /**
* Initializes the audio track for writing new buffers using {@link #handleBuffer}. * Configures (or reconfigures) the audio track to play back media in {@code format}, inferring a
* * buffer size from the format.
* @return The audio track session identifier.
*/
public int initialize() throws InitializationException {
return initialize(SESSION_ID_NOT_SET);
}
/**
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
*
* @param sessionId Audio track session identifier to re-use, or {@link #SESSION_ID_NOT_SET} to
* create a new one.
* @return The new (or re-used) session identifier.
*/
public int initialize(int sessionId) throws InitializationException {
// If we're asynchronously releasing a previous audio track then we block until it has been
// released. This guarantees that we cannot end up in a state where we have multiple audio
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
// the shared memory that's available for audio track buffers. This would in turn cause the
// initialization of the audio track to fail.
releasingConditionVariable.block();
if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId);
}
checkAudioTrackInitialized();
sessionId = audioTrack.getAudioSessionId();
if (enablePreV21AudioSessionWorkaround) {
if (Util.SDK_INT < 21) {
// The workaround creates an audio track with a two byte buffer on the same session, and
// does not release it until this object is released, which keeps the session active.
if (keepSessionIdAudioTrack != null
&& sessionId != keepSessionIdAudioTrack.getAudioSessionId()) {
releaseKeepSessionIdAudioTrack();
}
if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
int encoding = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
}
}
}
audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
setAudioTrackVolume();
return sessionId;
}
/**
* Reconfigures the audio track to play back media in {@code format}, inferring a buffer size from
* the 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 play back using a passthrough encoding. * @param passthrough Whether to play back using a passthrough encoding.
*/ */
public void reconfigure(MediaFormat format, boolean passthrough) { public void configure(MediaFormat format, boolean passthrough) {
reconfigure(format, passthrough, 0); configure(format, passthrough, 0);
} }
/** /**
* 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 playback 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.
*/ */
public void reconfigure(MediaFormat format, boolean passthrough, int specifiedBufferSize) { public void configure(MediaFormat format, boolean passthrough, int specifiedBufferSize) {
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
...@@ -395,9 +358,21 @@ public final class AudioTrack { ...@@ -395,9 +358,21 @@ public final class AudioTrack {
case 2: case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break; break;
case 3:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 4:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
break;
case 5:
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
break;
case 6: case 6:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break; break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8: case 8:
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND; channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break; break;
...@@ -438,11 +413,71 @@ public final class AudioTrack { ...@@ -438,11 +413,71 @@ public final class AudioTrack {
} }
/** /**
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
*
* @return The audio track session identifier.
*/
public int initialize() throws InitializationException {
return initialize(SESSION_ID_NOT_SET);
}
/**
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
*
* @param sessionId Audio track session identifier to re-use, or {@link #SESSION_ID_NOT_SET} to
* create a new one.
* @return The new (or re-used) session identifier.
*/
public int initialize(int sessionId) throws InitializationException {
// If we're asynchronously releasing a previous audio track then we block until it has been
// released. This guarantees that we cannot end up in a state where we have multiple audio
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
// the shared memory that's available for audio track buffers. This would in turn cause the
// initialization of the audio track to fail.
releasingConditionVariable.block();
if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, encoding,
bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId);
}
checkAudioTrackInitialized();
sessionId = audioTrack.getAudioSessionId();
if (enablePreV21AudioSessionWorkaround) {
if (Util.SDK_INT < 21) {
// The workaround creates an audio track with a two byte buffer on the same session, and
// does not release it until this object is released, which keeps the session active.
if (keepSessionIdAudioTrack != null
&& sessionId != keepSessionIdAudioTrack.getAudioSessionId()) {
releaseKeepSessionIdAudioTrack();
}
if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
int encoding = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate,
channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId);
}
}
}
audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
setAudioTrackVolume();
return sessionId;
}
/**
* Returns the size of this {@link AudioTrack}'s buffer in microseconds, given its current * Returns the size of this {@link AudioTrack}'s buffer in microseconds, given its current
* configuration. * configuration.
* <p> * <p>
* The duration returned from this method may change as a result of calling one of the * The duration returned from this method may change as a result of calling one of the
* {@link #reconfigure} methods. * {@link #configure} methods.
* *
* @return The size of the buffer in microseconds. * @return The size of the buffer in microseconds.
*/ */
......
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