diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 | 
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:43 -0800 | 
| commit | f013e1afd1e68af5e3b868c26a653bbfb39538f8 (patch) | |
| tree | 7ad6c8fd9c7b55f4b4017171dec1cb760bbd26bf /media/java | |
| parent | e70cfafe580c6f2994c4827cd8a534aabf3eb05c (diff) | |
| download | frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.zip frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.gz frameworks_base-f013e1afd1e68af5e3b868c26a653bbfb39538f8.tar.bz2 | |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'media/java')
| -rw-r--r-- | media/java/android/media/AmrInputStream.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(); }  } - | 
