1 /*
2  * Copyright (C) 2012 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 /* Original code copied from NDK Native-media sample code */
18 
19 #undef NDEBUG
20 #include <assert.h>
21 #include <jni.h>
22 #include <pthread.h>
23 #include <stdio.h>
24 #include <string.h>
25 
26 // for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
27 #include <android/log.h>
28 #define TAG "NativeMedia"
29 #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
30 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
31 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
32 // for native media
33 #include <OMXAL/OpenMAXAL.h>
34 #include <OMXAL/OpenMAXAL_Android.h>
35 
36 // for native window JNI
37 #include <android/native_window_jni.h>
38 
39 // engine interfaces
40 static XAObjectItf engineObject = NULL;
41 static XAEngineItf engineEngine = NULL;
42 
43 // output mix interfaces
44 static XAObjectItf outputMixObject = NULL;
45 
46 // streaming media player interfaces
47 static XAObjectItf             playerObj = NULL;
48 static XAPlayItf               playerPlayItf = NULL;
49 static XAAndroidBufferQueueItf playerBQItf = NULL;
50 static XAStreamInformationItf  playerStreamInfoItf = NULL;
51 static XAVolumeItf             playerVolItf = NULL;
52 
53 // number of required interfaces for the MediaPlayer creation
54 #define NB_MAXAL_INTERFACES 3 // XAAndroidBufferQueueItf, XAStreamInformationItf and XAPlayItf
55 
56 // video sink for the player
57 static ANativeWindow* theNativeWindow = NULL;
58 
59 /**
60  * Macro to clean-up already created stuffs and return JNI_FALSE when cond is not true
61  */
62 #define RETURN_ON_ASSERTION_FAILURE(cond, env, clazz)                                   \
63         if (!(cond)) {                                                                  \
64             ALOGE("assertion failure at file %s line %d", __FILE__, __LINE__);           \
65             Java_android_mediastress_cts_NativeMediaActivity_shutdown((env), (clazz));  \
66             return JNI_FALSE;                                                           \
67         }
68 
69 // number of buffers in our buffer queue, an arbitrary number
70 #define NB_BUFFERS 16
71 
72 // we're streaming MPEG-2 transport stream data, operate on transport stream block size
73 #define MPEG2_TS_BLOCK_SIZE 188
74 
75 // number of MPEG-2 transport stream blocks per buffer, an arbitrary number
76 #define BLOCKS_PER_BUFFER 20
77 
78 // determines how much memory we're dedicating to memory caching
79 #define BUFFER_SIZE (BLOCKS_PER_BUFFER*MPEG2_TS_BLOCK_SIZE)
80 
81 // where we cache in memory the data to play
82 // note this memory is re-used by the buffer queue callback
83 char dataCache[BUFFER_SIZE * NB_BUFFERS];
84 
85 // handle of the file to play
86 static FILE *file = NULL;
87 
88 // has the app reached the end of the file
89 static jboolean reachedEof = JNI_FALSE;
90 
91 // constant to identify a buffer context which is the end of the stream to decode
92 static const int kEosBufferCntxt = 1980; // a magic value we can compare against
93 
94 // for mutual exclusion between callback thread and application thread(s)
95 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
96 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
97 
enqueueInitialBuffers()98 static jboolean enqueueInitialBuffers() {
99 
100     /* Fill our cache */
101     size_t nbRead;
102     nbRead = fread(dataCache, BUFFER_SIZE, NB_BUFFERS, file);
103     if (nbRead <= 0) {
104         // could be premature EOF or I/O error
105         ALOGE("Error filling cache, exiting\n");
106         return JNI_FALSE;
107     }
108     assert(1 <= nbRead && nbRead <= NB_BUFFERS);
109     ALOGV("Initially queueing %u buffers of %u bytes each", nbRead, BUFFER_SIZE);
110 
111     /* Enqueue the content of our cache before starting to play,
112        we don't want to starve the player */
113     size_t i;
114     for (i = 0; i < nbRead; i++) {
115         XAresult res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/,
116                     dataCache + i*BUFFER_SIZE, BUFFER_SIZE, NULL, 0);
117         assert(XA_RESULT_SUCCESS == res);
118     }
119 
120     return JNI_TRUE;
121 }
122 // AndroidBufferQueueItf callback for an audio player
AndroidBufferQueueCallback(XAAndroidBufferQueueItf caller,void * pCallbackContext,void * pBufferContext,void * pBufferData,XAuint32 dataSize,XAuint32 dataUsed,const XAAndroidBufferItem * pItems,XAuint32 itemsLength)123 extern "C" XAresult AndroidBufferQueueCallback(
124         XAAndroidBufferQueueItf caller,
125         void *pCallbackContext,        /* input */
126         void *pBufferContext,          /* input */
127         void *pBufferData,             /* input */
128         XAuint32 dataSize,             /* input */
129         XAuint32 dataUsed,             /* input */
130         const XAAndroidBufferItem *pItems,/* input */
131         XAuint32 itemsLength           /* input */)
132 {
133     XAresult res;
134     int ok;
135 
136     // pCallbackContext was specified as NULL at RegisterCallback and is unused here
137     assert(NULL == pCallbackContext);
138 
139     // note there is never any contention on this mutex unless a discontinuity request is active
140     ok = pthread_mutex_lock(&mutex);
141     assert(0 == ok);
142 
143     if ((pBufferData == NULL) && (pBufferContext != NULL)) {
144         const int processedCommand = *(int *)pBufferContext;
145         if (kEosBufferCntxt == processedCommand) {
146             ALOGV("EOS was processed\n");
147             // our buffer with the EOS message has been consumed
148             assert(0 == dataSize);
149             goto exit;
150         }
151     }
152 
153     // pBufferData is a pointer to a buffer that we previously Enqueued
154     assert(BUFFER_SIZE == dataSize);
155     assert(dataCache <= (char *) pBufferData && (char *) pBufferData <
156             &dataCache[BUFFER_SIZE * NB_BUFFERS]);
157     assert(0 == (((char *) pBufferData - dataCache) % BUFFER_SIZE));
158 
159     // don't bother trying to read more data once we've hit EOF
160     if (reachedEof) {
161         goto exit;
162     }
163 
164     size_t nbRead;
165     // note we do call fread from multiple threads, but never concurrently
166     nbRead = fread(pBufferData, BUFFER_SIZE, 1, file);
167     if (nbRead > 0) {
168         assert(1 == nbRead);
169         res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/,
170                 pBufferData /*pData*/,
171                 nbRead * BUFFER_SIZE /*dataLength*/,
172                 NULL /*pMsg*/,
173                 0 /*msgLength*/);
174         assert(XA_RESULT_SUCCESS == res);
175     } else {
176         // signal EOS
177         XAAndroidBufferItem msgEos[1];
178         msgEos[0].itemKey = XA_ANDROID_ITEMKEY_EOS;
179         msgEos[0].itemSize = 0;
180         // EOS message has no parameters, so the total size of the message is the size of the key
181         //   plus the size if itemSize, both XAuint32
182         res = (*caller)->Enqueue(caller, (void *)&kEosBufferCntxt /*pBufferContext*/,
183                 NULL /*pData*/, 0 /*dataLength*/,
184                 msgEos /*pMsg*/,
185                 // FIXME == sizeof(BufferItem)? */
186                 sizeof(XAuint32)*2 /*msgLength*/);
187         assert(XA_RESULT_SUCCESS == res);
188         reachedEof = JNI_TRUE;
189     }
190 
191 exit:
192     ok = pthread_mutex_unlock(&mutex);
193     assert(0 == ok);
194     return XA_RESULT_SUCCESS;
195 }
196 
197 // callback invoked whenever there is new or changed stream information
StreamChangeCallback(XAStreamInformationItf caller,XAuint32 eventId,XAuint32 streamIndex,void * pEventData,void * pContext)198 static void StreamChangeCallback(XAStreamInformationItf caller,
199         XAuint32 eventId,
200         XAuint32 streamIndex,
201         void * pEventData,
202         void * pContext )
203 {
204     ALOGV("StreamChangeCallback called for stream %u", streamIndex);
205     // pContext was specified as NULL at RegisterStreamChangeCallback and is unused here
206     assert(NULL == pContext);
207     switch (eventId) {
208       case XA_STREAMCBEVENT_PROPERTYCHANGE: {
209         /** From spec 1.0.1:
210             "This event indicates that stream property change has occurred.
211             The streamIndex parameter identifies the stream with the property change.
212             The pEventData parameter for this event is not used and shall be ignored."
213          */
214         XAresult res;
215         XAuint32 domain;
216         res = (*caller)->QueryStreamType(caller, streamIndex, &domain);
217         assert(XA_RESULT_SUCCESS == res);
218         switch (domain) {
219           case XA_DOMAINTYPE_VIDEO: {
220             XAVideoStreamInformation videoInfo;
221             res = (*caller)->QueryStreamInformation(caller, streamIndex, &videoInfo);
222             assert(XA_RESULT_SUCCESS == res);
223             ALOGV("Found video size %u x %u, codec ID=%u, frameRate=%u, bitRate=%u, duration=%u ms",
224                         videoInfo.width, videoInfo.height, videoInfo.codecId, videoInfo.frameRate,
225                         videoInfo.bitRate, videoInfo.duration);
226           } break;
227           default:
228               ALOGE("Unexpected domain %u\n", domain);
229             break;
230         }
231       } break;
232       default:
233           ALOGE("Unexpected stream event ID %u\n", eventId);
234           break;
235     }
236 }
237 
238 // shut down the native media system
239 // force C naming convention for JNI interfaces to avoid registering manually
Java_android_mediastress_cts_NativeMediaActivity_shutdown(JNIEnv * env,jclass clazz)240 extern "C" void Java_android_mediastress_cts_NativeMediaActivity_shutdown(JNIEnv* env,
241         jclass clazz)
242 {
243     // destroy streaming media player object, and invalidate all associated interfaces
244     if (playerObj != NULL) {
245         if (playerPlayItf != NULL) {
246             (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_STOPPED);
247         }
248         (*playerObj)->Destroy(playerObj);
249         playerObj = NULL;
250         playerPlayItf = NULL;
251         playerBQItf = NULL;
252         playerStreamInfoItf = NULL;
253         playerVolItf = NULL;
254     }
255 
256     // destroy output mix object, and invalidate all associated interfaces
257     if (outputMixObject != NULL) {
258         (*outputMixObject)->Destroy(outputMixObject);
259         outputMixObject = NULL;
260     }
261 
262     // destroy engine object, and invalidate all associated interfaces
263     if (engineObject != NULL) {
264         (*engineObject)->Destroy(engineObject);
265         engineObject = NULL;
266         engineEngine = NULL;
267     }
268 
269     // make sure we don't leak native windows
270     if (theNativeWindow != NULL) {
271         ANativeWindow_release(theNativeWindow);
272         theNativeWindow = NULL;
273     }
274 
275     // close the file
276     if (file != NULL) {
277         fclose(file);
278         file = NULL;
279     }
280 
281 }
282 
283 // create the engine and output mix objects
Java_android_mediastress_cts_NativeMediaActivity_createEngine(JNIEnv * env,jclass clazz)284 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_createEngine(JNIEnv* env,
285         jclass clazz)
286 {
287     XAresult res;
288 
289     // create engine
290     res = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
291     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
292 
293     // realize the engine
294     res = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE);
295     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
296 
297     // get the engine interface, which is needed in order to create other objects
298     res = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine);
299     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
300 
301     // create output mix
302     res = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
303     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
304 
305     // realize the output mix
306     res = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE);
307     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
308 
309     return JNI_TRUE;
310 }
311 
312 
313 // create streaming media player
Java_android_mediastress_cts_NativeMediaActivity_createMediaPlayer(JNIEnv * env,jclass clazz,jstring fileUri)314 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_createMediaPlayer(JNIEnv* env,
315         jclass clazz, jstring fileUri)
316 {
317     XAresult res;
318 
319     // convert Java string to UTF-8
320     const char *utf8 = env->GetStringUTFChars(fileUri, NULL);
321     RETURN_ON_ASSERTION_FAILURE((NULL != utf8), env, clazz);
322 
323     RETURN_ON_ASSERTION_FAILURE(strstr(utf8, "file:///") == utf8, env, clazz);
324 
325     file = fopen(utf8 + 7, "rb");
326     RETURN_ON_ASSERTION_FAILURE(file != NULL, env, clazz);
327 
328 
329     // configure data source
330     XADataLocator_AndroidBufferQueue loc_abq = { XA_DATALOCATOR_ANDROIDBUFFERQUEUE, NB_BUFFERS };
331     XADataFormat_MIME format_mime = {
332             XA_DATAFORMAT_MIME, XA_ANDROID_MIME_MP2TS, XA_CONTAINERTYPE_MPEG_TS };
333     XADataSource dataSrc = {&loc_abq, &format_mime};
334 
335     // configure audio sink
336     XADataLocator_OutputMix loc_outmix = { XA_DATALOCATOR_OUTPUTMIX, outputMixObject };
337     XADataSink audioSnk = { &loc_outmix, NULL };
338 
339     // configure image video sink
340     XADataLocator_NativeDisplay loc_nd = {
341             XA_DATALOCATOR_NATIVEDISPLAY,        // locatorType
342             // the video sink must be an ANativeWindow created from a Surface or SurfaceTexture
343             (void*)theNativeWindow,              // hWindow
344             // must be NULL
345             NULL                                 // hDisplay
346     };
347     XADataSink imageVideoSink = {&loc_nd, NULL};
348 
349     // declare interfaces to use
350     XAboolean     required[NB_MAXAL_INTERFACES]
351                            = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE};
352     XAInterfaceID iidArray[NB_MAXAL_INTERFACES]
353                            = {XA_IID_PLAY,
354                               XA_IID_ANDROIDBUFFERQUEUESOURCE,
355                               XA_IID_STREAMINFORMATION};
356 
357     // create media player
358     res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc,
359             NULL, &audioSnk, &imageVideoSink, NULL, NULL,
360             NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/,
361             iidArray /*const XAInterfaceID *pInterfaceIds*/,
362             required /*const XAboolean *pInterfaceRequired*/);
363     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
364 
365     // release the Java string and UTF-8
366     env->ReleaseStringUTFChars(fileUri, utf8);
367 
368     // realize the player
369     res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE);
370     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
371 
372     // get the play interface
373     res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf);
374     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
375 
376     // get the stream information interface (for video size)
377     res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf);
378     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
379 
380     // get the volume interface
381     res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf);
382     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
383 
384     // get the Android buffer queue interface
385     res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf);
386     assert(XA_RESULT_SUCCESS == res);
387 
388     // specify which events we want to be notified of
389     res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED);
390 
391     // register the callback from which OpenMAX AL can retrieve the data to play
392     res = (*playerBQItf)->RegisterCallback(playerBQItf, AndroidBufferQueueCallback, NULL);
393     assert(XA_RESULT_SUCCESS == res);
394 
395     // enqueue the initial buffers
396     if (!enqueueInitialBuffers()) {
397         return JNI_FALSE;
398     }
399 
400     // we want to be notified of the video size once it's found, so we register a callback for that
401     res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf,
402             StreamChangeCallback, NULL);
403     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
404 
405     // prepare the player
406     res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED);
407     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
408 
409     // set the volume
410     res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0);
411     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
412 
413     // start the playback
414     res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PLAYING);
415     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
416 
417     return JNI_TRUE;
418 }
419 
420 
421 // set the playing state for the streaming media player
Java_android_mediastress_cts_NativeMediaActivity_playOrPauseMediaPlayer(JNIEnv * env,jclass clazz,jboolean play)422 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_playOrPauseMediaPlayer(
423         JNIEnv* env, jclass clazz, jboolean play)
424 {
425     XAresult res;
426 
427     // make sure the streaming media player was created
428     RETURN_ON_ASSERTION_FAILURE((NULL != playerPlayItf), env, clazz);
429     // set the player's state
430     res = (*playerPlayItf)->SetPlayState(playerPlayItf, play ?
431         XA_PLAYSTATE_PLAYING : XA_PLAYSTATE_PAUSED);
432     RETURN_ON_ASSERTION_FAILURE((XA_RESULT_SUCCESS == res), env, clazz);
433     return JNI_TRUE;
434 }
435 
436 // set the surface
Java_android_mediastress_cts_NativeMediaActivity_setSurface(JNIEnv * env,jclass clazz,jobject surface)437 extern "C" jboolean Java_android_mediastress_cts_NativeMediaActivity_setSurface(JNIEnv *env,
438         jclass clazz, jobject surface)
439 {
440     // obtain a native window from a Java surface
441     theNativeWindow = ANativeWindow_fromSurface(env, surface);
442     return JNI_TRUE;
443 }
444 
445 
446