• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "mediaplayer2/JAudioTrack.h"
22 
23 #include <android_media_AudioErrors.h>
24 #include <mediaplayer2/JavaVMHelper.h>
25 
26 namespace android {
27 
28 // TODO: Store Java class/methodID as a member variable in the class.
29 // TODO: Add NULL && Exception checks after every JNI call.
JAudioTrack(uint32_t sampleRate,audio_format_t format,audio_channel_mask_t channelMask,callback_t cbf,void * user,size_t frameCount,int32_t sessionId,const jobject attributes,float maxRequiredSpeed)30 JAudioTrack::JAudioTrack(                             // < Usages of the arguments are below >
31         uint32_t sampleRate,                          // AudioFormat && bufferSizeInBytes
32         audio_format_t format,                        // AudioFormat && bufferSizeInBytes
33         audio_channel_mask_t channelMask,             // AudioFormat && bufferSizeInBytes
34         callback_t cbf,                               // Offload
35         void* user,                                   // Offload
36         size_t frameCount,                            // bufferSizeInBytes
37         int32_t sessionId,                            // AudioTrack
38         const jobject attributes,                     // AudioAttributes
39         float maxRequiredSpeed) {                     // bufferSizeInBytes
40 
41     JNIEnv *env = JavaVMHelper::getJNIEnv();
42 
43     jclass jAudioTrackCls = env->FindClass("android/media/AudioTrack");
44     mAudioTrackCls = reinterpret_cast<jclass>(env->NewGlobalRef(jAudioTrackCls));
45     env->DeleteLocalRef(jAudioTrackCls);
46 
47     maxRequiredSpeed = std::min(std::max(maxRequiredSpeed, 1.0f), AUDIO_TIMESTRETCH_SPEED_MAX);
48 
49     int bufferSizeInBytes = 0;
50     if (sampleRate == 0 || frameCount > 0) {
51         // Manually calculate buffer size.
52         bufferSizeInBytes = audio_channel_count_from_out_mask(channelMask)
53                 * audio_bytes_per_sample(format) * (frameCount > 0 ? frameCount : 1);
54     } else if (sampleRate > 0) {
55         // Call Java AudioTrack::getMinBufferSize().
56         jmethodID jGetMinBufferSize =
57                 env->GetStaticMethodID(mAudioTrackCls, "getMinBufferSize", "(III)I");
58         bufferSizeInBytes = env->CallStaticIntMethod(mAudioTrackCls, jGetMinBufferSize,
59                 sampleRate, outChannelMaskFromNative(channelMask), audioFormatFromNative(format));
60     }
61     bufferSizeInBytes = (int) (bufferSizeInBytes * maxRequiredSpeed);
62 
63     // Create a Java AudioTrack object through its Builder.
64     jclass jBuilderCls = env->FindClass("android/media/AudioTrack$Builder");
65     jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
66     jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
67 
68     {
69         sp<JObjectHolder> audioAttributesObj;
70         if (attributes != NULL) {
71             audioAttributesObj = new JObjectHolder(attributes);
72         } else {
73             audioAttributesObj = new JObjectHolder(
74                     JAudioAttributes::createAudioAttributesObj(env, NULL));
75         }
76         jmethodID jSetAudioAttributes = env->GetMethodID(jBuilderCls, "setAudioAttributes",
77                 "(Landroid/media/AudioAttributes;)Landroid/media/AudioTrack$Builder;");
78         jBuilderObj = env->CallObjectMethod(jBuilderObj,
79                 jSetAudioAttributes, audioAttributesObj->getJObject());
80     }
81 
82     jmethodID jSetAudioFormat = env->GetMethodID(jBuilderCls, "setAudioFormat",
83             "(Landroid/media/AudioFormat;)Landroid/media/AudioTrack$Builder;");
84     jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetAudioFormat,
85             JAudioFormat::createAudioFormatObj(env, sampleRate, format, channelMask));
86 
87     jmethodID jSetBufferSizeInBytes = env->GetMethodID(jBuilderCls, "setBufferSizeInBytes",
88             "(I)Landroid/media/AudioTrack$Builder;");
89     jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetBufferSizeInBytes, bufferSizeInBytes);
90 
91     // We only use streaming mode of Java AudioTrack.
92     jfieldID jModeStream = env->GetStaticFieldID(mAudioTrackCls, "MODE_STREAM", "I");
93     jint transferMode = env->GetStaticIntField(mAudioTrackCls, jModeStream);
94     jmethodID jSetTransferMode = env->GetMethodID(jBuilderCls, "setTransferMode",
95             "(I)Landroid/media/AudioTrack$Builder;");
96     jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetTransferMode,
97             transferMode /* Java AudioTrack::MODE_STREAM */);
98 
99     if (sessionId != 0) {
100         jmethodID jSetSessionId = env->GetMethodID(jBuilderCls, "setSessionId",
101                 "(I)Landroid/media/AudioTrack$Builder;");
102         jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetSessionId, sessionId);
103     }
104 
105     mFlags = AUDIO_OUTPUT_FLAG_NONE;
106     if (cbf != NULL) {
107         jmethodID jSetOffloadedPlayback = env->GetMethodID(jBuilderCls, "setOffloadedPlayback",
108                 "(Z)Landroid/media/AudioTrack$Builder;");
109         jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetOffloadedPlayback, true);
110         mFlags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
111     }
112 
113     jmethodID jBuild = env->GetMethodID(jBuilderCls, "build", "()Landroid/media/AudioTrack;");
114     jobject jAudioTrackObj = env->CallObjectMethod(jBuilderObj, jBuild);
115     mAudioTrackObj = reinterpret_cast<jobject>(env->NewGlobalRef(jAudioTrackObj));
116     env->DeleteLocalRef(jBuilderObj);
117 
118     if (cbf != NULL) {
119         // Set offload mode callback
120         jobject jStreamEventCallbackObj = createStreamEventCallback(cbf, user);
121         jobject jExecutorObj = createCallbackExecutor();
122         jmethodID jSetStreamEventCallback = env->GetMethodID(
123                 jAudioTrackCls,
124                 "setStreamEventCallback",
125                 "(Ljava/util/concurrent/Executor;Landroid/media/AudioTrack$StreamEventCallback;)V");
126         env->CallVoidMethod(
127                 mAudioTrackObj, jSetStreamEventCallback, jExecutorObj, jStreamEventCallbackObj);
128     }
129 }
130 
~JAudioTrack()131 JAudioTrack::~JAudioTrack() {
132     JNIEnv *env = JavaVMHelper::getJNIEnv();
133     env->DeleteGlobalRef(mAudioTrackCls);
134     env->DeleteGlobalRef(mAudioTrackObj);
135 }
136 
frameCount()137 size_t JAudioTrack::frameCount() {
138     JNIEnv *env = JavaVMHelper::getJNIEnv();
139     jmethodID jGetBufferSizeInFrames = env->GetMethodID(
140             mAudioTrackCls, "getBufferSizeInFrames", "()I");
141     return env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);
142 }
143 
channelCount()144 size_t JAudioTrack::channelCount() {
145     JNIEnv *env = JavaVMHelper::getJNIEnv();
146     jmethodID jGetChannelCount = env->GetMethodID(mAudioTrackCls, "getChannelCount", "()I");
147     return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
148 }
149 
latency()150 uint32_t JAudioTrack::latency() {
151     // TODO: Currently hard-coded as returning zero.
152     return 0;
153 }
154 
getPosition(uint32_t * position)155 status_t JAudioTrack::getPosition(uint32_t *position) {
156     if (position == NULL) {
157         return BAD_VALUE;
158     }
159 
160     JNIEnv *env = JavaVMHelper::getJNIEnv();
161     jmethodID jGetPlaybackHeadPosition = env->GetMethodID(
162             mAudioTrackCls, "getPlaybackHeadPosition", "()I");
163     *position = env->CallIntMethod(mAudioTrackObj, jGetPlaybackHeadPosition);
164 
165     return NO_ERROR;
166 }
167 
getTimestamp(AudioTimestamp & timestamp)168 status_t JAudioTrack::getTimestamp(AudioTimestamp& timestamp) {
169     JNIEnv *env = JavaVMHelper::getJNIEnv();
170 
171     jclass jAudioTimeStampCls = env->FindClass("android/media/AudioTimestamp");
172     jobject jAudioTimeStampObj = env->AllocObject(jAudioTimeStampCls);
173 
174     jfieldID jFramePosition = env->GetFieldID(jAudioTimeStampCls, "framePosition", "J");
175     jfieldID jNanoTime = env->GetFieldID(jAudioTimeStampCls, "nanoTime", "J");
176 
177     jmethodID jGetTimestamp = env->GetMethodID(mAudioTrackCls,
178             "getTimestamp", "(Landroid/media/AudioTimestamp;)Z");
179     bool success = env->CallBooleanMethod(mAudioTrackObj, jGetTimestamp, jAudioTimeStampObj);
180 
181     if (!success) {
182         return NO_INIT;
183     }
184 
185     long long framePosition = env->GetLongField(jAudioTimeStampObj, jFramePosition);
186     long long nanoTime = env->GetLongField(jAudioTimeStampObj, jNanoTime);
187 
188     struct timespec ts;
189     const long long secondToNano = 1000000000LL; // 1E9
190     ts.tv_sec = nanoTime / secondToNano;
191     ts.tv_nsec = nanoTime % secondToNano;
192     timestamp.mTime = ts;
193     timestamp.mPosition = (uint32_t) framePosition;
194 
195     return NO_ERROR;
196 }
197 
getTimestamp(ExtendedTimestamp * timestamp __unused)198 status_t JAudioTrack::getTimestamp(ExtendedTimestamp *timestamp __unused) {
199     // TODO: Implement this after appropriate Java AudioTrack method is available.
200     return NO_ERROR;
201 }
202 
setPlaybackRate(const AudioPlaybackRate & playbackRate)203 status_t JAudioTrack::setPlaybackRate(const AudioPlaybackRate &playbackRate) {
204     // TODO: existing native AudioTrack returns INVALID_OPERATION on offload/direct/fast tracks.
205     // Should we do the same thing?
206     JNIEnv *env = JavaVMHelper::getJNIEnv();
207 
208     jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");
209     jmethodID jPlaybackParamsCtor = env->GetMethodID(jPlaybackParamsCls, "<init>", "()V");
210     jobject jPlaybackParamsObj = env->NewObject(jPlaybackParamsCls, jPlaybackParamsCtor);
211 
212     jmethodID jSetAudioFallbackMode = env->GetMethodID(
213             jPlaybackParamsCls, "setAudioFallbackMode", "(I)Landroid/media/PlaybackParams;");
214     jPlaybackParamsObj = env->CallObjectMethod(
215             jPlaybackParamsObj, jSetAudioFallbackMode, playbackRate.mFallbackMode);
216 
217     jmethodID jSetAudioStretchMode = env->GetMethodID(
218                 jPlaybackParamsCls, "setAudioStretchMode", "(I)Landroid/media/PlaybackParams;");
219     jPlaybackParamsObj = env->CallObjectMethod(
220             jPlaybackParamsObj, jSetAudioStretchMode, playbackRate.mStretchMode);
221 
222     jmethodID jSetPitch = env->GetMethodID(
223             jPlaybackParamsCls, "setPitch", "(F)Landroid/media/PlaybackParams;");
224     jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetPitch, playbackRate.mPitch);
225 
226     jmethodID jSetSpeed = env->GetMethodID(
227             jPlaybackParamsCls, "setSpeed", "(F)Landroid/media/PlaybackParams;");
228     jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetSpeed, playbackRate.mSpeed);
229 
230 
231     // Set this Java PlaybackParams object into Java AudioTrack.
232     jmethodID jSetPlaybackParams = env->GetMethodID(
233             mAudioTrackCls, "setPlaybackParams", "(Landroid/media/PlaybackParams;)V");
234     env->CallVoidMethod(mAudioTrackObj, jSetPlaybackParams, jPlaybackParamsObj);
235     // TODO: Should we catch the Java IllegalArgumentException?
236 
237     return NO_ERROR;
238 }
239 
getPlaybackRate()240 const AudioPlaybackRate JAudioTrack::getPlaybackRate() {
241     JNIEnv *env = JavaVMHelper::getJNIEnv();
242 
243     jmethodID jGetPlaybackParams = env->GetMethodID(
244             mAudioTrackCls, "getPlaybackParams", "()Landroid/media/PlaybackParams;");
245     jobject jPlaybackParamsObj = env->CallObjectMethod(mAudioTrackObj, jGetPlaybackParams);
246 
247     AudioPlaybackRate playbackRate;
248     jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");
249 
250     jmethodID jGetAudioFallbackMode = env->GetMethodID(
251             jPlaybackParamsCls, "getAudioFallbackMode", "()I");
252     // TODO: Should we enable passing AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT?
253     //       The enum is internal only, so it is not defined in PlaybackParmas.java.
254     // TODO: Is this right way to convert an int to an enum?
255     playbackRate.mFallbackMode = static_cast<AudioTimestretchFallbackMode>(
256             env->CallIntMethod(jPlaybackParamsObj, jGetAudioFallbackMode));
257 
258     jmethodID jGetAudioStretchMode = env->GetMethodID(
259             jPlaybackParamsCls, "getAudioStretchMode", "()I");
260     playbackRate.mStretchMode = static_cast<AudioTimestretchStretchMode>(
261             env->CallIntMethod(jPlaybackParamsObj, jGetAudioStretchMode));
262 
263     jmethodID jGetPitch = env->GetMethodID(jPlaybackParamsCls, "getPitch", "()F");
264     playbackRate.mPitch = env->CallFloatMethod(jPlaybackParamsObj, jGetPitch);
265 
266     jmethodID jGetSpeed = env->GetMethodID(jPlaybackParamsCls, "getSpeed", "()F");
267     playbackRate.mSpeed = env->CallFloatMethod(jPlaybackParamsObj, jGetSpeed);
268 
269     return playbackRate;
270 }
271 
applyVolumeShaper(const sp<media::VolumeShaper::Configuration> & configuration,const sp<media::VolumeShaper::Operation> & operation)272 media::VolumeShaper::Status JAudioTrack::applyVolumeShaper(
273         const sp<media::VolumeShaper::Configuration>& configuration,
274         const sp<media::VolumeShaper::Operation>& operation) {
275 
276     jobject jConfigurationObj = createVolumeShaperConfigurationObj(configuration);
277     jobject jOperationObj = createVolumeShaperOperationObj(operation);
278 
279     if (jConfigurationObj == NULL || jOperationObj == NULL) {
280         return media::VolumeShaper::Status(BAD_VALUE);
281     }
282 
283     JNIEnv *env = JavaVMHelper::getJNIEnv();
284 
285     jmethodID jCreateVolumeShaper = env->GetMethodID(mAudioTrackCls, "createVolumeShaper",
286             "(Landroid/media/VolumeShaper$Configuration;)Landroid/media/VolumeShaper;");
287     jobject jVolumeShaperObj = env->CallObjectMethod(
288             mAudioTrackObj, jCreateVolumeShaper, jConfigurationObj);
289 
290     jclass jVolumeShaperCls = env->FindClass("android/media/VolumeShaper");
291     jmethodID jApply = env->GetMethodID(jVolumeShaperCls, "apply",
292             "(Landroid/media/VolumeShaper$Operation;)V");
293     env->CallVoidMethod(jVolumeShaperObj, jApply, jOperationObj);
294 
295     return media::VolumeShaper::Status(NO_ERROR);
296 }
297 
setAuxEffectSendLevel(float level)298 status_t JAudioTrack::setAuxEffectSendLevel(float level) {
299     JNIEnv *env = JavaVMHelper::getJNIEnv();
300     jmethodID jSetAuxEffectSendLevel = env->GetMethodID(
301             mAudioTrackCls, "setAuxEffectSendLevel", "(F)I");
302     int result = env->CallIntMethod(mAudioTrackObj, jSetAuxEffectSendLevel, level);
303     return javaToNativeStatus(result);
304 }
305 
attachAuxEffect(int effectId)306 status_t JAudioTrack::attachAuxEffect(int effectId) {
307     JNIEnv *env = JavaVMHelper::getJNIEnv();
308     jmethodID jAttachAuxEffect = env->GetMethodID(mAudioTrackCls, "attachAuxEffect", "(I)I");
309     int result = env->CallIntMethod(mAudioTrackObj, jAttachAuxEffect, effectId);
310     return javaToNativeStatus(result);
311 }
312 
setVolume(float left,float right)313 status_t JAudioTrack::setVolume(float left, float right) {
314     JNIEnv *env = JavaVMHelper::getJNIEnv();
315     // TODO: Java setStereoVolume is deprecated. Do we really need this method?
316     jmethodID jSetStereoVolume = env->GetMethodID(mAudioTrackCls, "setStereoVolume", "(FF)I");
317     int result = env->CallIntMethod(mAudioTrackObj, jSetStereoVolume, left, right);
318     return javaToNativeStatus(result);
319 }
320 
setVolume(float volume)321 status_t JAudioTrack::setVolume(float volume) {
322     JNIEnv *env = JavaVMHelper::getJNIEnv();
323     jmethodID jSetVolume = env->GetMethodID(mAudioTrackCls, "setVolume", "(F)I");
324     int result = env->CallIntMethod(mAudioTrackObj, jSetVolume, volume);
325     return javaToNativeStatus(result);
326 }
327 
start()328 status_t JAudioTrack::start() {
329     JNIEnv *env = JavaVMHelper::getJNIEnv();
330     jmethodID jPlay = env->GetMethodID(mAudioTrackCls, "play", "()V");
331     // TODO: Should we catch the Java IllegalStateException from play()?
332     env->CallVoidMethod(mAudioTrackObj, jPlay);
333     return NO_ERROR;
334 }
335 
write(const void * buffer,size_t size,bool blocking)336 ssize_t JAudioTrack::write(const void* buffer, size_t size, bool blocking) {
337     if (buffer == NULL) {
338         return BAD_VALUE;
339     }
340 
341     JNIEnv *env = JavaVMHelper::getJNIEnv();
342     jbyteArray jAudioData = env->NewByteArray(size);
343     env->SetByteArrayRegion(jAudioData, 0, size, (jbyte *) buffer);
344 
345     jclass jByteBufferCls = env->FindClass("java/nio/ByteBuffer");
346     jmethodID jWrap = env->GetStaticMethodID(jByteBufferCls, "wrap", "([B)Ljava/nio/ByteBuffer;");
347     jobject jByteBufferObj = env->CallStaticObjectMethod(jByteBufferCls, jWrap, jAudioData);
348 
349     int writeMode = 0;
350     if (blocking) {
351         jfieldID jWriteBlocking = env->GetStaticFieldID(mAudioTrackCls, "WRITE_BLOCKING", "I");
352         writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteBlocking);
353     } else {
354         jfieldID jWriteNonBlocking = env->GetStaticFieldID(
355                 mAudioTrackCls, "WRITE_NON_BLOCKING", "I");
356         writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteNonBlocking);
357     }
358 
359     jmethodID jWrite = env->GetMethodID(mAudioTrackCls, "write", "(Ljava/nio/ByteBuffer;II)I");
360     int result = env->CallIntMethod(mAudioTrackObj, jWrite, jByteBufferObj, size, writeMode);
361 
362     if (result >= 0) {
363         return result;
364     } else {
365         return javaToNativeStatus(result);
366     }
367 }
368 
stop()369 void JAudioTrack::stop() {
370     JNIEnv *env = JavaVMHelper::getJNIEnv();
371     jmethodID jStop = env->GetMethodID(mAudioTrackCls, "stop", "()V");
372     env->CallVoidMethod(mAudioTrackObj, jStop);
373     // TODO: Should we catch IllegalStateException?
374 }
375 
376 // TODO: Is the right implementation?
stopped() const377 bool JAudioTrack::stopped() const {
378     return !isPlaying();
379 }
380 
flush()381 void JAudioTrack::flush() {
382     JNIEnv *env = JavaVMHelper::getJNIEnv();
383     jmethodID jFlush = env->GetMethodID(mAudioTrackCls, "flush", "()V");
384     env->CallVoidMethod(mAudioTrackObj, jFlush);
385 }
386 
pause()387 void JAudioTrack::pause() {
388     JNIEnv *env = JavaVMHelper::getJNIEnv();
389     jmethodID jPause = env->GetMethodID(mAudioTrackCls, "pause", "()V");
390     env->CallVoidMethod(mAudioTrackObj, jPause);
391     // TODO: Should we catch IllegalStateException?
392 }
393 
isPlaying() const394 bool JAudioTrack::isPlaying() const {
395     JNIEnv *env = JavaVMHelper::getJNIEnv();
396     jmethodID jGetPlayState = env->GetMethodID(mAudioTrackCls, "getPlayState", "()I");
397     int currentPlayState = env->CallIntMethod(mAudioTrackObj, jGetPlayState);
398 
399     // TODO: In Java AudioTrack, there is no STOPPING state.
400     // This means while stopping, isPlaying() will return different value in two class.
401     //  - in existing native AudioTrack: true
402     //  - in JAudioTrack: false
403     // If not okay, also modify the implementation of stopped().
404     jfieldID jPlayStatePlaying = env->GetStaticFieldID(mAudioTrackCls, "PLAYSTATE_PLAYING", "I");
405     int statePlaying = env->GetStaticIntField(mAudioTrackCls, jPlayStatePlaying);
406     return currentPlayState == statePlaying;
407 }
408 
getSampleRate()409 uint32_t JAudioTrack::getSampleRate() {
410     JNIEnv *env = JavaVMHelper::getJNIEnv();
411     jmethodID jGetSampleRate = env->GetMethodID(mAudioTrackCls, "getSampleRate", "()I");
412     return env->CallIntMethod(mAudioTrackObj, jGetSampleRate);
413 }
414 
getBufferDurationInUs(int64_t * duration)415 status_t JAudioTrack::getBufferDurationInUs(int64_t *duration) {
416     if (duration == nullptr) {
417         return BAD_VALUE;
418     }
419 
420     JNIEnv *env = JavaVMHelper::getJNIEnv();
421     jmethodID jGetBufferSizeInFrames = env->GetMethodID(
422             mAudioTrackCls, "getBufferSizeInFrames", "()I");
423     int bufferSizeInFrames = env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);
424 
425     const double secondToMicro = 1000000LL; // 1E6
426     int sampleRate = JAudioTrack::getSampleRate();
427     float speed = JAudioTrack::getPlaybackRate().mSpeed;
428 
429     *duration = (int64_t) (bufferSizeInFrames * secondToMicro / (sampleRate * speed));
430     return NO_ERROR;
431 }
432 
format()433 audio_format_t JAudioTrack::format() {
434     JNIEnv *env = JavaVMHelper::getJNIEnv();
435     jmethodID jGetAudioFormat = env->GetMethodID(mAudioTrackCls, "getAudioFormat", "()I");
436     int javaFormat = env->CallIntMethod(mAudioTrackObj, jGetAudioFormat);
437     return audioFormatToNative(javaFormat);
438 }
439 
frameSize()440 size_t JAudioTrack::frameSize() {
441     JNIEnv *env = JavaVMHelper::getJNIEnv();
442     jmethodID jGetFormat = env->GetMethodID(mAudioTrackCls,
443             "getFormat", "()Landroid/media/AudioFormat;");
444     jobject jAudioFormatObj = env->CallObjectMethod(mAudioTrackObj, jGetFormat);
445 
446     jclass jAudioFormatCls = env->FindClass("android/media/AudioFormat");
447     jmethodID jGetFrameSizeInBytes = env->GetMethodID(
448             jAudioFormatCls, "getFrameSizeInBytes", "()I");
449     jint javaFrameSizeInBytes = env->CallIntMethod(jAudioFormatObj, jGetFrameSizeInBytes);
450 
451     return (size_t)javaFrameSizeInBytes;
452 }
453 
dump(int fd,const Vector<String16> & args __unused) const454 status_t JAudioTrack::dump(int fd, const Vector<String16>& args __unused) const
455 {
456     String8 result;
457 
458     result.append(" JAudioTrack::dump\n");
459 
460     // TODO: Remove logs that includes unavailable information from below.
461 //    result.appendFormat("  status(%d), state(%d), session Id(%d), flags(%#x)\n",
462 //                        mStatus, mState, mSessionId, mFlags);
463 //    result.appendFormat("  format(%#x), channel mask(%#x), channel count(%u)\n",
464 //                  format(), mChannelMask, channelCount());
465 //    result.appendFormat("  sample rate(%u), original sample rate(%u), speed(%f)\n",
466 //            getSampleRate(), mOriginalSampleRate, mPlaybackRate.mSpeed);
467 //    result.appendFormat("  frame count(%zu), req. frame count(%zu)\n",
468 //                  frameCount(), mReqFrameCount);
469 //    result.appendFormat("  notif. frame count(%u), req. notif. frame count(%u),"
470 //            " req. notif. per buff(%u)\n",
471 //             mNotificationFramesAct, mNotificationFramesReq, mNotificationsPerBufferReq);
472 //    result.appendFormat("  latency (%d), selected device Id(%d), routed device Id(%d)\n",
473 //                        latency(), mSelectedDeviceId, getRoutedDeviceId());
474 //    result.appendFormat("  output(%d) AF latency (%u) AF frame count(%zu) AF SampleRate(%u)\n",
475 //                        mOutput, mAfLatency, mAfFrameCount, mAfSampleRate);
476     ::write(fd, result.string(), result.size());
477     return NO_ERROR;
478 }
479 
getRoutedDevice()480 jobject JAudioTrack::getRoutedDevice() {
481     JNIEnv *env = JavaVMHelper::getJNIEnv();
482     jmethodID jGetRoutedDevice = env->GetMethodID(mAudioTrackCls, "getRoutedDevice",
483             "()Landroid/media/AudioDeviceInfo;");
484     return env->CallObjectMethod(mAudioTrackObj, jGetRoutedDevice);
485 }
486 
getAudioSessionId()487 int32_t JAudioTrack::getAudioSessionId() {
488     JNIEnv *env = JavaVMHelper::getJNIEnv();
489     jmethodID jGetAudioSessionId = env->GetMethodID(mAudioTrackCls, "getAudioSessionId", "()I");
490     jint sessionId = env->CallIntMethod(mAudioTrackObj, jGetAudioSessionId);
491     return sessionId;
492 }
493 
setPreferredDevice(jobject device)494 status_t JAudioTrack::setPreferredDevice(jobject device) {
495     JNIEnv *env = JavaVMHelper::getJNIEnv();
496     jmethodID jSetPreferredDeviceId = env->GetMethodID(mAudioTrackCls, "setPreferredDevice",
497             "(Landroid/media/AudioDeviceInfo;)Z");
498     jboolean result = env->CallBooleanMethod(mAudioTrackObj, jSetPreferredDeviceId, device);
499     return result == true ? NO_ERROR : BAD_VALUE;
500 }
501 
getAudioStreamType()502 audio_stream_type_t JAudioTrack::getAudioStreamType() {
503     JNIEnv *env = JavaVMHelper::getJNIEnv();
504     jmethodID jGetAudioAttributes = env->GetMethodID(mAudioTrackCls, "getAudioAttributes",
505             "()Landroid/media/AudioAttributes;");
506     jobject jAudioAttributes = env->CallObjectMethod(mAudioTrackObj, jGetAudioAttributes);
507     jclass jAudioAttributesCls = env->FindClass("android/media/AudioAttributes");
508     jmethodID jGetVolumeControlStream = env->GetMethodID(jAudioAttributesCls,
509             "getVolumeControlStream", "()I");
510     int javaAudioStreamType = env->CallIntMethod(jAudioAttributes, jGetVolumeControlStream);
511     return (audio_stream_type_t)javaAudioStreamType;
512 }
513 
pendingDuration(int32_t * msec)514 status_t JAudioTrack::pendingDuration(int32_t *msec) {
515     if (msec == nullptr) {
516         return BAD_VALUE;
517     }
518 
519     bool isPurePcmData = audio_is_linear_pcm(format()) && (getFlags() & AUDIO_FLAG_HW_AV_SYNC) == 0;
520     if (!isPurePcmData) {
521         return INVALID_OPERATION;
522     }
523 
524     // TODO: Need to know the difference btw. client and server time.
525     // If getTimestamp(ExtendedTimestamp) is ready, and un-comment below and modify appropriately.
526     // (copied from AudioTrack.cpp)
527 
528 //    ExtendedTimestamp ets;
529 //    ExtendedTimestamp::LOCATION location = ExtendedTimestamp::LOCATION_SERVER;
530 //    if (getTimestamp_l(&ets) == OK && ets.mTimeNs[location] > 0) {
531 //        int64_t diff = ets.mPosition[ExtendedTimestamp::LOCATION_CLIENT]
532 //                - ets.mPosition[location];
533 //        if (diff < 0) {
534 //            *msec = 0;
535 //        } else {
536 //            // ms is the playback time by frames
537 //            int64_t ms = (int64_t)((double)diff * 1000 /
538 //                    ((double)mSampleRate * mPlaybackRate.mSpeed));
539 //            // clockdiff is the timestamp age (negative)
540 //            int64_t clockdiff = (mState != STATE_ACTIVE) ? 0 :
541 //                    ets.mTimeNs[location]
542 //                    + ets.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_MONOTONIC]
543 //                    - systemTime(SYSTEM_TIME_MONOTONIC);
544 //
545 //            //ALOGV("ms: %lld  clockdiff: %lld", (long long)ms, (long long)clockdiff);
546 //            static const int NANOS_PER_MILLIS = 1000000;
547 //            *msec = (int32_t)(ms + clockdiff / NANOS_PER_MILLIS);
548 //        }
549 //        return NO_ERROR;
550 //    }
551 
552     return NO_ERROR;
553 }
554 
addAudioDeviceCallback(jobject listener,jobject handler)555 status_t JAudioTrack::addAudioDeviceCallback(jobject listener, jobject handler) {
556     JNIEnv *env = JavaVMHelper::getJNIEnv();
557     jmethodID jAddOnRoutingChangedListener = env->GetMethodID(mAudioTrackCls,
558             "addOnRoutingChangedListener",
559             "(Landroid/media/AudioRouting$OnRoutingChangedListener;Landroid/os/Handler;)V");
560     env->CallVoidMethod(mAudioTrackObj, jAddOnRoutingChangedListener, listener, handler);
561     return NO_ERROR;
562 }
563 
removeAudioDeviceCallback(jobject listener)564 status_t JAudioTrack::removeAudioDeviceCallback(jobject listener) {
565     JNIEnv *env = JavaVMHelper::getJNIEnv();
566     jmethodID jRemoveOnRoutingChangedListener = env->GetMethodID(mAudioTrackCls,
567             "removeOnRoutingChangedListener",
568             "(Landroid/media/AudioRouting$OnRoutingChangedListener;)V");
569     env->CallVoidMethod(mAudioTrackObj, jRemoveOnRoutingChangedListener, listener);
570     return NO_ERROR;
571 }
572 
registerRoutingDelegates(Vector<std::pair<sp<JObjectHolder>,sp<JObjectHolder>>> & routingDelegates)573 void JAudioTrack::registerRoutingDelegates(
574         Vector<std::pair<sp<JObjectHolder>, sp<JObjectHolder>>>& routingDelegates) {
575     for (auto it = routingDelegates.begin(); it != routingDelegates.end(); it++) {
576         addAudioDeviceCallback(it->second->getJObject(), getHandler(it->second->getJObject()));
577     }
578 }
579 
580 /////////////////////////////////////////////////////////////
581 ///                Static methods begin                   ///
582 /////////////////////////////////////////////////////////////
getListener(const jobject routingDelegateObj)583 jobject JAudioTrack::getListener(const jobject routingDelegateObj) {
584     JNIEnv *env = JavaVMHelper::getJNIEnv();
585     jclass jRoutingDelegateCls = env->FindClass("android/media/RoutingDelegate");
586     jmethodID jGetListener = env->GetMethodID(jRoutingDelegateCls,
587             "getListener", "()Landroid/media/AudioRouting$OnRoutingChangedListener;");
588     return env->CallObjectMethod(routingDelegateObj, jGetListener);
589 }
590 
getHandler(const jobject routingDelegateObj)591 jobject JAudioTrack::getHandler(const jobject routingDelegateObj) {
592     JNIEnv *env = JavaVMHelper::getJNIEnv();
593     jclass jRoutingDelegateCls = env->FindClass("android/media/RoutingDelegate");
594     jmethodID jGetHandler = env->GetMethodID(jRoutingDelegateCls,
595         "getHandler", "()Landroid/os/Handler;");
596     return env->CallObjectMethod(routingDelegateObj, jGetHandler);
597 }
598 
findByKey(Vector<std::pair<sp<JObjectHolder>,sp<JObjectHolder>>> & mp,const jobject key)599 jobject JAudioTrack::findByKey(
600         Vector<std::pair<sp<JObjectHolder>, sp<JObjectHolder>>>& mp, const jobject key) {
601     JNIEnv *env = JavaVMHelper::getJNIEnv();
602     for (auto it = mp.begin(); it != mp.end(); it++) {
603         if (env->IsSameObject(it->first->getJObject(), key)) {
604             return it->second->getJObject();
605         }
606     }
607     return nullptr;
608 }
609 
eraseByKey(Vector<std::pair<sp<JObjectHolder>,sp<JObjectHolder>>> & mp,const jobject key)610 void JAudioTrack::eraseByKey(
611         Vector<std::pair<sp<JObjectHolder>, sp<JObjectHolder>>>& mp, const jobject key) {
612     JNIEnv *env = JavaVMHelper::getJNIEnv();
613     for (auto it = mp.begin(); it != mp.end(); it++) {
614         if (env->IsSameObject(it->first->getJObject(), key)) {
615             mp.erase(it);
616             return;
617         }
618     }
619 }
620 
621 /////////////////////////////////////////////////////////////
622 ///                Private method begins                  ///
623 /////////////////////////////////////////////////////////////
624 
createVolumeShaperConfigurationObj(const sp<media::VolumeShaper::Configuration> & config)625 jobject JAudioTrack::createVolumeShaperConfigurationObj(
626         const sp<media::VolumeShaper::Configuration>& config) {
627 
628     // TODO: Java VolumeShaper's setId() / setOptionFlags() are hidden.
629     if (config == NULL || config->getType() == media::VolumeShaper::Configuration::TYPE_ID) {
630         return NULL;
631     }
632 
633     JNIEnv *env = JavaVMHelper::getJNIEnv();
634 
635     // Referenced "android_media_VolumeShaper.h".
636     jfloatArray xarray = nullptr;
637     jfloatArray yarray = nullptr;
638     if (config->getType() == media::VolumeShaper::Configuration::TYPE_SCALE) {
639         // convert curve arrays
640         xarray = env->NewFloatArray(config->size());
641         yarray = env->NewFloatArray(config->size());
642         float * const x = env->GetFloatArrayElements(xarray, nullptr /* isCopy */);
643         float * const y = env->GetFloatArrayElements(yarray, nullptr /* isCopy */);
644         float *xptr = x, *yptr = y;
645         for (const auto &pt : *config.get()) {
646             *xptr++ = pt.first;
647             *yptr++ = pt.second;
648         }
649         env->ReleaseFloatArrayElements(xarray, x, 0 /* mode */);
650         env->ReleaseFloatArrayElements(yarray, y, 0 /* mode */);
651     }
652 
653     jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Configuration$Builder");
654     jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
655     jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
656 
657     jmethodID jSetDuration = env->GetMethodID(jBuilderCls, "setDuration",
658             "(L)Landroid/media/VolumeShaper$Configuration$Builder;");
659     jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetDuration, (jlong) config->getDurationMs());
660 
661     jmethodID jSetInterpolatorType = env->GetMethodID(jBuilderCls, "setInterpolatorType",
662             "(I)Landroid/media/VolumeShaper$Configuration$Builder;");
663     jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetInterpolatorType,
664             config->getInterpolatorType());
665 
666     jmethodID jSetCurve = env->GetMethodID(jBuilderCls, "setCurve",
667             "([F[F)Landroid/media/VolumeShaper$Configuration$Builder;");
668     jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetCurve, xarray, yarray);
669 
670     jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
671             "()Landroid/media/VolumeShaper$Configuration;");
672     return env->CallObjectMethod(jBuilderObj, jBuild);
673 }
674 
createVolumeShaperOperationObj(const sp<media::VolumeShaper::Operation> & operation)675 jobject JAudioTrack::createVolumeShaperOperationObj(
676         const sp<media::VolumeShaper::Operation>& operation) {
677 
678     JNIEnv *env = JavaVMHelper::getJNIEnv();
679 
680     jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Operation$Builder");
681     jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
682     jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);
683 
684     // Set XOffset
685     jmethodID jSetXOffset = env->GetMethodID(jBuilderCls, "setXOffset",
686             "(F)Landroid/media/VolumeShaper$Operation$Builder;");
687     jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetXOffset, operation->getXOffset());
688 
689     int32_t flags = operation->getFlags();
690 
691     if (operation->getReplaceId() >= 0) {
692         jmethodID jReplace = env->GetMethodID(jBuilderCls, "replace",
693                 "(IB)Landroid/media/VolumeShaper$Operation$Builder;");
694         bool join = (flags | media::VolumeShaper::Operation::FLAG_JOIN) != 0;
695         jBuilderObj = env->CallObjectMethod(jBuilderCls, jReplace, operation->getReplaceId(), join);
696     }
697 
698     if (flags | media::VolumeShaper::Operation::FLAG_REVERSE) {
699         jmethodID jReverse = env->GetMethodID(jBuilderCls, "reverse",
700                 "()Landroid/media/VolumeShaper$Operation$Builder;");
701         jBuilderObj = env->CallObjectMethod(jBuilderCls, jReverse);
702     }
703 
704     // TODO: VolumeShaper Javadoc says "Do not call terminate() directly". Can we call this?
705     if (flags | media::VolumeShaper::Operation::FLAG_TERMINATE) {
706         jmethodID jTerminate = env->GetMethodID(jBuilderCls, "terminate",
707                 "()Landroid/media/VolumeShaper$Operation$Builder;");
708         jBuilderObj = env->CallObjectMethod(jBuilderCls, jTerminate);
709     }
710 
711     if (flags | media::VolumeShaper::Operation::FLAG_DELAY) {
712         jmethodID jDefer = env->GetMethodID(jBuilderCls, "defer",
713                 "()Landroid/media/VolumeShaper$Operation$Builder;");
714         jBuilderObj = env->CallObjectMethod(jBuilderCls, jDefer);
715     }
716 
717     if (flags | media::VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) {
718         jmethodID jCreateIfNeeded = env->GetMethodID(jBuilderCls, "createIfNeeded",
719                 "()Landroid/media/VolumeShaper$Operation$Builder;");
720         jBuilderObj = env->CallObjectMethod(jBuilderCls, jCreateIfNeeded);
721     }
722 
723     // TODO: Handle error case (can it be NULL?)
724     jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
725             "()Landroid/media/VolumeShaper$Operation;");
726     return env->CallObjectMethod(jBuilderObj, jBuild);
727 }
728 
createStreamEventCallback(callback_t cbf,void * user)729 jobject JAudioTrack::createStreamEventCallback(callback_t cbf, void* user) {
730     JNIEnv *env = JavaVMHelper::getJNIEnv();
731     jclass jCallbackCls = env->FindClass("android/media/MediaPlayer2$StreamEventCallback");
732     jmethodID jCallbackCtor = env->GetMethodID(jCallbackCls, "<init>", "(JJJ)V");
733     jobject jCallbackObj = env->NewObject(jCallbackCls, jCallbackCtor, this, cbf, user);
734     return jCallbackObj;
735 }
736 
createCallbackExecutor()737 jobject JAudioTrack::createCallbackExecutor() {
738     JNIEnv *env = JavaVMHelper::getJNIEnv();
739     jclass jExecutorsCls = env->FindClass("java/util/concurrent/Executors");
740     jmethodID jNewSingleThreadExecutor = env->GetStaticMethodID(jExecutorsCls,
741             "newSingleThreadExecutor", "()Ljava/util/concurrent/ExecutorService;");
742     jobject jSingleThreadExecutorObj =
743             env->CallStaticObjectMethod(jExecutorsCls, jNewSingleThreadExecutor);
744     return jSingleThreadExecutorObj;
745 }
746 
javaToNativeStatus(int javaStatus)747 status_t JAudioTrack::javaToNativeStatus(int javaStatus) {
748     switch (javaStatus) {
749     case AUDIO_JAVA_SUCCESS:
750         return NO_ERROR;
751     case AUDIO_JAVA_BAD_VALUE:
752         return BAD_VALUE;
753     case AUDIO_JAVA_INVALID_OPERATION:
754         return INVALID_OPERATION;
755     case AUDIO_JAVA_PERMISSION_DENIED:
756         return PERMISSION_DENIED;
757     case AUDIO_JAVA_NO_INIT:
758         return NO_INIT;
759     case AUDIO_JAVA_WOULD_BLOCK:
760         return WOULD_BLOCK;
761     case AUDIO_JAVA_DEAD_OBJECT:
762         return DEAD_OBJECT;
763     default:
764         return UNKNOWN_ERROR;
765     }
766 }
767 
768 } // namespace android
769