1 /*
2  * Copyright (C) 2012 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "EmulatedCamera2_JpegCompressor"
19 
20 #include <utils/Log.h>
21 #include <ui/GraphicBufferMapper.h>
22 
23 #include "JpegCompressor.h"
24 #include "../EmulatedFakeCamera2.h"
25 #include "../EmulatedFakeCamera3.h"
26 
27 namespace android {
28 
JpegCompressor()29 JpegCompressor::JpegCompressor():
30         Thread(false),
31         mIsBusy(false),
32         mSynchronous(false),
33         mBuffers(NULL),
34         mListener(NULL) {
35 }
36 
~JpegCompressor()37 JpegCompressor::~JpegCompressor() {
38     Mutex::Autolock lock(mMutex);
39 }
40 
start(Buffers * buffers,JpegListener * listener)41 status_t JpegCompressor::start(Buffers *buffers, JpegListener *listener) {
42     if (listener == NULL) {
43         ALOGE("%s: NULL listener not allowed!", __FUNCTION__);
44         return BAD_VALUE;
45     }
46     Mutex::Autolock lock(mMutex);
47     {
48         Mutex::Autolock busyLock(mBusyMutex);
49 
50         if (mIsBusy) {
51             ALOGE("%s: Already processing a buffer!", __FUNCTION__);
52             return INVALID_OPERATION;
53         }
54 
55         mIsBusy = true;
56         mSynchronous = false;
57         mBuffers = buffers;
58         mListener = listener;
59     }
60 
61     status_t res;
62     res = run("EmulatedFakeCamera2::JpegCompressor");
63     if (res != OK) {
64         ALOGE("%s: Unable to start up compression thread: %s (%d)",
65                 __FUNCTION__, strerror(-res), res);
66         delete mBuffers;
67     }
68     return res;
69 }
70 
compressSynchronous(Buffers * buffers)71 status_t JpegCompressor::compressSynchronous(Buffers *buffers) {
72     status_t res;
73 
74     Mutex::Autolock lock(mMutex);
75     {
76         Mutex::Autolock busyLock(mBusyMutex);
77 
78         if (mIsBusy) {
79             ALOGE("%s: Already processing a buffer!", __FUNCTION__);
80             return INVALID_OPERATION;
81         }
82 
83         mIsBusy = true;
84         mSynchronous = true;
85         mBuffers = buffers;
86     }
87 
88     res = compress();
89 
90     cleanUp();
91 
92     return res;
93 }
94 
cancel()95 status_t JpegCompressor::cancel() {
96     requestExitAndWait();
97     return OK;
98 }
99 
readyToRun()100 status_t JpegCompressor::readyToRun() {
101     return OK;
102 }
103 
threadLoop()104 bool JpegCompressor::threadLoop() {
105     status_t res;
106     ALOGV("%s: Starting compression thread", __FUNCTION__);
107 
108     res = compress();
109 
110     mListener->onJpegDone(mJpegBuffer, res == OK);
111 
112     cleanUp();
113 
114     return false;
115 }
116 
compress()117 status_t JpegCompressor::compress() {
118     // Find source and target buffers. Assumes only one buffer matches
119     // each condition!
120 
121     bool foundJpeg = false, mFoundAux = false;
122     for (size_t i = 0; i < mBuffers->size(); i++) {
123         const StreamBuffer &b = (*mBuffers)[i];
124         if (b.format == HAL_PIXEL_FORMAT_BLOB) {
125             mJpegBuffer = b;
126             mFoundJpeg = true;
127         } else if (b.streamId <= 0) {
128             mAuxBuffer = b;
129             mFoundAux = true;
130         }
131         if (mFoundJpeg && mFoundAux) break;
132     }
133     if (!mFoundJpeg || !mFoundAux) {
134         ALOGE("%s: Unable to find buffers for JPEG source/destination",
135                 __FUNCTION__);
136         return BAD_VALUE;
137     }
138 
139     // Set up error management
140 
141     mJpegErrorInfo = NULL;
142     JpegError error;
143     error.parent = this;
144 
145     mCInfo.err = jpeg_std_error(&error);
146     mCInfo.err->error_exit = jpegErrorHandler;
147 
148     jpeg_create_compress(&mCInfo);
149     if (checkError("Error initializing compression")) return NO_INIT;
150 
151     // Route compressed data straight to output stream buffer
152 
153     JpegDestination jpegDestMgr;
154     jpegDestMgr.parent = this;
155     jpegDestMgr.init_destination = jpegInitDestination;
156     jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
157     jpegDestMgr.term_destination = jpegTermDestination;
158 
159     mCInfo.dest = &jpegDestMgr;
160 
161     // Set up compression parameters
162 
163     mCInfo.image_width = mAuxBuffer.width;
164     mCInfo.image_height = mAuxBuffer.height;
165     mCInfo.input_components = 3;
166     mCInfo.in_color_space = JCS_RGB;
167 
168     jpeg_set_defaults(&mCInfo);
169     if (checkError("Error configuring defaults")) return NO_INIT;
170 
171     // Do compression
172 
173     jpeg_start_compress(&mCInfo, TRUE);
174     if (checkError("Error starting compression")) return NO_INIT;
175 
176     size_t rowStride = mAuxBuffer.stride * 3;
177     const size_t kChunkSize = 32;
178     while (mCInfo.next_scanline < mCInfo.image_height) {
179         JSAMPROW chunk[kChunkSize];
180         for (size_t i = 0 ; i < kChunkSize; i++) {
181             chunk[i] = (JSAMPROW)
182                     (mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
183         }
184         jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
185         if (checkError("Error while compressing")) return NO_INIT;
186         if (exitPending()) {
187             ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
188             return TIMED_OUT;
189         }
190     }
191 
192     jpeg_finish_compress(&mCInfo);
193     if (checkError("Error while finishing compression")) return NO_INIT;
194 
195     // All done
196 
197     return OK;
198 }
199 
isBusy()200 bool JpegCompressor::isBusy() {
201     Mutex::Autolock busyLock(mBusyMutex);
202     return mIsBusy;
203 }
204 
isStreamInUse(uint32_t id)205 bool JpegCompressor::isStreamInUse(uint32_t id) {
206     Mutex::Autolock lock(mBusyMutex);
207 
208     if (mBuffers && mIsBusy) {
209         for (size_t i = 0; i < mBuffers->size(); i++) {
210             if ( (*mBuffers)[i].streamId == (int)id ) return true;
211         }
212     }
213     return false;
214 }
215 
waitForDone(nsecs_t timeout)216 bool JpegCompressor::waitForDone(nsecs_t timeout) {
217     Mutex::Autolock lock(mBusyMutex);
218     status_t res = OK;
219     if (mIsBusy) {
220         res = mDone.waitRelative(mBusyMutex, timeout);
221     }
222     return (res == OK);
223 }
224 
checkError(const char * msg)225 bool JpegCompressor::checkError(const char *msg) {
226     if (mJpegErrorInfo) {
227         char errBuffer[JMSG_LENGTH_MAX];
228         mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
229         ALOGE("%s: %s: %s",
230                 __FUNCTION__, msg, errBuffer);
231         mJpegErrorInfo = NULL;
232         return true;
233     }
234     return false;
235 }
236 
cleanUp()237 void JpegCompressor::cleanUp() {
238     status_t res;
239     jpeg_destroy_compress(&mCInfo);
240     Mutex::Autolock lock(mBusyMutex);
241 
242     if (mFoundAux) {
243         if (mAuxBuffer.streamId == 0) {
244             delete[] mAuxBuffer.img;
245         } else if (!mSynchronous) {
246             mListener->onJpegInputDone(mAuxBuffer);
247         }
248     }
249     if (!mSynchronous) {
250         delete mBuffers;
251     }
252 
253     mBuffers = NULL;
254 
255     mIsBusy = false;
256     mDone.signal();
257 }
258 
jpegErrorHandler(j_common_ptr cinfo)259 void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
260     JpegError *error = static_cast<JpegError*>(cinfo->err);
261     error->parent->mJpegErrorInfo = cinfo;
262 }
263 
jpegInitDestination(j_compress_ptr cinfo)264 void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
265     JpegDestination *dest= static_cast<JpegDestination*>(cinfo->dest);
266     ALOGV("%s: Setting destination to %p, size %zu",
267             __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize);
268     dest->next_output_byte = (JOCTET*)(dest->parent->mJpegBuffer.img);
269     dest->free_in_buffer = kMaxJpegSize;
270 }
271 
jpegEmptyOutputBuffer(j_compress_ptr cinfo)272 boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr cinfo) {
273     ALOGE("%s: JPEG destination buffer overflow!",
274             __FUNCTION__);
275     return true;
276 }
277 
jpegTermDestination(j_compress_ptr cinfo)278 void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
279     ALOGV("%s: Done writing JPEG data. %zu bytes left in buffer",
280             __FUNCTION__, cinfo->dest->free_in_buffer);
281 }
282 
~JpegListener()283 JpegCompressor::JpegListener::~JpegListener() {
284 }
285 
286 } // namespace android
287