brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 1 | #include <setjmp.h> |
| 2 | |
| 3 | #include "jni/aos_Natives.h" |
Brian Silverman | 14fd0fb | 2014-01-14 21:42:01 -0800 | [diff] [blame^] | 4 | #include "aos/linux_code/camera/Buffers.h" |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 5 | #include "aos/externals/libjpeg/include/jpeglib.h" |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 6 | #include "aos/common/logging/logging_impl.h" |
Brian Silverman | 14fd0fb | 2014-01-14 21:42:01 -0800 | [diff] [blame^] | 7 | #include "aos/linux_code/init.h" |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 8 | |
| 9 | using aos::camera::Buffers; |
| 10 | |
| 11 | namespace { |
| 12 | |
| 13 | jclass nativeError, bufferError, outOfMemoryError; |
| 14 | bool 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. |
| 27 | void *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 | |
| 51 | const int kImagePixels = Buffers::kWidth * Buffers::kHeight; |
| 52 | |
| 53 | void 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 | } |
| 58 | void jpeg_error_exit(jpeg_common_struct *cinfo) __attribute__((noreturn)); |
| 59 | void jpeg_error_exit(jpeg_common_struct *cinfo) { |
| 60 | jpeg_log_message(cinfo, ERROR); |
| 61 | longjmp(*static_cast<jmp_buf *>(cinfo->client_data), 1); |
| 62 | } |
| 63 | void 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. |
| 74 | struct BuffersHolder { |
| 75 | Buffers buffers; |
| 76 | timeval timestamp; |
| 77 | BuffersHolder() : buffers() {} |
| 78 | }; |
| 79 | |
| 80 | } // namespace |
| 81 | |
| 82 | void 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 | |
| 97 | static_assert(sizeof(jlong) >= sizeof(void *), |
| 98 | "can't stick pointers into jlongs"); |
| 99 | |
| 100 | jboolean 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 | |
| 187 | void 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 | } |
| 205 | void 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 | |
| 221 | jlong Java_aos_Natives_queueInit(JNIEnv *, jclass) { |
| 222 | return reinterpret_cast<intptr_t>(new BuffersHolder()); |
| 223 | } |
| 224 | void Java_aos_Natives_queueReleaseJPEG(JNIEnv *, jclass, jlong ptr) { |
| 225 | reinterpret_cast<BuffersHolder *>(ptr)->buffers.Release(); |
| 226 | } |
| 227 | jobject 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 | } |
| 235 | jdouble 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 | |
| 240 | void 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 Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 253 | // Can't use Get/ReleaseStringCritical because log_do might block waiting to |
| 254 | // put its message into the queue. |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 255 | 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 | } |