native-media-jni.c revision ac8c7318e1d7ec1358bbf924e1bc2cee45b44fc6
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_ANDROIDBUFFERQUEUE, XA_IID_STREAMINFORMATION};
367
368
369    // create media player
370    res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
371            NULL, &audioSnk, &imageVideoSink, NULL, NULL,
372            NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
373            iidArray /*const XAInterfaceID *pInterfaceIds*/,
374            required /*const XAboolean *pInterfaceRequired*/);
375    assert(XA_RESULT_SUCCESS == res);
376
377    // release the Java string and UTF-8
378    (*env)->ReleaseStringUTFChars(env, filename, utf8);
379
380    // realize the player
381    res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
382    assert(XA_RESULT_SUCCESS == res);
383
384    // get the play interface
385    res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
386    assert(XA_RESULT_SUCCESS == res);
387
388    // get the stream information interface (for video size)
389    res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
390    assert(XA_RESULT_SUCCESS == res);
391
392    // get the volume interface
393    res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
394    assert(XA_RESULT_SUCCESS == res);
395
396    // get the Android buffer queue interface
397    res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUE, &playerBQItf);
398    assert(XA_RESULT_SUCCESS == res);
399
400    // specify which events we want to be notified of
401    res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
402
403    // register the callback from which OpenMAX AL can retrieve the data to play
404    res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
405    assert(XA_RESULT_SUCCESS == res);
406
407    // we want to be notified of the video size once it's found, so we register a callback for that
408    res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
409            StreamChangeCallback, NULL);
410
411    // enqueue the initial buffers
412    if (!enqueueInitialBuffers(JNI_FALSE)) {
413        return JNI_FALSE;
414    }
415
416    // prepare the player
417    res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
418    assert(XA_RESULT_SUCCESS == res);
419
420    // set the volume
421    res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);//-300);
422    assert(XA_RESULT_SUCCESS == res);
423
424    // start the playback
425    res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
426        assert(XA_RESULT_SUCCESS == res);
427
428    return JNI_TRUE;
429}
430
431
432// set the playing state for the streaming media player
433void Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv* env,
434        jclass clazz, jboolean isPlaying)
435{
436    XAresult res;
437
438    // make sure the streaming media player was created
439    if (NULL != playerPlayItf) {
440
441        // set the player's state
442        res = (*playerPlayItf)->SetPlayState(playerPlayItf, isPlaying ?
443            XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
444        assert(XA_RESULT_SUCCESS == res);
445
446    }
447
448}
449
450
451// shut down the native media system
452void Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv* env, jclass clazz)
453{
454    // destroy streaming media player object, and invalidate all associated interfaces
455    if (playerObj != NULL) {
456        (*playerObj)->Destroy(playerObj);
457        playerObj = NULL;
458        playerPlayItf = NULL;
459        playerBQItf = NULL;
460        playerStreamInfoItf = NULL;
461        playerVolItf = NULL;
462    }
463
464    // destroy output mix object, and invalidate all associated interfaces
465    if (outputMixObject != NULL) {
466        (*outputMixObject)->Destroy(outputMixObject);
467        outputMixObject = NULL;
468    }
469
470    // destroy engine object, and invalidate all associated interfaces
471    if (engineObject != NULL) {
472        (*engineObject)->Destroy(engineObject);
473        engineObject = NULL;
474        engineEngine = NULL;
475    }
476
477    // close the file
478    if (file != NULL) {
479        fclose(file);
480        file = NULL;
481    }
482
483    // make sure we don't leak native windows
484    if (theNativeWindow != NULL) {
485        ANativeWindow_release(theNativeWindow);
486        theNativeWindow = NULL;
487    }
488}
489
490
491// set the surface
492void Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv *env, jclass clazz, jobject surface)
493{
494    // obtain a native window from a Java surface
495    theNativeWindow = ANativeWindow_fromSurface(env, surface);
496}
497
498
499// set the surface texture
500void Java_com_example_nativemedia_NativeMedia_setSurfaceTexture(JNIEnv *env, jclass clazz,
501        jobject surfaceTexture)
502{
503    // obtain a native window from a Java surface texture
504    theNativeWindow = ANativeWindow_fromSurfaceTexture(env, surfaceTexture);
505}
506
507
508// rewind the streaming media player
509void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
510{
511    XAresult res;
512
513    // make sure the streaming media player was created
514    if (NULL != playerBQItf && NULL != file) {
515        // first wait for buffers currently in queue to be drained
516        int ok;
517        ok = pthread_mutex_lock(&mutex);
518        assert(0 == ok);
519        discontinuity = JNI_TRUE;
520        // wait for discontinuity request to be observed by buffer queue callback
521        // FIXME sorry, can't rewind after EOS
522        while (discontinuity && !reachedEof) {
523            ok = pthread_cond_wait(&cond, &mutex);
524            assert(0 == ok);
525        }
526        ok = pthread_mutex_unlock(&mutex);
527        assert(0 == ok);
528    }
529
530}
531