Index: media/video/capture/linux/video_capture_device_linux.cc |
diff --git a/media/video/capture/linux/video_capture_device_linux.cc b/media/video/capture/linux/video_capture_device_linux.cc |
index 500be2c8fff3285669707ca8be10eecbc7a88e52..b9fb195f8ae4b0e670b7c482bd3a5245460f35c8 100644 |
--- a/media/video/capture/linux/video_capture_device_linux.cc |
+++ b/media/video/capture/linux/video_capture_device_linux.cc |
@@ -4,20 +4,100 @@ |
#include "media/video/capture/linux/video_capture_device_linux.h" |
+#include <errno.h> |
+#include <fcntl.h> |
+#include <poll.h> |
#if defined(OS_OPENBSD) |
#include <sys/videoio.h> |
#else |
#include <linux/videodev2.h> |
#endif |
+#include <sys/ioctl.h> |
+#include <sys/mman.h> |
#include <list> |
#include <string> |
#include "base/bind.h" |
+#include "base/files/file_enumerator.h" |
+#include "base/files/scoped_file.h" |
+#include "base/posix/eintr_wrapper.h" |
#include "base/strings/stringprintf.h" |
-#include "media/video/capture/linux/v4l2_capture_delegate.h" |
namespace media { |
+ |
+#define GET_V4L2_FOURCC_CHAR(a, index) ((char)( ((a) >> (8 * index)) & 0xff)) |
+ |
+// Desired number of video buffers to allocate. The actual number of allocated |
+// buffers by v4l2 driver can be higher or lower than this number. |
+// kNumVideoBuffers should not be too small, or Chrome may not return enough |
+// buffers back to driver in time. |
+const uint32 kNumVideoBuffers = 4; |
+// Timeout in milliseconds v4l2_thread_ blocks waiting for a frame from the hw. |
+enum { kCaptureTimeoutMs = 200 }; |
+// The number of continuous timeouts tolerated before treated as error. |
+enum { kContinuousTimeoutLimit = 10 }; |
+// MJPEG is preferred if the width or height is larger than this. |
+enum { kMjpegWidth = 640 }; |
+enum { kMjpegHeight = 480 }; |
+// Typical framerate, in fps |
+enum { kTypicalFramerate = 30 }; |
+ |
+class VideoCaptureDeviceLinux::V4L2CaptureDelegate |
+ : public base::RefCountedThreadSafe<V4L2CaptureDelegate>{ |
+ public: |
+ V4L2CaptureDelegate( |
+ const Name& device_name, |
+ const scoped_refptr<base::SingleThreadTaskRunner> v4l2_task_runner, |
+ int power_line_frequency); |
+ |
+ void AllocateAndStart(int width, |
+ int height, |
+ float frame_rate, |
+ scoped_ptr<Client> client); |
+ void StopAndDeAllocate(); |
+ void SetRotation(int rotation); |
+ bool DeAllocateVideoBuffers(); |
+ |
+ private: |
+ // Buffers used to receive captured frames from v4l2. |
+ struct Buffer { |
+ Buffer() : start(0), length(0) {} |
+ void* start; |
+ size_t length; |
+ }; |
+ |
+ friend class base::RefCountedThreadSafe<V4L2CaptureDelegate>; |
+ ~V4L2CaptureDelegate(); |
+ |
+ void DoCapture(); |
+ bool AllocateVideoBuffers(); |
+ void SetErrorState(const std::string& reason); |
+ |
+ const scoped_refptr<base::SingleThreadTaskRunner> v4l2_task_runner_; |
+ |
+ bool is_capturing_; |
+ scoped_ptr<VideoCaptureDevice::Client> client_; |
+ const Name device_name_; |
+ base::ScopedFD device_fd_; // File descriptor for the opened camera device. |
+ Buffer* buffer_pool_; |
+ int buffer_pool_size_; // Number of allocated buffers. |
+ int timeout_count_; |
+ VideoCaptureFormat capture_format_; |
+ const int power_line_frequency_; |
+ |
+ // Clockwise rotation in degrees. This value should be 0, 90, 180, or 270. |
+ int rotation_; |
+ |
+ DISALLOW_IMPLICIT_CONSTRUCTORS(V4L2CaptureDelegate); |
+}; |
+ |
+// V4L2 color formats VideoCaptureDeviceLinux support. |
+static const int32 kV4l2RawFmts[] = { |
+ V4L2_PIX_FMT_YUV420, |
+ V4L2_PIX_FMT_YUYV, |
+ V4L2_PIX_FMT_UYVY |
+}; |
// USB VID and PID are both 4 bytes long. |
static const size_t kVidPidSize = 4; |
@@ -42,18 +122,48 @@ |
return true; |
} |
-// Translates Video4Linux pixel formats to Chromium pixel formats. |
+// This function translates Video4Linux pixel formats to Chromium pixel formats, |
+// should only support those listed in GetListOfUsableFourCCs. |
// static |
VideoPixelFormat VideoCaptureDeviceLinux::V4l2FourCcToChromiumPixelFormat( |
uint32 v4l2_fourcc) { |
- return V4L2CaptureDelegate::V4l2FourCcToChromiumPixelFormat(v4l2_fourcc); |
-} |
- |
-// Gets a list of usable Four CC formats prioritised. |
+ const struct { |
+ uint32 fourcc; |
+ VideoPixelFormat pixel_format; |
+ } kFourCcAndChromiumPixelFormat[] = { |
+ {V4L2_PIX_FMT_YUV420, PIXEL_FORMAT_I420}, |
+ {V4L2_PIX_FMT_YUYV, PIXEL_FORMAT_YUY2}, |
+ {V4L2_PIX_FMT_UYVY, PIXEL_FORMAT_UYVY}, |
+ {V4L2_PIX_FMT_MJPEG, PIXEL_FORMAT_MJPEG}, |
+ {V4L2_PIX_FMT_JPEG, PIXEL_FORMAT_MJPEG}, |
+ }; |
+ for (const auto& fourcc_and_pixel_format : kFourCcAndChromiumPixelFormat) { |
+ if (fourcc_and_pixel_format.fourcc == v4l2_fourcc) |
+ return fourcc_and_pixel_format.pixel_format; |
+ } |
+ DVLOG(1) << "Unsupported pixel format: " |
+ << GET_V4L2_FOURCC_CHAR(v4l2_fourcc, 0) |
+ << GET_V4L2_FOURCC_CHAR(v4l2_fourcc, 1) |
+ << GET_V4L2_FOURCC_CHAR(v4l2_fourcc, 2) |
+ << GET_V4L2_FOURCC_CHAR(v4l2_fourcc, 3); |
+ return PIXEL_FORMAT_UNKNOWN; |
+} |
+ |
// static |
-std::list<uint32_t> VideoCaptureDeviceLinux::GetListOfUsableFourCCs( |
+std::list<int> VideoCaptureDeviceLinux::GetListOfUsableFourCCs( |
bool favour_mjpeg) { |
- return V4L2CaptureDelegate::GetListOfUsableFourCcs(favour_mjpeg); |
+ std::list<int> fourccs; |
+ for (size_t i = 0; i < arraysize(kV4l2RawFmts); ++i) |
+ fourccs.push_back(kV4l2RawFmts[i]); |
+ if (favour_mjpeg) |
+ fourccs.push_front(V4L2_PIX_FMT_MJPEG); |
+ else |
+ fourccs.push_back(V4L2_PIX_FMT_MJPEG); |
+ |
+ // JPEG works as MJPEG on some gspca webcams from field reports. |
+ // Put it as the least preferred format. |
+ fourccs.push_back(V4L2_PIX_FMT_JPEG); |
+ return fourccs; |
} |
const std::string VideoCaptureDevice::Name::GetModel() const { |
@@ -97,21 +207,18 @@ |
if (v4l2_thread_.IsRunning()) |
return; // Wrong state. |
v4l2_thread_.Start(); |
- |
- const int line_frequency = |
- TranslatePowerLineFrequencyToV4L2(GetPowerLineFrequencyForLocation()); |
- capture_impl_ = V4L2CaptureDelegate::CreateV4L2CaptureDelegate( |
- device_name_, v4l2_thread_.message_loop_proxy(), line_frequency); |
- if (!capture_impl_) { |
- client->OnError("Failed to create VideoCaptureDelegate"); |
- return; |
- } |
+ capture_impl_ = new V4L2CaptureDelegate(device_name_, |
+ v4l2_thread_.message_loop_proxy(), |
+ GetPowerLineFrequencyForLocation()); |
v4l2_thread_.message_loop()->PostTask( |
FROM_HERE, |
- base::Bind(&V4L2CaptureDelegate::AllocateAndStart, capture_impl_, |
- params.requested_format.frame_size.width(), |
- params.requested_format.frame_size.height(), |
- params.requested_format.frame_rate, base::Passed(&client))); |
+ base::Bind( |
+ &VideoCaptureDeviceLinux::V4L2CaptureDelegate::AllocateAndStart, |
+ capture_impl_, |
+ params.requested_format.frame_size.width(), |
+ params.requested_format.frame_size.height(), |
+ params.requested_format.frame_rate, |
+ base::Passed(&client))); |
} |
void VideoCaptureDeviceLinux::StopAndDeAllocate() { |
@@ -119,31 +226,309 @@ |
return; // Wrong state. |
v4l2_thread_.message_loop()->PostTask( |
FROM_HERE, |
- base::Bind(&V4L2CaptureDelegate::StopAndDeAllocate, capture_impl_)); |
+ base::Bind( |
+ &VideoCaptureDeviceLinux::V4L2CaptureDelegate::StopAndDeAllocate, |
+ capture_impl_)); |
v4l2_thread_.Stop(); |
- |
+ // TODO(mcasas): VCDLinux called DeAllocateVideoBuffers() a second time after |
+ // stopping |v4l2_thread_| to make sure buffers were completely deallocated. |
+ // Investigate if that's needed, otherwise remove the following line and make |
+ // V4L2CaptureDelegate::DeAllocateVideoBuffers() private. |
+ capture_impl_->DeAllocateVideoBuffers(); |
capture_impl_ = NULL; |
} |
void VideoCaptureDeviceLinux::SetRotation(int rotation) { |
if (v4l2_thread_.IsRunning()) { |
v4l2_thread_.message_loop()->PostTask( |
- FROM_HERE, base::Bind(&V4L2CaptureDelegate::SetRotation, |
- capture_impl_, rotation)); |
- } |
-} |
- |
-// static |
-int VideoCaptureDeviceLinux::TranslatePowerLineFrequencyToV4L2(int frequency) { |
- switch (frequency) { |
- case kPowerLine50Hz: |
- return V4L2_CID_POWER_LINE_FREQUENCY_50HZ; |
- case kPowerLine60Hz: |
- return V4L2_CID_POWER_LINE_FREQUENCY_60HZ; |
- default: |
- // If we have no idea of the frequency, at least try and set it to AUTO. |
- return V4L2_CID_POWER_LINE_FREQUENCY_AUTO; |
- } |
+ FROM_HERE, |
+ base::Bind( |
+ &VideoCaptureDeviceLinux::V4L2CaptureDelegate::SetRotation, |
+ capture_impl_, |
+ rotation)); |
+ } |
+} |
+ |
+VideoCaptureDeviceLinux::V4L2CaptureDelegate::V4L2CaptureDelegate( |
+ const Name& device_name, |
+ const scoped_refptr<base::SingleThreadTaskRunner> v4l2_task_runner, |
+ int power_line_frequency) |
+ : v4l2_task_runner_(v4l2_task_runner), |
+ is_capturing_(false), |
+ device_name_(device_name), |
+ buffer_pool_(NULL), |
+ buffer_pool_size_(0), |
+ timeout_count_(0), |
+ power_line_frequency_(power_line_frequency), |
+ rotation_(0) { |
+} |
+ |
+VideoCaptureDeviceLinux::V4L2CaptureDelegate::~V4L2CaptureDelegate() { |
+ DCHECK(!client_); |
+} |
+ |
+void VideoCaptureDeviceLinux::V4L2CaptureDelegate::AllocateAndStart( |
+ int width, |
+ int height, |
+ float frame_rate, |
+ scoped_ptr<Client> client) { |
+ DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
+ DCHECK(client); |
+ client_ = client.Pass(); |
+ |
+ // Need to open camera with O_RDWR after Linux kernel 3.3. |
+ device_fd_.reset(HANDLE_EINTR(open(device_name_.id().c_str(), O_RDWR))); |
+ if (!device_fd_.is_valid()) { |
+ SetErrorState("Failed to open V4L2 device driver file."); |
+ return; |
+ } |
+ |
+ v4l2_capability cap = {}; |
+ if (!((HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QUERYCAP, &cap)) == 0) && |
+ (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE && |
+ !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)))) { |
+ device_fd_.reset(); |
+ SetErrorState("This is not a V4L2 video capture device"); |
+ return; |
+ } |
+ |
+ // Get supported video formats in preferred order. |
+ // For large resolutions, favour mjpeg over raw formats. |
+ const std::list<int>& desired_v4l2_formats = |
+ GetListOfUsableFourCCs(width > kMjpegWidth || height > kMjpegHeight); |
+ std::list<int>::const_iterator best = desired_v4l2_formats.end(); |
+ |
+ v4l2_fmtdesc fmtdesc = {}; |
+ fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ for (; HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_ENUM_FMT, &fmtdesc)) == 0; |
+ ++fmtdesc.index) { |
+ best = std::find(desired_v4l2_formats.begin(), best, fmtdesc.pixelformat); |
+ } |
+ if (best == desired_v4l2_formats.end()) { |
+ SetErrorState("Failed to find a supported camera format."); |
+ return; |
+ } |
+ |
+ v4l2_format video_fmt = {}; |
+ video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ video_fmt.fmt.pix.sizeimage = 0; |
+ video_fmt.fmt.pix.width = width; |
+ video_fmt.fmt.pix.height = height; |
+ video_fmt.fmt.pix.pixelformat = *best; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_FMT, &video_fmt)) < 0) { |
+ SetErrorState("Failed to set video capture format"); |
+ return; |
+ } |
+ |
+ // Set capture framerate in the form of capture interval. |
+ v4l2_streamparm streamparm = {}; |
+ streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ // The following line checks that the driver knows about framerate get/set. |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_G_PARM, &streamparm)) >= 0) { |
+ // Now check if the device is able to accept a capture framerate set. |
+ if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { |
+ // |frame_rate| is float, approximate by a fraction. |
+ streamparm.parm.capture.timeperframe.numerator = |
+ media::kFrameRatePrecision; |
+ streamparm.parm.capture.timeperframe.denominator = (frame_rate) ? |
+ (frame_rate * media::kFrameRatePrecision) : |
+ (kTypicalFramerate * media::kFrameRatePrecision); |
+ |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_PARM, &streamparm)) < |
+ 0) { |
+ SetErrorState("Failed to set camera framerate"); |
+ return; |
+ } |
+ DVLOG(2) << "Actual camera driverframerate: " |
+ << streamparm.parm.capture.timeperframe.denominator << "/" |
+ << streamparm.parm.capture.timeperframe.numerator; |
+ } |
+ } |
+ // TODO(mcasas): what should be done if the camera driver does not allow |
+ // framerate configuration, or the actual one is different from the desired? |
+ |
+ // Set anti-banding/anti-flicker to 50/60Hz. May fail due to not supported |
+ // operation (|errno| == EINVAL in this case) or plain failure. |
+ if ((power_line_frequency_ == kPowerLine50Hz) || |
+ (power_line_frequency_ == kPowerLine60Hz)) { |
+ struct v4l2_control control = {}; |
+ control.id = V4L2_CID_POWER_LINE_FREQUENCY; |
+ control.value = (power_line_frequency_ == kPowerLine50Hz) |
+ ? V4L2_CID_POWER_LINE_FREQUENCY_50HZ |
+ : V4L2_CID_POWER_LINE_FREQUENCY_60HZ; |
+ HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_CTRL, &control)); |
+ } |
+ |
+ capture_format_.frame_size.SetSize(video_fmt.fmt.pix.width, |
+ video_fmt.fmt.pix.height); |
+ capture_format_.frame_rate = frame_rate; |
+ capture_format_.pixel_format = |
+ V4l2FourCcToChromiumPixelFormat(video_fmt.fmt.pix.pixelformat); |
+ |
+ if (!AllocateVideoBuffers()) { |
+ SetErrorState("Allocate buffer failed (Cannot recover from this error)"); |
+ return; |
+ } |
+ |
+ const v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_STREAMON, &type)) < 0) { |
+ SetErrorState("VIDIOC_STREAMON failed"); |
+ return; |
+ } |
+ |
+ is_capturing_ = true; |
+ // Post task to start fetching frames from v4l2. |
+ v4l2_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&VideoCaptureDeviceLinux::V4L2CaptureDelegate::DoCapture, |
+ this)); |
+} |
+ |
+void VideoCaptureDeviceLinux::V4L2CaptureDelegate::StopAndDeAllocate() { |
+ DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
+ |
+ const v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_STREAMOFF, &type)) < 0) { |
+ SetErrorState("VIDIOC_STREAMOFF failed"); |
+ return; |
+ } |
+ // We don't dare to deallocate the buffers if we can't stop the capture |
+ // device. |
+ if (!DeAllocateVideoBuffers()) |
+ SetErrorState("Failed to reset buffers"); |
+ |
+ // We need to close and open the device if we want to change the settings. |
+ // Otherwise VIDIOC_S_FMT will return error. Sad but true. |
+ device_fd_.reset(); |
+ is_capturing_ = false; |
+ client_.reset(); |
+} |
+ |
+void VideoCaptureDeviceLinux::V4L2CaptureDelegate::SetRotation(int rotation) { |
+ DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
+ DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0); |
+ rotation_ = rotation; |
+} |
+ |
+void VideoCaptureDeviceLinux::V4L2CaptureDelegate::DoCapture() { |
+ DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
+ if (!is_capturing_) |
+ return; |
+ |
+ pollfd device_pfd = {}; |
+ device_pfd.fd = device_fd_.get(); |
+ device_pfd.events = POLLIN; |
+ const int result = HANDLE_EINTR(poll(&device_pfd, 1, kCaptureTimeoutMs)); |
+ if (result < 0) { |
+ SetErrorState("Poll failed"); |
+ return; |
+ } |
+ // Check if poll() timed out; track the amount of times it did in a row and |
+ // throw an error if it times out too many times. |
+ if (result == 0) { |
+ timeout_count_++; |
+ if (timeout_count_ >= kContinuousTimeoutLimit) { |
+ SetErrorState("Multiple continuous timeouts while read-polling."); |
+ timeout_count_ = 0; |
+ return; |
+ } |
+ } else { |
+ timeout_count_ = 0; |
+ } |
+ |
+ // Check if the driver has filled a buffer. |
+ if (device_pfd.revents & POLLIN) { |
+ v4l2_buffer buffer = {}; |
+ buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ buffer.memory = V4L2_MEMORY_MMAP; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_DQBUF, &buffer)) < 0) { |
+ SetErrorState("Failed to dequeue capture buffer"); |
+ return; |
+ } |
+ client_->OnIncomingCapturedData( |
+ static_cast<uint8*>(buffer_pool_[buffer.index].start), |
+ buffer.bytesused, |
+ capture_format_, |
+ rotation_, |
+ base::TimeTicks::Now()); |
+ |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) |
+ SetErrorState("Failed to enqueue capture buffer"); |
+ } |
+ |
+ v4l2_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&VideoCaptureDeviceLinux::V4L2CaptureDelegate::DoCapture, |
+ this)); |
+} |
+ |
+bool VideoCaptureDeviceLinux::V4L2CaptureDelegate::AllocateVideoBuffers() { |
+ v4l2_requestbuffers r_buffer = {}; |
+ r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ r_buffer.memory = V4L2_MEMORY_MMAP; |
+ r_buffer.count = kNumVideoBuffers; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_REQBUFS, &r_buffer)) < 0) { |
+ DLOG(ERROR) << "Error requesting MMAP buffers from V4L2"; |
+ return false; |
+ } |
+ buffer_pool_size_ = r_buffer.count; |
+ buffer_pool_ = new Buffer[buffer_pool_size_]; |
+ for (unsigned int i = 0; i < r_buffer.count; ++i) { |
+ v4l2_buffer buffer = {}; |
+ buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ buffer.memory = V4L2_MEMORY_MMAP; |
+ buffer.index = i; |
+ buffer.length = 1; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QUERYBUF, &buffer)) < 0) { |
+ DLOG(ERROR) << "Error querying status of a MMAP V4L2 buffer"; |
+ return false; |
+ } |
+ |
+ // Some devices require mmap() to be called with both READ and WRITE. |
+ // See http://crbug.com/178582. |
+ buffer_pool_[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, |
+ MAP_SHARED, device_fd_.get(), buffer.m.offset); |
+ if (buffer_pool_[i].start == MAP_FAILED) { |
+ DLOG(ERROR) << "Error mmmap()ing a V4L2 buffer into userspace"; |
+ return false; |
+ } |
+ |
+ buffer_pool_[i].length = buffer.length; |
+ // Enqueue the buffer in the drivers incoming queue. |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) { |
+ DLOG(ERROR) |
+ << "Error enqueuing a V4L2 buffer back to the drivers incoming queue"; |
+ return false; |
+ } |
+ } |
+ return true; |
+} |
+ |
+bool VideoCaptureDeviceLinux::V4L2CaptureDelegate::DeAllocateVideoBuffers() { |
+ if (!buffer_pool_) |
+ return true; |
+ |
+ for (int i = 0; i < buffer_pool_size_; ++i) |
+ munmap(buffer_pool_[i].start, buffer_pool_[i].length); |
+ |
+ v4l2_requestbuffers r_buffer = {}; |
+ r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
+ r_buffer.memory = V4L2_MEMORY_MMAP; |
+ r_buffer.count = 0; |
+ if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_REQBUFS, &r_buffer)) < 0) |
+ return false; |
+ |
+ delete [] buffer_pool_; |
+ buffer_pool_ = NULL; |
+ buffer_pool_size_ = 0; |
+ return true; |
+} |
+ |
+void VideoCaptureDeviceLinux::V4L2CaptureDelegate::SetErrorState( |
+ const std::string& reason) { |
+ DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
+ is_capturing_ = false; |
+ client_->OnError(reason); |
} |
} // namespace media |