• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 #include <assert.h>
18 #include <fcntl.h>
19 #include <inttypes.h>
20 #include <limits.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 
24 //#define LOG_NDEBUG 0
25 #define LOG_TAG "MediaRecorderJNI"
26 #include <utils/Log.h>
27 
28 #include <gui/Surface.h>
29 #include <camera/ICameraService.h>
30 #include <camera/Camera.h>
31 #include <media/mediarecorder.h>
32 #include <utils/threads.h>
33 
34 #include "jni.h"
35 #include "JNIHelp.h"
36 #include "android_runtime/AndroidRuntime.h"
37 
38 #include <system/audio.h>
39 #include <android_runtime/android_view_Surface.h>
40 
41 // ----------------------------------------------------------------------------
42 
43 using namespace android;
44 
45 // ----------------------------------------------------------------------------
46 
47 // helper function to extract a native Camera object from a Camera Java object
48 extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct JNICameraContext** context);
49 
50 struct fields_t {
51     jfieldID    context;
52     jfieldID    surface;
53 
54     jmethodID   post_event;
55 };
56 static fields_t fields;
57 
58 static Mutex sLock;
59 
60 // ----------------------------------------------------------------------------
61 // ref-counted object for callbacks
62 class JNIMediaRecorderListener: public MediaRecorderListener
63 {
64 public:
65     JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
66     ~JNIMediaRecorderListener();
67     void notify(int msg, int ext1, int ext2);
68 private:
69     JNIMediaRecorderListener();
70     jclass      mClass;     // Reference to MediaRecorder class
71     jobject     mObject;    // Weak ref to MediaRecorder Java object to call on
72 };
73 
JNIMediaRecorderListener(JNIEnv * env,jobject thiz,jobject weak_thiz)74 JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
75 {
76 
77     // Hold onto the MediaRecorder class for use in calling the static method
78     // that posts events to the application thread.
79     jclass clazz = env->GetObjectClass(thiz);
80     if (clazz == NULL) {
81         ALOGE("Can't find android/media/MediaRecorder");
82         jniThrowException(env, "java/lang/Exception", NULL);
83         return;
84     }
85     mClass = (jclass)env->NewGlobalRef(clazz);
86 
87     // We use a weak reference so the MediaRecorder object can be garbage collected.
88     // The reference is only used as a proxy for callbacks.
89     mObject  = env->NewGlobalRef(weak_thiz);
90 }
91 
~JNIMediaRecorderListener()92 JNIMediaRecorderListener::~JNIMediaRecorderListener()
93 {
94     // remove global references
95     JNIEnv *env = AndroidRuntime::getJNIEnv();
96     env->DeleteGlobalRef(mObject);
97     env->DeleteGlobalRef(mClass);
98 }
99 
notify(int msg,int ext1,int ext2)100 void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2)
101 {
102     ALOGV("JNIMediaRecorderListener::notify");
103 
104     JNIEnv *env = AndroidRuntime::getJNIEnv();
105     env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL);
106 }
107 
108 // ----------------------------------------------------------------------------
109 
get_surface(JNIEnv * env,jobject clazz)110 static sp<Surface> get_surface(JNIEnv* env, jobject clazz)
111 {
112     ALOGV("get_surface");
113     return android_view_Surface_getSurface(env, clazz);
114 }
115 
116 // Returns true if it throws an exception.
process_media_recorder_call(JNIEnv * env,status_t opStatus,const char * exception,const char * message)117 static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const char* exception, const char* message)
118 {
119     ALOGV("process_media_recorder_call");
120     if (opStatus == (status_t)INVALID_OPERATION) {
121         jniThrowException(env, "java/lang/IllegalStateException", NULL);
122         return true;
123     } else if (opStatus != (status_t)OK) {
124         jniThrowException(env, exception, message);
125         return true;
126     }
127     return false;
128 }
129 
getMediaRecorder(JNIEnv * env,jobject thiz)130 static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz)
131 {
132     Mutex::Autolock l(sLock);
133     MediaRecorder* const p = (MediaRecorder*)env->GetLongField(thiz, fields.context);
134     return sp<MediaRecorder>(p);
135 }
136 
setMediaRecorder(JNIEnv * env,jobject thiz,const sp<MediaRecorder> & recorder)137 static sp<MediaRecorder> setMediaRecorder(JNIEnv* env, jobject thiz, const sp<MediaRecorder>& recorder)
138 {
139     Mutex::Autolock l(sLock);
140     sp<MediaRecorder> old = (MediaRecorder*)env->GetLongField(thiz, fields.context);
141     if (recorder.get()) {
142         recorder->incStrong(thiz);
143     }
144     if (old != 0) {
145         old->decStrong(thiz);
146     }
147     env->SetLongField(thiz, fields.context, (jlong)recorder.get());
148     return old;
149 }
150 
151 
android_media_MediaRecorder_setCamera(JNIEnv * env,jobject thiz,jobject camera)152 static void android_media_MediaRecorder_setCamera(JNIEnv* env, jobject thiz, jobject camera)
153 {
154     // we should not pass a null camera to get_native_camera() call.
155     if (camera == NULL) {
156         jniThrowNullPointerException(env, "camera object is a NULL pointer");
157         return;
158     }
159     sp<Camera> c = get_native_camera(env, camera, NULL);
160     if (c == NULL) {
161         // get_native_camera will throw an exception in this case
162         return;
163     }
164     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
165     process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
166             "java/lang/RuntimeException", "setCamera failed.");
167 }
168 
169 static void
android_media_MediaRecorder_setVideoSource(JNIEnv * env,jobject thiz,jint vs)170 android_media_MediaRecorder_setVideoSource(JNIEnv *env, jobject thiz, jint vs)
171 {
172     ALOGV("setVideoSource(%d)", vs);
173     if (vs < VIDEO_SOURCE_DEFAULT || vs >= VIDEO_SOURCE_LIST_END) {
174         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid video source");
175         return;
176     }
177     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
178     process_media_recorder_call(env, mr->setVideoSource(vs), "java/lang/RuntimeException", "setVideoSource failed.");
179 }
180 
181 static void
android_media_MediaRecorder_setAudioSource(JNIEnv * env,jobject thiz,jint as)182 android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as)
183 {
184     ALOGV("setAudioSource(%d)", as);
185     if (as < AUDIO_SOURCE_DEFAULT ||
186         (as >= AUDIO_SOURCE_CNT && as != AUDIO_SOURCE_FM_TUNER)) {
187         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid audio source");
188         return;
189     }
190 
191     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
192     process_media_recorder_call(env, mr->setAudioSource(as), "java/lang/RuntimeException", "setAudioSource failed.");
193 }
194 
195 static void
android_media_MediaRecorder_setOutputFormat(JNIEnv * env,jobject thiz,jint of)196 android_media_MediaRecorder_setOutputFormat(JNIEnv *env, jobject thiz, jint of)
197 {
198     ALOGV("setOutputFormat(%d)", of);
199     if (of < OUTPUT_FORMAT_DEFAULT || of >= OUTPUT_FORMAT_LIST_END) {
200         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid output format");
201         return;
202     }
203     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
204     process_media_recorder_call(env, mr->setOutputFormat(of), "java/lang/RuntimeException", "setOutputFormat failed.");
205 }
206 
207 static void
android_media_MediaRecorder_setVideoEncoder(JNIEnv * env,jobject thiz,jint ve)208 android_media_MediaRecorder_setVideoEncoder(JNIEnv *env, jobject thiz, jint ve)
209 {
210     ALOGV("setVideoEncoder(%d)", ve);
211     if (ve < VIDEO_ENCODER_DEFAULT || ve >= VIDEO_ENCODER_LIST_END) {
212         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid video encoder");
213         return;
214     }
215     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
216     process_media_recorder_call(env, mr->setVideoEncoder(ve), "java/lang/RuntimeException", "setVideoEncoder failed.");
217 }
218 
219 static void
android_media_MediaRecorder_setAudioEncoder(JNIEnv * env,jobject thiz,jint ae)220 android_media_MediaRecorder_setAudioEncoder(JNIEnv *env, jobject thiz, jint ae)
221 {
222     ALOGV("setAudioEncoder(%d)", ae);
223     if (ae < AUDIO_ENCODER_DEFAULT || ae >= AUDIO_ENCODER_LIST_END) {
224         jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid audio encoder");
225         return;
226     }
227     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
228     process_media_recorder_call(env, mr->setAudioEncoder(ae), "java/lang/RuntimeException", "setAudioEncoder failed.");
229 }
230 
231 static void
android_media_MediaRecorder_setParameter(JNIEnv * env,jobject thiz,jstring params)232 android_media_MediaRecorder_setParameter(JNIEnv *env, jobject thiz, jstring params)
233 {
234     ALOGV("setParameter()");
235     if (params == NULL)
236     {
237         ALOGE("Invalid or empty params string.  This parameter will be ignored.");
238         return;
239     }
240 
241     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
242 
243     const char* params8 = env->GetStringUTFChars(params, NULL);
244     if (params8 == NULL)
245     {
246         ALOGE("Failed to covert jstring to String8.  This parameter will be ignored.");
247         return;
248     }
249 
250     process_media_recorder_call(env, mr->setParameters(String8(params8)), "java/lang/RuntimeException", "setParameter failed.");
251     env->ReleaseStringUTFChars(params,params8);
252 }
253 
254 static void
android_media_MediaRecorder_setOutputFileFD(JNIEnv * env,jobject thiz,jobject fileDescriptor,jlong offset,jlong length)255 android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
256 {
257     ALOGV("setOutputFile");
258     if (fileDescriptor == NULL) {
259         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
260         return;
261     }
262     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
263     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
264     status_t opStatus = mr->setOutputFile(fd, offset, length);
265     process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed.");
266 }
267 
268 static void
android_media_MediaRecorder_setVideoSize(JNIEnv * env,jobject thiz,jint width,jint height)269 android_media_MediaRecorder_setVideoSize(JNIEnv *env, jobject thiz, jint width, jint height)
270 {
271     ALOGV("setVideoSize(%d, %d)", width, height);
272     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
273 
274     if (width <= 0 || height <= 0) {
275         jniThrowException(env, "java/lang/IllegalArgumentException", "invalid video size");
276         return;
277     }
278     process_media_recorder_call(env, mr->setVideoSize(width, height), "java/lang/RuntimeException", "setVideoSize failed.");
279 }
280 
281 static void
android_media_MediaRecorder_setVideoFrameRate(JNIEnv * env,jobject thiz,jint rate)282 android_media_MediaRecorder_setVideoFrameRate(JNIEnv *env, jobject thiz, jint rate)
283 {
284     ALOGV("setVideoFrameRate(%d)", rate);
285     if (rate <= 0) {
286         jniThrowException(env, "java/lang/IllegalArgumentException", "invalid frame rate");
287         return;
288     }
289     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
290     process_media_recorder_call(env, mr->setVideoFrameRate(rate), "java/lang/RuntimeException", "setVideoFrameRate failed.");
291 }
292 
293 static void
android_media_MediaRecorder_setMaxDuration(JNIEnv * env,jobject thiz,jint max_duration_ms)294 android_media_MediaRecorder_setMaxDuration(JNIEnv *env, jobject thiz, jint max_duration_ms)
295 {
296     ALOGV("setMaxDuration(%d)", max_duration_ms);
297     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
298 
299     char params[64];
300     sprintf(params, "max-duration=%d", max_duration_ms);
301 
302     process_media_recorder_call(env, mr->setParameters(String8(params)), "java/lang/RuntimeException", "setMaxDuration failed.");
303 }
304 
305 static void
android_media_MediaRecorder_setMaxFileSize(JNIEnv * env,jobject thiz,jlong max_filesize_bytes)306 android_media_MediaRecorder_setMaxFileSize(
307         JNIEnv *env, jobject thiz, jlong max_filesize_bytes)
308 {
309     ALOGV("setMaxFileSize(%lld)", max_filesize_bytes);
310     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
311 
312     char params[64];
313     sprintf(params, "max-filesize=%" PRId64, max_filesize_bytes);
314 
315     process_media_recorder_call(env, mr->setParameters(String8(params)), "java/lang/RuntimeException", "setMaxFileSize failed.");
316 }
317 
318 static void
android_media_MediaRecorder_prepare(JNIEnv * env,jobject thiz)319 android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz)
320 {
321     ALOGV("prepare");
322     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
323 
324     jobject surface = env->GetObjectField(thiz, fields.surface);
325     if (surface != NULL) {
326         const sp<Surface> native_surface = get_surface(env, surface);
327 
328         // The application may misbehave and
329         // the preview surface becomes unavailable
330         if (native_surface.get() == 0) {
331             ALOGE("Application lost the surface");
332             jniThrowException(env, "java/io/IOException", "invalid preview surface");
333             return;
334         }
335 
336         ALOGI("prepare: surface=%p", native_surface.get());
337         if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface->getIGraphicBufferProducer()), "java/lang/RuntimeException", "setPreviewSurface failed.")) {
338             return;
339         }
340     }
341     process_media_recorder_call(env, mr->prepare(), "java/io/IOException", "prepare failed.");
342 }
343 
344 static jint
android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv * env,jobject thiz)345 android_media_MediaRecorder_native_getMaxAmplitude(JNIEnv *env, jobject thiz)
346 {
347     ALOGV("getMaxAmplitude");
348     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
349     int result = 0;
350     process_media_recorder_call(env, mr->getMaxAmplitude(&result), "java/lang/RuntimeException", "getMaxAmplitude failed.");
351     return (jint) result;
352 }
353 
354 static jobject
android_media_MediaRecorder_getSurface(JNIEnv * env,jobject thiz)355 android_media_MediaRecorder_getSurface(JNIEnv *env, jobject thiz)
356 {
357     ALOGV("getSurface");
358     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
359 
360     sp<IGraphicBufferProducer> bufferProducer = mr->querySurfaceMediaSourceFromMediaServer();
361     if (bufferProducer == NULL) {
362         jniThrowException(
363                 env,
364                 "java/lang/IllegalStateException",
365                 "failed to get surface");
366         return NULL;
367     }
368 
369     // Wrap the IGBP in a Java-language Surface.
370     return android_view_Surface_createFromIGraphicBufferProducer(env,
371             bufferProducer);
372 }
373 
374 static void
android_media_MediaRecorder_start(JNIEnv * env,jobject thiz)375 android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
376 {
377     ALOGV("start");
378     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
379     process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
380 }
381 
382 static void
android_media_MediaRecorder_stop(JNIEnv * env,jobject thiz)383 android_media_MediaRecorder_stop(JNIEnv *env, jobject thiz)
384 {
385     ALOGV("stop");
386     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
387     process_media_recorder_call(env, mr->stop(), "java/lang/RuntimeException", "stop failed.");
388 }
389 
390 static void
android_media_MediaRecorder_native_reset(JNIEnv * env,jobject thiz)391 android_media_MediaRecorder_native_reset(JNIEnv *env, jobject thiz)
392 {
393     ALOGV("native_reset");
394     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
395     process_media_recorder_call(env, mr->reset(), "java/lang/RuntimeException", "native_reset failed.");
396 }
397 
398 static void
android_media_MediaRecorder_release(JNIEnv * env,jobject thiz)399 android_media_MediaRecorder_release(JNIEnv *env, jobject thiz)
400 {
401     ALOGV("release");
402     sp<MediaRecorder> mr = setMediaRecorder(env, thiz, 0);
403     if (mr != NULL) {
404         mr->setListener(NULL);
405         mr->release();
406     }
407 }
408 
409 // This function gets some field IDs, which in turn causes class initialization.
410 // It is called from a static block in MediaRecorder, which won't run until the
411 // first time an instance of this class is used.
412 static void
android_media_MediaRecorder_native_init(JNIEnv * env)413 android_media_MediaRecorder_native_init(JNIEnv *env)
414 {
415     jclass clazz;
416 
417     clazz = env->FindClass("android/media/MediaRecorder");
418     if (clazz == NULL) {
419         return;
420     }
421 
422     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
423     if (fields.context == NULL) {
424         return;
425     }
426 
427     fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
428     if (fields.surface == NULL) {
429         return;
430     }
431 
432     jclass surface = env->FindClass("android/view/Surface");
433     if (surface == NULL) {
434         return;
435     }
436 
437     fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
438                                                "(Ljava/lang/Object;IIILjava/lang/Object;)V");
439     if (fields.post_event == NULL) {
440         return;
441     }
442 }
443 
444 
445 static void
android_media_MediaRecorder_native_setup(JNIEnv * env,jobject thiz,jobject weak_this,jstring packageName)446 android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
447                                          jstring packageName)
448 {
449     ALOGV("setup");
450 
451     sp<MediaRecorder> mr = new MediaRecorder();
452     if (mr == NULL) {
453         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
454         return;
455     }
456     if (mr->initCheck() != NO_ERROR) {
457         jniThrowException(env, "java/lang/RuntimeException", "Unable to initialize media recorder");
458         return;
459     }
460 
461     // create new listener and give it to MediaRecorder
462     sp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this);
463     mr->setListener(listener);
464 
465    // Convert client name jstring to String16
466     const char16_t *rawClientName = env->GetStringChars(packageName, NULL);
467     jsize rawClientNameLen = env->GetStringLength(packageName);
468     String16 clientName(rawClientName, rawClientNameLen);
469     env->ReleaseStringChars(packageName, rawClientName);
470 
471     // pass client package name for permissions tracking
472     mr->setClientName(clientName);
473 
474     setMediaRecorder(env, thiz, mr);
475 }
476 
477 static void
android_media_MediaRecorder_native_finalize(JNIEnv * env,jobject thiz)478 android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz)
479 {
480     ALOGV("finalize");
481     android_media_MediaRecorder_release(env, thiz);
482 }
483 
484 // ----------------------------------------------------------------------------
485 
486 static JNINativeMethod gMethods[] = {
487     {"setCamera",            "(Landroid/hardware/Camera;)V",    (void *)android_media_MediaRecorder_setCamera},
488     {"setVideoSource",       "(I)V",                            (void *)android_media_MediaRecorder_setVideoSource},
489     {"setAudioSource",       "(I)V",                            (void *)android_media_MediaRecorder_setAudioSource},
490     {"setOutputFormat",      "(I)V",                            (void *)android_media_MediaRecorder_setOutputFormat},
491     {"setVideoEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setVideoEncoder},
492     {"setAudioEncoder",      "(I)V",                            (void *)android_media_MediaRecorder_setAudioEncoder},
493     {"setParameter",         "(Ljava/lang/String;)V",           (void *)android_media_MediaRecorder_setParameter},
494     {"_setOutputFile",       "(Ljava/io/FileDescriptor;JJ)V",   (void *)android_media_MediaRecorder_setOutputFileFD},
495     {"setVideoSize",         "(II)V",                           (void *)android_media_MediaRecorder_setVideoSize},
496     {"setVideoFrameRate",    "(I)V",                            (void *)android_media_MediaRecorder_setVideoFrameRate},
497     {"setMaxDuration",       "(I)V",                            (void *)android_media_MediaRecorder_setMaxDuration},
498     {"setMaxFileSize",       "(J)V",                            (void *)android_media_MediaRecorder_setMaxFileSize},
499     {"_prepare",             "()V",                             (void *)android_media_MediaRecorder_prepare},
500     {"getSurface",           "()Landroid/view/Surface;",        (void *)android_media_MediaRecorder_getSurface},
501     {"getMaxAmplitude",      "()I",                             (void *)android_media_MediaRecorder_native_getMaxAmplitude},
502     {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
503     {"stop",                 "()V",                             (void *)android_media_MediaRecorder_stop},
504     {"native_reset",         "()V",                             (void *)android_media_MediaRecorder_native_reset},
505     {"release",              "()V",                             (void *)android_media_MediaRecorder_release},
506     {"native_init",          "()V",                             (void *)android_media_MediaRecorder_native_init},
507     {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;)V", (void *)android_media_MediaRecorder_native_setup},
508     {"native_finalize",      "()V",                             (void *)android_media_MediaRecorder_native_finalize},
509 };
510 
511 static const char* const kClassPathName = "android/media/MediaRecorder";
512 
513 // This function only registers the native methods, and is called from
514 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaRecorder(JNIEnv * env)515 int register_android_media_MediaRecorder(JNIEnv *env)
516 {
517     return AndroidRuntime::registerNativeMethods(env,
518                 "android/media/MediaRecorder", gMethods, NELEM(gMethods));
519 }
520