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
31 static XAObjectItf engineObject = NULL;
32 static XAEngineItf engineEngine = NULL;
33 
34 // output mix interfaces
35 static XAObjectItf outputMixObject = NULL;
36 
37 // streaming media player interfaces
38 static XAObjectItf             playerObj = NULL;
39 static XAPlayItf               playerPlayItf = NULL;
40 static XAAndroidBufferQueueItf playerBQItf = NULL;
41 static XAStreamInformationItf  playerStreamInfoItf = NULL;
42 static 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
48 static 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
64 char dataCache[BUFFER_SIZE * NB_BUFFERS];
65 
66 // handle of the file to play
67 FILE *file;
68 
69 // has the app reached the end of the file
70 jboolean reachedEof = JNI_FALSE;
71 
72 // constant to identify a buffer context which is the end of the stream to decode
73 static const int kEosBufferCntxt = 1980; // a magic value we can compare against
74 
75 // for mutual exclusion between callback thread and application thread(s)
76 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
77 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
78 
79 // whether a discontinuity is in progress
80 jboolean discontinuity = JNI_FALSE;
81 
82 static jboolean enqueueInitialBuffers(jboolean discontinuity);
83 
84 // Callback for XAPlayItf through which we receive the XA_PLAYEVENT_HEADATEND event */
PlayCallback(XAPlayItf caller,void * pContext,XAuint32 event)85 void 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
AndroidBufferQueueCallback(XAAndroidBufferQueueItf caller,void * pCallbackContext,void * pBufferContext,void * pBufferData,XAuint32 dataSize,XAuint32 dataUsed,const XAAndroidBufferItem * pItems,XAuint32 itemsLength)92 XAresult 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 
218 exit:
219     ok = pthread_mutex_unlock(&mutex);
220     assert(0 == ok);
221     return XA_RESULT_SUCCESS;
222 }
223 
224 
StreamChangeCallback(XAStreamInformationItf caller,XAuint32 eventId,XAuint32 streamIndex,void * pEventData,void * pContext)225 void 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
Java_com_example_nativemedia_NativeMedia_createEngine(JNIEnv * env,jclass clazz)268 void 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
enqueueInitialBuffers(jboolean discontinuity)296 static 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 %zu 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
Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer(JNIEnv * env,jclass clazz,jstring filename)340 jboolean 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 SurfaceTextureClient
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
Java_com_example_nativemedia_NativeMedia_setPlayingStreamingMediaPlayer(JNIEnv * env,jclass clazz,jboolean isPlaying)455 void 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
Java_com_example_nativemedia_NativeMedia_shutdown(JNIEnv * env,jclass clazz)474 void 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
Java_com_example_nativemedia_NativeMedia_setSurface(JNIEnv * env,jclass clazz,jobject surface)514 void 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
Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv * env,jclass clazz)522 void Java_com_example_nativemedia_NativeMedia_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
523 {
524     // make sure the streaming media player was created
525     if (NULL != playerBQItf && NULL != file) {
526         // first wait for buffers currently in queue to be drained
527         int ok;
528         ok = pthread_mutex_lock(&mutex);
529         assert(0 == ok);
530         discontinuity = JNI_TRUE;
531         // wait for discontinuity request to be observed by buffer queue callback
532         // FIXME sorry, can't rewind after EOS
533         while (discontinuity && !reachedEof) {
534             ok = pthread_cond_wait(&cond, &mutex);
535             assert(0 == ok);
536         }
537         ok = pthread_mutex_unlock(&mutex);
538         assert(0 == ok);
539     }
540 
541 }
542