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
26namespace 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.
30JAudioTrack::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
118JAudioTrack::~JAudioTrack() {
119    JNIEnv *env = AndroidRuntime::getJNIEnv();
120    env->DeleteGlobalRef(mAudioTrackCls);
121}
122
123size_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
130size_t JAudioTrack::channelCount() {
131    JNIEnv *env = AndroidRuntime::getJNIEnv();
132    jmethodID jGetChannelCount = env->GetMethodID(mAudioTrackCls, "getChannelCount", "()I");
133    return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
134}
135
136uint32_t JAudioTrack::latency() {
137    // TODO: Currently hard-coded as returning zero.
138    return 0;
139}
140
141status_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
154bool 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
184status_t JAudioTrack::getTimestamp(ExtendedTimestamp *timestamp __unused) {
185    // TODO: Implement this after appropriate Java AudioTrack method is available.
186    return NO_ERROR;
187}
188
189status_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
226const 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
258media::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
284status_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
292status_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
299status_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
307status_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
314status_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
322ssize_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
355void 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?
363bool JAudioTrack::stopped() const {
364    return !isPlaying();
365}
366
367void JAudioTrack::flush() {
368    JNIEnv *env = AndroidRuntime::getJNIEnv();
369    jmethodID jFlush = env->GetMethodID(mAudioTrackCls, "flush", "()V");
370    env->CallVoidMethod(mAudioTrackObj, jFlush);
371}
372
373void 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
380bool 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
395uint32_t JAudioTrack::getSampleRate() {
396    JNIEnv *env = AndroidRuntime::getJNIEnv();
397    jmethodID jGetSampleRate = env->GetMethodID(mAudioTrackCls, "getSampleRate", "()I");
398    return env->CallIntMethod(mAudioTrackObj, jGetSampleRate);
399}
400
401status_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
419audio_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
426status_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
456audio_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
471audio_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
478status_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
488status_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
529status_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
535status_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
545jobject 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
595jobject 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
649jobject 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
657jobject 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
667status_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