JAudioTrack: Add 14 methods
This CL adds following methods in JAudioTrack:
- frameCount / channelCount
- getPosition
- setAuxEffectSendLevel / attachAuxEffect
- setVolume (2 different versions)
- start / pause / flush / isPlaying / stopped
- getSampleRate
- format
Test: build successfully
Change-Id: I790acca4405f41a94fbe14daf5824e82d1d67b4c
diff --git a/media/libmedia/JAudioTrack.cpp b/media/libmedia/JAudioTrack.cpp
index 4ba9425..3b60922 100644
--- a/media/libmedia/JAudioTrack.cpp
+++ b/media/libmedia/JAudioTrack.cpp
@@ -20,11 +20,13 @@
#include "media/JAudioFormat.h"
#include "media/JAudioTrack.h"
+#include <android_media_AudioErrors.h>
#include <android_runtime/AndroidRuntime.h>
#include <media/AudioResamplerPublic.h>
namespace android {
+// TODO: Store Java class/methodID as a member variable in the class.
// TODO: Add NULL && Exception checks after every JNI call.
JAudioTrack::JAudioTrack( // < Usages of the arguments are below >
audio_stream_type_t streamType, // AudioAudioAttributes
@@ -38,6 +40,7 @@
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass jAudioTrackCls = env->FindClass("android/media/AudioTrack");
+ mAudioTrackCls = (jclass) env->NewGlobalRef(jAudioTrackCls);
maxRequiredSpeed = std::min(std::max(maxRequiredSpeed, 1.0f), AUDIO_TIMESTRETCH_SPEED_MAX);
@@ -49,8 +52,8 @@
} else if (sampleRate > 0) {
// Call Java AudioTrack::getMinBufferSize().
jmethodID jGetMinBufferSize =
- env->GetStaticMethodID(jAudioTrackCls, "getMinBufferSize", "(III)I");
- bufferSizeInBytes = env->CallStaticIntMethod(jAudioTrackCls, jGetMinBufferSize,
+ env->GetStaticMethodID(mAudioTrackCls, "getMinBufferSize", "(III)I");
+ bufferSizeInBytes = env->CallStaticIntMethod(mAudioTrackCls, jGetMinBufferSize,
sampleRate, outChannelMaskFromNative(channelMask), audioFormatFromNative(format));
}
bufferSizeInBytes = (int) (bufferSizeInBytes * maxRequiredSpeed);
@@ -75,8 +78,8 @@
jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetBufferSizeInBytes, bufferSizeInBytes);
// We only use streaming mode of Java AudioTrack.
- jfieldID jModeStream = env->GetStaticFieldID(jAudioTrackCls, "MODE_STREAM", "I");
- jint transferMode = env->GetStaticIntField(jAudioTrackCls, jModeStream);
+ jfieldID jModeStream = env->GetStaticFieldID(mAudioTrackCls, "MODE_STREAM", "I");
+ jint transferMode = env->GetStaticIntField(mAudioTrackCls, jModeStream);
jmethodID jSetTransferMode = env->GetMethodID(jBuilderCls, "setTransferMode",
"(I)Landroid/media/AudioTrack$Builder;");
jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetTransferMode,
@@ -92,11 +95,147 @@
mAudioTrackObj = env->CallObjectMethod(jBuilderObj, jBuild);
}
+JAudioTrack::~JAudioTrack() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mAudioTrackCls);
+}
+
+size_t JAudioTrack::frameCount() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetBufferSizeInFrames = env->GetMethodID(
+ mAudioTrackCls, "getBufferSizeInFrames", "()I");
+ return env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);
+}
+
+size_t JAudioTrack::channelCount() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetChannelCount = env->GetMethodID(mAudioTrackCls, "getChannelCount", "()I");
+ return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
+}
+
+status_t JAudioTrack::getPosition(uint32_t *position) {
+ if (position == NULL) {
+ return BAD_VALUE;
+ }
+
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetPlaybackHeadPosition = env->GetMethodID(
+ mAudioTrackCls, "getPlaybackHeadPosition", "()I");
+ *position = env->CallIntMethod(mAudioTrackObj, jGetPlaybackHeadPosition);
+
+ return NO_ERROR;
+}
+
+status_t JAudioTrack::setAuxEffectSendLevel(float level) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jSetAuxEffectSendLevel = env->GetMethodID(
+ mAudioTrackCls, "setAuxEffectSendLevel", "(F)I");
+ int result = env->CallIntMethod(mAudioTrackObj, jSetAuxEffectSendLevel, level);
+ return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::attachAuxEffect(int effectId) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jAttachAuxEffect = env->GetMethodID(mAudioTrackCls, "attachAuxEffect", "(I)I");
+ int result = env->CallIntMethod(mAudioTrackObj, jAttachAuxEffect, effectId);
+ return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::setVolume(float left, float right) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ // TODO: Java setStereoVolume is deprecated. Do we really need this method?
+ jmethodID jSetStereoVolume = env->GetMethodID(mAudioTrackCls, "setStereoVolume", "(FF)I");
+ int result = env->CallIntMethod(mAudioTrackObj, jSetStereoVolume, left, right);
+ return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::setVolume(float volume) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jSetVolume = env->GetMethodID(mAudioTrackCls, "setVolume", "(F)I");
+ int result = env->CallIntMethod(mAudioTrackObj, jSetVolume, volume);
+ return javaToNativeStatus(result);
+}
+
+status_t JAudioTrack::start() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jPlay = env->GetMethodID(mAudioTrackCls, "play", "()V");
+ // TODO: Should we catch the Java IllegalStateException from play()?
+ env->CallVoidMethod(mAudioTrackObj, jPlay);
+ return NO_ERROR;
+}
+
void JAudioTrack::stop() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass jAudioTrackCls = env->FindClass("android/media/AudioTrack");
- jmethodID jStop = env->GetMethodID(jAudioTrackCls, "stop", "()V");
+ jmethodID jStop = env->GetMethodID(mAudioTrackCls, "stop", "()V");
env->CallVoidMethod(mAudioTrackObj, jStop);
+ // TODO: Should we catch IllegalStateException?
+}
+
+// TODO: Is the right implementation?
+bool JAudioTrack::stopped() const {
+ return !isPlaying();
+}
+
+void JAudioTrack::flush() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jFlush = env->GetMethodID(mAudioTrackCls, "flush", "()V");
+ env->CallVoidMethod(mAudioTrackObj, jFlush);
+}
+
+void JAudioTrack::pause() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jPause = env->GetMethodID(mAudioTrackCls, "pause", "()V");
+ env->CallVoidMethod(mAudioTrackObj, jPause);
+ // TODO: Should we catch IllegalStateException?
+}
+
+bool JAudioTrack::isPlaying() const {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetPlayState = env->GetMethodID(mAudioTrackCls, "getPlayState", "()I");
+ int currentPlayState = env->CallIntMethod(mAudioTrackObj, jGetPlayState);
+
+ // TODO: In Java AudioTrack, there is no STOPPING state.
+ // This means while stopping, isPlaying() will return different value in two class.
+ // - in existing native AudioTrack: true
+ // - in JAudioTrack: false
+ // If not okay, also modify the implementation of stopped().
+ jfieldID jPlayStatePlaying = env->GetStaticFieldID(mAudioTrackCls, "PLAYSTATE_PLAYING", "I");
+ int statePlaying = env->GetStaticIntField(mAudioTrackCls, jPlayStatePlaying);
+ return currentPlayState == statePlaying;
+}
+
+uint32_t JAudioTrack::getSampleRate() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetSampleRate = env->GetMethodID(mAudioTrackCls, "getSampleRate", "()I");
+ return env->CallIntMethod(mAudioTrackObj, jGetSampleRate);
+}
+
+audio_format_t JAudioTrack::format() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetAudioFormat = env->GetMethodID(mAudioTrackCls, "getAudioFormat", "()I");
+ int javaFormat = env->CallIntMethod(mAudioTrackObj, jGetAudioFormat);
+ return audioFormatToNative(javaFormat);
+}
+
+status_t JAudioTrack::javaToNativeStatus(int javaStatus) {
+ switch (javaStatus) {
+ case AUDIO_JAVA_SUCCESS:
+ return NO_ERROR;
+ case AUDIO_JAVA_BAD_VALUE:
+ return BAD_VALUE;
+ case AUDIO_JAVA_INVALID_OPERATION:
+ return INVALID_OPERATION;
+ case AUDIO_JAVA_PERMISSION_DENIED:
+ return PERMISSION_DENIED;
+ case AUDIO_JAVA_NO_INIT:
+ return NO_INIT;
+ case AUDIO_JAVA_WOULD_BLOCK:
+ return WOULD_BLOCK;
+ case AUDIO_JAVA_DEAD_OBJECT:
+ return DEAD_OBJECT;
+ default:
+ return UNKNOWN_ERROR;
+ }
}
} // namespace android
diff --git a/media/libmedia/include/media/JAudioTrack.h b/media/libmedia/include/media/JAudioTrack.h
index fb29c24..42e036b 100644
--- a/media/libmedia/include/media/JAudioTrack.h
+++ b/media/libmedia/include/media/JAudioTrack.h
@@ -93,11 +93,105 @@
threadCanCallJava
*/
+ virtual ~JAudioTrack();
+
+ size_t frameCount();
+ size_t channelCount();
+
+ /* Return the total number of frames played since playback start.
+ * The counter will wrap (overflow) periodically, e.g. every ~27 hours at 44.1 kHz.
+ * It is reset to zero by flush(), reload(), and stop().
+ *
+ * Parameters:
+ *
+ * position: Address where to return play head position.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - BAD_VALUE: position is NULL
+ */
+ status_t getPosition(uint32_t *position);
+
+ /* Set the send level for this track. An auxiliary effect should be attached
+ * to the track with attachAuxEffect(). Level must be >= 0.0 and <= 1.0.
+ */
+ status_t setAuxEffectSendLevel(float level);
+
+ /* Attach track auxiliary output to specified effect. Use effectId = 0
+ * to detach track from effect.
+ *
+ * Parameters:
+ *
+ * effectId: effectId obtained from AudioEffect::id().
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the effect is not an auxiliary effect.
+ * - BAD_VALUE: The specified effect ID is invalid
+ */
+ status_t attachAuxEffect(int effectId);
+
+ /* Set volume for this track, mostly used for games' sound effects
+ * left and right volumes. Levels must be >= 0.0 and <= 1.0.
+ * This is the older API. New applications should use setVolume(float) when possible.
+ */
+ status_t setVolume(float left, float right);
+
+ /* Set volume for all channels. This is the preferred API for new applications,
+ * especially for multi-channel content.
+ */
+ status_t setVolume(float volume);
+
+ // TODO: Does this comment equally apply to the Java AudioTrack::play()?
+ /* After it's created the track is not active. Call start() to
+ * make it active. If set, the callback will start being called.
+ * If the track was previously paused, volume is ramped up over the first mix buffer.
+ */
+ status_t start();
+
+ // TODO: Does this comment equally apply to the Java AudioTrack::stop()?
+ /* Stop a track.
+ * In static buffer mode, the track is stopped immediately.
+ * In streaming mode, the callback will cease being called. Note that obtainBuffer() still
+ * works and will fill up buffers until the pool is exhausted, and then will return WOULD_BLOCK.
+ * In streaming mode the stop does not occur immediately: any data remaining in the buffer
+ * is first drained, mixed, and output, and only then is the track marked as stopped.
+ */
void stop();
+ bool stopped() const;
-protected:
- jobject mAudioTrackObj; // Java AudioTrack object
+ // TODO: Does this comment equally apply to the Java AudioTrack::flush()?
+ /* Flush a stopped or paused track. All previously buffered data is discarded immediately.
+ * This has the effect of draining the buffers without mixing or output.
+ * Flush is intended for streaming mode, for example before switching to non-contiguous content.
+ * This function is a no-op if the track is not stopped or paused, or uses a static buffer.
+ */
+ void flush();
+ // TODO: Does this comment equally apply to the Java AudioTrack::pause()?
+ // At least we are not using obtainBuffer.
+ /* Pause a track. After pause, the callback will cease being called and
+ * obtainBuffer returns WOULD_BLOCK. Note that obtainBuffer() still works
+ * and will fill up buffers until the pool is exhausted.
+ * Volume is ramped down over the next mix buffer following the pause request,
+ * and then the track is marked as paused. It can be resumed with ramp up by start().
+ */
+ void pause();
+
+ bool isPlaying() const;
+
+ /* Return current source sample rate in Hz.
+ * If specified as zero in constructor, this will be the sink sample rate.
+ */
+ uint32_t getSampleRate();
+
+ audio_format_t format();
+
+private:
+ jclass mAudioTrackCls;
+ jobject mAudioTrackObj;
+
+ status_t javaToNativeStatus(int javaStatus);
};
}; // namespace android