1 /*
2 * Copyright (C) 2020 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_TAG "ExtCamOfflnSsn@3.6"
18 #define ATRACE_TAG ATRACE_TAG_CAMERA
19 #include <android/log.h>
20
21 #include <linux/videodev2.h>
22 #include <sync/sync.h>
23
24 #define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs
25 #include <libyuv.h>
26
27 #include <utils/Trace.h>
28 #include "ExternalCameraOfflineSession.h"
29
30 namespace {
31
32 // Size of request/result metadata fast message queue. Change to 0 to always use hwbinder buffer.
33 static constexpr size_t kMetadataMsgQueueSize = 1 << 18 /* 256kB */;
34
35 } // anonymous namespace
36
37 namespace android {
38 namespace hardware {
39 namespace camera {
40 namespace device {
41 namespace V3_6 {
42 namespace implementation {
43
44 // static instance
45 HandleImporter ExternalCameraOfflineSession::sHandleImporter;
46
47 using V3_5::implementation::ExternalCameraDeviceSession;
48
ExternalCameraOfflineSession(const CroppingType & croppingType,const common::V1_0::helper::CameraMetadata & chars,const std::string & cameraId,const std::string & exifMake,const std::string & exifModel,const uint32_t blobBufferSize,const bool afTrigger,const hidl_vec<Stream> & offlineStreams,std::deque<std::shared_ptr<HalRequest>> & offlineReqs,const std::map<int,CirculatingBuffers> & circulatingBuffers)49 ExternalCameraOfflineSession::ExternalCameraOfflineSession(
50 const CroppingType& croppingType,
51 const common::V1_0::helper::CameraMetadata& chars,
52 const std::string& cameraId,
53 const std::string& exifMake,
54 const std::string& exifModel,
55 const uint32_t blobBufferSize,
56 const bool afTrigger,
57 const hidl_vec<Stream>& offlineStreams,
58 std::deque<std::shared_ptr<HalRequest>>& offlineReqs,
59 const std::map<int, CirculatingBuffers>& circulatingBuffers) :
60 mCroppingType(croppingType), mChars(chars), mCameraId(cameraId),
61 mExifMake(exifMake), mExifModel(exifModel), mBlobBufferSize(blobBufferSize),
62 mAfTrigger(afTrigger), mOfflineStreams(offlineStreams), mOfflineReqs(offlineReqs),
63 mCirculatingBuffers(circulatingBuffers) {}
64
~ExternalCameraOfflineSession()65 ExternalCameraOfflineSession::~ExternalCameraOfflineSession() {
66 close();
67 }
68
initialize()69 bool ExternalCameraOfflineSession::initialize() {
70 mResultMetadataQueue = std::make_shared<ResultMetadataQueue>(
71 kMetadataMsgQueueSize, false /* non blocking */);
72 if (!mResultMetadataQueue->isValid()) {
73 ALOGE("%s: invalid result fmq", __FUNCTION__);
74 return true;
75 }
76 return false;
77 }
78
initOutputThread()79 void ExternalCameraOfflineSession::initOutputThread() {
80 if (mOutputThread != nullptr) {
81 ALOGE("%s: OutputThread already exist!", __FUNCTION__);
82 return;
83 }
84
85 mBufferRequestThread = new ExternalCameraDeviceSession::BufferRequestThread(
86 this, mCallback);
87 mBufferRequestThread->run("ExtCamBufReq", PRIORITY_DISPLAY);
88
89 mOutputThread = new OutputThread(this, mCroppingType, mChars,
90 mBufferRequestThread, mOfflineReqs);
91
92 mOutputThread->setExifMakeModel(mExifMake, mExifModel);
93
94 Size inputSize = { mOfflineReqs[0]->frameIn->mWidth, mOfflineReqs[0]->frameIn->mHeight};
95 Size maxThumbSize = V3_4::implementation::getMaxThumbnailResolution(mChars);
96 mOutputThread->allocateIntermediateBuffers(
97 inputSize, maxThumbSize, mOfflineStreams, mBlobBufferSize);
98
99 mOutputThread->run("ExtCamOfflnOut", PRIORITY_DISPLAY);
100 }
101
threadLoop()102 bool ExternalCameraOfflineSession::OutputThread::threadLoop() {
103 auto parent = mParent.promote();
104 if (parent == nullptr) {
105 ALOGE("%s: session has been disconnected!", __FUNCTION__);
106 return false;
107 }
108
109 if (mOfflineReqs.empty()) {
110 ALOGI("%s: all offline requests are processed. Stopping.", __FUNCTION__);
111 return false;
112 }
113
114 std::shared_ptr<HalRequest> req = mOfflineReqs.front();
115 mOfflineReqs.pop_front();
116
117 auto onDeviceError = [&](auto... args) {
118 ALOGE(args...);
119 parent->notifyError(
120 req->frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE);
121 signalRequestDone();
122 return false;
123 };
124
125 if (req->frameIn->mFourcc != V4L2_PIX_FMT_MJPEG && req->frameIn->mFourcc != V4L2_PIX_FMT_Z16) {
126 return onDeviceError("%s: do not support V4L2 format %c%c%c%c", __FUNCTION__,
127 req->frameIn->mFourcc & 0xFF,
128 (req->frameIn->mFourcc >> 8) & 0xFF,
129 (req->frameIn->mFourcc >> 16) & 0xFF,
130 (req->frameIn->mFourcc >> 24) & 0xFF);
131 }
132
133 int res = requestBufferStart(req->buffers);
134 if (res != 0) {
135 ALOGE("%s: send BufferRequest failed! res %d", __FUNCTION__, res);
136 return onDeviceError("%s: failed to send buffer request!", __FUNCTION__);
137 }
138
139 std::unique_lock<std::mutex> lk(mBufferLock);
140 // Convert input V4L2 frame to YU12 of the same size
141 // TODO: see if we can save some computation by converting to YV12 here
142 uint8_t* inData;
143 size_t inDataSize;
144 if (req->frameIn->getData(&inData, &inDataSize) != 0) {
145 lk.unlock();
146 return onDeviceError("%s: V4L2 buffer map failed", __FUNCTION__);
147 }
148
149 // TODO: in some special case maybe we can decode jpg directly to gralloc output?
150 if (req->frameIn->mFourcc == V4L2_PIX_FMT_MJPEG) {
151 ATRACE_BEGIN("MJPGtoI420");
152 int res = libyuv::MJPGToI420(
153 inData, inDataSize, static_cast<uint8_t*>(mYu12FrameLayout.y), mYu12FrameLayout.yStride,
154 static_cast<uint8_t*>(mYu12FrameLayout.cb), mYu12FrameLayout.cStride,
155 static_cast<uint8_t*>(mYu12FrameLayout.cr), mYu12FrameLayout.cStride,
156 mYu12Frame->mWidth, mYu12Frame->mHeight, mYu12Frame->mWidth, mYu12Frame->mHeight);
157 ATRACE_END();
158
159 if (res != 0) {
160 // For some webcam, the first few V4L2 frames might be malformed...
161 ALOGE("%s: Convert V4L2 frame to YU12 failed! res %d", __FUNCTION__, res);
162 lk.unlock();
163 Status st = parent->processCaptureRequestError(req);
164 if (st != Status::OK) {
165 return onDeviceError("%s: failed to process capture request error!", __FUNCTION__);
166 }
167 signalRequestDone();
168 return true;
169 }
170 }
171
172 ATRACE_BEGIN("Wait for BufferRequest done");
173 res = waitForBufferRequestDone(&req->buffers);
174 ATRACE_END();
175
176 if (res != 0) {
177 ALOGE("%s: wait for BufferRequest done failed! res %d", __FUNCTION__, res);
178 lk.unlock();
179 return onDeviceError("%s: failed to process buffer request error!", __FUNCTION__);
180 }
181
182 ALOGV("%s processing new request", __FUNCTION__);
183 const int kSyncWaitTimeoutMs = 500;
184 for (auto& halBuf : req->buffers) {
185 if (*(halBuf.bufPtr) == nullptr) {
186 ALOGW("%s: buffer for stream %d missing", __FUNCTION__, halBuf.streamId);
187 halBuf.fenceTimeout = true;
188 } else if (halBuf.acquireFence >= 0) {
189 int ret = sync_wait(halBuf.acquireFence, kSyncWaitTimeoutMs);
190 if (ret) {
191 halBuf.fenceTimeout = true;
192 } else {
193 ::close(halBuf.acquireFence);
194 halBuf.acquireFence = -1;
195 }
196 }
197
198 if (halBuf.fenceTimeout) {
199 continue;
200 }
201
202 // Gralloc lockYCbCr the buffer
203 switch (halBuf.format) {
204 case PixelFormat::BLOB: {
205 int ret = createJpegLocked(halBuf, req->setting);
206
207 if(ret != 0) {
208 lk.unlock();
209 return onDeviceError("%s: createJpegLocked failed with %d",
210 __FUNCTION__, ret);
211 }
212 } break;
213 case PixelFormat::Y16: {
214 void* outLayout = sHandleImporter.lock(*(halBuf.bufPtr), halBuf.usage, inDataSize);
215
216 std::memcpy(outLayout, inData, inDataSize);
217
218 int relFence = sHandleImporter.unlock(*(halBuf.bufPtr));
219 if (relFence >= 0) {
220 halBuf.acquireFence = relFence;
221 }
222 } break;
223 case PixelFormat::YCBCR_420_888:
224 case PixelFormat::YV12: {
225 android::Rect outRect{0, 0, static_cast<int32_t>(halBuf.width),
226 static_cast<int32_t>(halBuf.height)};
227 android_ycbcr result =
228 sHandleImporter.lockYCbCr(*(halBuf.bufPtr), halBuf.usage, outRect);
229 ALOGV("%s: outLayout y %p cb %p cr %p y_str %zu c_str %zu c_step %zu", __FUNCTION__,
230 result.y, result.cb, result.cr, result.ystride, result.cstride,
231 result.chroma_step);
232 if (result.ystride > UINT32_MAX || result.cstride > UINT32_MAX ||
233 result.chroma_step > UINT32_MAX) {
234 return onDeviceError("%s: lockYCbCr failed. Unexpected values!", __FUNCTION__);
235 }
236 YCbCrLayout outLayout = {.y = result.y,
237 .cb = result.cb,
238 .cr = result.cr,
239 .yStride = static_cast<uint32_t>(result.ystride),
240 .cStride = static_cast<uint32_t>(result.cstride),
241 .chromaStep = static_cast<uint32_t>(result.chroma_step)};
242
243 // Convert to output buffer size/format
244 uint32_t outputFourcc = V3_4::implementation::getFourCcFromLayout(outLayout);
245 ALOGV("%s: converting to format %c%c%c%c", __FUNCTION__,
246 outputFourcc & 0xFF,
247 (outputFourcc >> 8) & 0xFF,
248 (outputFourcc >> 16) & 0xFF,
249 (outputFourcc >> 24) & 0xFF);
250
251 YCbCrLayout cropAndScaled;
252 ATRACE_BEGIN("cropAndScaleLocked");
253 int ret = cropAndScaleLocked(
254 mYu12Frame,
255 Size { halBuf.width, halBuf.height },
256 &cropAndScaled);
257 ATRACE_END();
258 if (ret != 0) {
259 lk.unlock();
260 return onDeviceError("%s: crop and scale failed!", __FUNCTION__);
261 }
262
263 Size sz {halBuf.width, halBuf.height};
264 ATRACE_BEGIN("formatConvert");
265 ret = V3_4::implementation::formatConvert(cropAndScaled, outLayout, sz, outputFourcc);
266 ATRACE_END();
267 if (ret != 0) {
268 lk.unlock();
269 return onDeviceError("%s: format coversion failed!", __FUNCTION__);
270 }
271 int relFence = sHandleImporter.unlock(*(halBuf.bufPtr));
272 if (relFence >= 0) {
273 halBuf.acquireFence = relFence;
274 }
275 } break;
276 default:
277 lk.unlock();
278 return onDeviceError("%s: unknown output format %x", __FUNCTION__, halBuf.format);
279 }
280 } // for each buffer
281 mScaledYu12Frames.clear();
282
283 // Don't hold the lock while calling back to parent
284 lk.unlock();
285 Status st = parent->processCaptureResult(req);
286 if (st != Status::OK) {
287 return onDeviceError("%s: failed to process capture result!", __FUNCTION__);
288 }
289 signalRequestDone();
290 return true;
291 }
292
importBuffer(int32_t streamId,uint64_t bufId,buffer_handle_t buf,buffer_handle_t ** outBufPtr,bool allowEmptyBuf)293 Status ExternalCameraOfflineSession::importBuffer(int32_t streamId,
294 uint64_t bufId, buffer_handle_t buf,
295 /*out*/buffer_handle_t** outBufPtr,
296 bool allowEmptyBuf) {
297 Mutex::Autolock _l(mCbsLock);
298 return V3_4::implementation::importBufferImpl(
299 mCirculatingBuffers, sHandleImporter, streamId,
300 bufId, buf, outBufPtr, allowEmptyBuf);
301 return Status::OK;
302 };
303
304 #define UPDATE(md, tag, data, size) \
305 do { \
306 if ((md).update((tag), (data), (size))) { \
307 ALOGE("Update " #tag " failed!"); \
308 return BAD_VALUE; \
309 } \
310 } while (0)
311
fillCaptureResult(common::V1_0::helper::CameraMetadata & md,nsecs_t timestamp)312 status_t ExternalCameraOfflineSession::fillCaptureResult(
313 common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) {
314 bool afTrigger = false;
315 {
316 std::lock_guard<std::mutex> lk(mAfTriggerLock);
317 afTrigger = mAfTrigger;
318 if (md.exists(ANDROID_CONTROL_AF_TRIGGER)) {
319 camera_metadata_entry entry = md.find(ANDROID_CONTROL_AF_TRIGGER);
320 if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_START) {
321 mAfTrigger = afTrigger = true;
322 } else if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_CANCEL) {
323 mAfTrigger = afTrigger = false;
324 }
325 }
326 }
327
328 // For USB camera, the USB camera handles everything and we don't have control
329 // over AF. We only simply fake the AF metadata based on the request
330 // received here.
331 uint8_t afState;
332 if (afTrigger) {
333 afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
334 } else {
335 afState = ANDROID_CONTROL_AF_STATE_INACTIVE;
336 }
337 UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1);
338
339 camera_metadata_ro_entry activeArraySize =
340 mChars.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
341
342 return V3_4::implementation::fillCaptureResultCommon(md, timestamp, activeArraySize);
343 }
344
345 #undef UPDATE
346
processCaptureResult(std::shared_ptr<HalRequest> & req)347 Status ExternalCameraOfflineSession::processCaptureResult(std::shared_ptr<HalRequest>& req) {
348 ATRACE_CALL();
349 // Fill output buffers
350 hidl_vec<CaptureResult> results;
351 results.resize(1);
352 CaptureResult& result = results[0];
353 result.frameNumber = req->frameNumber;
354 result.partialResult = 1;
355 result.inputBuffer.streamId = -1;
356 result.outputBuffers.resize(req->buffers.size());
357 for (size_t i = 0; i < req->buffers.size(); i++) {
358 result.outputBuffers[i].streamId = req->buffers[i].streamId;
359 result.outputBuffers[i].bufferId = req->buffers[i].bufferId;
360 if (req->buffers[i].fenceTimeout) {
361 result.outputBuffers[i].status = BufferStatus::ERROR;
362 if (req->buffers[i].acquireFence >= 0) {
363 native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
364 handle->data[0] = req->buffers[i].acquireFence;
365 result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
366 }
367 notifyError(req->frameNumber, req->buffers[i].streamId, ErrorCode::ERROR_BUFFER);
368 } else {
369 result.outputBuffers[i].status = BufferStatus::OK;
370 // TODO: refactor
371 if (req->buffers[i].acquireFence >= 0) {
372 native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
373 handle->data[0] = req->buffers[i].acquireFence;
374 result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
375 }
376 }
377 }
378
379 // Fill capture result metadata
380 fillCaptureResult(req->setting, req->shutterTs);
381 const camera_metadata_t *rawResult = req->setting.getAndLock();
382 V3_2::implementation::convertToHidl(rawResult, &result.result);
383 req->setting.unlock(rawResult);
384
385 // Callback into framework
386 invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
387 V3_4::implementation::freeReleaseFences(results);
388 return Status::OK;
389 };
390
invokeProcessCaptureResultCallback(hidl_vec<CaptureResult> & results,bool tryWriteFmq)391 void ExternalCameraOfflineSession::invokeProcessCaptureResultCallback(
392 hidl_vec<CaptureResult> &results, bool tryWriteFmq) {
393 if (mProcessCaptureResultLock.tryLock() != OK) {
394 const nsecs_t NS_TO_SECOND = 1000000000;
395 ALOGV("%s: previous call is not finished! waiting 1s...", __FUNCTION__);
396 if (mProcessCaptureResultLock.timedLock(/* 1s */NS_TO_SECOND) != OK) {
397 ALOGE("%s: cannot acquire lock in 1s, cannot proceed",
398 __FUNCTION__);
399 return;
400 }
401 }
402 if (tryWriteFmq && mResultMetadataQueue->availableToWrite() > 0) {
403 for (CaptureResult &result : results) {
404 if (result.result.size() > 0) {
405 if (mResultMetadataQueue->write(result.result.data(), result.result.size())) {
406 result.fmqResultSize = result.result.size();
407 result.result.resize(0);
408 } else {
409 ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__);
410 result.fmqResultSize = 0;
411 }
412 } else {
413 result.fmqResultSize = 0;
414 }
415 }
416 }
417 auto status = mCallback->processCaptureResult(results);
418 if (!status.isOk()) {
419 ALOGE("%s: processCaptureResult ERROR : %s", __FUNCTION__,
420 status.description().c_str());
421 }
422
423 mProcessCaptureResultLock.unlock();
424 }
425
processCaptureRequestError(const std::shared_ptr<HalRequest> & req,std::vector<NotifyMsg> * outMsgs,std::vector<CaptureResult> * outResults)426 Status ExternalCameraOfflineSession::processCaptureRequestError(
427 const std::shared_ptr<HalRequest>& req,
428 /*out*/std::vector<NotifyMsg>* outMsgs,
429 /*out*/std::vector<CaptureResult>* outResults) {
430 ATRACE_CALL();
431
432 if (outMsgs == nullptr) {
433 notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST);
434 } else {
435 NotifyMsg shutter;
436 shutter.type = MsgType::SHUTTER;
437 shutter.msg.shutter.frameNumber = req->frameNumber;
438 shutter.msg.shutter.timestamp = req->shutterTs;
439
440 NotifyMsg error;
441 error.type = MsgType::ERROR;
442 error.msg.error.frameNumber = req->frameNumber;
443 error.msg.error.errorStreamId = -1;
444 error.msg.error.errorCode = ErrorCode::ERROR_REQUEST;
445 outMsgs->push_back(shutter);
446 outMsgs->push_back(error);
447 }
448
449 // Fill output buffers
450 hidl_vec<CaptureResult> results;
451 results.resize(1);
452 CaptureResult& result = results[0];
453 result.frameNumber = req->frameNumber;
454 result.partialResult = 1;
455 result.inputBuffer.streamId = -1;
456 result.outputBuffers.resize(req->buffers.size());
457 for (size_t i = 0; i < req->buffers.size(); i++) {
458 result.outputBuffers[i].streamId = req->buffers[i].streamId;
459 result.outputBuffers[i].bufferId = req->buffers[i].bufferId;
460 result.outputBuffers[i].status = BufferStatus::ERROR;
461 if (req->buffers[i].acquireFence >= 0) {
462 native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0);
463 handle->data[0] = req->buffers[i].acquireFence;
464 result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false);
465 }
466 }
467
468 if (outResults == nullptr) {
469 // Callback into framework
470 invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true);
471 V3_4::implementation::freeReleaseFences(results);
472 } else {
473 outResults->push_back(result);
474 }
475 return Status::OK;
476 };
477
getJpegBufferSize(uint32_t,uint32_t) const478 ssize_t ExternalCameraOfflineSession::getJpegBufferSize(
479 uint32_t /*width*/, uint32_t /*height*/) const {
480 // Empty implementation here as the jpeg buffer size is passed in by ctor
481 return 0;
482 };
483
notifyError(uint32_t frameNumber,int32_t streamId,ErrorCode ec)484 void ExternalCameraOfflineSession::notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) {
485 NotifyMsg msg;
486 msg.type = MsgType::ERROR;
487 msg.msg.error.frameNumber = frameNumber;
488 msg.msg.error.errorStreamId = streamId;
489 msg.msg.error.errorCode = ec;
490 mCallback->notify({msg});
491 };
492
setCallback(const sp<ICameraDeviceCallback> & cb)493 Return<void> ExternalCameraOfflineSession::setCallback(const sp<ICameraDeviceCallback>& cb) {
494 Mutex::Autolock _il(mInterfaceLock);
495 if (mCallback != nullptr && cb != nullptr) {
496 ALOGE("%s: callback must not be set twice!", __FUNCTION__);
497 return Void();
498 }
499 mCallback = cb;
500
501 initOutputThread();
502
503 if (mOutputThread == nullptr) {
504 ALOGE("%s: init OutputThread failed!", __FUNCTION__);
505 }
506 return Void();
507 }
508
getCaptureResultMetadataQueue(V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb)509 Return<void> ExternalCameraOfflineSession::getCaptureResultMetadataQueue(
510 V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) {
511 Mutex::Autolock _il(mInterfaceLock);
512 _hidl_cb(*mResultMetadataQueue->getDesc());
513 return Void();
514 }
515
cleanupBuffersLocked(int id)516 void ExternalCameraOfflineSession::cleanupBuffersLocked(int id) {
517 for (auto& pair : mCirculatingBuffers.at(id)) {
518 sHandleImporter.freeBuffer(pair.second);
519 }
520 mCirculatingBuffers[id].clear();
521 mCirculatingBuffers.erase(id);
522 }
523
close()524 Return<void> ExternalCameraOfflineSession::close() {
525 Mutex::Autolock _il(mInterfaceLock);
526 {
527 Mutex::Autolock _l(mLock);
528 if (mClosed) {
529 ALOGW("%s: offline session already closed!", __FUNCTION__);
530 return Void();
531 }
532 }
533 if (mBufferRequestThread) {
534 mBufferRequestThread->requestExit();
535 mBufferRequestThread->join();
536 mBufferRequestThread.clear();
537 }
538 if (mOutputThread) {
539 mOutputThread->flush();
540 mOutputThread->requestExit();
541 mOutputThread->join();
542 mOutputThread.clear();
543 }
544
545 Mutex::Autolock _l(mLock);
546 // free all buffers
547 {
548 Mutex::Autolock _cbl(mCbsLock);
549 for(auto stream : mOfflineStreams) {
550 cleanupBuffersLocked(stream.id);
551 }
552 }
553 mCallback.clear();
554 mClosed = true;
555 return Void();
556 }
557
558 } // namespace implementation
559 } // namespace V3_6
560 } // namespace device
561 } // namespace camera
562 } // namespace hardware
563 } // namespace android
564