/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import java.lang.ref.WeakReference; import java.lang.IllegalArgumentException; import java.lang.IllegalStateException; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.media.AudioManager; import android.util.Log; /** * The AudioTrack class manages and plays a single audio resource for Java applications. * It allows streaming PCM audio buffers to the audio hardware for playback. This is * achieved by "pushing" the data to the AudioTrack object using one of the * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods. * *
An AudioTrack instance can operate under two modes: static or streaming.
* In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using
* one of the {@code write()} methods. These are blocking and return when the data has been
* transferred from the Java layer to the native layer and queued for playback. The streaming
* mode is most useful when playing blocks of audio data that for instance are:
*
*
Upon creation, an AudioTrack object initializes its associated audio buffer.
* The size of this buffer, specified during the construction, determines how long an AudioTrack
* can play before running out of data. After creating an auxiliary effect (e.g.
* {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
* {@link android.media.audiofx.AudioEffect#getId()} and use it when calling
* this method to attach the audio track to the effect.
* To detach the effect from the audio track, call this method with a
* null effect id.
*
* @param effectId system wide unique id of the effect to attach
* @return error code or success, see {@link #SUCCESS},
* {@link #ERROR_INVALID_OPERATION}, {@link #ERROR_BAD_VALUE}
*/
public int attachAuxEffect(int effectId) {
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
return native_attachAuxEffect(effectId);
}
/**
* Sets the send level of the audio track to the attached auxiliary effect
* {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
* By default the send level is 0, so even if an effect is attached to the player
* this method must be called for the effect to be applied.
* Note that the passed level value is a raw scalar. UI controls should be scaled
* logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
* so an appropriate conversion from linear UI input x to level is:
* x == 0 -> level = 0
* 0 < x <= R -> level = 10^(72*(x-R)/20/R)
*
* @param level send level scalar
* @return error code or success, see {@link #SUCCESS},
* {@link #ERROR_INVALID_OPERATION}
*/
public int setAuxEffectSendLevel(float level) {
if (mState != STATE_INITIALIZED) {
return ERROR_INVALID_OPERATION;
}
// clamp the level
if (level < getMinVolume()) {
level = getMinVolume();
}
if (level > getMaxVolume()) {
level = getMaxVolume();
}
native_setAuxEffectSendLevel(level);
return SUCCESS;
}
//---------------------------------------------------------
// Interface definitions
//--------------------
/**
* Interface definition for a callback to be invoked when the playback head position of
* an AudioTrack has reached a notification marker or has increased by a certain period.
*/
public interface OnPlaybackPositionUpdateListener {
/**
* Called on the listener to notify it that the previously set marker has been reached
* by the playback head.
*/
void onMarkerReached(AudioTrack track);
/**
* Called on the listener to periodically notify it that the playback head has reached
* a multiple of the notification period.
*/
void onPeriodicNotification(AudioTrack track);
}
//---------------------------------------------------------
// Inner classes
//--------------------
/**
* Helper class to handle the forwarding of native events to the appropriate listener
* (potentially) handled in a different thread
*/
private class NativeEventHandlerDelegate {
private final AudioTrack mAudioTrack;
private final Handler mHandler;
NativeEventHandlerDelegate(AudioTrack track, Handler handler) {
mAudioTrack = track;
// find the looper for our new event handler
Looper looper;
if (handler != null) {
looper = handler.getLooper();
} else {
// no given handler, use the looper the AudioTrack was created in
looper = mInitializationLooper;
}
// construct the event handler with this looper
if (looper != null) {
// implement the event handler delegate
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
if (mAudioTrack == null) {
return;
}
OnPlaybackPositionUpdateListener listener = null;
synchronized (mPositionListenerLock) {
listener = mAudioTrack.mPositionListener;
}
switch(msg.what) {
case NATIVE_EVENT_MARKER:
if (listener != null) {
listener.onMarkerReached(mAudioTrack);
}
break;
case NATIVE_EVENT_NEW_POS:
if (listener != null) {
listener.onPeriodicNotification(mAudioTrack);
}
break;
default:
Log.e(TAG, "[ android.media.AudioTrack.NativeEventHandler ] " +
"Unknown event type: " + msg.what);
break;
}
}
};
} else {
mHandler = null;
}
}
Handler getHandler() {
return mHandler;
}
}
//---------------------------------------------------------
// Java methods called from the native side
//--------------------
@SuppressWarnings("unused")
private static void postEventFromNative(Object audiotrack_ref,
int what, int arg1, int arg2, Object obj) {
//logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
AudioTrack track = (AudioTrack)((WeakReference)audiotrack_ref).get();
if (track == null) {
return;
}
if (track.mEventHandlerDelegate != null) {
Message m =
track.mEventHandlerDelegate.getHandler().obtainMessage(what, arg1, arg2, obj);
track.mEventHandlerDelegate.getHandler().sendMessage(m);
}
}
//---------------------------------------------------------
// Native methods called from the Java side
//--------------------
private native final int native_setup(Object audiotrack_this,
int streamType, int sampleRate, int nbChannels, int audioFormat,
int buffSizeInBytes, int mode, int[] sessionId);
private native final void native_finalize();
private native final void native_release();
private native final void native_start();
private native final void native_stop();
private native final void native_pause();
private native final void native_flush();
private native final int native_write_byte(byte[] audioData,
int offsetInBytes, int sizeInBytes, int format);
private native final int native_write_short(short[] audioData,
int offsetInShorts, int sizeInShorts, int format);
private native final int native_reload_static();
private native final int native_get_native_frame_count();
private native final void native_setVolume(float leftVolume, float rightVolume);
private native final int native_set_playback_rate(int sampleRateInHz);
private native final int native_get_playback_rate();
private native final int native_set_marker_pos(int marker);
private native final int native_get_marker_pos();
private native final int native_set_pos_update_period(int updatePeriod);
private native final int native_get_pos_update_period();
private native final int native_set_position(int position);
private native final int native_get_position();
private native final int native_set_loop(int start, int end, int loopCount);
static private native final int native_get_output_sample_rate(int streamType);
static private native final int native_get_min_buff_size(
int sampleRateInHz, int channelConfig, int audioFormat);
private native final int native_get_session_id();
private native final int native_attachAuxEffect(int effectId);
private native final void native_setAuxEffectSendLevel(float level);
//---------------------------------------------------------
// Utility methods
//------------------
private static void logd(String msg) {
Log.d(TAG, "[ android.media.AudioTrack ] " + msg);
}
private static void loge(String msg) {
Log.e(TAG, "[ android.media.AudioTrack ] " + msg);
}
}
* For an AudioTrack using the static mode, this size is the maximum size of the sound that can
* be played from it.
* For the streaming mode, data will be written to the hardware in chunks of
* sizes less than or equal to the total buffer size.
*/
public class AudioTrack
{
//---------------------------------------------------------
// Constants
//--------------------
/** Minimum value for a channel volume */
private static final float VOLUME_MIN = 0.0f;
/** Maximum value for a channel volume */
private static final float VOLUME_MAX = 1.0f;
/** indicates AudioTrack state is stopped */
public static final int PLAYSTATE_STOPPED = 1; // matches SL_PLAYSTATE_STOPPED
/** indicates AudioTrack state is paused */
public static final int PLAYSTATE_PAUSED = 2; // matches SL_PLAYSTATE_PAUSED
/** indicates AudioTrack state is playing */
public static final int PLAYSTATE_PLAYING = 3; // matches SL_PLAYSTATE_PLAYING
// keep these values in sync with android_media_AudioTrack.cpp
/**
* Creation mode where audio data is transferred from Java to the native layer
* only once before the audio starts playing.
*/
public static final int MODE_STATIC = 0;
/**
* Creation mode where audio data is streamed from Java to the native layer
* as the audio is playing.
*/
public static final int MODE_STREAM = 1;
/**
* State of an AudioTrack that was not successfully initialized upon creation.
*/
public static final int STATE_UNINITIALIZED = 0;
/**
* State of an AudioTrack that is ready to be used.
*/
public static final int STATE_INITIALIZED = 1;
/**
* State of a successfully initialized AudioTrack that uses static data,
* but that hasn't received that data yet.
*/
public static final int STATE_NO_STATIC_DATA = 2;
// Error codes:
// to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp
/**
* Denotes a successful operation.
*/
public static final int SUCCESS = 0;
/**
* Denotes a generic operation failure.
*/
public static final int ERROR = -1;
/**
* Denotes a failure due to the use of an invalid value.
*/
public static final int ERROR_BAD_VALUE = -2;
/**
* Denotes a failure due to the improper use of a method.
*/
public static final int ERROR_INVALID_OPERATION = -3;
private static final int ERROR_NATIVESETUP_AUDIOSYSTEM = -16;
private static final int ERROR_NATIVESETUP_INVALIDCHANNELMASK = -17;
private static final int ERROR_NATIVESETUP_INVALIDFORMAT = -18;
private static final int ERROR_NATIVESETUP_INVALIDSTREAMTYPE = -19;
private static final int ERROR_NATIVESETUP_NATIVEINITFAILED = -20;
// Events:
// to keep in sync with frameworks/base/include/media/AudioTrack.h
/**
* Event id denotes when playback head has reached a previously set marker.
*/
private static final int NATIVE_EVENT_MARKER = 3;
/**
* Event id denotes when previously set update period has elapsed during playback.
*/
private static final int NATIVE_EVENT_NEW_POS = 4;
private final static String TAG = "AudioTrack-Java";
//--------------------------------------------------------------------------
// Member variables
//--------------------
/**
* Indicates the state of the AudioTrack instance.
*/
private int mState = STATE_UNINITIALIZED;
/**
* Indicates the play state of the AudioTrack instance.
*/
private int mPlayState = PLAYSTATE_STOPPED;
/**
* Lock to make sure mPlayState updates are reflecting the actual state of the object.
*/
private final Object mPlayStateLock = new Object();
/**
* The listener the AudioTrack notifies when the playback position reaches a marker
* or for periodic updates during the progression of the playback head.
* @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener)
*/
private OnPlaybackPositionUpdateListener mPositionListener = null;
/**
* Lock to protect event listener updates against event notifications.
*/
private final Object mPositionListenerLock = new Object();
/**
* Size of the native audio buffer.
*/
private int mNativeBufferSizeInBytes = 0;
/**
* Handler for marker events coming from the native code.
*/
private NativeEventHandlerDelegate mEventHandlerDelegate = null;
/**
* Looper associated with the thread that creates the AudioTrack instance.
*/
private Looper mInitializationLooper = null;
/**
* The audio data sampling rate in Hz.
*/
private int mSampleRate; // initialized by all constructors
/**
* The number of audio output channels (1 is mono, 2 is stereo).
*/
private int mChannelCount = 1;
/**
* The audio channel mask.
*/
private int mChannels = AudioFormat.CHANNEL_OUT_MONO;
/**
* The type of the audio stream to play. See
* {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
* {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},
* {@link AudioManager#STREAM_ALARM}, {@link AudioManager#STREAM_NOTIFICATION}, and
* {@link AudioManager#STREAM_DTMF}.
*/
private int mStreamType = AudioManager.STREAM_MUSIC;
/**
* The way audio is consumed by the hardware, streaming or static.
*/
private int mDataLoadMode = MODE_STREAM;
/**
* The current audio channel configuration.
*/
private int mChannelConfiguration = AudioFormat.CHANNEL_OUT_MONO;
/**
* The encoding of the audio samples.
* @see AudioFormat#ENCODING_PCM_8BIT
* @see AudioFormat#ENCODING_PCM_16BIT
*/
private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
/**
* Audio session ID
*/
private int mSessionId = 0;
//--------------------------------
// Used exclusively by native code
//--------------------
/**
* Accessed by native methods: provides access to C++ AudioTrack object.
*/
@SuppressWarnings("unused")
private int mNativeTrackInJavaObj;
/**
* Accessed by native methods: provides access to the JNI data (i.e. resources used by
* the native AudioTrack object, but not stored in it).
*/
@SuppressWarnings("unused")
private int mJniData;
//--------------------------------------------------------------------------
// Constructor, Finalize
//--------------------
/**
* Class constructor.
* @param streamType the type of the audio stream. See
* {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
* {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},
* {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}.
* @param sampleRateInHz the sample rate expressed in Hertz.
* @param channelConfig describes the configuration of the audio channels.
* See {@link AudioFormat#CHANNEL_OUT_MONO} and
* {@link AudioFormat#CHANNEL_OUT_STEREO}
* @param audioFormat the format in which the audio data is represented.
* See {@link AudioFormat#ENCODING_PCM_16BIT} and
* {@link AudioFormat#ENCODING_PCM_8BIT}
* @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is read
* from for playback. If using the AudioTrack in streaming mode, you can write data into
* this buffer in smaller chunks than this size. If using the AudioTrack in static mode,
* this is the maximum size of the sound that will be played for this instance.
* See {@link #getMinBufferSize(int, int, int)} to determine the minimum required buffer size
* for the successful creation of an AudioTrack instance in streaming mode. Using values
* smaller than getMinBufferSize() will result in an initialization failure.
* @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
* @throws java.lang.IllegalArgumentException
*/
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
bufferSizeInBytes, mode, 0);
}
/**
* Class constructor with audio session. Use this constructor when the AudioTrack must be
* attached to a particular audio session. The primary use of the audio session ID is to
* associate audio effects to a particular instance of AudioTrack: if an audio session ID
* is provided when creating an AudioEffect, this effect will be applied only to audio tracks
* and media players in the same session and not to the output mix.
* When an AudioTrack is created without specifying a session, it will create its own session
* which can be retreived by calling the {@link #getAudioSessionId()} method.
* If a non-zero session ID is provided, this AudioTrack will share effects attached to this
* session
* with all other media players or audio tracks in the same session, otherwise a new session
* will be created for this track if none is supplied.
* @param streamType the type of the audio stream. See
* {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
* {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},
* {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}.
* @param sampleRateInHz the sample rate expressed in Hertz.
* @param channelConfig describes the configuration of the audio channels.
* See {@link AudioFormat#CHANNEL_OUT_MONO} and
* {@link AudioFormat#CHANNEL_OUT_STEREO}
* @param audioFormat the format in which the audio data is represented.
* See {@link AudioFormat#ENCODING_PCM_16BIT} and
* {@link AudioFormat#ENCODING_PCM_8BIT}
* @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is read
* from for playback. If using the AudioTrack in streaming mode, you can write data into
* this buffer in smaller chunks than this size. If using the AudioTrack in static mode,
* this is the maximum size of the sound that will be played for this instance.
* See {@link #getMinBufferSize(int, int, int)} to determine the minimum required buffer size
* for the successful creation of an AudioTrack instance in streaming mode. Using values
* smaller than getMinBufferSize() will result in an initialization failure.
* @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
* @param sessionId Id of audio session the AudioTrack must be attached to
* @throws java.lang.IllegalArgumentException
*/
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode, int sessionId)
throws IllegalArgumentException {
mState = STATE_UNINITIALIZED;
// remember which looper is associated with the AudioTrack instantiation
if ((mInitializationLooper = Looper.myLooper()) == null) {
mInitializationLooper = Looper.getMainLooper();
}
audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
audioBuffSizeCheck(bufferSizeInBytes);
if (sessionId < 0) {
throw (new IllegalArgumentException("Invalid audio session ID: "+sessionId));
}
int[] session = new int[1];
session[0] = sessionId;
// native initialization
int initResult = native_setup(new WeakReference