Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #define LOG_TAG "JAudioTrack" |
| 18 | |
| 19 | #include "media/JAudioAttributes.h" |
| 20 | #include "media/JAudioFormat.h" |
| 21 | #include "media/JAudioTrack.h" |
| 22 | |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 23 | #include <android_media_AudioErrors.h> |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 24 | #include <android_runtime/AndroidRuntime.h> |
| 25 | #include <media/AudioResamplerPublic.h> |
| 26 | |
| 27 | namespace android { |
| 28 | |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 29 | // TODO: Store Java class/methodID as a member variable in the class. |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 30 | // TODO: Add NULL && Exception checks after every JNI call. |
| 31 | JAudioTrack::JAudioTrack( // < Usages of the arguments are below > |
| 32 | audio_stream_type_t streamType, // AudioAudioAttributes |
| 33 | uint32_t sampleRate, // AudioFormat && bufferSizeInBytes |
| 34 | audio_format_t format, // AudioFormat && bufferSizeInBytes |
| 35 | audio_channel_mask_t channelMask, // AudioFormat && bufferSizeInBytes |
| 36 | size_t frameCount, // bufferSizeInBytes |
| 37 | audio_session_t sessionId, // AudioTrack |
| 38 | const audio_attributes_t* pAttributes, // AudioAttributes |
| 39 | float maxRequiredSpeed) { // bufferSizeInBytes |
| 40 | |
| 41 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 42 | jclass jAudioTrackCls = env->FindClass("android/media/AudioTrack"); |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 43 | mAudioTrackCls = (jclass) env->NewGlobalRef(jAudioTrackCls); |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 44 | |
| 45 | maxRequiredSpeed = std::min(std::max(maxRequiredSpeed, 1.0f), AUDIO_TIMESTRETCH_SPEED_MAX); |
| 46 | |
| 47 | int bufferSizeInBytes = 0; |
| 48 | if (sampleRate == 0 || frameCount > 0) { |
| 49 | // Manually calculate buffer size. |
| 50 | bufferSizeInBytes = audio_channel_count_from_out_mask(channelMask) |
| 51 | * audio_bytes_per_sample(format) * (frameCount > 0 ? frameCount : 1); |
| 52 | } else if (sampleRate > 0) { |
| 53 | // Call Java AudioTrack::getMinBufferSize(). |
| 54 | jmethodID jGetMinBufferSize = |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 55 | env->GetStaticMethodID(mAudioTrackCls, "getMinBufferSize", "(III)I"); |
| 56 | bufferSizeInBytes = env->CallStaticIntMethod(mAudioTrackCls, jGetMinBufferSize, |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 57 | sampleRate, outChannelMaskFromNative(channelMask), audioFormatFromNative(format)); |
| 58 | } |
| 59 | bufferSizeInBytes = (int) (bufferSizeInBytes * maxRequiredSpeed); |
| 60 | |
| 61 | // Create a Java AudioTrack object through its Builder. |
| 62 | jclass jBuilderCls = env->FindClass("android/media/AudioTrack$Builder"); |
| 63 | jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V"); |
| 64 | jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor); |
| 65 | |
| 66 | jmethodID jSetAudioAttributes = env->GetMethodID(jBuilderCls, "setAudioAttributes", |
| 67 | "(Landroid/media/AudioAttributes;)Landroid/media/AudioTrack$Builder;"); |
| 68 | jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetAudioAttributes, |
| 69 | JAudioAttributes::createAudioAttributesObj(env, pAttributes, streamType)); |
| 70 | |
| 71 | jmethodID jSetAudioFormat = env->GetMethodID(jBuilderCls, "setAudioFormat", |
| 72 | "(Landroid/media/AudioFormat;)Landroid/media/AudioTrack$Builder;"); |
| 73 | jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetAudioFormat, |
| 74 | JAudioFormat::createAudioFormatObj(env, sampleRate, format, channelMask)); |
| 75 | |
| 76 | jmethodID jSetBufferSizeInBytes = env->GetMethodID(jBuilderCls, "setBufferSizeInBytes", |
| 77 | "(I)Landroid/media/AudioTrack$Builder;"); |
| 78 | jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetBufferSizeInBytes, bufferSizeInBytes); |
| 79 | |
| 80 | // We only use streaming mode of Java AudioTrack. |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 81 | jfieldID jModeStream = env->GetStaticFieldID(mAudioTrackCls, "MODE_STREAM", "I"); |
| 82 | jint transferMode = env->GetStaticIntField(mAudioTrackCls, jModeStream); |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 83 | jmethodID jSetTransferMode = env->GetMethodID(jBuilderCls, "setTransferMode", |
| 84 | "(I)Landroid/media/AudioTrack$Builder;"); |
| 85 | jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetTransferMode, |
| 86 | transferMode /* Java AudioTrack::MODE_STREAM */); |
| 87 | |
| 88 | if (sessionId != 0) { |
| 89 | jmethodID jSetSessionId = env->GetMethodID(jBuilderCls, "setSessionId", |
| 90 | "(I)Landroid/media/AudioTrack$Builder;"); |
| 91 | jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetSessionId, sessionId); |
| 92 | } |
| 93 | |
| 94 | jmethodID jBuild = env->GetMethodID(jBuilderCls, "build", "()Landroid/media/AudioTrack;"); |
| 95 | mAudioTrackObj = env->CallObjectMethod(jBuilderObj, jBuild); |
| 96 | } |
| 97 | |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 98 | JAudioTrack::~JAudioTrack() { |
| 99 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 100 | env->DeleteGlobalRef(mAudioTrackCls); |
| 101 | } |
| 102 | |
| 103 | size_t JAudioTrack::frameCount() { |
| 104 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 105 | jmethodID jGetBufferSizeInFrames = env->GetMethodID( |
| 106 | mAudioTrackCls, "getBufferSizeInFrames", "()I"); |
| 107 | return env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames); |
| 108 | } |
| 109 | |
| 110 | size_t JAudioTrack::channelCount() { |
| 111 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 112 | jmethodID jGetChannelCount = env->GetMethodID(mAudioTrackCls, "getChannelCount", "()I"); |
| 113 | return env->CallIntMethod(mAudioTrackObj, jGetChannelCount); |
| 114 | } |
| 115 | |
| 116 | status_t JAudioTrack::getPosition(uint32_t *position) { |
| 117 | if (position == NULL) { |
| 118 | return BAD_VALUE; |
| 119 | } |
| 120 | |
| 121 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 122 | jmethodID jGetPlaybackHeadPosition = env->GetMethodID( |
| 123 | mAudioTrackCls, "getPlaybackHeadPosition", "()I"); |
| 124 | *position = env->CallIntMethod(mAudioTrackObj, jGetPlaybackHeadPosition); |
| 125 | |
| 126 | return NO_ERROR; |
| 127 | } |
| 128 | |
| 129 | status_t JAudioTrack::setAuxEffectSendLevel(float level) { |
| 130 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 131 | jmethodID jSetAuxEffectSendLevel = env->GetMethodID( |
| 132 | mAudioTrackCls, "setAuxEffectSendLevel", "(F)I"); |
| 133 | int result = env->CallIntMethod(mAudioTrackObj, jSetAuxEffectSendLevel, level); |
| 134 | return javaToNativeStatus(result); |
| 135 | } |
| 136 | |
| 137 | status_t JAudioTrack::attachAuxEffect(int effectId) { |
| 138 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 139 | jmethodID jAttachAuxEffect = env->GetMethodID(mAudioTrackCls, "attachAuxEffect", "(I)I"); |
| 140 | int result = env->CallIntMethod(mAudioTrackObj, jAttachAuxEffect, effectId); |
| 141 | return javaToNativeStatus(result); |
| 142 | } |
| 143 | |
| 144 | status_t JAudioTrack::setVolume(float left, float right) { |
| 145 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 146 | // TODO: Java setStereoVolume is deprecated. Do we really need this method? |
| 147 | jmethodID jSetStereoVolume = env->GetMethodID(mAudioTrackCls, "setStereoVolume", "(FF)I"); |
| 148 | int result = env->CallIntMethod(mAudioTrackObj, jSetStereoVolume, left, right); |
| 149 | return javaToNativeStatus(result); |
| 150 | } |
| 151 | |
| 152 | status_t JAudioTrack::setVolume(float volume) { |
| 153 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 154 | jmethodID jSetVolume = env->GetMethodID(mAudioTrackCls, "setVolume", "(F)I"); |
| 155 | int result = env->CallIntMethod(mAudioTrackObj, jSetVolume, volume); |
| 156 | return javaToNativeStatus(result); |
| 157 | } |
| 158 | |
| 159 | status_t JAudioTrack::start() { |
| 160 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 161 | jmethodID jPlay = env->GetMethodID(mAudioTrackCls, "play", "()V"); |
| 162 | // TODO: Should we catch the Java IllegalStateException from play()? |
| 163 | env->CallVoidMethod(mAudioTrackObj, jPlay); |
| 164 | return NO_ERROR; |
| 165 | } |
| 166 | |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 167 | void JAudioTrack::stop() { |
| 168 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 169 | jmethodID jStop = env->GetMethodID(mAudioTrackCls, "stop", "()V"); |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 170 | env->CallVoidMethod(mAudioTrackObj, jStop); |
Hyundo Moon | 9b26e94 | 2017-12-14 10:46:54 +0900 | [diff] [blame^] | 171 | // TODO: Should we catch IllegalStateException? |
| 172 | } |
| 173 | |
| 174 | // TODO: Is the right implementation? |
| 175 | bool JAudioTrack::stopped() const { |
| 176 | return !isPlaying(); |
| 177 | } |
| 178 | |
| 179 | void JAudioTrack::flush() { |
| 180 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 181 | jmethodID jFlush = env->GetMethodID(mAudioTrackCls, "flush", "()V"); |
| 182 | env->CallVoidMethod(mAudioTrackObj, jFlush); |
| 183 | } |
| 184 | |
| 185 | void JAudioTrack::pause() { |
| 186 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 187 | jmethodID jPause = env->GetMethodID(mAudioTrackCls, "pause", "()V"); |
| 188 | env->CallVoidMethod(mAudioTrackObj, jPause); |
| 189 | // TODO: Should we catch IllegalStateException? |
| 190 | } |
| 191 | |
| 192 | bool JAudioTrack::isPlaying() const { |
| 193 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 194 | jmethodID jGetPlayState = env->GetMethodID(mAudioTrackCls, "getPlayState", "()I"); |
| 195 | int currentPlayState = env->CallIntMethod(mAudioTrackObj, jGetPlayState); |
| 196 | |
| 197 | // TODO: In Java AudioTrack, there is no STOPPING state. |
| 198 | // This means while stopping, isPlaying() will return different value in two class. |
| 199 | // - in existing native AudioTrack: true |
| 200 | // - in JAudioTrack: false |
| 201 | // If not okay, also modify the implementation of stopped(). |
| 202 | jfieldID jPlayStatePlaying = env->GetStaticFieldID(mAudioTrackCls, "PLAYSTATE_PLAYING", "I"); |
| 203 | int statePlaying = env->GetStaticIntField(mAudioTrackCls, jPlayStatePlaying); |
| 204 | return currentPlayState == statePlaying; |
| 205 | } |
| 206 | |
| 207 | uint32_t JAudioTrack::getSampleRate() { |
| 208 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 209 | jmethodID jGetSampleRate = env->GetMethodID(mAudioTrackCls, "getSampleRate", "()I"); |
| 210 | return env->CallIntMethod(mAudioTrackObj, jGetSampleRate); |
| 211 | } |
| 212 | |
| 213 | audio_format_t JAudioTrack::format() { |
| 214 | JNIEnv *env = AndroidRuntime::getJNIEnv(); |
| 215 | jmethodID jGetAudioFormat = env->GetMethodID(mAudioTrackCls, "getAudioFormat", "()I"); |
| 216 | int javaFormat = env->CallIntMethod(mAudioTrackObj, jGetAudioFormat); |
| 217 | return audioFormatToNative(javaFormat); |
| 218 | } |
| 219 | |
| 220 | status_t JAudioTrack::javaToNativeStatus(int javaStatus) { |
| 221 | switch (javaStatus) { |
| 222 | case AUDIO_JAVA_SUCCESS: |
| 223 | return NO_ERROR; |
| 224 | case AUDIO_JAVA_BAD_VALUE: |
| 225 | return BAD_VALUE; |
| 226 | case AUDIO_JAVA_INVALID_OPERATION: |
| 227 | return INVALID_OPERATION; |
| 228 | case AUDIO_JAVA_PERMISSION_DENIED: |
| 229 | return PERMISSION_DENIED; |
| 230 | case AUDIO_JAVA_NO_INIT: |
| 231 | return NO_INIT; |
| 232 | case AUDIO_JAVA_WOULD_BLOCK: |
| 233 | return WOULD_BLOCK; |
| 234 | case AUDIO_JAVA_DEAD_OBJECT: |
| 235 | return DEAD_OBJECT; |
| 236 | default: |
| 237 | return UNKNOWN_ERROR; |
| 238 | } |
Hyundo Moon | 660a74e | 2017-12-13 11:29:45 +0900 | [diff] [blame] | 239 | } |
| 240 | |
| 241 | } // namespace android |