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