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