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