blob: c547a9c36948b92ea423c405256f9d9fe4a4a502 [file] [log] [blame]
brians343bc112013-02-10 01:53:46 +00001#include <setjmp.h>
2
3#include "jni/aos_Natives.h"
Brian Silverman14fd0fb2014-01-14 21:42:01 -08004#include "aos/linux_code/camera/Buffers.h"
brians343bc112013-02-10 01:53:46 +00005#include "aos/externals/libjpeg/include/jpeglib.h"
Brian Silvermanf665d692013-02-17 22:11:39 -08006#include "aos/common/logging/logging_impl.h"
Brian Silverman14fd0fb2014-01-14 21:42:01 -08007#include "aos/linux_code/init.h"
brians343bc112013-02-10 01:53:46 +00008
9using aos::camera::Buffers;
10
11namespace {
12
13jclass nativeError, bufferError, outOfMemoryError;
14bool findClass(JNIEnv *env, const char *name, jclass *out) {
15 jclass local = env->FindClass(name);
16 if (out == NULL) return true;
17 *out = static_cast<jclass>(env->NewGlobalRef(local));
18 if (out == NULL) return true;
19 env->DeleteLocalRef(local);
20 return false;
21}
22
23// Checks that the size is correct and retrieves the address.
24// An expected_size of 0 means don't check it.
25// If this function returns NULL, a java exception will already have been
26// thrown.
27void *getBufferAddress(JNIEnv *env, jobject obj, jlong expected_size) {
28 if (obj == NULL) {
29 env->ThrowNew(nativeError, "null buffer");
30 return NULL;
31 }
32 if (expected_size != 0 &&
33 expected_size != env->GetDirectBufferCapacity(obj)) {
34 char *str;
35 if (asprintf(&str, "wrong size. expected %lld but got %lld",
36 expected_size, env->GetDirectBufferCapacity(obj)) < 0) {
37 env->ThrowNew(bufferError, "creating message failed");
38 return NULL;
39 }
40 env->ThrowNew(bufferError, str);
41 free(str);
42 return NULL;
43 }
44 void *const r = env->GetDirectBufferAddress(obj);
45 if (r == NULL) {
46 env->ThrowNew(bufferError, "couldn't get address");
47 }
48 return r;
49}
50
51const int kImagePixels = Buffers::kWidth * Buffers::kHeight;
52
53void jpeg_log_message(jpeg_common_struct *cinfo, log_level level) {
54 char buf[LOG_MESSAGE_LEN];
55 cinfo->err->format_message(cinfo, buf);
56 log_do(level, "libjpeg: %s\n", buf);
57}
58void jpeg_error_exit(jpeg_common_struct *cinfo) __attribute__((noreturn));
59void jpeg_error_exit(jpeg_common_struct *cinfo) {
60 jpeg_log_message(cinfo, ERROR);
61 longjmp(*static_cast<jmp_buf *>(cinfo->client_data), 1);
62}
63void jpeg_emit_message(jpeg_common_struct *cinfo, int msg_level) {
64 if (msg_level < 0) {
65 jpeg_log_message(cinfo, WARNING);
66 longjmp(*static_cast<jmp_buf *>(cinfo->client_data), 2);
67 }
68 // this spews a lot of messages out
69 //jpeg_log_message(cinfo, DEBUG);
70}
71
72// The structure used to hold all of the state for the functions that deal with
73// a Buffers. A pointer to this structure is stored java-side.
74struct BuffersHolder {
75 Buffers buffers;
76 timeval timestamp;
77 BuffersHolder() : buffers() {}
78};
79
80} // namespace
81
82void Java_aos_Natives_nativeInit(JNIEnv *env, jclass, jint width, jint height) {
83 if (findClass(env, "aos/NativeError", &nativeError)) return;
84 if (findClass(env, "aos/NativeBufferError", &bufferError)) return;
85 if (findClass(env, "java/lang/OutOfMemoryError", &outOfMemoryError)) return;
86
87 aos::InitNRT();
88
89 if (width != Buffers::kWidth || height != Buffers::kHeight) {
90 env->ThrowNew(nativeError, "dimensions mismatch");
91 return;
92 }
93
94 LOG(INFO, "nativeInit finished\n");
95}
96
97static_assert(sizeof(jlong) >= sizeof(void *),
98 "can't stick pointers into jlongs");
99
100jboolean Java_aos_Natives_decodeJPEG(JNIEnv *env, jclass, jlongArray stateArray,
101 jobject inobj, jint inLength,
102 jobject outobj) {
103 unsigned char *const in = static_cast<unsigned char *>(
104 getBufferAddress(env, inobj, 0));
105 if (in == NULL) return false;
106 if (env->GetDirectBufferCapacity(inobj) < inLength) {
107 env->ThrowNew(bufferError, "in is too small");
108 return false;
109 }
110 unsigned char *const out = static_cast<unsigned char *>(
111 getBufferAddress(env, outobj, kImagePixels * 3));
112 if (out == NULL) return false;
113
114 jpeg_decompress_struct *volatile cinfo; // volatile because of the setjmp call
115
116 jlong state;
117 env->GetLongArrayRegion(stateArray, 0, 1, &state);
118 if (env->ExceptionCheck()) return false;
119 if (state == 0) {
120 cinfo = static_cast<jpeg_decompress_struct *>(malloc(sizeof(*cinfo)));
121 if (cinfo == NULL) {
122 env->ThrowNew(outOfMemoryError, "malloc for jpeg_decompress_struct");
123 return false;
124 }
125 cinfo->err = jpeg_std_error(static_cast<jpeg_error_mgr *>(
126 malloc(sizeof(*cinfo->err))));
127 cinfo->client_data = malloc(sizeof(jmp_buf));
128 cinfo->err->error_exit = jpeg_error_exit;
129 cinfo->err->emit_message = jpeg_emit_message;
130 // if the error handler sees a failure, it needs to clean up
131 // (jpeg_abort_decompress) and then return the failure
132 // set cinfo->client_data to the jmp_buf
133 jpeg_create_decompress(cinfo);
134 state = reinterpret_cast<intptr_t>(cinfo);
135 env->SetLongArrayRegion(stateArray, 0, 1, &state);
136 if (env->ExceptionCheck()) return false;
137 } else {
138 cinfo = reinterpret_cast<jpeg_decompress_struct *>(state);
139 }
140
141 // set up the jump buffer
142 // this has to happen each time
143 if (setjmp(*static_cast<jmp_buf *>(cinfo->client_data))) {
144 jpeg_abort_decompress(cinfo);
145 return false;
146 }
147
148 jpeg_mem_src(cinfo, in, inLength);
149 jpeg_read_header(cinfo, TRUE);
150 if (cinfo->image_width != static_cast<unsigned int>(Buffers::kWidth) ||
151 cinfo->image_height != static_cast<unsigned int>(Buffers::kHeight)) {
152 LOG(WARNING, "got (%ux%u) image but expected (%dx%d)\n", cinfo->image_width,
153 cinfo->image_height, Buffers::kWidth, Buffers::kHeight);
154 jpeg_abort_decompress(cinfo);
155 return false;
156 }
157 cinfo->out_color_space = JCS_RGB;
158 jpeg_start_decompress(cinfo);
159 if (cinfo->output_components != 3) {
160 LOG(WARNING, "libjpeg wants to return %d color components instead of 3\n",
161 cinfo->out_color_components);
162 jpeg_abort_decompress(cinfo);
163 return false;
164 }
165 if (cinfo->output_width != static_cast<unsigned int>(Buffers::kWidth) ||
166 cinfo->output_height != static_cast<unsigned int>(Buffers::kHeight)) {
167 LOG(WARNING, "libjpeg wants to return a (%ux%u) image but need (%dx%d)\n",
168 cinfo->output_width, cinfo->output_height,
169 Buffers::kWidth, Buffers::kHeight);
170 jpeg_abort_decompress(cinfo);
171 return false;
172 }
173
174 unsigned char *buffers[Buffers::kHeight];
175 for (int i = 0; i < Buffers::kHeight; ++i) {
176 buffers[i] = &out[i * Buffers::kWidth * 3];
177 }
178 while (cinfo->output_scanline < cinfo->output_height) {
179 jpeg_read_scanlines(cinfo, &buffers[cinfo->output_scanline],
180 Buffers::kHeight - cinfo->output_scanline);
181 }
182
183 jpeg_finish_decompress(cinfo);
184 return true;
185}
186
187void Java_aos_Natives_threshold(JNIEnv *env, jclass, jobject inobj,
188 jobject outobj, jshort hoffset, jchar hmin,
189 jchar hmax, jchar smin, jchar smax, jchar vmin,
190 jchar vmax) {
191 const unsigned char *__restrict__ const in = static_cast<unsigned char *>(
192 getBufferAddress(env, inobj, kImagePixels * 3));
193 if (in == NULL) return;
194 char *__restrict__ const out = static_cast<char *>(
195 getBufferAddress(env, outobj, kImagePixels));
196 if (out == NULL) return;
197
198 for (int i = 0; i < kImagePixels; ++i) {
199 const uint8_t h = in[i * 3] + static_cast<uint8_t>(hoffset);
200 out[i] = h > hmin && h < hmax &&
201 in[i * 3 + 1] > smin && in[i * 3 + 1] < smax &&
202 in[i * 3 + 2] > vmin && in[i * 3 + 2] < vmax;
203 }
204}
205void Java_aos_Natives_convertBGR2BMP(JNIEnv *env, jclass,
206 jobject inobj, jobject outobj) {
207 const char *__restrict__ const in = static_cast<char *>(
208 getBufferAddress(env, inobj, kImagePixels * 3));
209 if (in == NULL) return;
210 char *__restrict__ const out = static_cast<char *>(
211 getBufferAddress(env, outobj, kImagePixels * 3));
212 if (out == NULL) return;
213
214 for (int i = 0; i < kImagePixels; ++i) {
215 out[i * 3 + 0] = in[i * 3 + 2];
216 out[i * 3 + 1] = in[i * 3 + 1];
217 out[i * 3 + 2] = in[i * 3 + 0];
218 }
219}
220
221jlong Java_aos_Natives_queueInit(JNIEnv *, jclass) {
222 return reinterpret_cast<intptr_t>(new BuffersHolder());
223}
224void Java_aos_Natives_queueReleaseJPEG(JNIEnv *, jclass, jlong ptr) {
225 reinterpret_cast<BuffersHolder *>(ptr)->buffers.Release();
226}
227jobject Java_aos_Natives_queueGetJPEG(JNIEnv *env, jclass, jlong ptr) {
228 uint32_t size;
229 BuffersHolder *const holder = reinterpret_cast<BuffersHolder *>(ptr);
230 const void *const r = holder->buffers.GetNext(true, &size,
231 &holder->timestamp, NULL);
232 if (r == NULL) return NULL;
233 return env->NewDirectByteBuffer(const_cast<void *>(r), size);
234}
235jdouble Java_aos_Natives_queueGetTimestamp(JNIEnv *, jclass, jlong ptr) {
236 const BuffersHolder *const holder = reinterpret_cast<BuffersHolder *>(ptr);
237 return holder->timestamp.tv_sec + holder->timestamp.tv_usec / 1000000.0;
238}
239
240void Java_aos_Natives_LOG(JNIEnv *env, jclass, jstring message, jint jlevel) {
241 log_level level;
242 if (jlevel >= 1000) {
243 // Don't want to use FATAL because the uncaught java exception that is
244 // likely to come next will be useful.
245 level = ERROR;
246 } else if (jlevel >= 900) {
247 level = WARNING;
248 } else if (jlevel >= 800) {
249 level = INFO;
250 } else {
251 level = DEBUG;
252 }
Brian Silvermanf665d692013-02-17 22:11:39 -0800253 // Can't use Get/ReleaseStringCritical because log_do might block waiting to
254 // put its message into the queue.
brians343bc112013-02-10 01:53:46 +0000255 const char *const message_chars = env->GetStringUTFChars(message, NULL);
256 if (message_chars == NULL) return;
257 log_do(level, "%s\n", message_chars);
258 env->ReleaseStringUTFChars(message, message_chars);
259}