android_media_MediaSync.cpp revision c98f58efd147c574faa4a4f9956b5ab95e3027a5
1/*
2 * Copyright 2015, 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_NDEBUG 0
18#define LOG_TAG "MediaSync-JNI"
19#include <utils/Log.h>
20
21#include "android_media_MediaSync.h"
22
23#include "android_media_AudioTrack.h"
24#include "android_media_SyncSettings.h"
25#include "android_runtime/AndroidRuntime.h"
26#include "android_runtime/android_view_Surface.h"
27#include "jni.h"
28#include "JNIHelp.h"
29
30#include <gui/Surface.h>
31
32#include <media/AudioTrack.h>
33#include <media/stagefright/MediaClock.h>
34#include <media/stagefright/MediaSync.h>
35#include <media/stagefright/foundation/ADebug.h>
36#include <media/stagefright/foundation/AString.h>
37
38#include <nativehelper/ScopedLocalRef.h>
39
40namespace android {
41
42struct fields_t {
43    jfieldID context;
44    jfieldID mediaTimestampMediaTimeUsID;
45    jfieldID mediaTimestampNanoTimeID;
46    jfieldID mediaTimestampClockRateID;
47};
48
49static fields_t gFields;
50static SyncSettings::fields_t gSyncSettingsFields;
51
52////////////////////////////////////////////////////////////////////////////////
53
54JMediaSync::JMediaSync() {
55    mSync = MediaSync::create();
56}
57
58JMediaSync::~JMediaSync() {
59}
60
61status_t JMediaSync::configureSurface(const sp<IGraphicBufferProducer> &bufferProducer) {
62    return mSync->configureSurface(bufferProducer);
63}
64
65status_t JMediaSync::configureAudioTrack(
66        const sp<AudioTrack> &audioTrack,
67        int32_t nativeSampleRateInHz) {
68    return mSync->configureAudioTrack(audioTrack, nativeSampleRateInHz);
69}
70
71status_t JMediaSync::createInputSurface(
72        sp<IGraphicBufferProducer>* bufferProducer) {
73    return mSync->createInputSurface(bufferProducer);
74}
75
76status_t JMediaSync::setPlaybackRate(float rate) {
77    return mSync->setPlaybackRate(rate);
78}
79
80sp<const MediaClock> JMediaSync::getMediaClock() {
81    return mSync->getMediaClock();
82}
83
84status_t JMediaSync::updateQueuedAudioData(
85        int sizeInBytes, int64_t presentationTimeUs) {
86    return mSync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
87}
88
89}  // namespace android
90
91////////////////////////////////////////////////////////////////////////////////
92
93using namespace android;
94
95static sp<JMediaSync> setMediaSync(JNIEnv *env, jobject thiz, const sp<JMediaSync> &sync) {
96    sp<JMediaSync> old = (JMediaSync *)env->GetLongField(thiz, gFields.context);
97    if (sync != NULL) {
98        sync->incStrong(thiz);
99    }
100    if (old != NULL) {
101        old->decStrong(thiz);
102    }
103
104    env->SetLongField(thiz, gFields.context, (jlong)sync.get());
105
106    return old;
107}
108
109static sp<JMediaSync> getMediaSync(JNIEnv *env, jobject thiz) {
110    return (JMediaSync *)env->GetLongField(thiz, gFields.context);
111}
112
113static void android_media_MediaSync_release(JNIEnv *env, jobject thiz) {
114    setMediaSync(env, thiz, NULL);
115}
116
117static void throwExceptionAsNecessary(
118        JNIEnv *env, status_t err, const char *msg = NULL) {
119    switch (err) {
120        case NO_ERROR:
121            break;
122
123        case BAD_VALUE:
124            jniThrowException(env, "java/lang/IllegalArgumentException", msg);
125            break;
126
127        case NO_INIT:
128        case INVALID_OPERATION:
129        default:
130            if (err > 0) {
131                break;
132            }
133            AString msgWithErrorCode(msg);
134            msgWithErrorCode.append(" error:");
135            msgWithErrorCode.append(err);
136            jniThrowException(env, "java/lang/IllegalStateException", msgWithErrorCode.c_str());
137            break;
138    }
139}
140
141static void android_media_MediaSync_native_configureSurface(
142        JNIEnv *env, jobject thiz, jobject jsurface) {
143    ALOGV("android_media_MediaSync_configureSurface");
144
145    sp<JMediaSync> sync = getMediaSync(env, thiz);
146    if (sync == NULL) {
147        throwExceptionAsNecessary(env, INVALID_OPERATION);
148        return;
149    }
150
151    sp<IGraphicBufferProducer> bufferProducer;
152    if (jsurface != NULL) {
153        sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
154        if (surface != NULL) {
155            bufferProducer = surface->getIGraphicBufferProducer();
156        } else {
157            throwExceptionAsNecessary(env, BAD_VALUE, "The surface has been released");
158            return;
159        }
160    }
161
162    status_t err = sync->configureSurface(bufferProducer);
163
164    if (err == INVALID_OPERATION) {
165        throwExceptionAsNecessary(
166                env, INVALID_OPERATION, "Surface has already been configured");
167    } if (err != NO_ERROR) {
168        AString msg("Failed to connect to surface with error ");
169        msg.append(err);
170        throwExceptionAsNecessary(env, BAD_VALUE, msg.c_str());
171    }
172}
173
174static void android_media_MediaSync_native_configureAudioTrack(
175        JNIEnv *env, jobject thiz, jobject jaudioTrack, jint nativeSampleRateInHz) {
176    ALOGV("android_media_MediaSync_configureAudioTrack");
177
178    sp<JMediaSync> sync = getMediaSync(env, thiz);
179    if (sync == NULL) {
180        throwExceptionAsNecessary(env, INVALID_OPERATION);
181        return;
182    }
183
184    sp<AudioTrack> audioTrack;
185    if (jaudioTrack != NULL) {
186        audioTrack = android_media_AudioTrack_getAudioTrack(env, jaudioTrack);
187        if (audioTrack == NULL) {
188            throwExceptionAsNecessary(env, BAD_VALUE, "The audio track has been released");
189            return;
190        }
191    }
192
193    status_t err = sync->configureAudioTrack(audioTrack, nativeSampleRateInHz);
194
195    if (err == INVALID_OPERATION) {
196        throwExceptionAsNecessary(
197                env, INVALID_OPERATION, "Audio track has already been configured");
198    } if (err != NO_ERROR) {
199        AString msg("Failed to configure audio track with error ");
200        msg.append(err);
201        throwExceptionAsNecessary(env, BAD_VALUE, msg.c_str());
202    }
203}
204
205static jobject android_media_MediaSync_createInputSurface(
206        JNIEnv* env, jobject thiz) {
207    ALOGV("android_media_MediaSync_createInputSurface");
208
209    sp<JMediaSync> sync = getMediaSync(env, thiz);
210    if (sync == NULL) {
211        throwExceptionAsNecessary(env, INVALID_OPERATION);
212        return NULL;
213    }
214
215    // Tell the MediaSync that we want to use a Surface as input.
216    sp<IGraphicBufferProducer> bufferProducer;
217    status_t err = sync->createInputSurface(&bufferProducer);
218    if (err != NO_ERROR) {
219        throwExceptionAsNecessary(env, INVALID_OPERATION);
220        return NULL;
221    }
222
223    // Wrap the IGBP in a Java-language Surface.
224    return android_view_Surface_createFromIGraphicBufferProducer(env,
225            bufferProducer);
226}
227
228static void android_media_MediaSync_native_updateQueuedAudioData(
229        JNIEnv *env, jobject thiz, jint sizeInBytes, jlong presentationTimeUs) {
230    sp<JMediaSync> sync = getMediaSync(env, thiz);
231    if (sync == NULL) {
232        throwExceptionAsNecessary(env, INVALID_OPERATION);
233        return;
234    }
235
236    status_t err = sync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
237    if (err != NO_ERROR) {
238        throwExceptionAsNecessary(env, err);
239        return;
240    }
241}
242
243static jboolean android_media_MediaSync_native_getTimestamp(
244        JNIEnv *env, jobject thiz, jobject timestamp) {
245    sp<JMediaSync> sync = getMediaSync(env, thiz);
246    if (sync == NULL) {
247        throwExceptionAsNecessary(env, INVALID_OPERATION);
248        return JNI_FALSE;
249    }
250
251    sp<const MediaClock> mediaClock = sync->getMediaClock();
252    if (mediaClock == NULL) {
253        return JNI_FALSE;
254    }
255
256    int64_t nowUs = ALooper::GetNowUs();
257    int64_t mediaUs = 0;
258    if (mediaClock->getMediaTime(nowUs, &mediaUs) != OK) {
259        return JNI_FALSE;
260    }
261
262    env->SetLongField(timestamp, gFields.mediaTimestampMediaTimeUsID,
263            (jlong)mediaUs);
264    env->SetLongField(timestamp, gFields.mediaTimestampNanoTimeID,
265            (jlong)(nowUs * 1000));
266    env->SetFloatField(timestamp, gFields.mediaTimestampClockRateID,
267            (jfloat)mediaClock->getPlaybackRate());
268    return JNI_TRUE;
269}
270
271static void
272android_media_MediaSync_setSyncSettings(JNIEnv *env, jobject thiz, jobject settings)
273{
274    sp<JMediaSync> sync = getMediaSync(env, thiz);
275    if (sync == NULL) {
276        throwExceptionAsNecessary(env, INVALID_OPERATION);
277        return;
278    }
279
280    SyncSettings scs;
281    scs.fillFromJobject(env, gSyncSettingsFields, settings);
282    ALOGV("setSyncSettings: %d:%d %d:%d %d:%f %d:%f",
283            scs.syncSourceSet, scs.syncSource,
284            scs.audioAdjustModeSet, scs.audioAdjustMode,
285            scs.toleranceSet, scs.tolerance,
286            scs.frameRateSet, scs.frameRate);
287
288    // TODO: pass sync settings to mediasync when it supports it
289}
290
291static jobject
292android_media_MediaSync_getSyncSettings(JNIEnv *env, jobject thiz)
293{
294    sp<JMediaSync> sync = getMediaSync(env, thiz);
295    if (sync == NULL) {
296        throwExceptionAsNecessary(env, INVALID_OPERATION);
297        return NULL;
298    }
299
300    SyncSettings scs;
301    scs.syncSource = 0; // SYNC_SOURCE_DEFAULT
302    scs.audioAdjustMode = 0; // AUDIO_ADJUST_MODE_DEFAULT
303    scs.tolerance = 0.f;
304    scs.frameRate = 0.f;
305
306    // TODO: get this from mediaplayer when it supports it
307    // process_media_player_call(
308    //        env, thiz, mp->getSyncSettings(&scs), NULL, NULL);
309    ALOGV("getSyncSettings: %d %d %f %f",
310            scs.syncSource, scs.audioAdjustMode, scs.tolerance, scs.frameRate);
311
312    scs.syncSourceSet = true;
313    scs.audioAdjustModeSet = true;
314    scs.toleranceSet = true;
315    scs.frameRateSet = false;
316
317    return scs.asJobject(env, gSyncSettingsFields);
318}
319
320static void android_media_MediaSync_native_init(JNIEnv *env) {
321    ScopedLocalRef<jclass> clazz(env, env->FindClass("android/media/MediaSync"));
322    CHECK(clazz.get() != NULL);
323
324    gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
325    CHECK(gFields.context != NULL);
326
327    clazz.reset(env->FindClass("android/media/MediaTimestamp"));
328    CHECK(clazz.get() != NULL);
329
330    gFields.mediaTimestampMediaTimeUsID =
331        env->GetFieldID(clazz.get(), "mediaTimeUs", "J");
332    CHECK(gFields.mediaTimestampMediaTimeUsID != NULL);
333
334    gFields.mediaTimestampNanoTimeID =
335        env->GetFieldID(clazz.get(), "nanoTime", "J");
336    CHECK(gFields.mediaTimestampNanoTimeID != NULL);
337
338    gFields.mediaTimestampClockRateID =
339        env->GetFieldID(clazz.get(), "clockRate", "F");
340    CHECK(gFields.mediaTimestampClockRateID != NULL);
341
342    gSyncSettingsFields.init(env);
343}
344
345static void android_media_MediaSync_native_setup(JNIEnv *env, jobject thiz) {
346    sp<JMediaSync> sync = new JMediaSync();
347
348    setMediaSync(env, thiz, sync);
349}
350
351static void android_media_MediaSync_native_setPlaybackRate(
352        JNIEnv *env, jobject thiz, jfloat rate) {
353    sp<JMediaSync> sync = getMediaSync(env, thiz);
354    if (sync == NULL) {
355        throwExceptionAsNecessary(env, INVALID_OPERATION);
356        return;
357    }
358
359    status_t err = sync->setPlaybackRate(rate);
360    if (err != NO_ERROR) {
361        throwExceptionAsNecessary(env, err);
362        return;
363    }
364}
365
366static void android_media_MediaSync_native_finalize(JNIEnv *env, jobject thiz) {
367    android_media_MediaSync_release(env, thiz);
368}
369
370static JNINativeMethod gMethods[] = {
371    { "native_configureSurface",
372      "(Landroid/view/Surface;)V",
373      (void *)android_media_MediaSync_native_configureSurface },
374
375    { "native_configureAudioTrack",
376      "(Landroid/media/AudioTrack;I)V",
377      (void *)android_media_MediaSync_native_configureAudioTrack },
378
379    { "createInputSurface", "()Landroid/view/Surface;",
380      (void *)android_media_MediaSync_createInputSurface },
381
382    { "native_updateQueuedAudioData",
383      "(IJ)V",
384      (void *)android_media_MediaSync_native_updateQueuedAudioData },
385
386    { "native_getTimestamp",
387      "(Landroid/media/MediaTimestamp;)Z",
388      (void *)android_media_MediaSync_native_getTimestamp },
389
390    { "native_init", "()V", (void *)android_media_MediaSync_native_init },
391
392    { "native_setup", "()V", (void *)android_media_MediaSync_native_setup },
393
394    { "native_release", "()V", (void *)android_media_MediaSync_release },
395
396    { "native_setPlaybackRate", "(F)V", (void *)android_media_MediaSync_native_setPlaybackRate },
397
398    { "setSyncSettings", "(Landroid/media/SyncSettings;)V", (void *)android_media_MediaSync_setSyncSettings},
399
400    { "getSyncSettings", "()Landroid/media/SyncSettings;", (void *)android_media_MediaSync_getSyncSettings},
401
402    { "native_finalize", "()V", (void *)android_media_MediaSync_native_finalize },
403};
404
405int register_android_media_MediaSync(JNIEnv *env) {
406    return AndroidRuntime::registerNativeMethods(
407                   env, "android/media/MediaSync", gMethods, NELEM(gMethods));
408}
409