android_media_MediaExtractor.cpp revision 60d610bf103379277a4b29a7ead4f013f6128e4e
1/*
2 * Copyright 2012, 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 "MediaExtractor-JNI"
19#include <utils/Log.h>
20
21#include "android_media_MediaExtractor.h"
22
23#include "android_media_Utils.h"
24#include "android_runtime/AndroidRuntime.h"
25#include "jni.h"
26#include "JNIHelp.h"
27
28#include <media/hardware/CryptoAPI.h>
29#include <media/stagefright/foundation/ABuffer.h>
30#include <media/stagefright/foundation/ADebug.h>
31#include <media/stagefright/foundation/AMessage.h>
32#include <media/stagefright/DataSource.h>
33#include <media/stagefright/MediaErrors.h>
34#include <media/stagefright/MetaData.h>
35#include <media/stagefright/NuMediaExtractor.h>
36
37namespace android {
38
39struct fields_t {
40    jfieldID context;
41
42    jmethodID cryptoInfoSetID;
43};
44
45static fields_t gFields;
46
47////////////////////////////////////////////////////////////////////////////////
48
49JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz)
50    : mClass(NULL),
51      mObject(NULL) {
52    jclass clazz = env->GetObjectClass(thiz);
53    CHECK(clazz != NULL);
54
55    mClass = (jclass)env->NewGlobalRef(clazz);
56    mObject = env->NewWeakGlobalRef(thiz);
57
58    mImpl = new NuMediaExtractor;
59}
60
61JMediaExtractor::~JMediaExtractor() {
62    JNIEnv *env = AndroidRuntime::getJNIEnv();
63
64    env->DeleteWeakGlobalRef(mObject);
65    mObject = NULL;
66    env->DeleteGlobalRef(mClass);
67    mClass = NULL;
68}
69
70status_t JMediaExtractor::setDataSource(
71        const char *path, const KeyedVector<String8, String8> *headers) {
72    return mImpl->setDataSource(path, headers);
73}
74
75status_t JMediaExtractor::setDataSource(int fd, off64_t offset, off64_t size) {
76    return mImpl->setDataSource(fd, offset, size);
77}
78
79size_t JMediaExtractor::countTracks() const {
80    return mImpl->countTracks();
81}
82
83status_t JMediaExtractor::getTrackFormat(size_t index, jobject *format) const {
84    sp<AMessage> msg;
85    status_t err;
86    if ((err = mImpl->getTrackFormat(index, &msg)) != OK) {
87        return err;
88    }
89
90    JNIEnv *env = AndroidRuntime::getJNIEnv();
91
92    return ConvertMessageToMap(env, msg, format);
93}
94
95status_t JMediaExtractor::selectTrack(size_t index) {
96    return mImpl->selectTrack(index);
97}
98
99status_t JMediaExtractor::unselectTrack(size_t index) {
100    return mImpl->unselectTrack(index);
101}
102
103status_t JMediaExtractor::seekTo(
104        int64_t timeUs, MediaSource::ReadOptions::SeekMode mode) {
105    return mImpl->seekTo(timeUs, mode);
106}
107
108status_t JMediaExtractor::advance() {
109    return mImpl->advance();
110}
111
112status_t JMediaExtractor::readSampleData(
113        jobject byteBuf, size_t offset, size_t *sampleSize) {
114    JNIEnv *env = AndroidRuntime::getJNIEnv();
115
116    void *dst = env->GetDirectBufferAddress(byteBuf);
117
118    jlong dstSize;
119    jbyteArray byteArray = NULL;
120
121    if (dst == NULL) {
122        jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
123        CHECK(byteBufClass != NULL);
124
125        jmethodID arrayID =
126            env->GetMethodID(byteBufClass, "array", "()[B");
127        CHECK(arrayID != NULL);
128
129        byteArray =
130            (jbyteArray)env->CallObjectMethod(byteBuf, arrayID);
131
132        if (byteArray == NULL) {
133            return INVALID_OPERATION;
134        }
135
136        jboolean isCopy;
137        dst = env->GetByteArrayElements(byteArray, &isCopy);
138
139        dstSize = env->GetArrayLength(byteArray);
140    } else {
141        dstSize = env->GetDirectBufferCapacity(byteBuf);
142    }
143
144    if (dstSize < offset) {
145        if (byteArray != NULL) {
146            env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
147        }
148
149        return -ERANGE;
150    }
151
152    sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
153
154    status_t err = mImpl->readSampleData(buffer);
155
156    if (byteArray != NULL) {
157        env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
158    }
159
160    if (err != OK) {
161        return err;
162    }
163
164    *sampleSize = buffer->size();
165
166    return OK;
167}
168
169status_t JMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
170    return mImpl->getSampleTrackIndex(trackIndex);
171}
172
173status_t JMediaExtractor::getSampleTime(int64_t *sampleTimeUs) {
174    return mImpl->getSampleTime(sampleTimeUs);
175}
176
177status_t JMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
178    *sampleFlags = 0;
179
180    sp<MetaData> meta;
181    status_t err = mImpl->getSampleMeta(&meta);
182
183    if (err != OK) {
184        return err;
185    }
186
187    int32_t val;
188    if (meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
189        (*sampleFlags) |= NuMediaExtractor::SAMPLE_FLAG_SYNC;
190    }
191
192    uint32_t type;
193    const void *data;
194    size_t size;
195    if (meta->findData(kKeyEncryptedSizes, &type, &data, &size)) {
196        (*sampleFlags) |= NuMediaExtractor::SAMPLE_FLAG_ENCRYPTED;
197    }
198
199    return OK;
200}
201
202status_t JMediaExtractor::getSampleMeta(sp<MetaData> *sampleMeta) {
203    return mImpl->getSampleMeta(sampleMeta);
204}
205
206bool JMediaExtractor::getCachedDuration(int64_t *durationUs, bool *eos) const {
207    return mImpl->getCachedDuration(durationUs, eos);
208}
209
210}  // namespace android
211
212////////////////////////////////////////////////////////////////////////////////
213
214using namespace android;
215
216static sp<JMediaExtractor> setMediaExtractor(
217        JNIEnv *env, jobject thiz, const sp<JMediaExtractor> &extractor) {
218    sp<JMediaExtractor> old =
219        (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
220
221    if (extractor != NULL) {
222        extractor->incStrong(thiz);
223    }
224    if (old != NULL) {
225        old->decStrong(thiz);
226    }
227    env->SetIntField(thiz, gFields.context, (int)extractor.get());
228
229    return old;
230}
231
232static sp<JMediaExtractor> getMediaExtractor(JNIEnv *env, jobject thiz) {
233    return (JMediaExtractor *)env->GetIntField(thiz, gFields.context);
234}
235
236static void android_media_MediaExtractor_release(JNIEnv *env, jobject thiz) {
237    setMediaExtractor(env, thiz, NULL);
238}
239
240static jint android_media_MediaExtractor_getTrackCount(
241        JNIEnv *env, jobject thiz) {
242    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
243
244    if (extractor == NULL) {
245        jniThrowException(env, "java/lang/IllegalStateException", NULL);
246        return -1;
247    }
248
249    return extractor->countTracks();
250}
251
252static jobject android_media_MediaExtractor_getTrackFormatNative(
253        JNIEnv *env, jobject thiz, jint index) {
254    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
255
256    if (extractor == NULL) {
257        jniThrowException(env, "java/lang/IllegalStateException", NULL);
258        return NULL;
259    }
260
261    jobject format;
262    status_t err = extractor->getTrackFormat(index, &format);
263
264    if (err != OK) {
265        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
266        return NULL;
267    }
268
269    return format;
270}
271
272static void android_media_MediaExtractor_selectTrack(
273        JNIEnv *env, jobject thiz, jint index) {
274    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
275
276    if (extractor == NULL) {
277        jniThrowException(env, "java/lang/IllegalStateException", NULL);
278        return;
279    }
280
281    status_t err = extractor->selectTrack(index);
282
283    if (err != OK) {
284        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
285        return;
286    }
287}
288
289static void android_media_MediaExtractor_unselectTrack(
290        JNIEnv *env, jobject thiz, jint index) {
291    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
292
293    if (extractor == NULL) {
294        jniThrowException(env, "java/lang/IllegalStateException", NULL);
295        return;
296    }
297
298    status_t err = extractor->unselectTrack(index);
299
300    if (err != OK) {
301        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
302        return;
303    }
304}
305
306static void android_media_MediaExtractor_seekTo(
307        JNIEnv *env, jobject thiz, jlong timeUs, jint mode) {
308    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
309
310    if (extractor == NULL) {
311        jniThrowException(env, "java/lang/IllegalStateException", NULL);
312        return;
313    }
314
315    if (mode < MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC
316            || mode > MediaSource::ReadOptions::SEEK_CLOSEST) {
317        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
318        return;
319    }
320
321    extractor->seekTo(timeUs, (MediaSource::ReadOptions::SeekMode)mode);
322}
323
324static jboolean android_media_MediaExtractor_advance(
325        JNIEnv *env, jobject thiz) {
326    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
327
328    if (extractor == NULL) {
329        jniThrowException(env, "java/lang/IllegalStateException", NULL);
330        return false;
331    }
332
333    status_t err = extractor->advance();
334
335    if (err == ERROR_END_OF_STREAM) {
336        return false;
337    } else if (err != OK) {
338        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
339        return false;
340    }
341
342    return true;
343}
344
345static jint android_media_MediaExtractor_readSampleData(
346        JNIEnv *env, jobject thiz, jobject byteBuf, jint offset) {
347    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
348
349    if (extractor == NULL) {
350        jniThrowException(env, "java/lang/IllegalStateException", NULL);
351        return -1;
352    }
353
354    size_t sampleSize;
355    status_t err = extractor->readSampleData(byteBuf, offset, &sampleSize);
356
357    if (err == ERROR_END_OF_STREAM) {
358        return -1;
359    } else if (err != OK) {
360        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
361        return false;
362    }
363
364    return sampleSize;
365}
366
367static jint android_media_MediaExtractor_getSampleTrackIndex(
368        JNIEnv *env, jobject thiz) {
369    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
370
371    if (extractor == NULL) {
372        jniThrowException(env, "java/lang/IllegalStateException", NULL);
373        return -1;
374    }
375
376    size_t trackIndex;
377    status_t err = extractor->getSampleTrackIndex(&trackIndex);
378
379    if (err == ERROR_END_OF_STREAM) {
380        return -1;
381    } else if (err != OK) {
382        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
383        return false;
384    }
385
386    return trackIndex;
387}
388
389static jlong android_media_MediaExtractor_getSampleTime(
390        JNIEnv *env, jobject thiz) {
391    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
392
393    if (extractor == NULL) {
394        jniThrowException(env, "java/lang/IllegalStateException", NULL);
395        return -1ll;
396    }
397
398    int64_t sampleTimeUs;
399    status_t err = extractor->getSampleTime(&sampleTimeUs);
400
401    if (err == ERROR_END_OF_STREAM) {
402        return -1ll;
403    } else if (err != OK) {
404        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
405        return false;
406    }
407
408    return sampleTimeUs;
409}
410
411static jint android_media_MediaExtractor_getSampleFlags(
412        JNIEnv *env, jobject thiz) {
413    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
414
415    if (extractor == NULL) {
416        jniThrowException(env, "java/lang/IllegalStateException", NULL);
417        return -1ll;
418    }
419
420    uint32_t sampleFlags;
421    status_t err = extractor->getSampleFlags(&sampleFlags);
422
423    if (err == ERROR_END_OF_STREAM) {
424        return -1ll;
425    } else if (err != OK) {
426        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
427        return false;
428    }
429
430    return sampleFlags;
431}
432
433static jboolean android_media_MediaExtractor_getSampleCryptoInfo(
434        JNIEnv *env, jobject thiz, jobject cryptoInfoObj) {
435    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
436
437    if (extractor == NULL) {
438        jniThrowException(env, "java/lang/IllegalStateException", NULL);
439        return -1ll;
440    }
441
442    sp<MetaData> meta;
443    status_t err = extractor->getSampleMeta(&meta);
444
445    if (err != OK) {
446        return false;
447    }
448
449    uint32_t type;
450    const void *data;
451    size_t size;
452    if (!meta->findData(kKeyEncryptedSizes, &type, &data, &size)) {
453        return false;
454    }
455
456    size_t numSubSamples = size / sizeof(size_t);
457
458    if (numSubSamples == 0) {
459        return false;
460    }
461
462    jintArray numBytesOfEncryptedDataObj = env->NewIntArray(numSubSamples);
463    jboolean isCopy;
464    jint *dst = env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy);
465    for (size_t i = 0; i < numSubSamples; ++i) {
466        dst[i] = ((const size_t *)data)[i];
467    }
468    env->ReleaseIntArrayElements(numBytesOfEncryptedDataObj, dst, 0);
469    dst = NULL;
470
471    size_t encSize = size;
472    jintArray numBytesOfPlainDataObj = NULL;
473    if (meta->findData(kKeyPlainSizes, &type, &data, &size)) {
474        if (size != encSize) {
475            // The two must be of the same length.
476            return false;
477        }
478
479        numBytesOfPlainDataObj = env->NewIntArray(numSubSamples);
480        jboolean isCopy;
481        jint *dst = env->GetIntArrayElements(numBytesOfPlainDataObj, &isCopy);
482        for (size_t i = 0; i < numSubSamples; ++i) {
483            dst[i] = ((const size_t *)data)[i];
484        }
485        env->ReleaseIntArrayElements(numBytesOfPlainDataObj, dst, 0);
486        dst = NULL;
487    }
488
489    jbyteArray keyObj = NULL;
490    if (meta->findData(kKeyCryptoKey, &type, &data, &size)) {
491        if (size != 16) {
492            // Keys must be 16 bytes in length.
493            return false;
494        }
495
496        keyObj = env->NewByteArray(size);
497        jboolean isCopy;
498        jbyte *dst = env->GetByteArrayElements(keyObj, &isCopy);
499        memcpy(dst, data, size);
500        env->ReleaseByteArrayElements(keyObj, dst, 0);
501        dst = NULL;
502    }
503
504    jbyteArray ivObj = NULL;
505    if (meta->findData(kKeyCryptoIV, &type, &data, &size)) {
506        if (size != 16) {
507            // IVs must be 16 bytes in length.
508            return false;
509        }
510
511        ivObj = env->NewByteArray(size);
512        jboolean isCopy;
513        jbyte *dst = env->GetByteArrayElements(ivObj, &isCopy);
514        memcpy(dst, data, size);
515        env->ReleaseByteArrayElements(ivObj, dst, 0);
516        dst = NULL;
517    }
518
519    int32_t mode;
520    if (!meta->findInt32(kKeyCryptoMode, &mode)) {
521        mode = CryptoPlugin::kMode_AES_CTR;
522    }
523
524    env->CallVoidMethod(
525            cryptoInfoObj,
526            gFields.cryptoInfoSetID,
527            numSubSamples,
528            numBytesOfPlainDataObj,
529            numBytesOfEncryptedDataObj,
530            keyObj,
531            ivObj,
532            mode);
533
534    return true;
535}
536
537static void android_media_MediaExtractor_native_init(JNIEnv *env) {
538    jclass clazz = env->FindClass("android/media/MediaExtractor");
539    CHECK(clazz != NULL);
540
541    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
542    CHECK(gFields.context != NULL);
543
544    clazz = env->FindClass("android/media/MediaCodec$CryptoInfo");
545    CHECK(clazz != NULL);
546
547    gFields.cryptoInfoSetID =
548        env->GetMethodID(clazz, "set", "(I[I[I[B[BI)V");
549
550    DataSource::RegisterDefaultSniffers();
551}
552
553static void android_media_MediaExtractor_native_setup(
554        JNIEnv *env, jobject thiz) {
555    sp<JMediaExtractor> extractor = new JMediaExtractor(env, thiz);
556    setMediaExtractor(env,thiz, extractor);
557}
558
559static void android_media_MediaExtractor_setDataSource(
560        JNIEnv *env, jobject thiz,
561        jstring pathObj, jobjectArray keysArray, jobjectArray valuesArray) {
562    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
563
564    if (extractor == NULL) {
565        jniThrowException(env, "java/lang/IllegalStateException", NULL);
566        return;
567    }
568
569    if (pathObj == NULL) {
570        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
571        return;
572    }
573
574    KeyedVector<String8, String8> headers;
575    if (!ConvertKeyValueArraysToKeyedVector(
576                env, keysArray, valuesArray, &headers)) {
577        return;
578    }
579
580    const char *path = env->GetStringUTFChars(pathObj, NULL);
581
582    if (path == NULL) {
583        return;
584    }
585
586    status_t err = extractor->setDataSource(path, &headers);
587
588    env->ReleaseStringUTFChars(pathObj, path);
589    path = NULL;
590
591    if (err != OK) {
592        jniThrowException(
593                env,
594                "java/io/IOException",
595                "Failed to instantiate extractor.");
596        return;
597    }
598}
599
600static void android_media_MediaExtractor_setDataSourceFd(
601        JNIEnv *env, jobject thiz,
602        jobject fileDescObj, jlong offset, jlong length) {
603    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
604
605    if (extractor == NULL) {
606        jniThrowException(env, "java/lang/IllegalStateException", NULL);
607        return;
608    }
609
610    if (fileDescObj == NULL) {
611        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
612        return;
613    }
614
615    int fd = jniGetFDFromFileDescriptor(env, fileDescObj);
616
617    status_t err = extractor->setDataSource(fd, offset, length);
618
619    if (err != OK) {
620        jniThrowException(
621                env,
622                "java/io/IOException",
623                "Failed to instantiate extractor.");
624        return;
625    }
626}
627
628static jlong android_media_MediaExtractor_getCachedDurationUs(
629        JNIEnv *env, jobject thiz) {
630    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
631
632    if (extractor == NULL) {
633        jniThrowException(env, "java/lang/IllegalStateException", NULL);
634        return -1ll;
635    }
636
637    int64_t cachedDurationUs;
638    bool eos;
639    if (!extractor->getCachedDuration(&cachedDurationUs, &eos)) {
640        return -1ll;
641    }
642
643    return cachedDurationUs;
644}
645
646static jboolean android_media_MediaExtractor_hasCacheReachedEOS(
647        JNIEnv *env, jobject thiz) {
648    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
649
650    if (extractor == NULL) {
651        jniThrowException(env, "java/lang/IllegalStateException", NULL);
652        return true;
653    }
654
655    int64_t cachedDurationUs;
656    bool eos;
657    if (!extractor->getCachedDuration(&cachedDurationUs, &eos)) {
658        return true;
659    }
660
661    return eos;
662}
663
664static void android_media_MediaExtractor_native_finalize(
665        JNIEnv *env, jobject thiz) {
666    android_media_MediaExtractor_release(env, thiz);
667}
668
669static JNINativeMethod gMethods[] = {
670    { "release", "()V", (void *)android_media_MediaExtractor_release },
671
672    { "getTrackCount", "()I", (void *)android_media_MediaExtractor_getTrackCount },
673
674    { "getTrackFormatNative", "(I)Ljava/util/Map;",
675        (void *)android_media_MediaExtractor_getTrackFormatNative },
676
677    { "selectTrack", "(I)V", (void *)android_media_MediaExtractor_selectTrack },
678
679    { "unselectTrack", "(I)V",
680        (void *)android_media_MediaExtractor_unselectTrack },
681
682    { "seekTo", "(JI)V", (void *)android_media_MediaExtractor_seekTo },
683
684    { "advance", "()Z", (void *)android_media_MediaExtractor_advance },
685
686    { "readSampleData", "(Ljava/nio/ByteBuffer;I)I",
687        (void *)android_media_MediaExtractor_readSampleData },
688
689    { "getSampleTrackIndex", "()I",
690        (void *)android_media_MediaExtractor_getSampleTrackIndex },
691
692    { "getSampleTime", "()J",
693        (void *)android_media_MediaExtractor_getSampleTime },
694
695    { "getSampleFlags", "()I",
696        (void *)android_media_MediaExtractor_getSampleFlags },
697
698    { "getSampleCryptoInfo", "(Landroid/media/MediaCodec$CryptoInfo;)Z",
699        (void *)android_media_MediaExtractor_getSampleCryptoInfo },
700
701    { "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
702
703    { "native_setup", "()V",
704      (void *)android_media_MediaExtractor_native_setup },
705
706    { "native_finalize", "()V",
707      (void *)android_media_MediaExtractor_native_finalize },
708
709    { "setDataSource", "(Ljava/lang/String;[Ljava/lang/String;"
710                       "[Ljava/lang/String;)V",
711      (void *)android_media_MediaExtractor_setDataSource },
712
713    { "setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
714      (void *)android_media_MediaExtractor_setDataSourceFd },
715
716    { "getCachedDuration", "()J",
717      (void *)android_media_MediaExtractor_getCachedDurationUs },
718
719    { "hasCacheReachedEndOfStream", "()Z",
720      (void *)android_media_MediaExtractor_hasCacheReachedEOS },
721};
722
723int register_android_media_MediaExtractor(JNIEnv *env) {
724    return AndroidRuntime::registerNativeMethods(env,
725                "android/media/MediaExtractor", gMethods, NELEM(gMethods));
726}
727