diff --git a/media/capture/video/apple/video_capture_device_apple.h b/media/capture/video/apple/video_capture_device_apple.h index 8b23902f42ac9..93a3a0ba206e2 100644 --- a/media/capture/video/apple/video_capture_device_apple.h +++ b/media/capture/video/apple/video_capture_device_apple.h @@ -15,6 +15,8 @@ #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" +#include "base/synchronization/lock.h" +#include "base/thread_annotations.h" #include "base/time/time.h" #include "media/capture/video/apple/video_capture_device_frame_receiver.h" #include "media/capture/video/video_capture_device.h" @@ -128,8 +130,11 @@ class CAPTURE_EXPORT VideoCaptureDeviceApple VideoCaptureDeviceAVFoundation* __strong capture_device_; + // Protects |photo_callback_| which is written from |task_runner_| and read + // and consumed from the thread that invokes OnPhotoTaken()/OnPhotoError(). + base::Lock lock_; // To hold on to the TakePhotoCallback while the picture is being taken. - TakePhotoCallback photo_callback_; + TakePhotoCallback photo_callback_ GUARDED_BY(lock_); // Used with Bind and PostTask to ensure that methods aren't called after the // VideoCaptureDeviceApple is destroyed. diff --git a/media/capture/video/apple/video_capture_device_apple.mm b/media/capture/video/apple/video_capture_device_apple.mm index b7899c4ff8c2a..3f9cb71811cdc 100644 --- a/media/capture/video/apple/video_capture_device_apple.mm +++ b/media/capture/video/apple/video_capture_device_apple.mm @@ -130,11 +130,13 @@ void VideoCaptureDeviceApple::TakePhoto(TakePhotoCallback callback) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(state_ == kCapturing) << state_; - if (photo_callback_) { // Only one picture can be in flight at a time. - return; + { + base::AutoLock auto_lock(lock_); + if (photo_callback_) { // Only one picture can be in flight at a time. + return; + } + photo_callback_ = std::move(callback); } - - photo_callback_ = std::move(callback); [capture_device_ takePhoto]; } @@ -264,20 +266,26 @@ void VideoCaptureDeviceApple::ReceiveExternalGpuMemoryBufferFrame( void VideoCaptureDeviceApple::OnPhotoTaken(const uint8_t* image_data, size_t image_length, const std::string& mime_type) { - DCHECK(photo_callback_); + TakePhotoCallback callback; + { + base::AutoLock auto_lock(lock_); + callback = std::move(photo_callback_); + } + DCHECK(callback); if (!image_data || !image_length) { - OnPhotoError(); + VLOG(1) << __func__ << " error taking picture"; return; } mojom::BlobPtr blob = mojom::Blob::New(); blob->data.assign(image_data, UNSAFE_TODO(image_data + image_length)); blob->mime_type = mime_type; - std::move(photo_callback_).Run(std::move(blob)); + std::move(callback).Run(std::move(blob)); } void VideoCaptureDeviceApple::OnPhotoError() { VLOG(1) << __func__ << " error taking picture"; + base::AutoLock auto_lock(lock_); photo_callback_.Reset(); } diff --git a/media/capture/video/apple/video_capture_device_apple_unittest.mm b/media/capture/video/apple/video_capture_device_apple_unittest.mm index 59554e2ac1ba5..b966435e95b62 100644 --- a/media/capture/video/apple/video_capture_device_apple_unittest.mm +++ b/media/capture/video/apple/video_capture_device_apple_unittest.mm @@ -9,8 +9,10 @@ #import "base/memory/ref_counted.h" #import "base/memory/scoped_refptr.h" #import "base/run_loop.h" +#include "base/synchronization/waitable_event.h" #include "base/test/bind.h" #include "base/test/gmock_callback_support.h" +#include "base/threading/thread.h" #include "media/capture/video/apple/test/fake_av_capture_device_format.h" #import "media/capture/video/apple/test/video_capture_test_utils.h" #include "media/capture/video/apple/video_capture_device_avfoundation.h" @@ -136,6 +138,36 @@ TEST(VideoCaptureDeviceMacTest, FindBestCaptureFormat) { EXPECT_EQ(result, fmt_640_480_2vuy_30); } +// OnPhotoTaken() and OnPhotoError() are documented as safe to call from any +// thread. Exercise OnPhotoError() concurrently from the device task runner and +// a background thread to ensure the in-flight TakePhoto callback is accessed +// safely. +TEST(VideoCaptureDeviceMacTest, ConcurrentOnPhotoErrorIsThreadSafe) { + RunTestCase(base::BindOnce([] { + constexpr int kIterations = 1000; + VideoCaptureDeviceDescriptor descriptor; + auto device = std::make_unique(descriptor); + VideoCaptureDeviceAVFoundationFrameReceiver* frame_receiver = device.get(); + + base::Thread other_thread("OnPhotoErrorTestThread"); + ASSERT_TRUE(other_thread.Start()); + + base::WaitableEvent done; + other_thread.task_runner()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&] { + for (int i = 0; i < kIterations; ++i) { + frame_receiver->OnPhotoError(); + } + done.Signal(); + })); + for (int i = 0; i < kIterations; ++i) { + frame_receiver->OnPhotoError(); + } + done.Wait(); + other_thread.Stop(); + })); +} + class MockImageCaptureClient : public base::RefCountedThreadSafe { public: