Commit 57ee90a9 by Oliver Woodman

Clean up AudioTrack.

parent a4f1e3ce
......@@ -385,7 +385,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
int result = readSource(positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format;
audioTrack.reconfigure(format.getFrameworkMediaFormatV16(), false);
audioTrack.configure(format.getFrameworkMediaFormatV16(), false);
return true;
}
return false;
......
......@@ -246,7 +246,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null;
audioTrack.reconfigure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
audioTrack.configure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
}
/**
......
......@@ -36,19 +36,24 @@ import java.nio.ByteBuffer;
/**
* Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles
* playback position smoothing, non-blocking writes and reconfiguration.
*
* <p>If {@link #isInitialized} returns {@code false}, the instance can be {@link #initialize}d.
* After initialization, start playback by calling {@link #play}.
*
* <p>Call {@link #handleBuffer} to write data for playback.
*
* <p>Call {@link #handleDiscontinuity} when a buffer is skipped.
*
* <p>Call {@link #reconfigure} when the output format changes.
*
* <p>Call {@link #reset} to free resources. It is safe to re-{@link #initialize} the instance.
*
* <p>Call {@link #release} when the instance will no longer be used.
* <p>
* Before starting playback, specify the input audio format by calling one of the {@link #configure}
* methods and {@link #initialize} the instance, optionally specifying an audio session.
* <p>
* 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
* back written data.
* <p>
* 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
* instance before writing more data.
* <p>
* 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)
public final class AudioTrack {
......@@ -58,7 +63,9 @@ public final class AudioTrack {
*/
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 InitializationException(
......@@ -75,7 +82,9 @@ public final class AudioTrack {
*/
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 WriteException(int errorCode) {
......@@ -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;
/** 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;
/** 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;
/** 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;
/** 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;
/** 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;
/**
* A multiplication factor to apply to the minimum buffer size requested by the underlying
......@@ -171,7 +192,9 @@ public final class AudioTrack {
private final long[] playheadOffsets;
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 audioTrack;
......@@ -307,85 +330,25 @@ 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;
}
/**
* Reconfigures the audio track to play back media in {@code format}, inferring a buffer size from
* the format.
* Configures (or 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 passthrough Whether to play back using a passthrough encoding.
*/
public void reconfigure(MediaFormat format, boolean passthrough) {
reconfigure(format, passthrough, 0);
public void configure(MediaFormat format, boolean passthrough) {
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 passthrough Whether to playback using a passthrough encoding.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a
* 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 channelConfig;
switch (channelCount) {
......@@ -395,9 +358,21 @@ public final class AudioTrack {
case 2:
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
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:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
break;
case 7:
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
break;
case 8:
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break;
......@@ -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
* configuration.
* <p>
* 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.
*/
......
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