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_TAG "Camera2-StreamingProcessor"
18#define ATRACE_TAG ATRACE_TAG_CAMERA
19//#define LOG_NDEBUG 0
20
21#include <utils/Log.h>
22#include <utils/Trace.h>
23#include <gui/SurfaceTextureClient.h>
24#include <media/hardware/MetadataBufferType.h>
25
26#include "StreamingProcessor.h"
27#include "Camera2Heap.h"
28#include "../Camera2Client.h"
29#include "../Camera2Device.h"
30
31namespace android {
32namespace camera2 {
33
34StreamingProcessor::StreamingProcessor(wp<Camera2Client> client):
35        mClient(client),
36        mActiveRequest(NONE),
37        mPreviewRequestId(Camera2Client::kPreviewRequestIdStart),
38        mPreviewStreamId(NO_STREAM),
39        mRecordingRequestId(Camera2Client::kRecordingRequestIdStart),
40        mRecordingStreamId(NO_STREAM),
41        mRecordingHeapCount(kDefaultRecordingHeapCount)
42{
43
44}
45
46StreamingProcessor::~StreamingProcessor() {
47    deletePreviewStream();
48    deleteRecordingStream();
49}
50
51status_t StreamingProcessor::setPreviewWindow(sp<ANativeWindow> window) {
52    ATRACE_CALL();
53    status_t res;
54
55    res = deletePreviewStream();
56    if (res != OK) return res;
57
58    Mutex::Autolock m(mMutex);
59
60    mPreviewWindow = window;
61
62    return OK;
63}
64
65bool StreamingProcessor::haveValidPreviewWindow() const {
66    Mutex::Autolock m(mMutex);
67    return mPreviewWindow != 0;
68}
69
70status_t StreamingProcessor::updatePreviewRequest(const Parameters &params) {
71    ATRACE_CALL();
72    status_t res;
73    sp<Camera2Client> client = mClient.promote();
74    if (client == 0) return INVALID_OPERATION;
75
76    Mutex::Autolock m(mMutex);
77    if (mPreviewRequest.entryCount() == 0) {
78        res = client->getCameraDevice()->createDefaultRequest(CAMERA2_TEMPLATE_PREVIEW,
79                &mPreviewRequest);
80        if (res != OK) {
81            ALOGE("%s: Camera %d: Unable to create default preview request: "
82                    "%s (%d)", __FUNCTION__, client->getCameraId(), strerror(-res), res);
83            return res;
84        }
85    }
86
87    res = params.updateRequest(&mPreviewRequest);
88    if (res != OK) {
89        ALOGE("%s: Camera %d: Unable to update common entries of preview "
90                "request: %s (%d)", __FUNCTION__, client->getCameraId(),
91                strerror(-res), res);
92        return res;
93    }
94
95    res = mPreviewRequest.update(ANDROID_REQUEST_ID,
96            &mPreviewRequestId, 1);
97    if (res != OK) {
98        ALOGE("%s: Camera %d: Unable to update request id for preview: %s (%d)",
99                __FUNCTION__, client->getCameraId(), strerror(-res), res);
100        return res;
101    }
102
103    return OK;
104}
105
106status_t StreamingProcessor::updatePreviewStream(const Parameters &params) {
107    ATRACE_CALL();
108    Mutex::Autolock m(mMutex);
109
110    status_t res;
111    sp<Camera2Client> client = mClient.promote();
112    if (client == 0) return INVALID_OPERATION;
113    sp<Camera2Device> device = client->getCameraDevice();
114
115    if (mPreviewStreamId != NO_STREAM) {
116        // Check if stream parameters have to change
117        uint32_t currentWidth, currentHeight;
118        res = device->getStreamInfo(mPreviewStreamId,
119                &currentWidth, &currentHeight, 0);
120        if (res != OK) {
121            ALOGE("%s: Camera %d: Error querying preview stream info: "
122                    "%s (%d)", __FUNCTION__, client->getCameraId(), strerror(-res), res);
123            return res;
124        }
125        if (currentWidth != (uint32_t)params.previewWidth ||
126                currentHeight != (uint32_t)params.previewHeight) {
127            ALOGV("%s: Camera %d: Preview size switch: %d x %d -> %d x %d",
128                    __FUNCTION__, client->getCameraId(), currentWidth, currentHeight,
129                    params.previewWidth, params.previewHeight);
130            res = device->waitUntilDrained();
131            if (res != OK) {
132                ALOGE("%s: Camera %d: Error waiting for preview to drain: "
133                        "%s (%d)", __FUNCTION__, client->getCameraId(), strerror(-res), res);
134                return res;
135            }
136            res = device->deleteStream(mPreviewStreamId);
137            if (res != OK) {
138                ALOGE("%s: Camera %d: Unable to delete old output stream "
139                        "for preview: %s (%d)", __FUNCTION__, client->getCameraId(),
140                        strerror(-res), res);
141                return res;
142            }
143            mPreviewStreamId = NO_STREAM;
144        }
145    }
146
147    if (mPreviewStreamId == NO_STREAM) {
148        res = device->createStream(mPreviewWindow,
149                params.previewWidth, params.previewHeight,
150                CAMERA2_HAL_PIXEL_FORMAT_OPAQUE, 0,
151                &mPreviewStreamId);
152        if (res != OK) {
153            ALOGE("%s: Camera %d: Unable to create preview stream: %s (%d)",
154                    __FUNCTION__, client->getCameraId(), strerror(-res), res);
155            return res;
156        }
157    }
158
159    res = device->setStreamTransform(mPreviewStreamId,
160            params.previewTransform);
161    if (res != OK) {
162        ALOGE("%s: Camera %d: Unable to set preview stream transform: "
163                "%s (%d)", __FUNCTION__, client->getCameraId(), strerror(-res), res);
164        return res;
165    }
166
167    return OK;
168}
169
170status_t StreamingProcessor::deletePreviewStream() {
171    ATRACE_CALL();
172    status_t res;
173
174    Mutex::Autolock m(mMutex);
175
176    if (mPreviewStreamId != NO_STREAM) {
177        sp<Camera2Client> client = mClient.promote();
178        if (client == 0) return INVALID_OPERATION;
179        sp<Camera2Device> device = client->getCameraDevice();
180
181        ALOGV("%s: for cameraId %d on streamId %d",
182            __FUNCTION__, client->getCameraId(), mPreviewStreamId);
183
184        res = device->waitUntilDrained();
185        if (res != OK) {
186            ALOGE("%s: Error waiting for preview to drain: %s (%d)",
187                    __FUNCTION__, strerror(-res), res);
188            return res;
189        }
190        res = device->deleteStream(mPreviewStreamId);
191        if (res != OK) {
192            ALOGE("%s: Unable to delete old preview stream: %s (%d)",
193                    __FUNCTION__, strerror(-res), res);
194            return res;
195        }
196        mPreviewStreamId = NO_STREAM;
197    }
198    return OK;
199}
200
201int StreamingProcessor::getPreviewStreamId() const {
202    Mutex::Autolock m(mMutex);
203    return mPreviewStreamId;
204}
205
206status_t StreamingProcessor::setRecordingBufferCount(size_t count) {
207    ATRACE_CALL();
208    // 32 is the current upper limit on the video buffer count for BufferQueue
209    sp<Camera2Client> client = mClient.promote();
210    if (client == 0) return INVALID_OPERATION;
211    if (count > 32) {
212        ALOGE("%s: Camera %d: Error setting %d as video buffer count value",
213                __FUNCTION__, client->getCameraId(), count);
214        return BAD_VALUE;
215    }
216
217    Mutex::Autolock m(mMutex);
218
219    // Need to reallocate memory for heap
220    if (mRecordingHeapCount != count) {
221        if  (mRecordingHeap != 0) {
222            mRecordingHeap.clear();
223            mRecordingHeap = NULL;
224        }
225        mRecordingHeapCount = count;
226    }
227
228    return OK;
229}
230
231status_t StreamingProcessor::updateRecordingRequest(const Parameters &params) {
232    ATRACE_CALL();
233    status_t res;
234    Mutex::Autolock m(mMutex);
235
236    sp<Camera2Client> client = mClient.promote();
237    if (client == 0) return INVALID_OPERATION;
238
239    if (mRecordingRequest.entryCount() == 0) {
240        res = client->getCameraDevice()->createDefaultRequest(CAMERA2_TEMPLATE_VIDEO_RECORD,
241                &mRecordingRequest);
242        if (res != OK) {
243            ALOGE("%s: Camera %d: Unable to create default recording request:"
244                    " %s (%d)", __FUNCTION__, client->getCameraId(), strerror(-res), res);
245            return res;
246        }
247    }
248
249    res = params.updateRequest(&mRecordingRequest);
250    if (res != OK) {
251        ALOGE("%s: Camera %d: Unable to update common entries of recording "
252                "request: %s (%d)", __FUNCTION__, client->getCameraId(),
253                strerror(-res), res);
254        return res;
255    }
256
257    res = mRecordingRequest.update(ANDROID_REQUEST_ID,
258            &mRecordingRequestId, 1);
259    if (res != OK) {
260        ALOGE("%s: Camera %d: Unable to update request id for request: %s (%d)",
261                __FUNCTION__, client->getCameraId(), strerror(-res), res);
262        return res;
263    }
264
265    return OK;
266}
267
268status_t StreamingProcessor::updateRecordingStream(const Parameters &params) {
269    ATRACE_CALL();
270    status_t res;
271    Mutex::Autolock m(mMutex);
272
273    sp<Camera2Client> client = mClient.promote();
274    if (client == 0) return INVALID_OPERATION;
275    sp<Camera2Device> device = client->getCameraDevice();
276
277    if (mRecordingConsumer == 0) {
278        // Create CPU buffer queue endpoint. We need one more buffer here so that we can
279        // always acquire and free a buffer when the heap is full; otherwise the consumer
280        // will have buffers in flight we'll never clear out.
281        mRecordingConsumer = new BufferItemConsumer(
282                GRALLOC_USAGE_HW_VIDEO_ENCODER,
283                mRecordingHeapCount + 1,
284                true);
285        mRecordingConsumer->setFrameAvailableListener(this);
286        mRecordingConsumer->setName(String8("Camera2-RecordingConsumer"));
287        mRecordingWindow = new SurfaceTextureClient(
288            mRecordingConsumer->getProducerInterface());
289        // Allocate memory later, since we don't know buffer size until receipt
290    }
291
292    if (mRecordingStreamId != NO_STREAM) {
293        // Check if stream parameters have to change
294        uint32_t currentWidth, currentHeight;
295        res = device->getStreamInfo(mRecordingStreamId,
296                &currentWidth, &currentHeight, 0);
297        if (res != OK) {
298            ALOGE("%s: Camera %d: Error querying recording output stream info: "
299                    "%s (%d)", __FUNCTION__, client->getCameraId(),
300                    strerror(-res), res);
301            return res;
302        }
303        if (currentWidth != (uint32_t)params.videoWidth ||
304                currentHeight != (uint32_t)params.videoHeight) {
305            // TODO: Should wait to be sure previous recording has finished
306            res = device->deleteStream(mRecordingStreamId);
307            if (res != OK) {
308                ALOGE("%s: Camera %d: Unable to delete old output stream "
309                        "for recording: %s (%d)", __FUNCTION__,
310                        client->getCameraId(), strerror(-res), res);
311                return res;
312            }
313            mRecordingStreamId = NO_STREAM;
314        }
315    }
316
317    if (mRecordingStreamId == NO_STREAM) {
318        mRecordingFrameCount = 0;
319        res = device->createStream(mRecordingWindow,
320                params.videoWidth, params.videoHeight,
321                CAMERA2_HAL_PIXEL_FORMAT_OPAQUE, 0, &mRecordingStreamId);
322        if (res != OK) {
323            ALOGE("%s: Camera %d: Can't create output stream for recording: "
324                    "%s (%d)", __FUNCTION__, client->getCameraId(),
325                    strerror(-res), res);
326            return res;
327        }
328    }
329
330    return OK;
331}
332
333status_t StreamingProcessor::deleteRecordingStream() {
334    ATRACE_CALL();
335    status_t res;
336
337    Mutex::Autolock m(mMutex);
338
339    if (mRecordingStreamId != NO_STREAM) {
340        sp<Camera2Client> client = mClient.promote();
341        if (client == 0) return INVALID_OPERATION;
342        sp<Camera2Device> device = client->getCameraDevice();
343
344        res = device->waitUntilDrained();
345        if (res != OK) {
346            ALOGE("%s: Error waiting for HAL to drain: %s (%d)",
347                    __FUNCTION__, strerror(-res), res);
348            return res;
349        }
350        res = device->deleteStream(mRecordingStreamId);
351        if (res != OK) {
352            ALOGE("%s: Unable to delete recording stream: %s (%d)",
353                    __FUNCTION__, strerror(-res), res);
354            return res;
355        }
356        mRecordingStreamId = NO_STREAM;
357    }
358    return OK;
359}
360
361int StreamingProcessor::getRecordingStreamId() const {
362    return mRecordingStreamId;
363}
364
365status_t StreamingProcessor::startStream(StreamType type,
366        const Vector<uint8_t> &outputStreams) {
367    ATRACE_CALL();
368    status_t res;
369
370    if (type == NONE) return INVALID_OPERATION;
371
372    sp<Camera2Client> client = mClient.promote();
373    if (client == 0) return INVALID_OPERATION;
374
375    ALOGV("%s: Camera %d: type = %d", __FUNCTION__, client->getCameraId(), type);
376
377    Mutex::Autolock m(mMutex);
378
379    CameraMetadata &request = (type == PREVIEW) ?
380            mPreviewRequest : mRecordingRequest;
381
382    res = request.update(
383        ANDROID_REQUEST_OUTPUT_STREAMS,
384        outputStreams);
385    if (res != OK) {
386        ALOGE("%s: Camera %d: Unable to set up preview request: %s (%d)",
387                __FUNCTION__, client->getCameraId(), strerror(-res), res);
388        return res;
389    }
390
391    res = request.sort();
392    if (res != OK) {
393        ALOGE("%s: Camera %d: Error sorting preview request: %s (%d)",
394                __FUNCTION__, client->getCameraId(), strerror(-res), res);
395        return res;
396    }
397
398    res = client->getCameraDevice()->setStreamingRequest(request);
399    if (res != OK) {
400        ALOGE("%s: Camera %d: Unable to set preview request to start preview: "
401                "%s (%d)",
402                __FUNCTION__, client->getCameraId(), strerror(-res), res);
403        return res;
404    }
405    mActiveRequest = type;
406
407    return OK;
408}
409
410status_t StreamingProcessor::stopStream() {
411    ATRACE_CALL();
412    status_t res;
413
414    Mutex::Autolock m(mMutex);
415
416    sp<Camera2Client> client = mClient.promote();
417    if (client == 0) return INVALID_OPERATION;
418    sp<Camera2Device> device = client->getCameraDevice();
419
420    res = device->clearStreamingRequest();
421    if (res != OK) {
422        ALOGE("%s: Camera %d: Can't clear stream request: %s (%d)",
423                __FUNCTION__, client->getCameraId(), strerror(-res), res);
424        return res;
425    }
426    mActiveRequest = NONE;
427
428    return OK;
429}
430
431int32_t StreamingProcessor::getActiveRequestId() const {
432    Mutex::Autolock m(mMutex);
433    switch (mActiveRequest) {
434        case NONE:
435            return 0;
436        case PREVIEW:
437            return mPreviewRequestId;
438        case RECORD:
439            return mRecordingRequestId;
440        default:
441            ALOGE("%s: Unexpected mode %d", __FUNCTION__, mActiveRequest);
442            return 0;
443    }
444}
445
446status_t StreamingProcessor::incrementStreamingIds() {
447    ATRACE_CALL();
448    Mutex::Autolock m(mMutex);
449
450    status_t res;
451    mPreviewRequestId++;
452    if (mPreviewRequestId >= Camera2Client::kPreviewRequestIdEnd) {
453        mPreviewRequestId = Camera2Client::kPreviewRequestIdStart;
454    }
455    mRecordingRequestId++;
456    if (mRecordingRequestId >= Camera2Client::kRecordingRequestIdEnd) {
457        mRecordingRequestId = Camera2Client::kRecordingRequestIdStart;
458    }
459    return OK;
460}
461
462void StreamingProcessor::onFrameAvailable() {
463    ATRACE_CALL();
464    status_t res;
465    sp<Camera2Heap> recordingHeap;
466    size_t heapIdx = 0;
467    nsecs_t timestamp;
468
469    sp<Camera2Client> client = mClient.promote();
470    if (client == 0) return;
471
472    {
473        /* acquire SharedParameters before mMutex so we don't dead lock
474            with Camera2Client code calling into StreamingProcessor */
475        SharedParameters::Lock l(client->getParameters());
476        Mutex::Autolock m(mMutex);
477        BufferItemConsumer::BufferItem imgBuffer;
478        res = mRecordingConsumer->acquireBuffer(&imgBuffer);
479        if (res != OK) {
480            ALOGE("%s: Camera %d: Error receiving recording buffer: %s (%d)",
481                    __FUNCTION__, client->getCameraId(), strerror(-res), res);
482            return;
483        }
484        timestamp = imgBuffer.mTimestamp;
485
486        mRecordingFrameCount++;
487        ALOGV("OnRecordingFrame: Frame %d", mRecordingFrameCount);
488
489        // TODO: Signal errors here upstream
490        if (l.mParameters.state != Parameters::RECORD &&
491                l.mParameters.state != Parameters::VIDEO_SNAPSHOT) {
492            ALOGV("%s: Camera %d: Discarding recording image buffers "
493                    "received after recording done", __FUNCTION__,
494                    client->getCameraId());
495            mRecordingConsumer->releaseBuffer(imgBuffer);
496            return;
497        }
498
499        if (mRecordingHeap == 0) {
500            const size_t bufferSize = 4 + sizeof(buffer_handle_t);
501            ALOGV("%s: Camera %d: Creating recording heap with %d buffers of "
502                    "size %d bytes", __FUNCTION__, client->getCameraId(),
503                    mRecordingHeapCount, bufferSize);
504
505            mRecordingHeap = new Camera2Heap(bufferSize, mRecordingHeapCount,
506                    "Camera2Client::RecordingHeap");
507            if (mRecordingHeap->mHeap->getSize() == 0) {
508                ALOGE("%s: Camera %d: Unable to allocate memory for recording",
509                        __FUNCTION__, client->getCameraId());
510                mRecordingConsumer->releaseBuffer(imgBuffer);
511                return;
512            }
513            for (size_t i = 0; i < mRecordingBuffers.size(); i++) {
514                if (mRecordingBuffers[i].mBuf !=
515                        BufferItemConsumer::INVALID_BUFFER_SLOT) {
516                    ALOGE("%s: Camera %d: Non-empty recording buffers list!",
517                            __FUNCTION__, client->getCameraId());
518                }
519            }
520            mRecordingBuffers.clear();
521            mRecordingBuffers.setCapacity(mRecordingHeapCount);
522            mRecordingBuffers.insertAt(0, mRecordingHeapCount);
523
524            mRecordingHeapHead = 0;
525            mRecordingHeapFree = mRecordingHeapCount;
526        }
527
528        if ( mRecordingHeapFree == 0) {
529            ALOGE("%s: Camera %d: No free recording buffers, dropping frame",
530                    __FUNCTION__, client->getCameraId());
531            mRecordingConsumer->releaseBuffer(imgBuffer);
532            return;
533        }
534
535        heapIdx = mRecordingHeapHead;
536        mRecordingHeapHead = (mRecordingHeapHead + 1) % mRecordingHeapCount;
537        mRecordingHeapFree--;
538
539        ALOGV("%s: Camera %d: Timestamp %lld",
540                __FUNCTION__, client->getCameraId(), timestamp);
541
542        ssize_t offset;
543        size_t size;
544        sp<IMemoryHeap> heap =
545                mRecordingHeap->mBuffers[heapIdx]->getMemory(&offset,
546                        &size);
547
548        uint8_t *data = (uint8_t*)heap->getBase() + offset;
549        uint32_t type = kMetadataBufferTypeGrallocSource;
550        *((uint32_t*)data) = type;
551        *((buffer_handle_t*)(data + 4)) = imgBuffer.mGraphicBuffer->handle;
552        ALOGV("%s: Camera %d: Sending out buffer_handle_t %p",
553                __FUNCTION__, client->getCameraId(),
554                imgBuffer.mGraphicBuffer->handle);
555        mRecordingBuffers.replaceAt(imgBuffer, heapIdx);
556        recordingHeap = mRecordingHeap;
557    }
558
559    // Call outside locked parameters to allow re-entrancy from notification
560    Camera2Client::SharedCameraClient::Lock l(client->mSharedCameraClient);
561    if (l.mCameraClient != 0) {
562        l.mCameraClient->dataCallbackTimestamp(timestamp,
563                CAMERA_MSG_VIDEO_FRAME,
564                recordingHeap->mBuffers[heapIdx]);
565    }
566}
567
568void StreamingProcessor::releaseRecordingFrame(const sp<IMemory>& mem) {
569    ATRACE_CALL();
570    status_t res;
571
572    sp<Camera2Client> client = mClient.promote();
573    if (client == 0) return;
574
575    Mutex::Autolock m(mMutex);
576    // Make sure this is for the current heap
577    ssize_t offset;
578    size_t size;
579    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
580    if (heap->getHeapID() != mRecordingHeap->mHeap->getHeapID()) {
581        ALOGW("%s: Camera %d: Mismatched heap ID, ignoring release "
582                "(got %x, expected %x)", __FUNCTION__, client->getCameraId(),
583                heap->getHeapID(), mRecordingHeap->mHeap->getHeapID());
584        return;
585    }
586    uint8_t *data = (uint8_t*)heap->getBase() + offset;
587    uint32_t type = *(uint32_t*)data;
588    if (type != kMetadataBufferTypeGrallocSource) {
589        ALOGE("%s: Camera %d: Recording frame type invalid (got %x, expected %x)",
590                __FUNCTION__, client->getCameraId(), type,
591                kMetadataBufferTypeGrallocSource);
592        return;
593    }
594
595    // Release the buffer back to the recording queue
596
597    buffer_handle_t imgHandle = *(buffer_handle_t*)(data + 4);
598
599    size_t itemIndex;
600    for (itemIndex = 0; itemIndex < mRecordingBuffers.size(); itemIndex++) {
601        const BufferItemConsumer::BufferItem item =
602                mRecordingBuffers[itemIndex];
603        if (item.mBuf != BufferItemConsumer::INVALID_BUFFER_SLOT &&
604                item.mGraphicBuffer->handle == imgHandle) {
605            break;
606        }
607    }
608    if (itemIndex == mRecordingBuffers.size()) {
609        ALOGE("%s: Camera %d: Can't find buffer_handle_t %p in list of "
610                "outstanding buffers", __FUNCTION__, client->getCameraId(),
611                imgHandle);
612        return;
613    }
614
615    ALOGV("%s: Camera %d: Freeing buffer_handle_t %p", __FUNCTION__,
616            client->getCameraId(), imgHandle);
617
618    res = mRecordingConsumer->releaseBuffer(mRecordingBuffers[itemIndex]);
619    if (res != OK) {
620        ALOGE("%s: Camera %d: Unable to free recording frame "
621                "(buffer_handle_t: %p): %s (%d)", __FUNCTION__,
622                client->getCameraId(), imgHandle, strerror(-res), res);
623        return;
624    }
625    mRecordingBuffers.replaceAt(itemIndex);
626
627    mRecordingHeapFree++;
628}
629
630
631status_t StreamingProcessor::dump(int fd, const Vector<String16>& args) {
632    String8 result;
633
634    result.append("  Current requests:\n");
635    if (mPreviewRequest.entryCount() != 0) {
636        result.append("    Preview request:\n");
637        write(fd, result.string(), result.size());
638        mPreviewRequest.dump(fd, 2, 6);
639    } else {
640        result.append("    Preview request: undefined\n");
641        write(fd, result.string(), result.size());
642    }
643
644    if (mRecordingRequest.entryCount() != 0) {
645        result = "    Recording request:\n";
646        write(fd, result.string(), result.size());
647        mRecordingRequest.dump(fd, 2, 6);
648    } else {
649        result = "    Recording request: undefined\n";
650        write(fd, result.string(), result.size());
651    }
652
653    return OK;
654}
655
656}; // namespace camera2
657}; // namespace android
658