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