native-media-jni.c revision 6bc8af4e67051af7c86c311cb9c50e294e547500
1/*
2 * Copyright (C) 2010 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 <jni.h>
19#include <pthread.h>
20#include <string.h>
21//#define LOG_NDEBUG 0
22#define LOG_TAG "NativeMedia"
23#include <utils/Log.h>
24
25#include <OMXAL/OpenMAXAL.h>
26#include <OMXAL/OpenMAXAL_Android.h>
27
28#include <android/native_window_jni.h>
29
30// engine interfaces
31static XAObjectItf engineObject = NULL;
32static XAEngineItf engineEngine = NULL;
33
34// output mix interfaces
35static XAObjectItf outputMixObject = NULL;
36
37// streaming media player interfaces
38static XAObjectItf             playerObj = NULL;
39static XAPlayItf               playerPlayItf = NULL;
40static XAAndroidBufferQueueItf playerBQItf = NULL;
41static XAStreamInformationItf  playerStreamInfoItf = NULL;
42static XAVolumeItf             playerVolItf = NULL;
43
44// number of required interfaces for the MediaPlayer creation
45#define NB_MAXAL_INTERFACES 3 // XAAndroidBufferQueueItf, XAStreamInformationItf and XAPlayItf
46
47// video sink for the player
48static ANativeWindow* theNativeWindow;
49
50// number of buffers in our buffer queue, an arbitrary number
51#define NB_BUFFERS 16
52
53// we're streaming MPEG-2 transport stream data, operate on transport stream block size
54#define MPEG2_TS_BLOCK_SIZE 188
55
56// number of MPEG-2 transport stream blocks per buffer, an arbitrary number
57#define BLOCKS_PER_BUFFER 20
58
59// determines how much memory we're dedicating to memory caching
60#define BUFFER_SIZE (BLOCKS_PER_BUFFER*MPEG2_TS_BLOCK_SIZE)
61
62// where we cache in memory the data to play
63// note this memory is re-used by the buffer queue callback
64char dataCache[BUFFER_SIZE * NB_BUFFERS];
65
66// handle of the file to play
67FILE *file;
68
69// has the app reached the end of the file
70jboolean reachedEof = JNI_FALSE;
71
72// constant to identify a buffer context which is the end of the stream to decode
73static const int kEosBufferCntxt = 1980; // a magic value we can compare against
74
75// for mutual exclusion between callback thread and application thread(s)
76pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
77pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
78
79// whether a discontinuity is in progress
80jboolean discontinuity = JNI_FALSE;
81
82static jboolean enqueueInitialBuffers(jboolean discontinuity);
83
84// AndroidBufferQueueItf callback for an audio player
85XAresult AndroidBufferQueueCallback(
86        XAAndroidBufferQueueItf caller,
87        void *pCallbackContext,        /* input */
88        void *pBufferContext,          /* input */
89        void *pBufferData,             /* input */
90        XAuint32 dataSize,             /* input */
91        XAuint32 dataUsed,             /* input */
92        const XAAndroidBufferItem *pItems,/* input */
93        XAuint32 itemsLength           /* input */)
94{
95    XAresult res;
96    int ok;
97
98    // pCallbackContext was specified as NULL at RegisterCallback and is unused here
99    assert(NULL == pCallbackContext);
100
101    // note there is never any contention on this mutex unless a discontinuity request is active
102    ok = pthread_mutex_lock(&mutex);
103    assert(0 == ok);
104
105    // was a discontinuity requested?
106    if (discontinuity) {
107        // FIXME sorry, can't rewind after EOS
108        if (!reachedEof) {
109            // clear the buffer queue
110            res = (*playerBQItf)->Clear(playerBQItf);
111            assert(XA_RESULT_SUCCESS == res);
112            // rewind the data source so we are guaranteed to be at an appropriate point
113            rewind(file);
114            // Enqueue the initial buffers, with a discontinuity indicator on first buffer
115            (void) enqueueInitialBuffers(JNI_TRUE);
116        }
117        // acknowledge the discontinuity request
118        discontinuity = JNI_FALSE;
119        ok = pthread_cond_signal(&cond);
120        assert(0 == ok);
121        goto exit;
122    }
123
124    if ((pBufferData == NULL) && (pBufferContext != NULL)) {
125        const int processedCommand = *(int *)pBufferContext;
126        if (kEosBufferCntxt == processedCommand) {
127            LOGV("EOS was processed\n");
128            // our buffer with the EOS message has been consumed
129            assert(0 == dataSize);
130            goto exit;
131        }
132    }
133
134    // pBufferData is a pointer to a buffer that we previously Enqueued
135    assert(BUFFER_SIZE == dataSize);
136    assert(dataCache <= (char *) pBufferData && (char *) pBufferData <
137            &dataCache[BUFFER_SIZE * NB_BUFFERS]);
138    assert(0 == (((char *) pBufferData - dataCache) % BUFFER_SIZE));
139
140#if 0
141    // sample code to use the XAVolumeItf
142    XAAndroidBufferQueueState state;
143    (*caller)->GetState(caller, &state);
144    switch (state.index) {
145    case 300:
146        (*playerVolItf)->SetVolumeLevel(playerVolItf, -600); // -6dB
147        LOGV("setting volume to -6dB");
148        break;
149    case 400:
150        (*playerVolItf)->SetVolumeLevel(playerVolItf, -1200); // -12dB
151        LOGV("setting volume to -12dB");
152        break;
153    case 500:
154        (*playerVolItf)->SetVolumeLevel(playerVolItf, 0); // full volume
155        LOGV("setting volume to 0dB (full volume)");
156        break;
157    case 600:
158        (*playerVolItf)->SetMute(playerVolItf, XA_BOOLEAN_TRUE); // mute
159        LOGV("muting player");
160        break;
161    case 700:
162        (*playerVolItf)->SetMute(playerVolItf, XA_BOOLEAN_FALSE); // unmute
163        LOGV("unmuting player");
164        break;
165    case 800:
166        (*playerVolItf)->SetStereoPosition(playerVolItf, -1000);
167        (*playerVolItf)->EnableStereoPosition(playerVolItf, XA_BOOLEAN_TRUE);
168        LOGV("pan sound to the left (hard-left)");
169        break;
170    case 900:
171        (*playerVolItf)->EnableStereoPosition(playerVolItf, XA_BOOLEAN_FALSE);
172        LOGV("disabling stereo position");
173        break;
174    default:
175        break;
176    }
177#endif
178
179    // don't bother trying to read more data once we've hit EOF
180    if (reachedEof) {
181        goto exit;
182    }
183
184    size_t nbRead;
185    // note we do call fread from multiple threads, but never concurrently
186    nbRead = fread(pBufferData, BUFFER_SIZE, 1, file);
187    if (nbRead > 0) {
188        assert(1 == nbRead);
189        res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/,
190                pBufferData /*pData*/,
191                nbRead * BUFFER_SIZE /*dataLength*/,
192                NULL /*pMsg*/,
193                0 /*msgLength*/);
194        assert(XA_RESULT_SUCCESS == res);
195    } else {
196        // signal EOS
197        XAAndroidBufferItem msgEos[1];
198        msgEos[0].itemKey = XA_ANDROID_ITEMKEY_EOS;
199        msgEos[0].itemSize = 0;
200        // EOS message has no parameters, so the total size of the message is the size of the key
201        //   plus the size if itemSize, both XAuint32
202        res = (*caller)->Enqueue(caller, (void *)&kEosBufferCntxt /*pBufferContext*/,
203                NULL /*pData*/, 0 /*dataLength*/,
204                msgEos /*pMsg*/,
205                // FIXME == sizeof(BufferItem)? */
206                sizeof(XAuint32)*2 /*msgLength*/);
207        assert(XA_RESULT_SUCCESS == res);
208        reachedEof = JNI_TRUE;
209    }
210
211exit:
212    ok = pthread_mutex_unlock(&mutex);
213    assert(0 == ok);
214    return XA_RESULT_SUCCESS;
215}
216
217
218void StreamChangeCallback (XAStreamInformationItf caller,
219        XAuint32 eventId,
220        XAuint32 streamIndex,
221        void * pEventData,
222        void * pContext )
223{
224    LOGV("StreamChangeCallback called for stream %u", streamIndex);
225    // pContext was specified as NULL at RegisterStreamChangeCallback and is unused here
226    assert(NULL == pContext);
227    switch (eventId) {
228    case XA_STREAMCBEVENT_PROPERTYCHANGE: {
229        /** From spec 1.0.1:
230            "This event indicates that stream property change has occurred.
231            The streamIndex parameter identifies the stream with the property change.
232            The pEventData parameter for this event is not used and shall be ignored."
233         */
234
235        XAresult res;
236        XAuint32 domain;
237        res = (*caller)->QueryStreamType(caller, streamIndex, &domain);
238        assert(XA_RESULT_SUCCESS == res);
239        switch (domain) {
240        case XA_DOMAINTYPE_VIDEO: {
241            XAVideoStreamInformation videoInfo;
242            res = (*caller)->QueryStreamInformation(caller, streamIndex, &videoInfo);
243            assert(XA_RESULT_SUCCESS == res);
244            LOGI("Found video size %u x %u, codec ID=%u, frameRate=%u, bitRate=%u, duration=%u ms",
245                        videoInfo.width, videoInfo.height, videoInfo.codecId, videoInfo.frameRate,
246                        videoInfo.bitRate, videoInfo.duration);
247        } break;
248        default:
249            fprintf(stderr, "Unexpected domain %u\n", domain);
250            break;
251        }
252        } break;
253    default:
254        fprintf(stderr, "Unexpected stream event ID %u\n", eventId);
255        break;
256    }
257}
258
259
260// create the engine and output mix objects
261void Java_com_example_nativemedia_NativeMedia_createEngine(JNIEnv* env, jclass clazz)
262{
263    XAresult res;
264
265    // create engine
266    res = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
267    assert(XA_RESULT_SUCCESS == res);
268
269    // realize the engine
270    res = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
271    assert(XA_RESULT_SUCCESS == res);
272
273    // get the engine interface, which is needed in order to create other objects
274    res = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
275    assert(XA_RESULT_SUCCESS == res);
276
277    // create output mix
278    res = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
279    assert(XA_RESULT_SUCCESS == res);
280
281    // realize the output mix
282    res = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
283    assert(XA_RESULT_SUCCESS == res);
284
285}
286
287
288// Enqueue the initial buffers, and optionally signal a discontinuity in the first buffer
289static jboolean enqueueInitialBuffers(jboolean discontinuity)
290{
291
292    /* Fill our cache */
293    size_t nbRead;
294    nbRead = fread(dataCache, BUFFER_SIZE, NB_BUFFERS, file);
295    if (nbRead <= 0) {
296        // could be premature EOF or I/O error
297        LOGE("Error filling cache, exiting\n");
298        return JNI_FALSE;
299    }
300    assert(1 <= nbRead && nbRead <= NB_BUFFERS);
301    LOGV("Initially queueing %u buffers of %u bytes each", nbRead, BUFFER_SIZE);
302
303    /* Enqueue the content of our cache before starting to play,
304       we don't want to starve the player */
305    size_t i;
306    for (i = 0; i < nbRead; i++) {
307        XAresult res;
308        if (discontinuity) {
309            // signal discontinuity
310            XAAndroidBufferItem items[1];
311            items[0].itemKey = XA_ANDROID_ITEMKEY_DISCONTINUITY;
312            items[0].itemSize = 0;
313            // DISCONTINUITY message has no parameters,
314            //   so the total size of the message is the size of the key
315            //   plus the size if itemSize, both XAuint32
316            res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
317                    dataCache + i*BUFFER_SIZE, BUFFER_SIZE, items /*pMsg*/,
318                    // FIXME == sizeof(BufferItem)? */
319                    sizeof(XAuint32)*2 /*msgLength*/);
320            discontinuity = JNI_FALSE;
321        } else {
322            res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
323                    dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0);
324        }
325        assert(XA_RESULT_SUCCESS == res);
326    }
327
328    return JNI_TRUE;
329}
330
331
332// create streaming media player
333jboolean Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer(JNIEnv* env,
334        jclass clazz, jstring filename)
335{
336    XAresult res;
337
338    // convert Java string to UTF-8
339    const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
340    assert(NULL != utf8);
341
342    // open the file to play
343    file = fopen(utf8, "rb");
344    if (file == NULL) {
345        LOGE("Failed to open %s", utf8);
346        return JNI_FALSE;
347    }
348
349    // configure data source
350    XADataLocator_AndroidBufferQueue loc_abq = { XA_DATALOCATOR_ANDROIDBUFFERQUEUE, NB_BUFFERS };
351    XADataFormat_MIME format_mime = {
352            XA_DATAFORMAT_MIME, (XAchar *)"video/mp2ts", XA_CONTAINERTYPE_MPEG_TS };
353    XADataSource dataSrc = {&loc_abq, &format_mime};
354
355    // configure audio sink
356    XADataLocator_OutputMix loc_outmix = { XA_DATALOCATOR_OUTPUTMIX, outputMixObject };
357    XADataSink audioSnk = { &loc_outmix, NULL };
358
359    // configure image video sink
360    XADataLocator_NativeDisplay loc_nd = {
361            XA_DATALOCATOR_NATIVEDISPLAY,        // locatorType
362            // the video sink must be an ANativeWindow created from a Surface or SurfaceTexture
363            (void*)theNativeWindow,              // hWindow
364            // must be NULL
365            NULL                                 // hDisplay
366    };
367    XADataSink imageVideoSink = {&loc_nd, NULL};
368
369    // declare interfaces to use
370    XAboolean     required[NB_MAXAL_INTERFACES]
371                           = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE,           XA_BOOLEAN_TRUE};
372    XAInterfaceID iidArray[NB_MAXAL_INTERFACES]
373                           = {XA_IID_PLAY,     XA_IID_ANDROIDBUFFERQUEUESOURCE,
374                                               XA_IID_STREAMINFORMATION};
375
376
377    // create media player
378    res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
379            NULL, &audioSnk, &imageVideoSink, NULL, NULL,
380            NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
381            iidArray /*const XAInterfaceID *pInterfaceIds*/,
382            required /*const XAboolean *pInterfaceRequired*/);
383    assert(XA_RESULT_SUCCESS == res);
384
385    // release the Java string and UTF-8
386    (*env)->ReleaseStringUTFChars(env, filename, utf8);
387
388    // realize the player
389    res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
390    assert(XA_RESULT_SUCCESS == res);
391
392    // get the play interface
393    res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
394    assert(XA_RESULT_SUCCESS == res);
395
396    // get the stream information interface (for video size)
397    res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
398    assert(XA_RESULT_SUCCESS == res);
399
400    // get the volume interface
401    res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
402    assert(XA_RESULT_SUCCESS == res);
403
404    // get the Android buffer queue interface
405    res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf);
406    assert(XA_RESULT_SUCCESS == res);
407
408    // specify which events we want to be notified of
409    res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
410
411    // register the callback from which OpenMAX AL can retrieve the data to play
412    res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
413    assert(XA_RESULT_SUCCESS == res);
414
415    // we want to be notified of the video size once it's found, so we register a callback for that
416    res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
417            StreamChangeCallback, NULL);
418
419    // enqueue the initial buffers
420    if (!enqueueInitialBuffers(JNI_FALSE)) {
421        return JNI_FALSE;
422    }
423
424    // prepare the player
425    res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
426    assert(XA_RESULT_SUCCESS == res);
427
428    // set the volume
429    res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);//-300);
430    assert(XA_RESULT_SUCCESS == res);
431
432    // start the playback
433    res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
434        assert(XA_RESULT_SUCCESS == res);
435
436    return JNI_TRUE;
437}
438
439
440// set the playing state for the streaming media player
441void Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv* env,
442        jclass clazz, jboolean isPlaying)
443{
444    XAresult res;
445
446    // make sure the streaming media player was created
447    if (NULL != playerPlayItf) {
448
449        // set the player's state
450        res = (*playerPlayItf)->SetPlayState(playerPlayItf, isPlaying ?
451            XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
452        assert(XA_RESULT_SUCCESS == res);
453
454    }
455
456}
457
458
459// shut down the native media system
460void Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv* env, jclass clazz)
461{
462    // destroy streaming media player object, and invalidate all associated interfaces
463    if (playerObj != NULL) {
464        (*playerObj)->Destroy(playerObj);
465        playerObj = NULL;
466        playerPlayItf = NULL;
467        playerBQItf = NULL;
468        playerStreamInfoItf = NULL;
469        playerVolItf = NULL;
470    }
471
472    // destroy output mix object, and invalidate all associated interfaces
473    if (outputMixObject != NULL) {
474        (*outputMixObject)->Destroy(outputMixObject);
475        outputMixObject = NULL;
476    }
477
478    // destroy engine object, and invalidate all associated interfaces
479    if (engineObject != NULL) {
480        (*engineObject)->Destroy(engineObject);
481        engineObject = NULL;
482        engineEngine = NULL;
483    }
484
485    // close the file
486    if (file != NULL) {
487        fclose(file);
488        file = NULL;
489    }
490
491    // make sure we don't leak native windows
492    if (theNativeWindow != NULL) {
493        ANativeWindow_release(theNativeWindow);
494        theNativeWindow = NULL;
495    }
496}
497
498
499// set the surface
500void Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv *env, jclass clazz, jobject surface)
501{
502    // obtain a native window from a Java surface
503    theNativeWindow = ANativeWindow_fromSurface(env, surface);
504}
505
506
507// set the surface texture
508void Java_com_example_nativemedia_NativeMedia_setSurfaceTexture(JNIEnv *env, jclass clazz,
509        jobject surfaceTexture)
510{
511    // obtain a native window from a Java surface texture
512    theNativeWindow = ANativeWindow_fromSurfaceTexture(env, surfaceTexture);
513}
514
515
516// rewind the streaming media player
517void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
518{
519    XAresult res;
520
521    // make sure the streaming media player was created
522    if (NULL != playerBQItf && NULL != file) {
523        // first wait for buffers currently in queue to be drained
524        int ok;
525        ok = pthread_mutex_lock(&mutex);
526        assert(0 == ok);
527        discontinuity = JNI_TRUE;
528        // wait for discontinuity request to be observed by buffer queue callback
529        // FIXME sorry, can't rewind after EOS
530        while (discontinuity && !reachedEof) {
531            ok = pthread_cond_wait(&cond, &mutex);
532            assert(0 == ok);
533        }
534        ok = pthread_mutex_unlock(&mutex);
535        assert(0 == ok);
536    }
537
538}
539