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