diff options
Diffstat (limited to 'media/java')
| -rw-r--r-- | media/java/android/media/AmrInputStream.java | 141 | ||||
| -rw-r--r-- | media/java/android/media/AudioFormat.java | 51 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 61 | ||||
| -rw-r--r-- | media/java/android/media/AudioRecord.java | 721 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 53 | ||||
| -rw-r--r-- | media/java/android/media/AudioSystem.java | 17 | ||||
| -rw-r--r-- | media/java/android/media/AudioTrack.java | 934 | ||||
| -rw-r--r-- | media/java/android/media/MediaMetadataRetriever.java | 108 | ||||
| -rw-r--r-- | media/java/android/media/MediaPlayer.java | 552 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 24 | ||||
| -rw-r--r-- | media/java/android/media/MediaScanner.java | 11 | ||||
| -rw-r--r-- | media/java/android/media/Ringtone.java | 13 | ||||
| -rw-r--r-- | media/java/android/media/RingtoneManager.java | 51 | ||||
| -rw-r--r-- | media/java/android/media/SoundPool.java | 13 |
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 <uses-permission>} + * 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(); } } - |
