1 /*
2  * Copyright (C) 2014 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 the native AMedia* APIs.
19  * See the corresponding Java source file located at:
20  *
21  *   src/com/example/nativecodec/NativeMedia.java
22  *
23  * In this example we use assert() for "impossible" error conditions,
24  * and explicit handling and recovery for more likely error conditions.
25  */
26 
27 #include <assert.h>
28 #include <jni.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 #include <limits.h>
37 
38 #include "looper.h"
39 #include "media/NdkMediaCodec.h"
40 #include "media/NdkMediaExtractor.h"
41 
42 // for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
43 #include <android/log.h>
44 #define TAG "NativeCodec"
45 #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
46 
47 // for native window JNI
48 #include <android/native_window_jni.h>
49 
50 typedef struct {
51     int fd;
52     ANativeWindow* window;
53     AMediaExtractor* ex;
54     AMediaCodec *codec;
55     int64_t renderstart;
56     bool sawInputEOS;
57     bool sawOutputEOS;
58     bool isPlaying;
59     bool renderonce;
60 } workerdata;
61 
62 workerdata data = {-1, NULL, NULL, NULL, 0, false, false, false, false};
63 
64 enum {
65     kMsgCodecBuffer,
66     kMsgPause,
67     kMsgResume,
68     kMsgPauseAck,
69     kMsgDecodeDone,
70     kMsgSeek,
71 };
72 
73 
74 
75 class mylooper: public looper {
76     virtual void handle(int what, void* obj);
77 };
78 
79 static mylooper *mlooper = NULL;
80 
systemnanotime()81 int64_t systemnanotime() {
82     timespec now;
83     clock_gettime(CLOCK_MONOTONIC, &now);
84     return now.tv_sec * 1000000000LL + now.tv_nsec;
85 }
86 
doCodecWork(workerdata * d)87 void doCodecWork(workerdata *d) {
88 
89     ssize_t bufidx = -1;
90     if (!d->sawInputEOS) {
91         bufidx = AMediaCodec_dequeueInputBuffer(d->codec, 2000);
92         LOGV("input buffer %zd", bufidx);
93         if (bufidx >= 0) {
94             size_t bufsize;
95             uint8_t *buf = AMediaCodec_getInputBuffer(d->codec, bufidx, &bufsize);
96             ssize_t sampleSize = AMediaExtractor_readSampleData(d->ex, buf, bufsize);
97             if (sampleSize < 0) {
98                 sampleSize = 0;
99                 d->sawInputEOS = true;
100                 LOGV("EOS");
101             }
102             int64_t presentationTimeUs = AMediaExtractor_getSampleTime(d->ex);
103 
104             AMediaCodec_queueInputBuffer(d->codec, bufidx, 0, sampleSize, presentationTimeUs,
105                     d->sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
106             AMediaExtractor_advance(d->ex);
107         }
108     }
109 
110     if (!d->sawOutputEOS) {
111         AMediaCodecBufferInfo info;
112         ssize_t status = AMediaCodec_dequeueOutputBuffer(d->codec, &info, 0);
113         if (status >= 0) {
114             if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
115                 LOGV("output EOS");
116                 d->sawOutputEOS = true;
117             }
118             int64_t presentationNano = info.presentationTimeUs * 1000;
119             if (d->renderstart < 0) {
120                 d->renderstart = systemnanotime() - presentationNano;
121             }
122             int64_t delay = (d->renderstart + presentationNano) - systemnanotime();
123             if (delay > 0) {
124                 usleep(delay / 1000);
125             }
126             AMediaCodec_releaseOutputBuffer(d->codec, status, info.size != 0);
127             if (d->renderonce) {
128                 d->renderonce = false;
129                 return;
130             }
131         } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
132             LOGV("output buffers changed");
133         } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
134             AMediaFormat *format = NULL;
135             format = AMediaCodec_getOutputFormat(d->codec);
136             LOGV("format changed to: %s", AMediaFormat_toString(format));
137             AMediaFormat_delete(format);
138         } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
139             LOGV("no output buffer right now");
140         } else {
141             LOGV("unexpected info code: %zd", status);
142         }
143     }
144 
145     if (!d->sawInputEOS || !d->sawOutputEOS) {
146         mlooper->post(kMsgCodecBuffer, d);
147     }
148 }
149 
handle(int what,void * obj)150 void mylooper::handle(int what, void* obj) {
151     switch (what) {
152         case kMsgCodecBuffer:
153             doCodecWork((workerdata*)obj);
154             break;
155 
156         case kMsgDecodeDone:
157         {
158             workerdata *d = (workerdata*)obj;
159             AMediaCodec_stop(d->codec);
160             AMediaCodec_delete(d->codec);
161             AMediaExtractor_delete(d->ex);
162             d->sawInputEOS = true;
163             d->sawOutputEOS = true;
164         }
165         break;
166 
167         case kMsgSeek:
168         {
169             workerdata *d = (workerdata*)obj;
170             AMediaExtractor_seekTo(d->ex, 0, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC);
171             AMediaCodec_flush(d->codec);
172             d->renderstart = -1;
173             d->sawInputEOS = false;
174             d->sawOutputEOS = false;
175             if (!d->isPlaying) {
176                 d->renderonce = true;
177                 post(kMsgCodecBuffer, d);
178             }
179             LOGV("seeked");
180         }
181         break;
182 
183         case kMsgPause:
184         {
185             workerdata *d = (workerdata*)obj;
186             if (d->isPlaying) {
187                 // flush all outstanding codecbuffer messages with a no-op message
188                 d->isPlaying = false;
189                 post(kMsgPauseAck, NULL, true);
190             }
191         }
192         break;
193 
194         case kMsgResume:
195         {
196             workerdata *d = (workerdata*)obj;
197             if (!d->isPlaying) {
198                 d->renderstart = -1;
199                 d->isPlaying = true;
200                 post(kMsgCodecBuffer, d);
201             }
202         }
203         break;
204     }
205 }
206 
207 
208 
209 
210 extern "C" {
211 
Java_com_example_nativecodec_NativeCodec_createStreamingMediaPlayer(JNIEnv * env,jclass clazz,jstring filename)212 jboolean Java_com_example_nativecodec_NativeCodec_createStreamingMediaPlayer(JNIEnv* env,
213         jclass clazz, jstring filename)
214 {
215     LOGV("@@@ create");
216 
217     // convert Java string to UTF-8
218     const char *utf8 = env->GetStringUTFChars(filename, NULL);
219     LOGV("opening %s", utf8);
220     int fd = open(utf8, O_RDONLY);
221     env->ReleaseStringUTFChars(filename, utf8);
222     if (fd < 0) {
223         LOGV("failed: %d (%s)", fd, strerror(errno));
224         return JNI_FALSE;
225     }
226 
227     data.fd = fd;
228 
229     workerdata *d = &data;
230 
231     AMediaExtractor *ex = AMediaExtractor_new();
232     media_status_t err = AMediaExtractor_setDataSourceFd(ex, d->fd, 0 , LONG_MAX);
233     close(d->fd);
234     if (err != AMEDIA_OK) {
235         LOGV("setDataSource error: %d", err);
236         return JNI_FALSE;
237     }
238 
239     int numtracks = AMediaExtractor_getTrackCount(ex);
240 
241     AMediaCodec *codec = NULL;
242 
243     LOGV("input has %d tracks", numtracks);
244     for (int i = 0; i < numtracks; i++) {
245         AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
246         const char *s = AMediaFormat_toString(format);
247         LOGV("track %d format: %s", i, s);
248         const char *mime;
249         if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
250             LOGV("no mime type");
251             return JNI_FALSE;
252         } else if (!strncmp(mime, "video/", 6)) {
253             // Omitting most error handling for clarity.
254             // Production code should check for errors.
255             AMediaExtractor_selectTrack(ex, i);
256             codec = AMediaCodec_createDecoderByType(mime);
257             AMediaCodec_configure(codec, format, d->window, NULL, 0);
258             d->ex = ex;
259             d->codec = codec;
260             d->renderstart = -1;
261             d->sawInputEOS = false;
262             d->sawOutputEOS = false;
263             d->isPlaying = false;
264             d->renderonce = true;
265             AMediaCodec_start(codec);
266         }
267         AMediaFormat_delete(format);
268     }
269 
270     mlooper = new mylooper();
271     mlooper->post(kMsgCodecBuffer, d);
272 
273     return JNI_TRUE;
274 }
275 
276 // set the playing state for the streaming media player
Java_com_example_nativecodec_NativeCodec_setPlayingStreamingMediaPlayer(JNIEnv * env,jclass clazz,jboolean isPlaying)277 void Java_com_example_nativecodec_NativeCodec_setPlayingStreamingMediaPlayer(JNIEnv* env,
278         jclass clazz, jboolean isPlaying)
279 {
280     LOGV("@@@ playpause: %d", isPlaying);
281     if (mlooper) {
282         if (isPlaying) {
283             mlooper->post(kMsgResume, &data);
284         } else {
285             mlooper->post(kMsgPause, &data);
286         }
287     }
288 }
289 
290 
291 // shut down the native media system
Java_com_example_nativecodec_NativeCodec_shutdown(JNIEnv * env,jclass clazz)292 void Java_com_example_nativecodec_NativeCodec_shutdown(JNIEnv* env, jclass clazz)
293 {
294     LOGV("@@@ shutdown");
295     if (mlooper) {
296         mlooper->post(kMsgDecodeDone, &data, true /* flush */);
297         mlooper->quit();
298         delete mlooper;
299         mlooper = NULL;
300     }
301     if (data.window) {
302         ANativeWindow_release(data.window);
303         data.window = NULL;
304     }
305 }
306 
307 
308 // set the surface
Java_com_example_nativecodec_NativeCodec_setSurface(JNIEnv * env,jclass clazz,jobject surface)309 void Java_com_example_nativecodec_NativeCodec_setSurface(JNIEnv *env, jclass clazz, jobject surface)
310 {
311     // obtain a native window from a Java surface
312     if (data.window) {
313         ANativeWindow_release(data.window);
314         data.window = NULL;
315     }
316     data.window = ANativeWindow_fromSurface(env, surface);
317     LOGV("@@@ setsurface %p", data.window);
318 }
319 
320 
321 // rewind the streaming media player
Java_com_example_nativecodec_NativeCodec_rewindStreamingMediaPlayer(JNIEnv * env,jclass clazz)322 void Java_com_example_nativecodec_NativeCodec_rewindStreamingMediaPlayer(JNIEnv *env, jclass clazz)
323 {
324     LOGV("@@@ rewind");
325     mlooper->post(kMsgSeek, &data);
326 }
327 
328 }
329