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