summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:05:43 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:05:43 -0800
commitf013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch)
tree7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /media/java
parente70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff)
downloadframeworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip
frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz
frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AmrInputStream.java141
-rw-r--r--media/java/android/media/AudioFormat.java51
-rw-r--r--media/java/android/media/AudioManager.java61
-rw-r--r--media/java/android/media/AudioRecord.java721
-rw-r--r--media/java/android/media/AudioService.java53
-rw-r--r--media/java/android/media/AudioSystem.java17
-rw-r--r--media/java/android/media/AudioTrack.java934
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java108
-rw-r--r--media/java/android/media/MediaPlayer.java552
-rw-r--r--media/java/android/media/MediaRecorder.java24
-rw-r--r--media/java/android/media/MediaScanner.java11
-rw-r--r--media/java/android/media/Ringtone.java13
-rw-r--r--media/java/android/media/RingtoneManager.java51
-rw-r--r--media/java/android/media/SoundPool.java13
14 files changed, 2636 insertions, 114 deletions
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
new file mode 100644
index 0000000..d40ca5a
--- /dev/null
+++ b/media/java/android/media/AmrInputStream.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.util.Config;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+
+/**
+ * AmrInputStream
+ * @hide
+ */
+public final class AmrInputStream extends InputStream
+{
+ static {
+ System.loadLibrary("media_jni");
+ }
+
+ private final static String TAG = "AmrInputStream";
+
+ // frame is 20 msec at 8.000 khz
+ private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
+
+ // pcm input stream
+ private InputStream mInputStream;
+
+ // native handle
+ private int mGae;
+
+ // result amr stream
+ private byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
+ private int mBufIn = 0;
+ private int mBufOut = 0;
+
+ // helper for bytewise read()
+ private byte[] mOneByte = new byte[1];
+
+ /**
+ * Create a new AmrInputStream, which converts 16 bit PCM to AMR
+ * @param inputStream InputStream containing 16 bit PCM.
+ */
+ public AmrInputStream(InputStream inputStream) {
+ mInputStream = inputStream;
+ mGae = GsmAmrEncoderNew();
+ GsmAmrEncoderInitialize(mGae);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int rtn = read(mOneByte, 0, 1);
+ return rtn == 1 ? (0xff & mOneByte[0]) : -1;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int offset, int length) throws IOException {
+ if (mGae == 0) throw new IllegalStateException("not open");
+
+ // local buffer of amr encoded audio empty
+ if (mBufOut >= mBufIn) {
+ // reset the buffer
+ mBufOut = 0;
+ mBufIn = 0;
+
+ // fetch a 20 msec frame of pcm
+ for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
+ int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
+ if (n == -1) return -1;
+ i += n;
+ }
+
+ // encode it
+ mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
+ }
+
+ // return encoded audio to user
+ if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
+ System.arraycopy(mBuf, mBufOut, b, offset, length);
+ mBufOut += length;
+
+ return length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ if (mInputStream != null) mInputStream.close();
+ } finally {
+ mInputStream = null;
+ try {
+ if (mGae != 0) GsmAmrEncoderCleanup(mGae);
+ } finally {
+ try {
+ if (mGae != 0) GsmAmrEncoderDelete(mGae);
+ } finally {
+ mGae = 0;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mGae != 0) {
+ close();
+ throw new IllegalStateException("someone forgot to close AmrInputStream");
+ }
+ }
+
+ //
+ // AudioRecord JNI interface
+ //
+ private static native int GsmAmrEncoderNew();
+ private static native void GsmAmrEncoderInitialize(int gae);
+ private static native int GsmAmrEncoderEncode(int gae,
+ byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
+ private static native void GsmAmrEncoderCleanup(int gae);
+ private static native void GsmAmrEncoderDelete(int gae);
+
+}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
new file mode 100644
index 0000000..c857e17
--- /dev/null
+++ b/media/java/android/media/AudioFormat.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * The AudioFormat class is used to access a number of audio format and
+ * channel configuration constants.
+ *
+ * {@hide Pending API council review}
+ */
+public class AudioFormat {
+
+ //---------------------------------------------------------
+ // Constants
+ //--------------------
+ /** Invalid audio data format */
+ public static final int ENCODING_INVALID = 0;
+ /** Default audio data format */
+ public static final int ENCODING_DEFAULT = 1;
+ /** Audio data format: PCM 16 bit per sample */
+ public static final int ENCODING_PCM_16BIT = 2; // accessed by native code
+ /** Audio data format: PCM 8 bit per sample */
+ public static final int ENCODING_PCM_8BIT = 3; // accessed by native code
+
+ /** Invalid audio channel configuration */
+ public static final int CHANNEL_CONFIGURATION_INVALID = 0;
+ /** Default audio channel configuration */
+ public static final int CHANNEL_CONFIGURATION_DEFAULT = 1;
+ /** Mono audio configuration */
+ public static final int CHANNEL_CONFIGURATION_MONO = 2;
+ /** Stereo (2 channel) audio configuration */
+ public static final int CHANNEL_CONFIGURATION_STEREO = 3;
+
+}
+
+
+
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 1c09c29..fb325f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -107,7 +107,12 @@ public class AudioManager {
public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
/** The audio stream for alarms */
public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
+ /** The audio stream for notifications */
+ public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
/** Number of audio streams */
+ /**
+ * @deprecated Use AudioSystem.getNumStreamTypes() instead
+ */
public static final int NUM_STREAMS = AudioSystem.NUM_STREAMS;
@@ -116,8 +121,9 @@ public class AudioManager {
6, // STREAM_VOICE_CALL
8, // STREAM_SYSTEM
8, // STREAM_RING
- 16, // STREAM_MUSIC
- 8 // STREAM_ALARM
+ 16, // STREAM_MUSIC
+ 8, // STREAM_ALARM
+ 8 // STREAM_NOTIFICATION
};
/** @hide Default volume index values for audio streams */
@@ -126,7 +132,8 @@ public class AudioManager {
5, // STREAM_SYSTEM
5, // STREAM_RING
11, // STREAM_MUSIC
- 6 // STREAM_ALARM
+ 6, // STREAM_ALARM
+ 5 // STREAM_NOTIFICATION
};
/**
@@ -617,7 +624,14 @@ public class AudioManager {
* headset; <var>false</var> to route audio to/from phone earpiece
*/
public void setBluetoothScoOn(boolean on){
- setRouting(MODE_IN_CALL, on ? ROUTE_BLUETOOTH : ROUTE_EARPIECE, ROUTE_ALL);
+ // Don't disable A2DP when turning off SCO.
+ // A2DP does not affect in-call routing.
+ setRouting(MODE_RINGTONE,
+ on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+ setRouting(MODE_NORMAL,
+ on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+ setRouting(MODE_IN_CALL,
+ on ? ROUTE_BLUETOOTH_SCO: ROUTE_EARPIECE, ROUTE_ALL);
}
/**
@@ -627,7 +641,32 @@ public class AudioManager {
* false if otherwise
*/
public boolean isBluetoothScoOn() {
- return (getRouting(MODE_IN_CALL) & ROUTE_BLUETOOTH) == 0 ? false : true;
+ return (getRouting(MODE_IN_CALL) & ROUTE_BLUETOOTH_SCO) == 0 ? false : true;
+ }
+
+ /**
+ * Sets A2DP audio routing to the Bluetooth headset on or off.
+ *
+ * @param on set <var>true</var> to route A2DP audio to/from Bluetooth
+ * headset; <var>false</var> disable A2DP audio
+ */
+ public void setBluetoothA2dpOn(boolean on){
+ // the audio flinger chooses A2DP as a higher priority,
+ // so there is no need to disable other routes.
+ setRouting(MODE_RINGTONE,
+ on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
+ setRouting(MODE_NORMAL,
+ on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
+ }
+
+ /**
+ * Checks whether A2DP audio routing to the Bluetooth headset is on or off.
+ *
+ * @return true if A2DP audio is being routed to/from Bluetooth headset;
+ * false if otherwise
+ */
+ public boolean isBluetoothA2dpOn() {
+ return (getRouting(MODE_NORMAL) & ROUTE_BLUETOOTH_A2DP) == 0 ? false : true;
}
/**
@@ -727,14 +766,22 @@ public class AudioManager {
*/
public static final int ROUTE_SPEAKER = AudioSystem.ROUTE_SPEAKER;
/**
- * Routing audio output to bluetooth
+ * @deprecated use {@link #ROUTE_BLUETOOTH_SCO}
*/
- public static final int ROUTE_BLUETOOTH = AudioSystem.ROUTE_BLUETOOTH;
+ @Deprecated public static final int ROUTE_BLUETOOTH = AudioSystem.ROUTE_BLUETOOTH_SCO;
+ /**
+ * Routing audio output to bluetooth SCO
+ */
+ public static final int ROUTE_BLUETOOTH_SCO = AudioSystem.ROUTE_BLUETOOTH_SCO;
/**
* Routing audio output to headset
*/
public static final int ROUTE_HEADSET = AudioSystem.ROUTE_HEADSET;
/**
+ * Routing audio output to bluetooth A2DP
+ */
+ public static final int ROUTE_BLUETOOTH_A2DP = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+ /**
* Used for mask parameter of {@link #setRouting(int,int,int)}.
*/
public static final int ROUTE_ALL = AudioSystem.ROUTE_ALL;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
new file mode 100644
index 0000000..7912003b
--- /dev/null
+++ b/media/java/android/media/AudioRecord.java
@@ -0,0 +1,721 @@
+/*
+ * 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.io.OutputStream;
+import java.io.IOException;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Thread;
+import java.nio.ByteBuffer;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * The AudioRecord class manages the audio resources for Java applications
+ * to record audio from the audio input hardware of the platform. This is
+ * achieved by "pulling" (reading) the data from the AudioRecord object. The
+ * application is responsible for polling the AudioRecord object in time using one of
+ * the following three methods: {@link #read(byte[], int)}, {@link #read(short[], int)}
+ * or {@link #read(ByteBuffer, int)}. The choice of which method to use will be based
+ * on the audio data storage format that is the most convenient for the user of AudioRecord.
+ *
+ * {@hide Pending API council review}
+ */
+public class AudioRecord
+{
+ //---------------------------------------------------------
+ // Constants
+ //--------------------
+ /**
+ * State of an AudioRecord that was not successfully initialized upon creation
+ */
+ public static final int STATE_UNINITIALIZED = 0;
+ /**
+ * State of an AudioRecord that is ready to be used
+ */
+ public static final int STATE_INITIALIZED = 1;
+
+ /**
+ * State of an AudioRecord this is not recording
+ */
+ public static final int RECORDSTATE_STOPPED = 1; // matches SL_RECORDSTATE_STOPPED
+ /**
+ * State of an AudioRecord this is recording
+ */
+ public static final int RECORDSTATE_RECORDING = 3;// matches SL_RECORDSTATE_RECORDING
+
+ // Error codes:
+ // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.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 AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT = -4;
+ private static final int AUDIORECORD_ERROR_SETUP_INVALIDCHANNELCOUNT = -5;
+ private static final int AUDIORECORD_ERROR_SETUP_INVALIDFORMAT = -6;
+ private static final int AUDIORECORD_ERROR_SETUP_INVALIDSTREAMTYPE = -7;
+ private static final int AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED = -8;
+
+ // Events:
+ // to keep in sync with frameworks/base/include/media/AudioRecord.h
+ /**
+ * Event id for when the recording head has reached a previously set marker.
+ */
+ protected static final int EVENT_MARKER = 2;
+ /**
+ * Event id for when the previously set update period has passed during recording.
+ */
+ protected static final int EVENT_NEW_POS = 3;
+
+ private final static String TAG = "AudioRecord-Java";
+
+
+ //---------------------------------------------------------
+ // Used exclusively by native code
+ //--------------------
+ /**
+ * Accessed by native methods: provides access to C++ AudioRecord object
+ */
+ @SuppressWarnings("unused")
+ private int mNativeRecorderInJavaObj;
+ /**
+ * Accessed by native methods: provides access to record source constants
+ */
+ @SuppressWarnings("unused")
+ private final static int SOURCE_DEFAULT = MediaRecorder.AudioSource.DEFAULT;
+ @SuppressWarnings("unused")
+ private final static int SOURCE_MIC = MediaRecorder.AudioSource.MIC;
+ /**
+ * Accessed by native methods: provides access to the callback data.
+ */
+ @SuppressWarnings("unused")
+ private int mNativeCallbackCookie;
+
+
+ //---------------------------------------------------------
+ // Member variables
+ //--------------------
+ /**
+ * The audio data sampling rate in Hz.
+ */
+ protected int mSampleRate = 22050;
+ /**
+ * The number of input audio channels (1 is mono, 2 is stereo)
+ */
+ protected int mChannelCount = 1;
+ /**
+ * The current audio channel configuration
+ */
+ protected int mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ /**
+ * The encoding of the audio samples.
+ * @see #AudioFormat.ENCODING_PCM_8BIT
+ * @see #AudioFormat.ENCODING_PCM_16BIT
+ */
+ protected int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ /**
+ * Where the audio data is recorded from.
+ */
+ protected int mRecordSource = MediaRecorder.AudioSource.DEFAULT;
+ /**
+ * Indicates the state of the AudioRecord instance.
+ */
+ protected int mState = STATE_UNINITIALIZED;
+ /**
+ * Indicates the recording state of the AudioRecord instance.
+ */
+ protected int mRecordingState = RECORDSTATE_STOPPED;
+ /**
+ * Lock to make sure mRecordingState updates are reflecting the actual state of the object.
+ */
+ protected Object mRecordingStateLock = new Object();
+ /**
+ * The listener the AudioRecord notifies when a previously set marker is reached.
+ * @see #setMarkerReachedListener(OnMarkerReachedListener)
+ */
+ protected OnMarkerReachedListener mMarkerListener = null;
+ /**
+ * Lock to protect marker listener updates against event notifications
+ */
+ protected final Object mMarkerListenerLock = new Object();
+ /**
+ * The listener the AudioRecord notifies periodically during recording.
+ * @see #setPeriodicNotificationListener(OnPeriodicNotificationListener)
+ */
+ protected OnPeriodicNotificationListener mPeriodicListener = null;
+ /**
+ * Lock to protect periodic listener updates against event notifications
+ */
+ protected final Object mPeriodicListenerLock = new Object();
+ /**
+ * Handler for events coming from the native code
+ */
+ protected NativeEventHandler mNativeEventHandler = null;
+ /**
+ * Size of the native audio buffer.
+ */
+ protected int mNativeBufferSizeInBytes = 0;
+
+
+ //---------------------------------------------------------
+ // Constructor, Finalize
+ //--------------------
+ /**
+ * Class constructor.
+ * @param audioSource the recording source. See {@link MediaRecorder.AudioSource.DEFAULT}
+ * and {@link MediaRecorder.AudioSource.MIC}.
+ * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
+ * not limited to) 44100, 22050 and 11025.
+ * @param channelConfig describes the configuration of the audio channels.
+ * See {@link AudioFormat.CHANNEL_CONFIGURATION_MONO} and
+ * {@link AudioFormat.CHANNEL_CONFIGURATION_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 written
+ * to during the recording. New audio data can be read from this buffer in smaller chunks
+ * than this size.
+ * @throws java.lang.IllegalArgumentException
+ */
+ public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
+ int blockSizeInBytes)
+ throws IllegalArgumentException {
+ mState = STATE_UNINITIALIZED;
+ mRecordingState = RECORDSTATE_STOPPED;
+
+ audioParamCheck(audioSource, sampleRateInHz, channelConfig, audioFormat);
+
+ audioBuffSizeCheck(blockSizeInBytes);
+
+ // native initialization
+ //TODO: update native initialization when information about hardware init failure
+ // due to capture device already open is available.
+ int initResult = native_setup( new WeakReference<AudioRecord>(this),
+ mRecordSource, mSampleRate, mChannelCount, mAudioFormat, mNativeBufferSizeInBytes);
+ if (initResult != SUCCESS) {
+ loge("Error code "+initResult+" when initializing native AudioRecord object.");
+ return; // with mState == STATE_UNINITIALIZED
+ }
+
+ mState = STATE_INITIALIZED;
+ }
+
+
+ // Convenience method for the constructor's parameter checks.
+ // This is where constructor IllegalArgumentException-s are thrown
+ // postconditions:
+ // mRecordSource is valid
+ // mChannelCount is valid
+ // mAudioFormat is valid
+ // mSampleRate is valid
+ private void audioParamCheck(int audioSource, int sampleRateInHz,
+ int channelConfig, int audioFormat) {
+
+ //--------------
+ // audio source
+ if ( (audioSource != MediaRecorder.AudioSource.DEFAULT)
+ && (audioSource != MediaRecorder.AudioSource.MIC) ) {
+ throw (new IllegalArgumentException("Invalid audio source."));
+ } else {
+ mRecordSource = audioSource;
+ }
+
+ //--------------
+ // sample rate
+ if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+ throw (new IllegalArgumentException(sampleRateInHz
+ + "Hz is not a supported sample rate."));
+ } else {
+ mSampleRate = sampleRateInHz;
+ }
+
+ //--------------
+ // channel config
+ switch (channelConfig) {
+ case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:
+ case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+ mChannelCount = 1;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ break;
+ case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+ mChannelCount = 2;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+ break;
+ default:
+ mChannelCount = 0;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
+ throw (new IllegalArgumentException("Unsupported channel configuration."));
+ }
+
+ //--------------
+ // audio format
+ switch (audioFormat) {
+ case AudioFormat.ENCODING_DEFAULT:
+ mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ case AudioFormat.ENCODING_PCM_8BIT:
+ mAudioFormat = audioFormat;
+ break;
+ default:
+ mAudioFormat = AudioFormat.ENCODING_INVALID;
+ throw (new IllegalArgumentException("Unsupported sample encoding."
+ + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."));
+ }
+ }
+
+
+ // Convenience method for the contructor's audio buffer size check.
+ // preconditions:
+ // mChannelCount is valid
+ // mAudioFormat is AudioFormat.ENCODING_PCM_8BIT OR AudioFormat.ENCODING_PCM_16BIT
+ // postcondition:
+ // mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
+ private void audioBuffSizeCheck(int audioBufferSize) {
+ // NB: this section is only valid with PCM data.
+ // To update when supporting compressed formats
+ int frameSizeInBytes = mChannelCount
+ * (mAudioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+ if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
+ throw (new IllegalArgumentException("Invalid audio buffer size."));
+ }
+
+ mNativeBufferSizeInBytes = audioBufferSize;
+ }
+
+
+ // Convenience method for the creation of the native event handler
+ // It is called only when a non-null event listener is set.
+ // precondition:
+ // mNativeEventHandler is null
+ private void createNativeEventHandler() {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mNativeEventHandler = new NativeEventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mNativeEventHandler = new NativeEventHandler(this, looper);
+ } else {
+ mNativeEventHandler = null;
+ }
+ }
+
+
+ /**
+ * Releases the native AudioRecord resources.
+ */
+ public void release() {
+ stop();
+ native_release();
+ mState = STATE_UNINITIALIZED;
+ }
+
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Getters
+ //--------------------
+ /**
+ * Returns the configured audio data sample rate in Hz
+ */
+ public int getSampleRate() {
+ return mSampleRate;
+ }
+
+ /**
+ * Returns the audio recording source.
+ * @see MediaRecorder.AudioSource
+ */
+ public int getAudioSource() {
+ return mRecordSource;
+ }
+
+ /**
+ * Returns the configured audio data format. See {@link #AudioFormat.ENCODING_PCM_16BIT}
+ * and {@link #AudioFormat.ENCODING_PCM_8BIT}.
+ */
+ public int getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Returns the configured channel configuration.
+ * See {@link #AudioFormat.CHANNEL_CONFIGURATION_MONO}
+ * and {@link #AudioFormat.CHANNEL_CONFIGURATION_STEREO}.
+ */
+ public int getChannelConfiguration() {
+ return mChannelConfiguration;
+ }
+
+ /**
+ * Returns the configured number of channels.
+ */
+ public int getChannelCount() {
+ return mChannelCount;
+ }
+
+ /**
+ * Returns the state of the AudioRecord instance. This is useful after the
+ * AudioRecord instance has been created to check if it was initialized
+ * properly. This ensures that the appropriate hardware resources have been
+ * acquired.
+ * @see AudioRecord.STATE_INITIALIZED
+ * @see AudioRecord.STATE_UNINITIALIZED
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Returns the recording state of the AudioRecord instance.
+ * @see AudioRecord.RECORDSTATE_STOPPED
+ * @see AudioRecord.RECORDSTATE_RECORDING
+ */
+ public int getRecordingState() {
+ return mRecordingState;
+ }
+
+ /**
+ * @return marker position in frames
+ */
+ public int getNotificationMarkerPosition() {
+ return native_get_marker_pos();
+ }
+
+ /**
+ * @return update period in frames
+ */
+ public int getPositionNotificationPeriod() {
+ return native_get_pos_update_period();
+ }
+
+
+ //---------------------------------------------------------
+ // Transport control methods
+ //--------------------
+ /**
+ * Starts recording from the AudioRecord instance.
+ * @throws IllegalStateException
+ */
+ public void startRecording()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("startRecording() called on an "
+ +"uninitialized AudioRecord."));
+ }
+
+ // start recording
+ synchronized(mRecordingStateLock) {
+ native_start();
+ mRecordingState = RECORDSTATE_RECORDING;
+ }
+ }
+
+
+
+ /**
+ * Stops recording.
+ * @throws IllegalStateException
+ */
+ public void stop()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("stop() called on an uninitialized AudioRecord."));
+ }
+
+ // stop recording
+ synchronized(mRecordingStateLock) {
+ native_stop();
+ mRecordingState = RECORDSTATE_STOPPED;
+ }
+ }
+
+
+ //---------------------------------------------------------
+ // Audio data supply
+ //--------------------
+ /**
+ * Reads audio data from the audio hardware for recording into a buffer.
+ * @throws IllegalStateException
+ * @param audioData the array to which the recorded audio data is written.
+ * @param offsetInBytes index in audioData from which the data is written.
+ * @param sizeInBytes the number of requested bytes.
+ * @return the number of bytes that were read. This will not exceed sizeInBytes
+ */
+ public int read(byte[] audioData, int offsetInBytes, int sizeInBytes)
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("read() called on uninitialized AudioRecord."));
+ }
+
+ return native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes);
+ }
+
+
+ /**
+ * Reads audio data from the audio hardware for recording into a buffer.
+ * @throws IllegalStateException
+ * @param audioData the array to which the recorded audio data is written.
+ * @param offsetInShorts index in audioData from which the data is written.
+ * @param sizeInShorts the number of requested shorts.
+ * @return the number of shorts that were read. This will not exceed sizeInShorts
+ */
+ public int read(short[] audioData, int offsetInShorts, int sizeInShorts)
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("read() called on uninitialized AudioRecord."));
+ }
+
+ return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts);
+ }
+
+
+ /**
+ * Reads audio data from the audio hardware for recording into a direct buffer. If this buffer
+ * is not a direct buffer, this method will always return 0.
+ * @throws IllegalStateException
+ * @param audioBuffer the direct buffer to which the recorded audio data is written.
+ * @param sizeInBytes the number of requested bytes.
+ * @return the number of bytes that were read. This will not exceed sizeInBytes.
+ */
+ public int read(ByteBuffer audioBuffer, int sizeInBytes)
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("read() called on uninitialized AudioRecord."));
+ }
+
+ return native_read_in_direct_buffer(audioBuffer, sizeInBytes);
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Initialization / configuration
+ //--------------------
+ /**
+ * Sets the listener the AudioRecord notifies when a previously set marker is reached.
+ * @param listener
+ */
+ public void setMarkerReachedListener(OnMarkerReachedListener listener) {
+ synchronized (mMarkerListenerLock) {
+ mMarkerListener = listener;
+ }
+ if ((listener != null) && (mNativeEventHandler == null)) {
+ createNativeEventHandler();
+ }
+ }
+
+
+ /**
+ * Sets the listener the AudioRecord notifies periodically during recording.
+ * @param listener
+ */
+ public void setPeriodicNotificationListener(OnPeriodicNotificationListener listener) {
+ synchronized (mPeriodicListenerLock) {
+ mPeriodicListener = listener;
+ }
+ if ((listener != null) && (mNativeEventHandler == null)) {
+ createNativeEventHandler();
+ }
+ }
+
+
+ /**
+ * Sets the marker position at which the listener, if set with
+ * {@link #setMarkerReachedListener(OnMarkerReachedListener)}, is called.
+ * @param markerInFrames marker position expressed in frames
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+ * {@link #ERROR_INVALID_OPERATION}
+ */
+ public int setNotificationMarkerPosition(int markerInFrames) {
+ return native_set_marker_pos(markerInFrames);
+ }
+
+
+ /**
+ * Sets the period at which the listener, if set with
+ * {@link #setPositionNotificationPeriod(int)}, is called.
+ * @param periodInFrames update period expressed in frames
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION}
+ */
+ public int setPositionNotificationPeriod(int periodInFrames) {
+ return native_set_pos_update_period(periodInFrames);
+ }
+
+
+ //---------------------------------------------------------
+ // Interface definitions
+ //--------------------
+ /**
+ * Interface definition for a callback to be invoked when an AudioRecord has
+ * reached a notification marker set by setNotificationMarkerPosition().
+ */
+ public interface OnMarkerReachedListener {
+ /**
+ * Called on the listener to notify it that the previously set marker has been reached
+ * by the recording head.
+ */
+ void onMarkerReached(AudioRecord track);
+ }
+
+
+ /**
+ * Interface definition for a callback to be invoked for each periodic AudioRecord
+ * update during recording. The update interval is set by setPositionNotificationPeriod().
+ */
+ public interface OnPeriodicNotificationListener {
+ /**
+ * Called on the listener to periodically notify it that the recording head has reached
+ * a multiple of the notification period.
+ */
+ void onPeriodicNotification(AudioRecord track);
+ }
+
+
+ //---------------------------------------------------------
+ // Inner classes
+ //--------------------
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listeners
+ */
+ private class NativeEventHandler extends Handler
+ {
+ private AudioRecord mAudioRecord;
+
+ public NativeEventHandler(AudioRecord ar, Looper looper) {
+ super(looper);
+ mAudioRecord = ar;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mAudioRecord == null) {
+ return;
+ }
+ switch(msg.what) {
+ case EVENT_MARKER:
+ synchronized (mMarkerListenerLock) {
+ if (mAudioRecord.mMarkerListener != null) {
+ mAudioRecord.mMarkerListener.onMarkerReached(mAudioRecord);
+ }
+ }
+ break;
+ case EVENT_NEW_POS:
+ synchronized (mPeriodicListenerLock) {
+ if (mAudioRecord.mPeriodicListener != null) {
+ mAudioRecord.mPeriodicListener.onPeriodicNotification(mAudioRecord);
+ }
+ }
+ break;
+ default:
+ Log.e(TAG, "[ android.media.AudioTrack.NativeEventHandler ] " +
+ "Unknown event type: " + msg.what);
+ break;
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------
+ // Java methods called from the native side
+ //--------------------
+ @SuppressWarnings("unused")
+ private static void postEventFromNative(Object audiorecord_ref,
+ int what, int arg1, int arg2, Object obj) {
+ //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
+ AudioRecord track = (AudioRecord)((WeakReference)audiorecord_ref).get();
+ if (track == null) {
+ return;
+ }
+
+ if (track.mNativeEventHandler != null) {
+ Message m = track.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
+ track.mNativeEventHandler.sendMessage(m);
+ }
+
+ }
+
+
+ //---------------------------------------------------------
+ // Native methods called from the Java side
+ //--------------------
+
+ private native final int native_setup(Object audiorecord_this,
+ int recordSource, int sampleRate, int nbChannels, int audioFormat, int buffSizeInBytes);
+
+ 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 int native_read_in_byte_array(byte[] audioData,
+ int offsetInBytes, int sizeInBytes);
+
+ private native final int native_read_in_short_array(short[] audioData,
+ int offsetInShorts, int sizeInShorts);
+
+ private native final int native_read_in_direct_buffer(Object jBuffer, int sizeInBytes);
+
+ 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();
+
+
+ //---------------------------------------------------------
+ // Utility methods
+ //------------------
+
+ private static void logd(String msg) {
+ Log.d(TAG, "[ android.media.AudioRecord ] " + msg);
+ }
+
+ private static void loge(String msg) {
+ Log.e(TAG, "[ android.media.AudioRecord ] " + msg);
+ }
+
+}
+
+
+
+
+
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 0ee903d..b39e7bb 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -84,7 +84,7 @@ public class AudioService extends IAudioService.Stub {
private static final int MSG_MEDIA_SERVER_DIED = 5;
private static final int MSG_MEDIA_SERVER_STARTED = 6;
private static final int MSG_PLAY_SOUND_EFFECT = 7;
-
+
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
@@ -209,9 +209,10 @@ public class AudioService extends IAudioService.Stub {
final int[] volumeLevelsCoarse = createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_SYSTEM]);
final int[] volumeLevelsFine = createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]);
- VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[AudioSystem.NUM_STREAMS];
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
- for (int i = 0; i < AudioSystem.NUM_STREAMS; i++) {
+ for (int i = 0; i < numStreamTypes; i++) {
final int[] levels;
switch (i) {
@@ -318,7 +319,14 @@ public class AudioService extends IAudioService.Stub {
public void adjustStreamVolume(int streamType, int direction, int flags) {
ensureValidDirection(direction);
ensureValidStreamType(streamType);
-
+
+ boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
+ Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
+ if (notificationsUseRingVolume && streamType == AudioManager.STREAM_NOTIFICATION) {
+ // Redirect the volume change to the ring stream
+ streamType = AudioManager.STREAM_RING;
+ }
+
VolumeStreamState streamState = mStreamStates[streamType];
final int oldIndex = streamState.mIndex;
boolean adjustVolume = true;
@@ -333,11 +341,23 @@ public class AudioService extends IAudioService.Stub {
}
if (adjustVolume && streamState.adjustIndex(direction)) {
+
+ boolean alsoUpdateNotificationVolume = notificationsUseRingVolume &&
+ streamType == AudioManager.STREAM_RING;
+ if (alsoUpdateNotificationVolume) {
+ mStreamStates[AudioManager.STREAM_NOTIFICATION].adjustIndex(direction);
+ }
+
// Post message to set system volume (it in turn will post a message
// to persist). Do not change volume if stream is muted.
if (streamState.muteCount() == 0) {
sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
streamState, 0);
+
+ if (alsoUpdateNotificationVolume) {
+ sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, AudioManager.STREAM_NOTIFICATION,
+ SENDMSG_NOOP, 0, 0, mStreamStates[AudioManager.STREAM_NOTIFICATION], 0);
+ }
}
}
@@ -348,7 +368,22 @@ public class AudioService extends IAudioService.Stub {
/** @see AudioManager#setStreamVolume(int, int, int) */
public void setStreamVolume(int streamType, int index, int flags) {
ensureValidStreamType(streamType);
+
+ boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
+ Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
+ if (notificationsUseRingVolume) {
+ if (streamType == AudioManager.STREAM_NOTIFICATION) {
+ // Redirect the volume change to the ring stream
+ streamType = AudioManager.STREAM_RING;
+ }
+ if (streamType == AudioManager.STREAM_RING) {
+ // One-off to sync notification volume to ringer volume
+ setStreamVolumeInt(AudioManager.STREAM_NOTIFICATION, index);
+ }
+ }
+
setStreamVolumeInt(streamType, index);
+
// UI, etc.
mVolumePanel.postVolumeChanged(streamType, flags);
}
@@ -408,14 +443,15 @@ public class AudioService extends IAudioService.Stub {
mRingerMode = ringerMode;
// Adjust volumes via posting message
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
- for (int streamType = AudioSystem.NUM_STREAMS - 1; streamType >= 0; streamType--) {
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (!isStreamAffectedByRingerMode(streamType)) continue;
// Bring back last audible volume
setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex);
}
} else {
- for (int streamType = AudioSystem.NUM_STREAMS - 1; streamType >= 0; streamType--) {
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
if (!isStreamAffectedByRingerMode(streamType)) continue;
// Either silent or vibrate, either way volume is 0
setStreamVolumeInt(streamType, 0);
@@ -725,7 +761,9 @@ public class AudioService extends IAudioService.Stub {
// Send sticky broadcast
Intent broadcast = new Intent(AudioManager.RINGER_MODE_CHANGED_ACTION);
broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, mRingerMode);
+ long origCallerIdentityToken = Binder.clearCallingIdentity();
mContext.sendStickyBroadcast(broadcast);
+ Binder.restoreCallingIdentity(origCallerIdentityToken);
}
private void broadcastVibrateSetting(int vibrateType) {
@@ -1080,7 +1118,8 @@ public class AudioService extends IAudioService.Stub {
Log.e(TAG, "Media server started.");
// Restore audio routing and stream volumes
applyAudioSettings();
- for (int streamType = AudioSystem.NUM_STREAMS - 1; streamType >= 0; streamType--) {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
int volume;
VolumeStreamState streamState = mStreamStates[streamType];
if (streamState.muteCount() == 0) {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 7526379..37100677 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -41,7 +41,16 @@ public class AudioSystem
public static final int STREAM_MUSIC = 3;
/* The audio stream for alarms */
public static final int STREAM_ALARM = 4;
+ /* The audio stream for notifications */
+ public static final int STREAM_NOTIFICATION = 5;
+ /**
+ * @deprecated Use {@link #numStreamTypes() instead}
+ */
public static final int NUM_STREAMS = 5;
+
+ // Expose only the getter method publicly so we can change it in the future
+ private static final int NUM_STREAM_TYPES = 6;
+ public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
/* max and min volume levels */
/* Maximum volume setting, for use with setVolume(int,int) */
@@ -112,9 +121,13 @@ public class AudioSystem
/* Routing bits for setRouting/getRouting API */
public static final int ROUTE_EARPIECE = (1 << 0);
public static final int ROUTE_SPEAKER = (1 << 1);
- public static final int ROUTE_BLUETOOTH = (1 << 2);
+
+ /** @deprecated use {@link #ROUTE_BLUETOOTH_SCO} */
+ @Deprecated public static final int ROUTE_BLUETOOTH = (1 << 2);
+ public static final int ROUTE_BLUETOOTH_SCO = (1 << 2);
public static final int ROUTE_HEADSET = (1 << 3);
- public static final int ROUTE_ALL = 0xFFFFFFFF;
+ public static final int ROUTE_BLUETOOTH_A2DP = (1 << 4);
+ public static final int ROUTE_ALL = 0xFFFFFFFF;
/*
* Sets the audio routing for a specified mode
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
new file mode 100644
index 0000000..c246905
--- /dev/null
+++ b/media/java/android/media/AudioTrack.java
@@ -0,0 +1,934 @@
+/*
+ * 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 to stream PCM audio buffers to the audio hardware for playback. This is
+ * be achieved by "pushing" the data to the AudioTrack object using the
+ * {@link #write(byte[], int, int)} or {@link #write(short[], int, int)} method.
+ * During construction, an AudioTrack object can be initialized with a given buffer.
+ * This size determines how long an AudioTrack can play before running out of data.
+ *
+ * {@hide Pending API council review}
+ */
+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;
+
+ /** state of an AudioTrack this is stopped */
+ public static final int PLAYSTATE_STOPPED = 1; // matches SL_PLAYSTATE_STOPPED
+ /** state of an AudioTrack this is paused */
+ public static final int PLAYSTATE_PAUSED = 2; // matches SL_PLAYSTATE_PAUSED
+ /** state of an AudioTrack this is playing */
+ public static final int PLAYSTATE_PLAYING = 3; // matches SL_PLAYSTATE_PLAYING
+
+ /**
+ * 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;
+
+
+ // to keep in sync with libs/android_runtime/android_media_AudioTrack.cpp
+ // error codes
+ /**
+ * Denotes a successful operation.
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Denotes a generic operation failure.
+ */
+ public static final int ERROR = -1;
+ private static final int ERROR_NATIVESETUP_AUDIOSYSTEM = -2;
+ private static final int ERROR_NATIVESETUP_INVALIDCHANNELCOUNT = -3;
+ private static final int ERROR_NATIVESETUP_INVALIDFORMAT = -4;
+ private static final int ERROR_NATIVESETUP_INVALIDSTREAMTYPE = -5;
+ private static final int ERROR_NATIVESETUP_NATIVEINITFAILED = -6;
+ /**
+ * Denotes a failure due to the use of an invalid value.
+ */
+ public static final int ERROR_BAD_VALUE = -7;
+ /**
+ * Denotes a failure due to the improper use of a method.
+ */
+ public static final int ERROR_INVALID_OPERATION = -8;
+ // events
+ /**
+ * Event id for when the playback head has reached a previously set marker.
+ */
+ protected static final int NATIVE_EVENT_MARKER = 3;
+ /**
+ * Event id for when the previously set update period has passed during playback.
+ */
+ protected static final int NATIVE_EVENT_NEW_POS = 4;
+
+ private final static String TAG = "AudioTrack-Java";
+
+
+ //--------------------------------------------------------------------------
+ // Member variables
+ //--------------------
+ /**
+ * Indicates the state of the AudioTrack instance
+ */
+ protected int mState = STATE_UNINITIALIZED;
+ /**
+ * Indicates the play state of the AudioTrack instance
+ */
+ protected int mPlayState = PLAYSTATE_STOPPED;
+ /**
+ * Lock to make sure mPlayState updates are reflecting the actual state of the object.
+ */
+ protected final Object mPlayStateLock = new Object();
+ /**
+ * The listener the AudioTrack notifies previously set marker is reached.
+ * @see #setMarkerReachedListener(OnMarkerReachedListener)
+ */
+ protected OnMarkerReachedListener mMarkerListener = null;
+ /**
+ * Lock to protect marker listener updates against event notifications
+ */
+ protected final Object mMarkerListenerLock = new Object();
+ /**
+ * The listener the AudioTrack notifies periodically during playback.
+ * @see #setPeriodicNotificationListener(OnPeriodicNotificationListener)
+ */
+ protected OnPeriodicNotificationListener mPeriodicListener = null;
+ /**
+ * Lock to protect periodic listener updates against event notifications
+ */
+ protected final Object mPeriodicListenerLock = new Object();
+ /**
+ * Size of the native audio buffer.
+ */
+ protected int mNativeBufferSizeInBytes = 0;
+ /**
+ * Handler for events coming from the native code
+ */
+ protected NativeEventHandler mNativeEventHandler = null;
+ /**
+ * The audio data sampling rate in Hz.
+ */
+ protected int mSampleRate = 22050;
+ /**
+ * The number of input audio channels (1 is mono, 2 is stereo)
+ */
+ protected int mChannelCount = 1;
+ /**
+ * 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} and
+ * {@link AudioManager.STREAM_ALARM}
+ */
+ protected int mStreamType = AudioManager.STREAM_MUSIC;
+ /**
+ * The way audio is consumed by the hardware, streaming or static.
+ */
+ protected int mDataLoadMode = MODE_STREAM;
+ /**
+ * The current audio channel configuration
+ */
+ protected int mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ /**
+ * The encoding of the audio samples.
+ * @see #AudioFormat.ENCODING_PCM_8BIT
+ * @see #AudioFormat.ENCODING_PCM_16BIT
+ */
+ protected int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+
+
+ //--------------------------------
+ // 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 AudioSystem.STREAM_VOICE_CALL}, {@link AudioSystem.STREAM_SYSTEM},
+ * {@link AudioSystem.STREAM_RING}, {@link AudioSystem.STREAM_MUSIC} and
+ * {@link AudioSystem.STREAM_ALARM}
+ * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
+ * not limited to) 44100, 22050 and 11025.
+ * @param channelConfig describes the configuration of the audio channels.
+ * See {@link AudioFormat.CHANNEL_CONFIGURATION_MONO} and
+ * {@link AudioFormat.CHANNEL_CONFIGURATION_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.
+ * @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 {
+ mState = STATE_UNINITIALIZED;
+
+ audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
+
+ audioBuffSizeCheck(bufferSizeInBytes);
+
+ // native initialization
+ int initResult = native_setup(new WeakReference<AudioTrack>(this),
+ mStreamType, mSampleRate, mChannelCount, mAudioFormat,
+ mNativeBufferSizeInBytes, mDataLoadMode);
+ if (initResult != SUCCESS) {
+ loge("Error code "+initResult+" when initializing AudioTrack.");
+ return; // with mState == STATE_UNINITIALIZED
+ }
+
+ if (mDataLoadMode == MODE_STATIC) {
+ mState = STATE_NO_STATIC_DATA;
+ } else {
+ mState = STATE_INITIALIZED;
+ }
+ }
+
+
+ // Convenience method for the constructor's parameter checks.
+ // This is where constructor IllegalArgumentException-s are thrown
+ // postconditions:
+ // mStreamType is valid
+ // mChannelCount is valid
+ // mAudioFormat is valid
+ // mSampleRate is valid
+ // mDataLoadMode is valid
+ private void audioParamCheck(int streamType, int sampleRateInHz,
+ int channelConfig, int audioFormat, int mode) {
+
+ //--------------
+ // stream type
+ if( (streamType != AudioManager.STREAM_ALARM) && (streamType != AudioManager.STREAM_MUSIC)
+ && (streamType != AudioManager.STREAM_RING) && (streamType != AudioManager.STREAM_SYSTEM)
+ && (streamType != AudioManager.STREAM_VOICE_CALL) ) {
+ throw (new IllegalArgumentException("Invalid stream type."));
+ } else {
+ mStreamType = streamType;
+ }
+
+ //--------------
+ // sample rate
+ if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+ throw (new IllegalArgumentException(sampleRateInHz
+ + "Hz is not a supported sample rate."));
+ } else {
+ mSampleRate = sampleRateInHz;
+ }
+
+ //--------------
+ // channel config
+ switch (channelConfig) {
+ case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:
+ case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+ mChannelCount = 1;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ break;
+ case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+ mChannelCount = 2;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+ break;
+ default:
+ mChannelCount = 0;
+ mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
+ throw(new IllegalArgumentException("Unsupported channel configuration."));
+ }
+
+ //--------------
+ // audio format
+ switch (audioFormat) {
+ case AudioFormat.ENCODING_DEFAULT:
+ mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ case AudioFormat.ENCODING_PCM_8BIT:
+ mAudioFormat = audioFormat;
+ break;
+ default:
+ mAudioFormat = AudioFormat.ENCODING_INVALID;
+ throw(new IllegalArgumentException("Unsupported sample encoding."
+ + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."));
+ }
+
+ //--------------
+ // audio load mode
+ if ( (mode != MODE_STREAM) && (mode != MODE_STATIC) ) {
+ throw(new IllegalArgumentException("Invalid mode."));
+ } else {
+ mDataLoadMode = mode;
+ }
+ }
+
+
+ // Convenience method for the contructor's audio buffer size check.
+ // preconditions:
+ // mChannelCount is valid
+ // mAudioFormat is valid
+ // postcondition:
+ // mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
+ private void audioBuffSizeCheck(int audioBufferSize) {
+ // NB: this section is only valid with PCM data.
+ // To update when supporting compressed formats
+ int frameSizeInBytes = mChannelCount
+ * (mAudioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+ if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
+ throw (new IllegalArgumentException("Invalid audio buffer size."));
+ }
+
+ mNativeBufferSizeInBytes = audioBufferSize;
+ }
+
+
+ // Convenience method for the creation of the native event handler
+ // It is called only when a non-null event listener is set.
+ // precondition:
+ // mNativeEventHandler is null
+ private void createNativeEventHandler() {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mNativeEventHandler = new NativeEventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mNativeEventHandler = new NativeEventHandler(this, looper);
+ } else {
+ mNativeEventHandler = null;
+ }
+ }
+
+
+ /**
+ * Releases the native AudioTrack resources.
+ */
+ public void release() {
+ // even though native_release() stops the native AudioTrack, we need to stop
+ // AudioTrack subclasses too.
+ stop();
+ native_release();
+ mState = STATE_UNINITIALIZED;
+ }
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ //--------------------------------------------------------------------------
+ // Getters
+ //--------------------
+ /**
+ * Returns the minimum valid volume value. Volume values set under this one will
+ * be clamped at this value.
+ * @return the minimum volume expressed as a linear attenuation.
+ */
+ static public float getMinVolume() {
+ return AudioTrack.VOLUME_MIN;
+ }
+
+ /**
+ * Returns the maximum valid volume value. Volume values set above this one will
+ * be clamped at this value.
+ * @return the maximum volume expressed as a linear attenuation.
+ */
+ static public float getMaxVolume() {
+ return AudioTrack.VOLUME_MAX;
+ }
+
+ /**
+ * Returns the configured audio data sample rate in Hz
+ */
+ public int getSampleRate() {
+ return mSampleRate;
+ }
+
+ /**
+ * Returns the configured audio data format. See {@link #AudioFormat.ENCODING_PCM_16BIT}
+ * and {@link #AudioFormat.ENCODING_PCM_8BIT}.
+ */
+ public int getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Returns the type of audio stream this AudioTrack is configured for.
+ * Compare the result against {@link AudioManager.STREAM_VOICE_CALL},
+ * {@link AudioManager.STREAM_SYSTEM}, {@link AudioManager.STREAM_RING},
+ * {@link AudioManager.STREAM_MUSIC} or {@link AudioManager.STREAM_ALARM}
+ */
+ public int getStreamType() {
+ return mStreamType;
+ }
+
+ /**
+ * Returns the configured channel configuration.
+ * See {@link #AudioFormat.CHANNEL_CONFIGURATION_MONO}
+ * and {@link #AudioFormat.CHANNEL_CONFIGURATION_STEREO}.
+ */
+ public int getChannelConfiguration() {
+ return mChannelConfiguration;
+ }
+
+ /**
+ * Returns the configured number of channels.
+ */
+ public int getChannelCount() {
+ return mChannelCount;
+ }
+
+ /**
+ * Returns the state of the AudioTrack instance. This is useful after the
+ * AudioTrack instance has been created to check if it was initialized
+ * properly. This ensures that the appropriate hardware resources have been
+ * acquired.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Returns the playback state of the AudioTrack instance.
+ * @see AudioTrack.PLAYSTATE_STOPPED
+ * @see AudioTrack.PLAYSTATE_PAUSED
+ * @see AudioTrack.PLAYSTATE_PLAYING
+ */
+ public int getPlayState() {
+ return mPlayState;
+ }
+
+ /**
+ * Returns the native frame count used by the hardware
+ */
+ protected int getNativeFrameCount() {
+ return native_get_native_frame_count();
+ }
+
+ /**
+ * @return marker position in frames
+ */
+ public int getNotificationMarkerPosition() {
+ return native_get_marker_pos();
+ }
+
+ /**
+ * @return update period in frames
+ */
+ public int getPositionNotificationPeriod() {
+ return native_get_pos_update_period();
+ }
+
+ /**
+ * @return playback head position in frames
+ */
+ public int getPlaybackHeadPosition() {
+ return native_get_position();
+ }
+
+ /**
+ * Returns the hardware output sample rate
+ */
+ static public int getNativeOutputSampleRate() {
+ return native_get_output_sample_rate();
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Initialization / configuration
+ //--------------------
+ /**
+ * Sets the listener the AudioTrack notifies when a previously set marker is reached.
+ * @param listener
+ */
+ public void setMarkerReachedListener(OnMarkerReachedListener listener) {
+ synchronized (mMarkerListenerLock) {
+ mMarkerListener = listener;
+ }
+ if ((listener != null) && (mNativeEventHandler == null)) {
+ createNativeEventHandler();
+ }
+ }
+
+
+ /**
+ * Sets the listener the AudioTrack notifies periodically during playback.
+ * @param listener
+ */
+ public void setPeriodicNotificationListener(OnPeriodicNotificationListener listener) {
+ synchronized (mPeriodicListenerLock) {
+ mPeriodicListener = listener;
+ }
+ if ((listener != null) && (mNativeEventHandler == null)) {
+ createNativeEventHandler();
+ }
+ }
+
+
+ /**
+ * Sets the specified left/right output volume values on the AudioTrack. Values are clamped
+ * to the ({@link #getMinVolume()}, {@link #getMaxVolume()}) interval if outside this range.
+ * @param leftVolume output attenuation for the left channel. A value of 0.0f is silence,
+ * a value of 1.0f is no attenuation.
+ * @param rightVolume output attenuation for the right channel
+ * @return {@link #SUCCESS}
+ * @throws IllegalStateException
+ */
+ public int setStereoVolume(float leftVolume, float rightVolume)
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("setStereoVolume() called on an "+
+ "uninitialized AudioTrack."));
+ }
+
+ // clamp the volumes
+ if (leftVolume < getMinVolume()) {
+ leftVolume = getMinVolume();
+ }
+ if (leftVolume > getMaxVolume()) {
+ leftVolume = getMaxVolume();
+ }
+ if (rightVolume < getMinVolume()) {
+ rightVolume = getMinVolume();
+ }
+ if (rightVolume > getMaxVolume()) {
+ rightVolume = getMaxVolume();
+ }
+
+ native_setVolume(leftVolume, rightVolume);
+
+ return SUCCESS;
+ }
+
+
+ /**
+ * Sets the playback sample rate for this track. This sets the sampling rate at which
+ * the audio data will be consumed and played back, not the original sampling rate of the
+ * content. Setting it to half the sample rate of the content will cause the playback to
+ * last twice as long, but will also result result in a negative pitch shift.
+ * The current implementation supports a maximum sample rate of twice the hardware output
+ * sample rate (see {@link #getNativeOutputSampleRate()}). Use {@link #getSampleRate()} to
+ * check the rate actually used in hardware after potential clamping.
+ * @param sampleRateInHz
+ * @return {@link #SUCCESS}
+ */
+ public int setPlaybackRate(int sampleRateInHz) {
+ native_set_playback_rate(sampleRateInHz);
+ return SUCCESS;
+ }
+
+
+ /**
+ *
+ * @param markerInFrames marker in frames
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+ * {@link #ERROR_INVALID_OPERATION}
+ */
+ public int setNotificationMarkerPosition(int markerInFrames) {
+ return native_set_marker_pos(markerInFrames);
+ }
+
+
+ /**
+ * @param periodInFrames update period in frames
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION}
+ */
+ public int setPositionNotificationPeriod(int periodInFrames) {
+ return native_set_pos_update_period(periodInFrames);
+ }
+
+
+ /**
+ * Sets the playback head position. The track must be stopped for the position to be changed.
+ * @param positionInFrames playback head position in frames
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}
+ * @throws java.lang.IllegalStateException if the track is not in
+ * the {@link #PLAYSTATE_STOPPED} state.
+ */
+ public int setPlaybackHeadPosition(int positionInFrames)
+ throws IllegalStateException {
+ synchronized(mPlayStateLock) {
+ if(mPlayState == PLAYSTATE_STOPPED) {
+ return native_set_position(positionInFrames);
+ }
+ }
+ throw(new IllegalStateException("setPlaybackHeadPosition() called on a track that is "+
+ "not in the PLAYSTATE_STOPPED play state."));
+ }
+
+ /**
+ * Sets the loop points and the loop count. The loop can be infinite.
+ * @param startInFrames loop start marker in frames
+ * @param endInFrames loop end marker in frames
+ * @param loopCount the number of times the loop is looped.
+ * A value of -1 means infinite looping.
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}
+ */
+ public int setLoopPoints(int startInFrames, int endInFrames, int loopCount) {
+ return native_set_loop(startInFrames, endInFrames, loopCount);
+ }
+
+
+ //---------------------------------------------------------
+ // Transport control methods
+ //--------------------
+ /**
+ * Starts playing an AudioTrack.
+ * @throws IllegalStateException
+ */
+ public void play()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("play() called on uninitialized AudioTrack."));
+ }
+
+ synchronized(mPlayStateLock) {
+ native_start();
+ mPlayState = PLAYSTATE_PLAYING;
+ }
+ }
+
+ /**
+ * Stops playing the audio data.
+ * @throws IllegalStateException
+ */
+ public void stop()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("stop() called on uninitialized AudioTrack."));
+ }
+
+ // stop playing
+ synchronized(mPlayStateLock) {
+ native_stop();
+ mPlayState = PLAYSTATE_STOPPED;
+ }
+ }
+
+ /**
+ * Pauses the playback of the audio data.
+ * @throws IllegalStateException
+ */
+ public void pause()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("pause() called on uninitialized AudioTrack."));
+ }
+ //logd("pause()");
+
+ // pause playback
+ synchronized(mPlayStateLock) {
+ native_pause();
+ mPlayState = PLAYSTATE_PAUSED;
+ }
+ }
+
+
+ //---------------------------------------------------------
+ // Audio data supply
+ //--------------------
+
+ /**
+ * Flushes the audio data currently queued for playback.
+ * @throws IllegalStateException
+ */
+ public void flush()
+ throws IllegalStateException {
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("flush() called on uninitialized AudioTrack."));
+ }
+ //logd("flush()");
+
+ // flush the data in native layer
+ native_flush();
+
+ }
+
+ /**
+ * Writes the audio data to the audio hardware for playback.
+ * @param audioData the array that holds the data to play.
+ * @param offsetInBytes the offset in audioData where the data to play starts.
+ * @param sizeInBytes the number of bytes to read in audioData after the offset.
+ * @return the number of bytes that were written.
+ * @throws IllegalStateException
+ */
+ public int write(byte[] audioData,int offsetInBytes, int sizeInBytes)
+ throws IllegalStateException {
+ if ((mDataLoadMode == MODE_STATIC)
+ && (mState == STATE_NO_STATIC_DATA)
+ && (sizeInBytes > 0)) {
+ mState = STATE_INITIALIZED;
+ }
+ //TODO check if future writes should be forbidden for static tracks
+ // or: how to update data for static tracks?
+
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("write() called on uninitialized AudioTrack."));
+ }
+
+ return native_write_byte(audioData, offsetInBytes, sizeInBytes);
+ }
+
+
+ /**
+ * Writes the audio data to the audio hardware for playback.
+ * @param audioData the array that holds the data to play.
+ * @param offsetInShorts the offset in audioData where the data to play starts.
+ * @param sizeInShorts the number of bytes to read in audioData after the offset.
+ * @return the number of shorts that were written.
+ * @throws IllegalStateException
+ */
+ public int write(short[] audioData, int offsetInShorts, int sizeInShorts)
+ throws IllegalStateException {
+ if ((mDataLoadMode == MODE_STATIC)
+ && (mState == STATE_NO_STATIC_DATA)
+ && (sizeInShorts > 0)) {
+ mState = STATE_INITIALIZED;
+ }
+ //TODO check if future writes should be forbidden for static tracks
+ // or: how to update data for static tracks?
+
+ if (mState != STATE_INITIALIZED) {
+ throw(new IllegalStateException("write() called on uninitialized AudioTrack."));
+ }
+
+ return native_write_short(audioData, offsetInShorts, sizeInShorts);
+ }
+
+
+ /**
+ * Notifies the native resource to reuse the audio data already loaded in the native
+ * layer. This call is only valid with AudioTrack instances that don't use the streaming
+ * model.
+ * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+ * {@link #ERROR_INVALID_OPERATION}
+ */
+ public int reloadStaticData() {
+ if (mDataLoadMode == MODE_STREAM) {
+ return ERROR_INVALID_OPERATION;
+ }
+ return native_reload_static();
+ }
+
+
+ //---------------------------------------------------------
+ // Interface definitions
+ //--------------------
+ /**
+ * Interface definition for a callback to be invoked when an AudioTrack has
+ * reached a notification marker set by setNotificationMarkerPosition().
+ */
+ public interface OnMarkerReachedListener {
+ /**
+ * Called on the listener to notify it that the previously set marker has been reached
+ * by the playback head.
+ */
+ void onMarkerReached(AudioTrack track);
+ }
+
+
+ /**
+ * Interface definition for a callback to be invoked for each periodic AudioTrack
+ * update during playback. The update interval is set by setPositionNotificationPeriod().
+ */
+ public interface OnPeriodicNotificationListener {
+ /**
+ * 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 listeners
+ */
+ private class NativeEventHandler extends Handler
+ {
+ private AudioTrack mAudioTrack;
+
+ public NativeEventHandler(AudioTrack mp, Looper looper) {
+ super(looper);
+ mAudioTrack = mp;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mAudioTrack == null) {
+ return;
+ }
+ switch(msg.what) {
+ case NATIVE_EVENT_MARKER:
+ synchronized (mMarkerListenerLock) {
+ if (mAudioTrack.mMarkerListener != null) {
+ mAudioTrack.mMarkerListener.onMarkerReached(mAudioTrack);
+ }
+ }
+ break;
+ case NATIVE_EVENT_NEW_POS:
+ synchronized (mPeriodicListenerLock) {
+ if (mAudioTrack.mPeriodicListener != null) {
+ mAudioTrack.mPeriodicListener.onPeriodicNotification(mAudioTrack);
+ }
+ }
+ break;
+ default:
+ Log.e(TAG, "[ android.media.AudioTrack.NativeEventHandler ] " +
+ "Unknown event type: " + msg.what);
+ break;
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------
+ // 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.mNativeEventHandler != null) {
+ Message m = track.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
+ track.mNativeEventHandler.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);
+
+ 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);
+
+ private native final int native_write_short(short[] audioData,
+ int offsetInShorts, int sizeInShorts);
+
+ 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 void 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();
+
+
+ //---------------------------------------------------------
+ // 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);
+ }
+
+}
+
+
+
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index c0c6dad..9763bc7 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -16,7 +16,14 @@
package android.media;
+import android.content.ContentResolver;
+import android.content.Context;
import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.FileNotFoundException;
/**
* MediaMetadataRetriever class provides a unified interface for retrieving
@@ -29,6 +36,10 @@ public class MediaMetadataRetriever
System.loadLibrary("media_jni");
}
+ // The field below is accessed by native methods
+ @SuppressWarnings("unused")
+ private int mNativeContext;
+
public MediaMetadataRetriever() {
native_setup();
}
@@ -56,14 +67,104 @@ public class MediaMetadataRetriever
* For both frame capture and meta data retrieval
*/
public native void setMode(int mode);
+
+ /**
+ * @return the current mode of operation. A negative return value indicates
+ * some runtime error has occurred.
+ */
+ public native int getMode();
/**
- * Call this method before the rest. This method may be time-consuming.
+ * Sets the data source (file pathname) to use. Call this
+ * method before the rest of the methods in this class. This method may be
+ * time-consuming.
*
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
public native void setDataSource(String path) throws IllegalArgumentException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. It is the caller's
+ * responsibility to close the file descriptor. It is safe to do so as soon
+ * as this call returns. Call this method before the rest of the methods in
+ * this class. This method may be time-consuming.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts,
+ * in bytes. It must be non-negative
+ * @param length the length in bytes of the data to be played. It must be
+ * non-negative.
+ * @throws IllegalArgumentException if the arguments are invalid
+ */
+ public native void setDataSource(FileDescriptor fd, long offset, long length)
+ throws IllegalArgumentException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. It is the caller's
+ * responsibility to close the file descriptor. It is safe to do so as soon
+ * as this call returns. Call this method before the rest of the methods in
+ * this class. This method may be time-consuming.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @throws IllegalArgumentException if the FileDescriptor is invalid
+ */
+ public void setDataSource(FileDescriptor fd)
+ throws IllegalArgumentException {
+ // intentionally less than LONG_MAX
+ setDataSource(fd, 0, 0x7ffffffffffffffL);
+ }
+
+ /**
+ * Sets the data source as a content Uri. Call this method before
+ * the rest of the methods in this class. This method may be time-consuming.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @throws IllegalArgumentException if the Uri is invalid
+ * @throws SecurityException if the Uri cannot be used due to lack of
+ * permission.
+ */
+ public void setDataSource(Context context, Uri uri)
+ throws IllegalArgumentException, SecurityException {
+ if (uri == null) {
+ throw new IllegalArgumentException();
+ }
+
+ String scheme = uri.getScheme();
+ if(scheme == null || scheme.equals("file")) {
+ setDataSource(uri.getPath());
+ return;
+ }
+
+ ParcelFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ try {
+ fd = resolver.openFileDescriptor(uri, "r");
+ } catch(FileNotFoundException e) {
+ throw new IllegalArgumentException();
+ }
+ if (fd == null) {
+ throw new IllegalArgumentException();
+ }
+ FileDescriptor descriptor = fd.getFileDescriptor();
+ if (!descriptor.valid()) {
+ throw new IllegalArgumentException();
+ }
+ setDataSource(descriptor);
+ return;
+ } catch (SecurityException ex) {
+ } finally {
+ try {
+ if (fd != null) {
+ fd.close();
+ }
+ } catch(IOException ioEx) {
+ }
+ }
+ setDataSource(uri.toString());
+ }
/**
* Call this method after setDataSource(). This method retrieves the
@@ -133,4 +234,9 @@ public class MediaMetadataRetriever
public static final int METADATA_KEY_YEAR = 8;
public static final int METADATA_KEY_DURATION = 9;
public static final int METADATA_KEY_NUM_TRACKS = 10;
+ public static final int METADATA_KEY_IS_DRM_CRIPPLED= 11;
+ public static final int METADATA_KEY_CODEC = 12;
+ public static final int METADATA_KEY_RATING = 13;
+ public static final int METADATA_KEY_COMMENT = 14;
+ public static final int METADATA_KEY_COPYRIGHT = 15;
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index b6064e1..1a82654 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -37,9 +37,396 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
/**
- * Used to play audio and video files and streams.
- * See the <a href="/android/toolbox/apis/media.html">Android Media APIs</a>
- * page for help using using MediaPlayer.
+ * MediaPlayer class can be used to control playback
+ * of audio/video files and streams. An example on how to use the methods in
+ * this class can be found in <a href="../widget/VideoView.html">VideoView</a>.
+ * Please see <a href="../../../toolbox/apis/media.html">Android Media APIs</a>
+ * for additional help using MediaPlayer.
+ *
+ * <p>Topics covered are:
+ * <ol>
+ * <li><a href="#StateDiagram">State Diagram</a>
+ * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * </ol>
+ *
+ * <a name="StateDiagram"></a>
+ * <h3>State Diagram</h3>
+ *
+ * <p>Playback control of audio/video files and streams is managed as a state
+ * machine. The following diagram shows the life cycle and the states of a
+ * MediaPlayer object driven by the supported playback control operations.
+ * The ovals represent the states a MediaPlayer object may reside
+ * in. The arcs represent the playback control operations that drive the object
+ * state transition. There are two types of arcs. The arcs with a single arrow
+ * head represent synchronous method calls, while those with
+ * a double arrow head represent asynchronous method calls.</p>
+ *
+ * <p><img src="../../../images/mediaplayer_state_diagram.gif"
+ * alt="MediaPlayer State diagram"
+ * border="0" /></p>
+ *
+ * <p>From this state diagram, one can see that a MediaPlayer object has the
+ * following states:</p>
+ * <ul>
+ * <li>When a MediaPlayer object is just created using <code>new</code> or
+ * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after
+ * {@link #release()} is called, it is in the <em>End</em> state. Between these
+ * two states is the life cycle of the MediaPlayer object.
+ * <ul>
+ * <li>There is a subtle but important difference between a newly constructed
+ * MediaPlayer object and the MediaPlayer object after {@link #reset()}
+ * is called. It is a programming error to invoke methods such
+ * as {@link #getCurrentPosition()},
+ * {@link #getDuration()}, {@link #getVideoHeight()},
+ * {@link #getVideoWidth()}, {@link #setAudioStreamType(int)},
+ * {@link #setLooping(boolean)},
+ * {@link #setVolume(float, float)}, {@link #pause()}, {@link #start()},
+ * {@link #stop()}, {@link #seekTo(int)}, {@link #prepare()} or
+ * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these
+ * methods is called right after a MediaPlayer object is constructed,
+ * the user supplied callback method OnErrorListener.onError() won't be
+ * called by the internal player engine and the object state remains
+ * unchanged; but if these methods are called right after {@link #reset()},
+ * the user supplied callback method OnErrorListener.onError() will be
+ * invoked by the internal player engine and the object will be
+ * transfered to the <em>Error</em> state. </li>
+ * <li>It is also recommended that once
+ * a MediaPlayer object is no longer being used, call {@link #release()} immediately
+ * so that resources used by the internal player engine associated with the
+ * MediaPlayer object can be released immediately. Resource may include
+ * singleton resources such as hardware acceleration components and
+ * failure to call {@link #release()} may cause subsequent instances of
+ * MediaPlayer objects to fallback to software implementations or fail
+ * altogether. Once the MediaPlayer
+ * object is in the <em>End</em> state, it can no longer be used and
+ * there is no way to bring it back to any other state. </li>
+ * <li>Furthermore,
+ * the MediaPlayer objects created using <code>new</code> is in the
+ * <em>Idle</em> state, while those created with one
+ * of the overloaded convenient <code>create</code> methods are <em>NOT</em>
+ * in the <em>Idle</em> state. In fact, the objects are in the <em>Prepared</em>
+ * state if the creation using <code>create</code> method is successful.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>In general, some playback control operation may fail due to various
+ * reasons, such as unsupported audio/video format, poorly interleaved
+ * audio/video, resolution too high, streaming timeout, and the like.
+ * Thus, error reporting and recovery is an important concern under
+ * these circumstances. Sometimes, due to programming errors, invoking a playback
+ * control operation in an invalid state may also occur. Under all these
+ * error conditions, the internal player engine invokes a user supplied
+ * OnErrorListener.onError() method if an OnErrorListener has been
+ * registered beforehand via
+ * {@link #setOnErrorListener(android.media.MediaPlayer.OnErrorListener)}.
+ * <ul>
+ * <li>It is important to note that once an error occurs, the
+ * MediaPlayer object enters the <em>Error</em> state (except as noted
+ * above), even if an error listener has not been registered by the application.</li>
+ * <li>In order to reuse a MediaPlayer object that is in the <em>
+ * Error</em> state and recover from the error,
+ * {@link #reset()} can be called to restore the object to its <em>Idle</em>
+ * state.</li>
+ * <li>It is good programming practice to have your application
+ * register a OnErrorListener to look out for error notifications from
+ * the internal player engine.</li>
+ * <li>IlleglStateException is
+ * thrown to prevent programming errors such as calling {@link #prepare()},
+ * {@link #prepareAsync()}, or one of the overloaded <code>setDataSource
+ * </code> methods in an invalid state. </li>
+ * </ul>
+ * </li>
+ * <li>Calling
+ * {@link #setDataSource(FileDescriptor)}, or
+ * {@link #setDataSource(String)}, or
+ * {@link #setDataSource(Context, Uri)}, or
+ * {@link #setDataSource(FileDescriptor, long, long)} transfers a
+ * MediaPlayer object in the <em>Idle</em> state to the
+ * <em>Initialized</em> state.
+ * <ul>
+ * <li>An IllegalStateException is thrown if
+ * setDataSource() is called in any other state.</li>
+ * <li>It is good programming
+ * practice to always look out for <code>IllegalArgumentException</code>
+ * and <code>IOException</code> that may be thrown from the overloaded
+ * <code>setDataSource</code> methods.</li>
+ * </ul>
+ * </li>
+ * <li>A MediaPlayer object must first enter the <em>Prepared</em> state
+ * before playback can be started.
+ * <ul>
+ * <li>There are two ways (synchronous vs.
+ * asynchronous) that the <em>Prepared</em> state can be reached:
+ * either a call to {@link #prepare()} (synchronous) which
+ * transfers the object to the <em>Prepared</em> state once the method call
+ * returns, or a call to {@link #prepareAsync()} (asynchronous) which
+ * first transfers the object to the <em>Preparing</em> state after the
+ * call returns (which occurs almost right way) while the internal
+ * player engine continues working on the rest of preparation work
+ * until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns,
+ * the internal player engine then calls a user supplied callback method,
+ * onPrepared() of the OnPreparedListener interface, if an
+ * OnPreparedListener is registered beforehand via {@link
+ * #setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)}.</li>
+ * <li>It is important to note that
+ * the <em>Preparing</em> state is a transient state, and the behavior
+ * of calling any method with side effect while a MediaPlayer object is
+ * in the <em>Preparing</em> state is undefined.</li>
+ * <li>An IllegalStateException is
+ * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in
+ * any other state.</li>
+ * <li>While in the <em>Prepared</em> state, properties
+ * such as audio/sound volume, screenOnWhilePlaying, looping can be
+ * adjusted by invoking the corresponding set methods.</li>
+ * </ul>
+ * </li>
+ * <li>To start the playback, {@link #start()} must be called. After
+ * {@link #start()} returns successfully, the MediaPlayer object is in the
+ * <em>Started</em> state. {@link #isPlaying()} can be called to test
+ * whether the MediaPlayer object is in the <em>Started</em> state.
+ * <ul>
+ * <li>While in the <em>Started</em> state, the internal player engine calls
+ * a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback
+ * method if a OnBufferingUpdateListener has been registered beforehand
+ * via {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}.
+ * This callback allows applications to keep track of the buffering status
+ * while streaming audio/video.</li>
+ * <li>Calling {@link #start()} has not effect
+ * on a MediaPlayer object that is already in the <em>Started</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>Playback can be paused and stopped, and the current playback position
+ * can be adjusted. Playback can be paused via {@link #pause()}. When the call to
+ * {@link #pause()} returns, the MediaPlayer object enters the
+ * <em>Paused</em> state. Note that the transition from the <em>Started</em>
+ * state to the <em>Paused</em> state and vice versa happens
+ * asynchronously in the player engine. It may take some time before
+ * the state is updated in calls to {@link #isPlaying()}, and it can be
+ * a number of seconds in the case of streamed content.
+ * <ul>
+ * <li>Calling {@link #start()} to resume playback for a paused
+ * MediaPlayer object, and the resumed playback
+ * position is the same as where it was paused. When the call to
+ * {@link #start()} returns, the paused MediaPlayer object goes back to
+ * the <em>Started</em> state.</li>
+ * <li>Calling {@link #pause()} has no effect on
+ * a MediaPlayer object that is already in the <em>Paused</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>Calling {@link #stop()} stops playback and causes a
+ * MediaPlayer in the <em>Started</em>, <em>Paused</em>, <em>Prepared
+ * </em> or <em>PlaybackCompleted</em> state to enter the
+ * <em>Stopped</em> state.
+ * <ul>
+ * <li>Once in the <em>Stopped</em> state, playback cannot be started
+ * until {@link #prepare()} or {@link #prepareAsync()} are called to set
+ * the MediaPlayer object to the <em>Prepared</em> state again.</li>
+ * <li>Calling {@link #stop()} has no effect on a MediaPlayer
+ * object that is already in the <em>Stopped</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>The playback position can be adjusted with a call to
+ * {@link #seekTo(int)}.
+ * <ul>
+ * <li>Although the asynchronuous {@link #seekTo(int)}
+ * call returns right way, the actual seek operation may take a while to
+ * finish, especially for audio/video being streamed. When the actual
+ * seek operation completes, the internal player engine calls a user
+ * supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener
+ * has been registered beforehand via
+ * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}.</li>
+ * <li>Please
+ * note that {@link #seekTo(int)} can also be called in the other states,
+ * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted
+ * </em> state.</li>
+ * <li>Furthermore, the actual current playback position
+ * can be retrieved with a call to {@link #getCurrentPosition()}, which
+ * is helpful for applications such as a Music player that need to keep
+ * track of the playback progress.</li>
+ * </ul>
+ * </li>
+ * <li>When the playback reaches the end of stream, the playback completes.
+ * <ul>
+ * <li>If the looping mode was being set to <var>true</var>with
+ * {@link #setLooping(boolean)}, the MediaPlayer object shall remain in
+ * the <em>Started</em> state.</li>
+ * <li>If the looping mode was set to <var>false
+ * </var>, the player engine calls a user supplied callback method,
+ * OnCompletion.onCompletion(), if a OnCompletionListener is registered
+ * beforehand via {@link #setOnCompletionListener(OnCompletionListener)}.
+ * The invoke of the callback signals that the object is now in the <em>
+ * PlaybackCompleted</em> state.</li>
+ * <li>While in the <em>PlaybackCompleted</em>
+ * state, calling {@link #start()} can restart the playback from the
+ * beginning of the audio/video source.</li>
+ * </ul>
+ *
+ *
+ * <a name="Valid_and_Invalid_States"></a>
+ * <h3>Valid and invalid states</h3>
+ *
+ * <table border="0" cellspacing="0" cellpadding="0">
+ * <tr><td>Method Name </p></td>
+ * <td>Valid Sates </p></td>
+ * <td>Invalid States </p></td>
+ * <td>Comments </p></td></tr>
+ * <tr><td>getCurrentPosition </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted} </p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getDuration </p></td>
+ * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoHeight </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoWidth </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>isPlaying </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>pause </p></td>
+ * <td>{Started, Paused}</p></td>
+ * <td>{Idle, Initialized, Prepared, Stopped, PlaybackCompleted, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Paused</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>prepare </p></td>
+ * <td>{Initialized, Stopped} </p></td>
+ * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Prepared</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>prepareAsync </p></td>
+ * <td>{Initialized, Stopped} </p></td>
+ * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Preparing</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>release </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>After {@link #release()}, the object is no longer available. </p></td></tr>
+ * <tr><td>reset </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted, Error}</p></td>
+ * <td>{}</p></td>
+ * <td>After {@link #reset()}, the object is like being just created.</p></td></tr>
+ * <tr><td>seekTo </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioStreamType </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>setDataSource </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Initialized</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setDisplay </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setLooping </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>isLooping </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setOnBufferingUpdateListener </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setOnCompletionListener </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setOnErrorListener </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setOnPreparedListener </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setOnSeekCompleteListener </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setScreenOnWhilePlaying</></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setVolume </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.
+ * <tr><td>setWakeMode </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state.</p></td></tr>
+ * <tr><td>start </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Started</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>stop </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Stopped</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * </table>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * <p>One may need to declare a corresponding WAKE_LOCK permission {@link
+ * android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * element.
+ *
*/
public class MediaPlayer
{
@@ -182,14 +569,11 @@ public class MediaPlayer
}
/**
- * Sets the data source as a content Uri. Call this after reset(), or before
- * any other method (including setDataSource()) that might throw
- * IllegalStateException in this class.
+ * Sets the data source as a content Uri.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(Context context, Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
@@ -221,25 +605,19 @@ public class MediaPlayer
}
/**
- * Sets the data source (file-path or http/rtsp URL) to use. Call this after
- * reset(), or before any other method (including setDataSource()) that might
- * throw IllegalStateException in this class.
+ * Sets the data source (file-path or http/rtsp URL) to use.
*
* @param path the path of the file, or the http/rtsp URL of the stream you want to play
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public native void setDataSource(String path) throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Sets the data source (FileDescriptor) to use. It is the caller's responsibility
* to close the file descriptor. It is safe to do so as soon as this call returns.
- * Call this after reset(), or before any other method (including setDataSource())
- * that might throw IllegalStateException in this class.
*
* @param fd the FileDescriptor for the file you want to play
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
@@ -250,44 +628,35 @@ public class MediaPlayer
/**
* Sets the data source (FileDescriptor) to use. It is the caller's responsibility
* to close the file descriptor. It is safe to do so as soon as this call returns.
- * Call this after reset(), or before any other method (including setDataSource())
- * that might throw IllegalStateException in this class.
*
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts, in bytes
* @param length the length in bytes of the data to be played
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
- * Prepares the player for playback, synchronously. Call this after
- * setDataSource() or stop(), and before any other method that might
- * throw IllegalStateException in this class.
+ * Prepares the player for playback, synchronously.
*
* After setting the datasource and the display surface, you need to either
* call prepare() or prepareAsync(). For files, it is OK to call prepare(),
* which blocks until MediaPlayer is ready for playback.
*
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public native void prepare() throws IOException, IllegalStateException;
/**
- * Prepares the player for playback, asynchronously. Call this after
- * setDataSource() or stop(), and before any other method that might
- * throw IllegalStateException in this class.
+ * Prepares the player for playback, asynchronously.
*
* After setting the datasource and the display surface, you need to either
* call prepare() or prepareAsync(). For streams, you should call prepareAsync(),
* which returns immediately, rather than blocking until enough data has been
* buffered.
*
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public native void prepareAsync() throws IllegalStateException;
@@ -295,12 +664,9 @@ public class MediaPlayer
* Starts or resumes playback. If playback had previously been paused,
* playback will continue from where it was paused. If playback had
* been stopped, or never started before, playback will start at the
- * beginning. Call this after receiving onCompletion or onPrepared
- * event notification from OnCompletionListener or OnPreparedListener
- * interface, or called after prepare() or pause().
+ * beginning.
*
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if it is called in an invalid state
*/
public void start() throws IllegalStateException {
stayAwake(true);
@@ -311,11 +677,9 @@ public class MediaPlayer
/**
* Stops playback after playback has been stopped or paused.
- * Call this after start() or pause(), or after receiving the onPrepared
- * event notification from OnPreparedListener interface.
*
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
*/
public void stop() throws IllegalStateException {
stayAwake(false);
@@ -325,11 +689,10 @@ public class MediaPlayer
private native void _stop() throws IllegalStateException;
/**
- * Pauses playback. Call start() to resume. Call this after start()
- * and before any other method that might throw IllegalStateException in this class.
+ * Pauses playback. Call start() to resume.
*
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
*/
public void pause() throws IllegalStateException {
stayAwake(false);
@@ -409,7 +772,7 @@ public class MediaPlayer
}
/**
- * Returns the width of the video. Call this after setDataSource() method.
+ * Returns the width of the video.
*
* @return the width of the video, or 0 if there is no video,
* no display surface was set, or prepare()/prepareAsync()
@@ -418,7 +781,7 @@ public class MediaPlayer
public native int getVideoWidth();
/**
- * Returns the height of the video. Call this after setDataSource() method.
+ * Returns the height of the video.
*
* @return the height of the video, or 0 if there is no video,
* no display surface was set, or prepare()/prepareAsync()
@@ -427,33 +790,30 @@ public class MediaPlayer
public native int getVideoHeight();
/**
- * Checks whether the MediaPlayer is playing. Call this after
- * setDataSource() method.
+ * Checks whether the MediaPlayer is playing.
*
* @return true if currently playing, false otherwise
*/
public native boolean isPlaying();
/**
- * Seeks to specified time position. Call this after start(), pause(), or
- * prepare(), or after receiving onPrepared or onCompletion event notification
- * from OnPreparedListener or OnCompletionListener interface.
+ * Seeks to specified time position.
*
* @param msec the offset in milliseconds from the start to seek to
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized
*/
public native void seekTo(int msec) throws IllegalStateException;
/**
- * Gets the current playback position. Call this after setDataSource() method.
+ * Gets the current playback position.
*
* @return the current position in milliseconds
*/
public native int getCurrentPosition();
/**
- * Gets the duration of the file. Call this after setDataSource() method.
+ * Gets the duration of the file.
*
* @return the duration in milliseconds
*/
@@ -472,6 +832,7 @@ public class MediaPlayer
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnErrorListener = null;
+ mOnVideoSizeChangedListener = null;
_release();
}
@@ -500,19 +861,25 @@ public class MediaPlayer
public native void setAudioStreamType(int streamtype);
/**
- * Sets the player to be looping or non-looping. Call this
- * after setDataSource method.
+ * Sets the player to be looping or non-looping.
*
* @param looping whether to loop or not
*/
public native void setLooping(boolean looping);
/**
- * Sets the volume on this player. Call after setDataSource method.
+ * Checks whether the MediaPlayer is looping or non-looping.
+ *
+ * @return true if the MediaPlayer is currently looping, false otherwise
+ */
+ public native boolean isLooping();
+
+ /**
+ * Sets the volume on this player.
* This API is recommended for balancing the output of audio streams
* within an application. Unless you are writing an application to
* control user settings, this API should be used in preference to
- * AudioManager::setStreamVolume API which sets the volume of ALL streams of
+ * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of
* a particular type. Note that the passed volume values are raw scalars.
* UI controls should be scaled logarithmically.
*
@@ -522,19 +889,15 @@ public class MediaPlayer
public native void setVolume(float leftVolume, float rightVolume);
/**
- * Returns a Bitmap containing the video frame at the specified time. Call
- * this after setDataSource() or stop().
- *
- * @param msec the time at which to capture the video frame, in milliseconds
- * @return a Bitmap containing the video frame at the specified time
- * @throws IllegalStateException if it is called
- * in an order other than the one specified above
+ * Currently not implemented, returns null.
+ * @deprecated
* @hide
*/
public native Bitmap getFrameAt(int msec) throws IllegalStateException;
private native final void native_setup(Object mediaplayer_this);
private native final void native_finalize();
+ @Override
protected void finalize() { native_finalize(); }
/* Do not change these values without updating their counterparts
@@ -545,6 +908,7 @@ public class MediaPlayer
private static final int MEDIA_PLAYBACK_COMPLETE = 2;
private static final int MEDIA_BUFFERING_UPDATE = 3;
private static final int MEDIA_SEEK_COMPLETE = 4;
+ private static final int MEDIA_SET_VIDEO_SIZE = 5;
private static final int MEDIA_ERROR = 100;
// error codes from framework that indicate content issues
@@ -596,6 +960,11 @@ public class MediaPlayer
mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
return;
+ case MEDIA_SET_VIDEO_SIZE:
+ if (mOnVideoSizeChangedListener != null)
+ mOnVideoSizeChangedListener.onVideoSizeChanged(mMediaPlayer, msg.arg1, msg.arg2);
+ return;
+
case MEDIA_ERROR:
Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
boolean error_was_handled = false;
@@ -640,7 +1009,7 @@ public class MediaPlayer
/**
* Interface definition for a callback to be invoked when the media
- * file is ready for playback.
+ * source is ready for playback.
*/
public interface OnPreparedListener
{
@@ -653,7 +1022,7 @@ public class MediaPlayer
}
/**
- * Register a callback to be invoked when the media file is ready
+ * Register a callback to be invoked when the media source is ready
* for playback.
*
* @param l the callback that will be run
@@ -667,12 +1036,12 @@ public class MediaPlayer
/**
* Interface definition for a callback to be invoked when playback of
- * a media file has completed.
+ * a media source has completed.
*/
public interface OnCompletionListener
{
/**
- * Called when the end of a media file is reached during playback.
+ * Called when the end of a media source is reached during playback.
*
* @param mp the MediaPlayer that reached the end of the file
*/
@@ -680,7 +1049,7 @@ public class MediaPlayer
}
/**
- * Register a callback to be invoked when the end of a media file
+ * Register a callback to be invoked when the end of a media source
* has been reached during playback.
*
* @param l the callback that will be run
@@ -748,13 +1117,50 @@ public class MediaPlayer
private OnSeekCompleteListener mOnSeekCompleteListener;
+ /**
+ * Interface definition of a callback to be invoked when the
+ * video size is first known or updated
+ * FIXME: Unhide this API after approval
+ * @hide
+ */
+ public interface OnVideoSizeChangedListener
+ {
+ /**
+ * Called to indicate the video size
+ *
+ * @param mp the MediaPlayer associated with this callback
+ * @param width the width of the video
+ * @param height the height of the video
+ * @hide
+ */
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height);
+ }
+
+ /**
+ * Register a callback to be invoked when the video size is
+ * known or updated.
+ *
+ * @param l the callback that will be run
+ * @hide
+ */
+ public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener l)
+ {
+ mOnVideoSizeChangedListener = l;
+ }
+
+ private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
+
/* Do not change these values without updating their counterparts
* in include/media/mediaplayer.h!
*/
- /** Unspecified media player error. @see #OnErrorListener */
+ /** Unspecified media player error.
+ * @see android.media.MediaPlayer.OnErrorListener
+ */
public static final int MEDIA_ERROR_UNKNOWN = 1;
/** Media server died. In this case, the application must release the
- * MediaPlayer object and instantiate a new one. @see #OnErrorListener */
+ * MediaPlayer object and instantiate a new one.
+ * @see android.media.MediaPlayer.OnErrorListener
+ */
public static final int MEDIA_ERROR_SERVER_DIED = 100;
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 870d7f3..651cc41 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -17,6 +17,8 @@
package android.media;
import android.view.Surface;
+import android.hardware.Camera;
+import java.io.IOException;
/**
* Used to record audio and video. The recording control is based on a
@@ -64,6 +66,17 @@ public class MediaRecorder
}
/**
+ * Sets a Camera to use for recording. Use this function to switch
+ * quickly between preview and capture mode without a teardown of
+ * the camera object. Must call before prepare().
+ *
+ * @param c the Camera to use for recording
+ * FIXME: Temporarily hidden until API approval
+ * @hide
+ */
+ public native void setCamera(Camera c);
+
+ /**
* Sets a Surface to show a preview of recorded media (video). Calls this
* before prepare() to make sure that the desirable preview display is
* set.
@@ -113,11 +126,13 @@ public class MediaRecorder
*/
private OutputFormat() {}
public static final int DEFAULT = 0;
- /** 3GPP media codec */
+ /** 3GPP media file format*/
public static final int THREE_GPP = 1;
- /** MPEG4 media codec */
+ /** MPEG4 media file format*/
public static final int MPEG_4 = 2;
- }
+ /** Raw AMR file format */
+ public static final int RAW_AMR = 3;
+ };
/**
* Defines the audio encoding. These constants are used with
@@ -260,8 +275,9 @@ public class MediaRecorder
*
* @throws IllegalStateException if it is called after
* start() or before setOutputFormat().
+ * @throws IOException if prepare fails otherwise.
*/
- public native void prepare() throws IllegalStateException;
+ public native void prepare() throws IllegalStateException, IOException;
/**
* Begins capturing and encoding data to the file specified with
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index dbffefd..38203b6 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -635,15 +635,7 @@ public class MediaScanner
values.put(Audio.Media.IS_ALARM, alarms);
values.put(Audio.Media.IS_MUSIC, music);
} else if (isImage) {
- File parentFile = new File(entry.mPath).getParentFile();
- if (parentFile == null) {
- return null;
- }
- String path = parentFile.toString().toLowerCase();
- String name = parentFile.getName().toLowerCase();
-
- values.put(Images.ImageColumns.BUCKET_ID, path.hashCode());
- values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, name);
+ // nothing right now
}
Uri result = null;
@@ -1342,6 +1334,7 @@ public class MediaScanner
private native void processDirectory(String path, String extensions, MediaScannerClient client);
private native void processFile(String path, String mimeType, MediaScannerClient client);
+ public native void setLocale(String locale);
public native byte[] extractAlbumArt(FileDescriptor fd);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index aba0e7c..cfcb5eb 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -23,7 +23,6 @@ import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import android.provider.DrmStore;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -78,6 +77,18 @@ public class Ringtone {
*/
public void setStreamType(int streamType) {
mStreamType = streamType;
+
+ if (mAudio != null) {
+ /*
+ * The stream type has to be set before the media player is
+ * prepared. Re-initialize it.
+ */
+ try {
+ openMediaPlayer();
+ } catch (IOException e) {
+ Log.w(TAG, "Couldn't set the stream type", e);
+ }
+ }
}
/**
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7397218..2f0007f 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -21,21 +21,16 @@ import com.android.internal.database.SortCursor;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
-import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
-import android.database.MergeCursor;
import android.net.Uri;
import android.os.Environment;
-import android.os.ParcelFileDescriptor;
import android.provider.DrmStore;
import android.provider.MediaStore;
import android.provider.Settings;
import android.provider.Settings.System;
-import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -218,7 +213,7 @@ public class RingtoneManager {
private Cursor mCursor;
private int mType = TYPE_RINGTONE;
-
+
/**
* If a column (item from this list) exists in the Cursor, its value must
* be true (value of 1) for the row to be returned.
@@ -271,6 +266,27 @@ public class RingtoneManager {
mType = type;
setFilterColumnsList(type);
}
+
+ /**
+ * Infers the playback stream type based on what type of ringtones this
+ * manager is returning.
+ *
+ * @return The stream type.
+ * @hide Pending API Council approval
+ */
+ public int inferStreamType() {
+ switch (mType) {
+
+ case TYPE_ALARM:
+ return AudioManager.STREAM_ALARM;
+
+ case TYPE_NOTIFICATION:
+ return AudioManager.STREAM_NOTIFICATION;
+
+ default:
+ return AudioManager.STREAM_RING;
+ }
+ }
/**
* Whether retrieving another {@link Ringtone} will stop playing the
@@ -361,7 +377,8 @@ public class RingtoneManager {
mPreviousRingtone.stop();
}
- return mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position));
+ mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType());
+ return mPreviousRingtone;
}
/**
@@ -556,10 +573,27 @@ public class RingtoneManager {
* @return A {@link Ringtone} for the given URI, or null.
*/
public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
- ContentResolver res = context.getContentResolver();
+ // Don't set the stream type
+ return getRingtone(context, ringtoneUri, -1);
+ }
+
+ /**
+ * Returns a {@link Ringtone} for a given sound URI on the given stream
+ * type. Normally, if you change the stream type on the returned
+ * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
+ * an optimized route to avoid that.
+ *
+ * @param streamType The stream type for the ringtone, or -1 if it should
+ * not be set (and the default used instead).
+ * @see #getRingtone(Context, Uri)
+ */
+ private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
try {
Ringtone r = new Ringtone(context);
+ if (streamType >= 0) {
+ r.setStreamType(streamType);
+ }
r.open(ringtoneUri);
return r;
} catch (Exception ex) {
@@ -584,7 +618,6 @@ public class RingtoneManager {
return null;
}
-
/**
* Gets the current default sound's {@link Uri}. This will give the actual
* sound {@link Uri}, instead of using this, most clients can use
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 1688b1b..427f173 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -77,6 +77,18 @@ public class SoundPool
return id;
}
+ public int load(AssetFileDescriptor afd, int priority) {
+ if (afd != null) {
+ return _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
+ } else {
+ return 0;
+ }
+ }
+
+ public int load(FileDescriptor fd, long offset, long length, int priority) {
+ return _load(fd, offset, length, priority);
+ }
+
private native final int _load(String uri, int priority);
private native final int _load(FileDescriptor fd, long offset, long length, int priority);
@@ -108,4 +120,3 @@ public class SoundPool
protected void finalize() { release(); }
}
-