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