• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 #include "v4l2_wrapper.h"
18 
19 #include <algorithm>
20 #include <array>
21 #include <limits>
22 #include <mutex>
23 #include <vector>
24 
25 #include <fcntl.h>
26 #include <linux/videodev2.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 
30 #include <android-base/unique_fd.h>
31 
32 #include "common.h"
33 #include "stream_format.h"
34 #include "v4l2_gralloc.h"
35 
36 namespace v4l2_camera_hal {
37 
38 const int32_t kStandardSizes[][2] = {{640, 480}, {320, 240}};
39 
NewV4L2Wrapper(const std::string device_path)40 V4L2Wrapper* V4L2Wrapper::NewV4L2Wrapper(const std::string device_path) {
41   std::unique_ptr<V4L2Gralloc> gralloc(V4L2Gralloc::NewV4L2Gralloc());
42   if (!gralloc) {
43     HAL_LOGE("Failed to initialize gralloc helper.");
44     return nullptr;
45   }
46 
47   return new V4L2Wrapper(device_path, std::move(gralloc));
48 }
49 
V4L2Wrapper(const std::string device_path,std::unique_ptr<V4L2Gralloc> gralloc)50 V4L2Wrapper::V4L2Wrapper(const std::string device_path,
51                          std::unique_ptr<V4L2Gralloc> gralloc)
52     : device_path_(std::move(device_path)),
53       gralloc_(std::move(gralloc)),
54       connection_count_(0) {}
55 
~V4L2Wrapper()56 V4L2Wrapper::~V4L2Wrapper() {}
57 
Connect()58 int V4L2Wrapper::Connect() {
59   HAL_LOG_ENTER();
60   std::lock_guard<std::mutex> lock(connection_lock_);
61 
62   if (connected()) {
63     HAL_LOGV("Camera device %s is already connected.", device_path_.c_str());
64     ++connection_count_;
65     return 0;
66   }
67 
68   // Open in nonblocking mode (DQBUF may return EAGAIN).
69   int fd = TEMP_FAILURE_RETRY(open(device_path_.c_str(), O_RDWR | O_NONBLOCK));
70   if (fd < 0) {
71     HAL_LOGE("failed to open %s (%s)", device_path_.c_str(), strerror(errno));
72     return -ENODEV;
73   }
74   device_fd_.reset(fd);
75   ++connection_count_;
76 
77   // Check if this connection has the extended control query capability.
78   v4l2_query_ext_ctrl query;
79   query.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
80   extended_query_supported_ = (IoctlLocked(VIDIOC_QUERY_EXT_CTRL, &query) == 0);
81 
82   // TODO(b/29185945): confirm this is a supported device.
83   // This is checked by the HAL, but the device at device_path_ may
84   // not be the same one that was there when the HAL was loaded.
85   // (Alternatively, better hotplugging support may make this unecessary
86   // by disabling cameras that get disconnected and checking newly connected
87   // cameras, so Connect() is never called on an unsupported camera)
88   return 0;
89 }
90 
Disconnect()91 void V4L2Wrapper::Disconnect() {
92   HAL_LOG_ENTER();
93   std::lock_guard<std::mutex> lock(connection_lock_);
94 
95   if (connection_count_ == 0) {
96     // Not connected.
97     HAL_LOGE("Camera device %s is not connected, cannot disconnect.",
98              device_path_.c_str());
99     return;
100   }
101 
102   --connection_count_;
103   if (connection_count_ > 0) {
104     HAL_LOGV("Disconnected from camera device %s. %d connections remain.",
105              device_path_.c_str());
106     return;
107   }
108 
109   device_fd_.reset(-1);  // Includes close().
110   format_.reset();
111   buffers_.clear();
112   // Closing the device releases all queued buffers back to the user.
113   gralloc_->unlockAllBuffers();
114 }
115 
116 // Helper function. Should be used instead of ioctl throughout this class.
117 template <typename T>
IoctlLocked(int request,T data)118 int V4L2Wrapper::IoctlLocked(int request, T data) {
119   // Potentially called so many times logging entry is a bad idea.
120   std::lock_guard<std::mutex> lock(device_lock_);
121 
122   if (!connected()) {
123     HAL_LOGE("Device %s not connected.", device_path_.c_str());
124     return -ENODEV;
125   }
126   return TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), request, data));
127 }
128 
StreamOn()129 int V4L2Wrapper::StreamOn() {
130   if (!format_) {
131     HAL_LOGE("Stream format must be set before turning on stream.");
132     return -EINVAL;
133   }
134 
135   int32_t type = format_->type();
136   if (IoctlLocked(VIDIOC_STREAMON, &type) < 0) {
137     HAL_LOGE("STREAMON fails: %s", strerror(errno));
138     return -ENODEV;
139   }
140 
141   HAL_LOGV("Stream turned on.");
142   return 0;
143 }
144 
StreamOff()145 int V4L2Wrapper::StreamOff() {
146   if (!format_) {
147     // Can't have turned on the stream without format being set,
148     // so nothing to turn off here.
149     return 0;
150   }
151 
152   int32_t type = format_->type();
153   int res = IoctlLocked(VIDIOC_STREAMOFF, &type);
154   // Calling STREAMOFF releases all queued buffers back to the user.
155   int gralloc_res = gralloc_->unlockAllBuffers();
156   // No buffers in flight.
157   for (size_t i = 0; i < buffers_.size(); ++i) {
158     buffers_[i] = false;
159   }
160   if (res < 0) {
161     HAL_LOGE("STREAMOFF fails: %s", strerror(errno));
162     return -ENODEV;
163   }
164   if (gralloc_res < 0) {
165     HAL_LOGE("Failed to unlock all buffers after turning stream off.");
166     return gralloc_res;
167   }
168 
169   HAL_LOGV("Stream turned off.");
170   return 0;
171 }
172 
QueryControl(uint32_t control_id,v4l2_query_ext_ctrl * result)173 int V4L2Wrapper::QueryControl(uint32_t control_id,
174                               v4l2_query_ext_ctrl* result) {
175   int res;
176 
177   memset(result, 0, sizeof(*result));
178 
179   if (extended_query_supported_) {
180     result->id = control_id;
181     res = IoctlLocked(VIDIOC_QUERY_EXT_CTRL, result);
182     // Assuming the operation was supported (not ENOTTY), no more to do.
183     if (errno != ENOTTY) {
184       if (res) {
185         HAL_LOGE("QUERY_EXT_CTRL fails: %s", strerror(errno));
186         return -ENODEV;
187       }
188       return 0;
189     }
190   }
191 
192   // Extended control querying not supported, fall back to basic control query.
193   v4l2_queryctrl query;
194   query.id = control_id;
195   if (IoctlLocked(VIDIOC_QUERYCTRL, &query)) {
196     HAL_LOGE("QUERYCTRL fails: %s", strerror(errno));
197     return -ENODEV;
198   }
199 
200   // Convert the basic result to the extended result.
201   result->id = query.id;
202   result->type = query.type;
203   memcpy(result->name, query.name, sizeof(query.name));
204   result->minimum = query.minimum;
205   if (query.type == V4L2_CTRL_TYPE_BITMASK) {
206     // According to the V4L2 documentation, when type is BITMASK,
207     // max and default should be interpreted as __u32. Practically,
208     // this means the conversion from 32 bit to 64 will pad with 0s not 1s.
209     result->maximum = static_cast<uint32_t>(query.maximum);
210     result->default_value = static_cast<uint32_t>(query.default_value);
211   } else {
212     result->maximum = query.maximum;
213     result->default_value = query.default_value;
214   }
215   result->step = static_cast<uint32_t>(query.step);
216   result->flags = query.flags;
217   result->elems = 1;
218   switch (result->type) {
219     case V4L2_CTRL_TYPE_INTEGER64:
220       result->elem_size = sizeof(int64_t);
221       break;
222     case V4L2_CTRL_TYPE_STRING:
223       result->elem_size = result->maximum + 1;
224       break;
225     default:
226       result->elem_size = sizeof(int32_t);
227       break;
228   }
229 
230   return 0;
231 }
232 
GetControl(uint32_t control_id,int32_t * value)233 int V4L2Wrapper::GetControl(uint32_t control_id, int32_t* value) {
234   // For extended controls (any control class other than "user"),
235   // G_EXT_CTRL must be used instead of G_CTRL.
236   if (V4L2_CTRL_ID2CLASS(control_id) != V4L2_CTRL_CLASS_USER) {
237     v4l2_ext_control control;
238     v4l2_ext_controls controls;
239     memset(&control, 0, sizeof(control));
240     memset(&controls, 0, sizeof(controls));
241 
242     control.id = control_id;
243     controls.ctrl_class = V4L2_CTRL_ID2CLASS(control_id);
244     controls.count = 1;
245     controls.controls = &control;
246 
247     if (IoctlLocked(VIDIOC_G_EXT_CTRLS, &controls) < 0) {
248       HAL_LOGE("G_EXT_CTRLS fails: %s", strerror(errno));
249       return -ENODEV;
250     }
251     *value = control.value;
252   } else {
253     v4l2_control control{control_id, 0};
254     if (IoctlLocked(VIDIOC_G_CTRL, &control) < 0) {
255       HAL_LOGE("G_CTRL fails: %s", strerror(errno));
256       return -ENODEV;
257     }
258     *value = control.value;
259   }
260   return 0;
261 }
262 
SetControl(uint32_t control_id,int32_t desired,int32_t * result)263 int V4L2Wrapper::SetControl(uint32_t control_id,
264                             int32_t desired,
265                             int32_t* result) {
266   int32_t result_value = 0;
267 
268   // TODO(b/29334616): When async, this may need to check if the stream
269   // is on, and if so, lock it off while setting format. Need to look
270   // into if V4L2 supports adjusting controls while the stream is on.
271 
272   // For extended controls (any control class other than "user"),
273   // S_EXT_CTRL must be used instead of S_CTRL.
274   if (V4L2_CTRL_ID2CLASS(control_id) != V4L2_CTRL_CLASS_USER) {
275     v4l2_ext_control control;
276     v4l2_ext_controls controls;
277     memset(&control, 0, sizeof(control));
278     memset(&controls, 0, sizeof(controls));
279 
280     control.id = control_id;
281     control.value = desired;
282     controls.ctrl_class = V4L2_CTRL_ID2CLASS(control_id);
283     controls.count = 1;
284     controls.controls = &control;
285 
286     if (IoctlLocked(VIDIOC_S_EXT_CTRLS, &controls) < 0) {
287       HAL_LOGE("S_EXT_CTRLS fails: %s", strerror(errno));
288       return -ENODEV;
289     }
290     result_value = control.value;
291   } else {
292     v4l2_control control{control_id, desired};
293     if (IoctlLocked(VIDIOC_S_CTRL, &control) < 0) {
294       HAL_LOGE("S_CTRL fails: %s", strerror(errno));
295       return -ENODEV;
296     }
297     result_value = control.value;
298   }
299 
300   // If the caller wants to know the result, pass it back.
301   if (result != nullptr) {
302     *result = result_value;
303   }
304   return 0;
305 }
306 
GetFormats(std::set<uint32_t> * v4l2_formats)307 int V4L2Wrapper::GetFormats(std::set<uint32_t>* v4l2_formats) {
308   HAL_LOG_ENTER();
309 
310   v4l2_fmtdesc format_query;
311   memset(&format_query, 0, sizeof(format_query));
312   // TODO(b/30000211): multiplanar support.
313   format_query.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
314   while (IoctlLocked(VIDIOC_ENUM_FMT, &format_query) >= 0) {
315     v4l2_formats->insert(format_query.pixelformat);
316     ++format_query.index;
317   }
318 
319   if (errno != EINVAL) {
320     HAL_LOGE(
321         "ENUM_FMT fails at index %d: %s", format_query.index, strerror(errno));
322     return -ENODEV;
323   }
324   return 0;
325 }
326 
GetFormatFrameSizes(uint32_t v4l2_format,std::set<std::array<int32_t,2>> * sizes)327 int V4L2Wrapper::GetFormatFrameSizes(uint32_t v4l2_format,
328                                      std::set<std::array<int32_t, 2>>* sizes) {
329   v4l2_frmsizeenum size_query;
330   memset(&size_query, 0, sizeof(size_query));
331   size_query.pixel_format = v4l2_format;
332   if (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) < 0) {
333     HAL_LOGE("ENUM_FRAMESIZES failed: %s", strerror(errno));
334     return -ENODEV;
335   }
336   if (size_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
337     // Discrete: enumerate all sizes using VIDIOC_ENUM_FRAMESIZES.
338     // Assuming that a driver with discrete frame sizes has a reasonable number
339     // of them.
340     do {
341       sizes->insert({{{static_cast<int32_t>(size_query.discrete.width),
342                        static_cast<int32_t>(size_query.discrete.height)}}});
343       ++size_query.index;
344     } while (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) >= 0);
345     if (errno != EINVAL) {
346       HAL_LOGE("ENUM_FRAMESIZES fails at index %d: %s",
347                size_query.index,
348                strerror(errno));
349       return -ENODEV;
350     }
351   } else {
352     // Continuous/Step-wise: based on the stepwise struct returned by the query.
353     // Fully listing all possible sizes, with large enough range/small enough
354     // step size, may produce far too many potential sizes. Instead, find the
355     // closest to a set of standard sizes.
356     for (const auto size : kStandardSizes) {
357       // Find the closest size, rounding up.
358       uint32_t desired_width = size[0];
359       uint32_t desired_height = size[1];
360       if (desired_width < size_query.stepwise.min_width ||
361           desired_height < size_query.stepwise.min_height) {
362         HAL_LOGV("Standard size %u x %u is too small for format %d",
363                  desired_width,
364                  desired_height,
365                  v4l2_format);
366         continue;
367       } else if (desired_width > size_query.stepwise.max_width &&
368                  desired_height > size_query.stepwise.max_height) {
369         HAL_LOGV("Standard size %u x %u is too big for format %d",
370                  desired_width,
371                  desired_height,
372                  v4l2_format);
373         continue;
374       }
375 
376       // Round up.
377       uint32_t width_steps = (desired_width - size_query.stepwise.min_width +
378                               size_query.stepwise.step_width - 1) /
379                              size_query.stepwise.step_width;
380       uint32_t height_steps = (desired_height - size_query.stepwise.min_height +
381                                size_query.stepwise.step_height - 1) /
382                               size_query.stepwise.step_height;
383       sizes->insert(
384           {{{static_cast<int32_t>(size_query.stepwise.min_width +
385                                   width_steps * size_query.stepwise.step_width),
386              static_cast<int32_t>(size_query.stepwise.min_height +
387                                   height_steps *
388                                       size_query.stepwise.step_height)}}});
389     }
390   }
391   return 0;
392 }
393 
394 // Converts a v4l2_fract with units of seconds to an int64_t with units of ns.
FractToNs(const v4l2_fract & fract)395 inline int64_t FractToNs(const v4l2_fract& fract) {
396   return (1000000000LL * fract.numerator) / fract.denominator;
397 }
398 
GetFormatFrameDurationRange(uint32_t v4l2_format,const std::array<int32_t,2> & size,std::array<int64_t,2> * duration_range)399 int V4L2Wrapper::GetFormatFrameDurationRange(
400     uint32_t v4l2_format,
401     const std::array<int32_t, 2>& size,
402     std::array<int64_t, 2>* duration_range) {
403   // Potentially called so many times logging entry is a bad idea.
404 
405   v4l2_frmivalenum duration_query;
406   memset(&duration_query, 0, sizeof(duration_query));
407   duration_query.pixel_format = v4l2_format;
408   duration_query.width = size[0];
409   duration_query.height = size[1];
410   if (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) < 0) {
411     HAL_LOGE("ENUM_FRAMEINTERVALS failed: %s", strerror(errno));
412     return -ENODEV;
413   }
414 
415   int64_t min = std::numeric_limits<int64_t>::max();
416   int64_t max = std::numeric_limits<int64_t>::min();
417   if (duration_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
418     // Discrete: enumerate all durations using VIDIOC_ENUM_FRAMEINTERVALS.
419     do {
420       min = std::min(min, FractToNs(duration_query.discrete));
421       max = std::max(max, FractToNs(duration_query.discrete));
422       ++duration_query.index;
423     } while (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) >= 0);
424     if (errno != EINVAL) {
425       HAL_LOGE("ENUM_FRAMEINTERVALS fails at index %d: %s",
426                duration_query.index,
427                strerror(errno));
428       return -ENODEV;
429     }
430   } else {
431     // Continuous/Step-wise: simply convert the given min and max.
432     min = FractToNs(duration_query.stepwise.min);
433     max = FractToNs(duration_query.stepwise.max);
434   }
435   (*duration_range)[0] = min;
436   (*duration_range)[1] = max;
437   return 0;
438 }
439 
SetFormat(const StreamFormat & desired_format,uint32_t * result_max_buffers)440 int V4L2Wrapper::SetFormat(const StreamFormat& desired_format,
441                            uint32_t* result_max_buffers) {
442   HAL_LOG_ENTER();
443 
444   if (format_ && desired_format == *format_) {
445     HAL_LOGV("Already in correct format, skipping format setting.");
446     *result_max_buffers = buffers_.size();
447     return 0;
448   }
449 
450   // Not in the correct format, set the new one.
451 
452   if (format_) {
453     // If we had an old format, first request 0 buffers to inform the device
454     // we're no longer using any previously "allocated" buffers from the old
455     // format. This seems like it shouldn't be necessary for USERPTR memory,
456     // and/or should happen from turning the stream off, but the driver
457     // complained. May be a driver issue, or may be intended behavior.
458     int res = RequestBuffers(0);
459     if (res) {
460       return res;
461     }
462   }
463 
464   // Set the camera to the new format.
465   v4l2_format new_format;
466   desired_format.FillFormatRequest(&new_format);
467   // TODO(b/29334616): When async, this will need to check if the stream
468   // is on, and if so, lock it off while setting format.
469 
470   if (IoctlLocked(VIDIOC_S_FMT, &new_format) < 0) {
471     HAL_LOGE("S_FMT failed: %s", strerror(errno));
472     return -ENODEV;
473   }
474 
475   // Check that the driver actually set to the requested values.
476   if (desired_format != new_format) {
477     HAL_LOGE("Device doesn't support desired stream configuration.");
478     return -EINVAL;
479   }
480 
481   // Keep track of our new format.
482   format_.reset(new StreamFormat(new_format));
483 
484   // Format changed, request new buffers.
485   int res = RequestBuffers(1);
486   if (res) {
487     HAL_LOGE("Requesting buffers for new format failed.");
488     return res;
489   }
490   *result_max_buffers = buffers_.size();
491   return 0;
492 }
493 
RequestBuffers(uint32_t num_requested)494 int V4L2Wrapper::RequestBuffers(uint32_t num_requested) {
495   v4l2_requestbuffers req_buffers;
496   memset(&req_buffers, 0, sizeof(req_buffers));
497   req_buffers.type = format_->type();
498   req_buffers.memory = V4L2_MEMORY_USERPTR;
499   req_buffers.count = num_requested;
500 
501   int res = IoctlLocked(VIDIOC_REQBUFS, &req_buffers);
502   // Calling REQBUFS releases all queued buffers back to the user.
503   int gralloc_res = gralloc_->unlockAllBuffers();
504   if (res < 0) {
505     HAL_LOGE("REQBUFS failed: %s", strerror(errno));
506     return -ENODEV;
507   }
508   if (gralloc_res < 0) {
509     HAL_LOGE("Failed to unlock all buffers when setting up new buffers.");
510     return gralloc_res;
511   }
512 
513   // V4L2 will set req_buffers.count to a number of buffers it can handle.
514   if (num_requested > 0 && req_buffers.count < 1) {
515     HAL_LOGE("REQBUFS claims it can't handle any buffers.");
516     return -ENODEV;
517   }
518   buffers_.resize(req_buffers.count, false);
519 
520   return 0;
521 }
522 
EnqueueBuffer(const camera3_stream_buffer_t * camera_buffer,uint32_t * enqueued_index)523 int V4L2Wrapper::EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer,
524                                uint32_t* enqueued_index) {
525   if (!format_) {
526     HAL_LOGE("Stream format must be set before enqueuing buffers.");
527     return -ENODEV;
528   }
529 
530   // Find a free buffer index. Could use some sort of persistent hinting
531   // here to improve expected efficiency, but buffers_.size() is expected
532   // to be low enough (<10 experimentally) that it's not worth it.
533   int index = -1;
534   {
535     std::lock_guard<std::mutex> guard(buffer_queue_lock_);
536     for (int i = 0; i < buffers_.size(); ++i) {
537       if (!buffers_[i]) {
538         index = i;
539         break;
540       }
541     }
542   }
543   if (index < 0) {
544     // Note: The HAL should be tracking the number of buffers in flight
545     // for each stream, and should never overflow the device.
546     HAL_LOGE("Cannot enqueue buffer: stream is already full.");
547     return -ENODEV;
548   }
549 
550   // Set up a v4l2 buffer struct.
551   v4l2_buffer device_buffer;
552   memset(&device_buffer, 0, sizeof(device_buffer));
553   device_buffer.type = format_->type();
554   device_buffer.index = index;
555 
556   // Use QUERYBUF to ensure our buffer/device is in good shape,
557   // and fill out remaining fields.
558   if (IoctlLocked(VIDIOC_QUERYBUF, &device_buffer) < 0) {
559     HAL_LOGE("QUERYBUF fails: %s", strerror(errno));
560     return -ENODEV;
561   }
562 
563   // Lock the buffer for writing (fills in the user pointer field).
564   int res =
565       gralloc_->lock(camera_buffer, format_->bytes_per_line(), &device_buffer);
566   if (res) {
567     HAL_LOGE("Gralloc failed to lock buffer.");
568     return res;
569   }
570   if (IoctlLocked(VIDIOC_QBUF, &device_buffer) < 0) {
571     HAL_LOGE("QBUF fails: %s", strerror(errno));
572     gralloc_->unlock(&device_buffer);
573     return -ENODEV;
574   }
575 
576   // Mark the buffer as in flight.
577   std::lock_guard<std::mutex> guard(buffer_queue_lock_);
578   buffers_[index] = true;
579 
580   if (enqueued_index) {
581     *enqueued_index = index;
582   }
583   return 0;
584 }
585 
DequeueBuffer(uint32_t * dequeued_index)586 int V4L2Wrapper::DequeueBuffer(uint32_t* dequeued_index) {
587   if (!format_) {
588     HAL_LOGV(
589         "Format not set, so stream can't be on, "
590         "so no buffers available for dequeueing");
591     return -EAGAIN;
592   }
593 
594   v4l2_buffer buffer;
595   memset(&buffer, 0, sizeof(buffer));
596   buffer.type = format_->type();
597   buffer.memory = V4L2_MEMORY_USERPTR;
598   int res = IoctlLocked(VIDIOC_DQBUF, &buffer);
599   if (res) {
600     if (errno == EAGAIN) {
601       // Expected failure.
602       return -EAGAIN;
603     } else {
604       // Unexpected failure.
605       HAL_LOGE("DQBUF fails: %s", strerror(errno));
606       return -ENODEV;
607     }
608   }
609 
610   // Mark the buffer as no longer in flight.
611   {
612     std::lock_guard<std::mutex> guard(buffer_queue_lock_);
613     buffers_[buffer.index] = false;
614   }
615 
616   // Now that we're done painting the buffer, we can unlock it.
617   res = gralloc_->unlock(&buffer);
618   if (res) {
619     HAL_LOGE("Gralloc failed to unlock buffer after dequeueing.");
620     return res;
621   }
622 
623   if (dequeued_index) {
624     *dequeued_index = buffer.index;
625   }
626   return 0;
627 }
628 
629 }  // namespace v4l2_camera_hal
630