Commit 5a18d024 authored by xianglu's avatar xianglu Committed by Commit bot

ImageCapture: Implement TakePhoto() for Linux/CrOs

This CL implements ImageCapture's takePhoto() method by enqueueing a
callback every time the method is called. Check the queue when a new
frame is captured in DoCapture(), and execute callbacks with a blob
argument.

Add a new method GetPhotoBlob() to construct blob. If the source image
is originally in JPEG format, it's copied to blob directly. Otherwise
convert the source image to RGB format and encode it to PNG.

R=mcasas@chromium.org
BUG=646430
TEST=All tests in VideoCaptureDeviceTests passed.

Review-Url: https://codereview.chromium.org/2344123002
Cr-Commit-Position: refs/heads/master@{#419484}
parent a9a4ec98
...@@ -31,6 +31,7 @@ component("capture") { ...@@ -31,6 +31,7 @@ component("capture") {
"video/file_video_capture_device_factory.cc", "video/file_video_capture_device_factory.cc",
"video/file_video_capture_device_factory.h", "video/file_video_capture_device_factory.h",
"video/linux/v4l2_capture_delegate.cc", "video/linux/v4l2_capture_delegate.cc",
"video/linux/v4l2_capture_delegate.h",
"video/linux/video_capture_device_chromeos.cc", "video/linux/video_capture_device_chromeos.cc",
"video/linux/video_capture_device_chromeos.h", "video/linux/video_capture_device_chromeos.h",
"video/linux/video_capture_device_factory_linux.cc", "video/linux/video_capture_device_factory_linux.cc",
...@@ -131,6 +132,10 @@ component("capture") { ...@@ -131,6 +132,10 @@ component("capture") {
# TODO(jschuh): https://crbug.com/167187 fix size_t to int truncations. # TODO(jschuh): https://crbug.com/167187 fix size_t to int truncations.
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
} }
if (is_linux || is_chromeos) {
deps += [ "//third_party/libyuv" ]
}
} }
test("capture_unittests") { test("capture_unittests") {
......
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "media/base/bind_to_current_loop.h" #include "media/base/bind_to_current_loop.h"
#include "media/capture/video/linux/video_capture_device_linux.h" #include "media/capture/video/linux/video_capture_device_linux.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/gfx/codec/png_codec.h"
namespace media { namespace media {
...@@ -321,6 +324,12 @@ void V4L2CaptureDelegate::StopAndDeAllocate() { ...@@ -321,6 +324,12 @@ void V4L2CaptureDelegate::StopAndDeAllocate() {
client_.reset(); client_.reset();
} }
void V4L2CaptureDelegate::TakePhoto(
VideoCaptureDevice::TakePhotoCallback callback) {
DCHECK(v4l2_task_runner_->BelongsToCurrentThread());
take_photo_callbacks_.push(std::move(callback));
}
void V4L2CaptureDelegate::SetRotation(int rotation) { void V4L2CaptureDelegate::SetRotation(int rotation) {
DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); DCHECK(v4l2_task_runner_->BelongsToCurrentThread());
DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0); DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0);
...@@ -401,6 +410,16 @@ void V4L2CaptureDelegate::DoCapture() { ...@@ -401,6 +410,16 @@ void V4L2CaptureDelegate::DoCapture() {
buffer_tracker->start(), buffer_tracker->payload_size(), buffer_tracker->start(), buffer_tracker->payload_size(),
capture_format_, rotation_, base::TimeTicks::Now(), timestamp); capture_format_, rotation_, base::TimeTicks::Now(), timestamp);
while (!take_photo_callbacks_.empty()) {
VideoCaptureDevice::TakePhotoCallback cb =
std::move(take_photo_callbacks_.front());
take_photo_callbacks_.pop();
mojom::BlobPtr blob = GetPhotoBlob(buffer_tracker, buffer.bytesused);
if (blob)
cb.Run(std::move(blob));
}
if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) { if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) {
SetErrorState(FROM_HERE, "Failed to enqueue capture buffer"); SetErrorState(FROM_HERE, "Failed to enqueue capture buffer");
return; return;
...@@ -411,6 +430,59 @@ void V4L2CaptureDelegate::DoCapture() { ...@@ -411,6 +430,59 @@ void V4L2CaptureDelegate::DoCapture() {
FROM_HERE, base::Bind(&V4L2CaptureDelegate::DoCapture, this)); FROM_HERE, base::Bind(&V4L2CaptureDelegate::DoCapture, this));
} }
mojom::BlobPtr V4L2CaptureDelegate::GetPhotoBlob(
const scoped_refptr<BufferTracker>& buffer_tracker,
const uint32_t bytesused) {
uint32 src_format;
if (capture_format_.pixel_format == VideoPixelFormat::PIXEL_FORMAT_MJPEG) {
mojom::BlobPtr blob = mojom::Blob::New();
blob->data.resize(bytesused);
memcpy(blob->data.data(), buffer_tracker->start(), bytesused);
blob->mime_type = "image/jpeg";
return blob;
} else if (capture_format_.pixel_format ==
VideoPixelFormat::PIXEL_FORMAT_UYVY) {
src_format = libyuv::FOURCC_UYVY;
} else if (capture_format_.pixel_format ==
VideoPixelFormat::PIXEL_FORMAT_YUY2) {
src_format = libyuv::FOURCC_YUY2;
} else if (capture_format_.pixel_format ==
VideoPixelFormat::PIXEL_FORMAT_I420) {
src_format = libyuv::FOURCC_I420;
} else if (capture_format_.pixel_format ==
VideoPixelFormat::PIXEL_FORMAT_RGB24) {
src_format = libyuv::FOURCC_24BG;
} else {
return nullptr;
}
std::unique_ptr<uint8_t[]> dst_argb(new uint8_t[VideoFrame::AllocationSize(
PIXEL_FORMAT_ARGB, capture_format_.frame_size)]);
if (ConvertToARGB(buffer_tracker->start(), bytesused, dst_argb.get(),
capture_format_.frame_size.width() * 4, 0 /* crop_x_pos */,
0 /* crop_y_pos */, capture_format_.frame_size.width(),
capture_format_.frame_size.height(),
capture_format_.frame_size.width(),
capture_format_.frame_size.height(),
libyuv::RotationMode::kRotate0, src_format) != 0) {
return nullptr;
}
mojom::BlobPtr blob = mojom::Blob::New();
const gfx::PNGCodec::ColorFormat codec_color_format =
(kN32_SkColorType == kRGBA_8888_SkColorType) ? gfx::PNGCodec::FORMAT_RGBA
: gfx::PNGCodec::FORMAT_BGRA;
const bool result = gfx::PNGCodec::Encode(
dst_argb.get(), codec_color_format, capture_format_.frame_size,
capture_format_.frame_size.width() * 4, true /* discard_transparency */,
std::vector<gfx::PNGCodec::Comment>(), &blob->data);
DCHECK(result);
blob->mime_type = "image/png";
return blob;
}
void V4L2CaptureDelegate::SetErrorState( void V4L2CaptureDelegate::SetErrorState(
const tracked_objects::Location& from_here, const tracked_objects::Location& from_here,
const std::string& reason) { const std::string& reason) {
......
...@@ -55,6 +55,8 @@ class V4L2CaptureDelegate final ...@@ -55,6 +55,8 @@ class V4L2CaptureDelegate final
std::unique_ptr<VideoCaptureDevice::Client> client); std::unique_ptr<VideoCaptureDevice::Client> client);
void StopAndDeAllocate(); void StopAndDeAllocate();
void TakePhoto(VideoCaptureDevice::TakePhotoCallback callback);
void SetRotation(int rotation); void SetRotation(int rotation);
private: private:
...@@ -67,6 +69,11 @@ class V4L2CaptureDelegate final ...@@ -67,6 +69,11 @@ class V4L2CaptureDelegate final
void DoCapture(); void DoCapture();
class BufferTracker;
mojom::BlobPtr GetPhotoBlob(
const scoped_refptr<BufferTracker>& buffer_tracker,
const uint32_t bytesused);
void SetErrorState(const tracked_objects::Location& from_here, void SetErrorState(const tracked_objects::Location& from_here,
const std::string& reason); const std::string& reason);
...@@ -80,8 +87,9 @@ class V4L2CaptureDelegate final ...@@ -80,8 +87,9 @@ class V4L2CaptureDelegate final
std::unique_ptr<VideoCaptureDevice::Client> client_; std::unique_ptr<VideoCaptureDevice::Client> client_;
base::ScopedFD device_fd_; base::ScopedFD device_fd_;
std::queue<VideoCaptureDevice::TakePhotoCallback> take_photo_callbacks_;
// Vector of BufferTracker to keep track of mmap()ed pointers and their use. // Vector of BufferTracker to keep track of mmap()ed pointers and their use.
class BufferTracker;
std::vector<scoped_refptr<BufferTracker>> buffer_tracker_pool_; std::vector<scoped_refptr<BufferTracker>> buffer_tracker_pool_;
bool is_capturing_; bool is_capturing_;
......
...@@ -79,7 +79,16 @@ void VideoCaptureDeviceLinux::StopAndDeAllocate() { ...@@ -79,7 +79,16 @@ void VideoCaptureDeviceLinux::StopAndDeAllocate() {
base::Bind(&V4L2CaptureDelegate::StopAndDeAllocate, capture_impl_)); base::Bind(&V4L2CaptureDelegate::StopAndDeAllocate, capture_impl_));
v4l2_thread_.Stop(); v4l2_thread_.Stop();
capture_impl_ = NULL; capture_impl_ = nullptr;
}
void VideoCaptureDeviceLinux::TakePhoto(TakePhotoCallback callback) {
DCHECK(capture_impl_);
if (!v4l2_thread_.IsRunning())
return;
v4l2_thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&V4L2CaptureDelegate::TakePhoto, capture_impl_,
base::Passed(&callback)));
} }
void VideoCaptureDeviceLinux::SetRotation(int rotation) { void VideoCaptureDeviceLinux::SetRotation(int rotation) {
......
...@@ -39,6 +39,7 @@ class VideoCaptureDeviceLinux : public VideoCaptureDevice { ...@@ -39,6 +39,7 @@ class VideoCaptureDeviceLinux : public VideoCaptureDevice {
void AllocateAndStart(const VideoCaptureParams& params, void AllocateAndStart(const VideoCaptureParams& params,
std::unique_ptr<Client> client) override; std::unique_ptr<Client> client) override;
void StopAndDeAllocate() override; void StopAndDeAllocate() override;
void TakePhoto(TakePhotoCallback callback) override;
protected: protected:
void SetRotation(int rotation); void SetRotation(int rotation);
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
// http://crbug.com/94134 http://crbug.com/137260 http://crbug.com/417824 // http://crbug.com/94134 http://crbug.com/137260 http://crbug.com/417824
#define MAYBE_AllocateBadSize DISABLED_AllocateBadSize #define MAYBE_AllocateBadSize DISABLED_AllocateBadSize
#define MAYBE_CaptureMjpeg CaptureMjpeg #define MAYBE_CaptureMjpeg CaptureMjpeg
#define MAYBE_TakePhoto DISABLED_TakePhoto #define MAYBE_TakePhoto TakePhoto
#else #else
#define MAYBE_AllocateBadSize AllocateBadSize #define MAYBE_AllocateBadSize AllocateBadSize
#define MAYBE_CaptureMjpeg CaptureMjpeg #define MAYBE_CaptureMjpeg CaptureMjpeg
...@@ -142,15 +142,24 @@ class MockImageCaptureClient : public base::RefCounted<MockImageCaptureClient> { ...@@ -142,15 +142,24 @@ class MockImageCaptureClient : public base::RefCounted<MockImageCaptureClient> {
public: public:
// GMock doesn't support move-only arguments, so we use this forward method. // GMock doesn't support move-only arguments, so we use this forward method.
void DoOnPhotoTaken(mojom::BlobPtr blob) { void DoOnPhotoTaken(mojom::BlobPtr blob) {
EXPECT_STREQ("image/jpeg", blob->mime_type.c_str()); if (strcmp("image/jpeg", blob->mime_type.c_str()) == 0) {
ASSERT_GT(blob->data.size(), 4u); ASSERT_GT(blob->data.size(), 4u);
// Check some bytes that univocally identify |data| as a JPEG File. // Check some bytes that univocally identify |data| as a JPEG File.
// https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
EXPECT_EQ(0xFF, blob->data[0]); // First SOI byte EXPECT_EQ(0xFF, blob->data[0]); // First SOI byte
EXPECT_EQ(0xD8, blob->data[1]); // Second SOI byte EXPECT_EQ(0xD8, blob->data[1]); // Second SOI byte
EXPECT_EQ(0xFF, blob->data[2]); // First JFIF-APP0 byte EXPECT_EQ(0xFF, blob->data[2]); // First JFIF-APP0 byte
EXPECT_EQ(0xE0, blob->data[3] & 0xF0); // Second JFIF-APP0/APP1 byte EXPECT_EQ(0xE0, blob->data[3] & 0xF0); // Second JFIF-APP0 byte
OnCorrectPhotoTaken(); OnCorrectPhotoTaken();
} else if (strcmp("image/png", blob->mime_type.c_str()) == 0) {
ASSERT_GT(blob->data.size(), 4u);
EXPECT_EQ('P', blob->data[1]);
EXPECT_EQ('N', blob->data[2]);
EXPECT_EQ('G', blob->data[3]);
OnCorrectPhotoTaken();
} else {
ADD_FAILURE() << "Photo format should be jpeg or png";
}
} }
MOCK_METHOD0(OnCorrectPhotoTaken, void(void)); MOCK_METHOD0(OnCorrectPhotoTaken, void(void));
MOCK_METHOD1(OnTakePhotoFailure, MOCK_METHOD1(OnTakePhotoFailure,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment