media_perception: Multiple input device access.

for the VideoCaptureServiceClient object.

BUG=chromium:911347,b:117872925
TEST=Verified on device that multiple clients can access same device.

Change-Id: I4d35980116c06d53447826538496c4a5ee09b93e
Reviewed-on: https://chromium-review.googlesource.com/1352734
Commit-Ready: Luke Sorenson <lasoren@chromium.org>
Tested-by: Luke Sorenson <lasoren@chromium.org>
Reviewed-by: Rahul Chaturvedi <rkc@chromium.org>
(cherry picked from commit c3de6011ee803c4c2b4239a48ed9962d29da5c79)
Reviewed-on: https://chromium-review.googlesource.com/c/1398864
Reviewed-by: Jacob Dufault <jdufault@chromium.org>
Commit-Queue: Luke Sorenson <lasoren@chromium.org>
diff --git a/media_perception/fake_video_capture_service_client.cc b/media_perception/fake_video_capture_service_client.cc
index 7bee671..6c70a2e 100644
--- a/media_perception/fake_video_capture_service_client.cc
+++ b/media_perception/fake_video_capture_service_client.cc
@@ -25,14 +25,18 @@
   callback(devices_);
 }
 
-void FakeVideoCaptureServiceClient::SetActiveDevice(
+void FakeVideoCaptureServiceClient::OpenDevice(
     const std::string& device_id,
-    const SetActiveDeviceCallback& callback) {}
+    const OpenDeviceCallback& callback) {}
 
-void FakeVideoCaptureServiceClient::StartVideoCapture(
-    const SerializedVideoStreamParams& capture_format) {}
+bool FakeVideoCaptureServiceClient::IsVideoCaptureStartedForDevice(
+    const std::string& device_id,
+    SerializedVideoStreamParams* capture_format) { return false; }
 
-void FakeVideoCaptureServiceClient::StopVideoCapture() {}
+int FakeVideoCaptureServiceClient::AddFrameHandler(
+    const std::string& device_id,
+    const SerializedVideoStreamParams& capture_format,
+    FrameHandler frame_handler) { return 0; }
 
 void FakeVideoCaptureServiceClient::CreateVirtualDevice(
     const SerializedVideoDevice& video_device,
@@ -49,4 +53,9 @@
 void FakeVideoCaptureServiceClient::CloseVirtualDevice(
     const std::string& device_id) {}
 
+bool FakeVideoCaptureServiceClient::RemoveFrameHandler(
+    const std::string& device_id, int frame_handler_id) {
+  return false;
+}
+
 }  // namespace mri
diff --git a/media_perception/fake_video_capture_service_client.h b/media_perception/fake_video_capture_service_client.h
index 8c9b959..651f5a3 100644
--- a/media_perception/fake_video_capture_service_client.h
+++ b/media_perception/fake_video_capture_service_client.h
@@ -24,11 +24,17 @@
   bool Connect() override;
   bool IsConnected() override;
   void GetDevices(const GetDevicesCallback& callback) override;
-  void SetActiveDevice(const std::string& device_id,
-                       const SetActiveDeviceCallback& callback) override;
-  void StartVideoCapture(
-      const SerializedVideoStreamParams& capture_format) override;
-  void StopVideoCapture() override;
+  void OpenDevice(const std::string& device_id,
+                  const OpenDeviceCallback& callback) override;
+  bool IsVideoCaptureStartedForDevice(
+      const std::string& device_id,
+      SerializedVideoStreamParams* capture_format) override;
+  int AddFrameHandler(
+      const std::string& device_id,
+      const SerializedVideoStreamParams& capture_format,
+      FrameHandler handler) override;
+  bool RemoveFrameHandler(
+      const std::string& device_id, int frame_handler_id) override;
   void CreateVirtualDevice(const SerializedVideoDevice& video_device,
                            const VirtualDeviceCallback& callback) override;
   void PushFrameToVirtualDevice(const std::string& device_id,
diff --git a/media_perception/media_perception_controller_impl.h b/media_perception/media_perception_controller_impl.h
index ecf2bc3..6344fad 100644
--- a/media_perception/media_perception_controller_impl.h
+++ b/media_perception/media_perception_controller_impl.h
@@ -8,8 +8,8 @@
 #include <memory>
 #include <mojo/public/cpp/bindings/binding.h>
 
-#include "media_perception/video_capture_service_client.h"
 #include "media_perception/rtanalytics.h"
+#include "media_perception/video_capture_service_client.h"
 #include "mojom/media_perception_service.mojom.h"
 
 namespace mri {
diff --git a/media_perception/mojo_connector.cc b/media_perception/mojo_connector.cc
index 1cd4a42..06b56e8 100644
--- a/media_perception/mojo_connector.cc
+++ b/media_perception/mojo_connector.cc
@@ -126,6 +126,8 @@
 }
 
 void MojoConnector::ConnectToVideoCaptureServiceOnIpcThread() {
+  unique_device_counter_ = 1;
+
   media_perception_service_impl_->ConnectToVideoCaptureService(
       mojo::MakeRequest(&device_factory_));
   device_factory_.set_connection_error_handler(
@@ -206,51 +208,76 @@
   callback(devices);
 }
 
-void MojoConnector::SetActiveDevice(
-    std::string device_id,
-    const VideoCaptureServiceClient::SetActiveDeviceCallback& callback) {
+void MojoConnector::OpenDevice(
+    const std::string& device_id,
+    const VideoCaptureServiceClient::OpenDeviceCallback& callback) {
   ipc_thread_.task_runner()->PostTask(
-      FROM_HERE, base::Bind(&MojoConnector::SetActiveDeviceOnIpcThread,
+      FROM_HERE, base::Bind(&MojoConnector::OpenDeviceOnIpcThread,
                             base::Unretained(this), device_id, callback));
 }
 
-void MojoConnector::SetActiveDeviceOnIpcThread(
-    std::string device_id,
-    const VideoCaptureServiceClient::SetActiveDeviceCallback& callback) {
+void MojoConnector::OpenDeviceOnIpcThread(
+    const std::string& device_id,
+    const VideoCaptureServiceClient::OpenDeviceCallback& callback) {
   std::map<std::string, std::string>::iterator it =
       obfuscated_device_id_map_.find(device_id);
   if (it == obfuscated_device_id_map_.end()) {
     LOG(ERROR) << "Device id not found in obfuscated_device_id map.";
     return;
   }
+
+  std::map<std::string, video_capture::mojom::DevicePtr>::iterator
+      device_it = device_id_to_active_device_map_.find(device_id);
+  // Check to see if the device is already successfully opened.
+  if (device_it != device_id_to_active_device_map_.end()) {
+    callback(DeviceAccessResultCode::SUCCESS);
+    return;
+  }
+
+  video_capture::mojom::DevicePtr new_device;
   device_factory_->CreateDevice(
-      it->second, mojo::MakeRequest(&active_device_),
-      base::Bind(&MojoConnector::OnSetActiveDeviceCallback,
-                 base::Unretained(this), callback));
+      it->second, mojo::MakeRequest(&new_device),
+      base::Bind(&MojoConnector::OnOpenDeviceCallback,
+                 base::Unretained(this), device_id, callback));
+  device_id_to_active_device_map_.insert(
+      std::make_pair(device_id, std::move(new_device)));
 }
 
-void MojoConnector::OnSetActiveDeviceCallback(
-    const VideoCaptureServiceClient::SetActiveDeviceCallback& callback,
+void MojoConnector::OnOpenDeviceCallback(
+    const std::string& device_id,
+    const VideoCaptureServiceClient::OpenDeviceCallback& callback,
     video_capture::mojom::DeviceAccessResultCode code) {
+  // Delete the device from the active device map if we were not successful in
+  // opening it.
+  if (code != video_capture::mojom::DeviceAccessResultCode::SUCCESS) {
+    device_id_to_active_device_map_.erase(device_id);
+  }
   callback(GetDeviceAccessResultCode(code));
 }
 
 void MojoConnector::StartVideoCapture(
-    const VideoStreamParams& capture_format,
-    std::function<void(uint64_t timestamp_in_microseconds, const uint8_t* data,
-                       int data_size)>
-        frame_handler) {
-  LOG(INFO) << "Setting frame handler.";
-  receiver_impl_.SetFrameHandler(std::move(frame_handler));
-  // Mojo code to start video capture and pass frames to the frame handler.
+    const std::string& device_id,
+    std::shared_ptr<ReceiverImpl> receiver_impl,
+    const VideoStreamParams& capture_format) {
   ipc_thread_.task_runner()->PostTask(
       FROM_HERE, base::Bind(&MojoConnector::StartVideoCaptureOnIpcThread,
-                            base::Unretained(this), capture_format));
+                            base::Unretained(this),
+                            device_id,
+                            receiver_impl,
+                            capture_format));
 }
 
 void MojoConnector::StartVideoCaptureOnIpcThread(
+    const std::string& device_id,
+    std::shared_ptr<ReceiverImpl> receiver_impl,
     const VideoStreamParams& capture_format) {
   LOG(INFO) << "Starting video capture on ipc thread.";
+  std::map<std::string, video_capture::mojom::DevicePtr>::iterator
+      it = device_id_to_active_device_map_.find(device_id);
+  if (it == device_id_to_active_device_map_.end()) {
+    LOG(ERROR) << "Device id not found in active device map.";
+    return;
+  }
 
   auto requested_settings = media::mojom::VideoCaptureParams::New();
   requested_settings->requested_format =
@@ -270,18 +297,19 @@
 
   requested_settings->buffer_type =
       media::mojom::VideoCaptureBufferType::kSharedMemoryViaRawFileDescriptor;
-  active_device_->Start(std::move(requested_settings),
-                        receiver_impl_.CreateInterfacePtr());
+
+  it->second->Start(std::move(requested_settings),
+                    receiver_impl->CreateInterfacePtr());
 }
 
-void MojoConnector::StopVideoCapture() {
+void MojoConnector::StopVideoCapture(const std::string& device_id) {
   ipc_thread_.task_runner()->PostTask(
       FROM_HERE, base::Bind(&MojoConnector::StopVideoCaptureOnIpcThread,
-                            base::Unretained(this)));
+                            base::Unretained(this), device_id));
 }
 
-void MojoConnector::StopVideoCaptureOnIpcThread() {
-  active_device_ = video_capture::mojom::DevicePtr();
+void MojoConnector::StopVideoCaptureOnIpcThread(const std::string& device_id) {
+  device_id_to_active_device_map_.erase(device_id);
 }
 
 void MojoConnector::CreateVirtualDevice(
diff --git a/media_perception/mojo_connector.h b/media_perception/mojo_connector.h
index f760f5f..d0ef581 100644
--- a/media_perception/mojo_connector.h
+++ b/media_perception/mojo_connector.h
@@ -56,18 +56,18 @@
   // Attempts to acquire exclusive access to a video device. Note that this does
   // not block another client of the video capture service from taking over
   // access on this device, which would disconnect this client.
-  void SetActiveDevice(
-      std::string device_id,
-      const VideoCaptureServiceClient::SetActiveDeviceCallback& callback);
+  void OpenDevice(
+      const std::string& device_id,
+      const VideoCaptureServiceClient::OpenDeviceCallback& callback);
 
   // Starts video capture on the active device.
-  void StartVideoCapture(const VideoStreamParams& capture_format,
-                         std::function<void(uint64_t timestamp_in_microseconds,
-                                            const uint8_t* data, int data_size)>
-                             frame_handler);
+  void StartVideoCapture(
+      const std::string& device_id,
+      std::shared_ptr<ReceiverImpl> receiver_impl,
+      const VideoStreamParams& capture_format);
 
-  // Stops video capture on the active device.
-  void StopVideoCapture();
+  // Stops video capture on the specified active device.
+  void StopVideoCapture(const std::string& device_id);
 
   // Creates a new virtual device that frames can be fed into.
   void CreateVirtualDevice(
@@ -104,17 +104,21 @@
       const VideoCaptureServiceClient::GetDevicesCallback& callback,
       std::vector<media::mojom::VideoCaptureDeviceInfoPtr> infos);
 
-  void SetActiveDeviceOnIpcThread(
-      std::string device_id,
-      const VideoCaptureServiceClient::SetActiveDeviceCallback& callback);
+  void OpenDeviceOnIpcThread(
+      const std::string& device_id,
+      const VideoCaptureServiceClient::OpenDeviceCallback& callback);
 
-  void OnSetActiveDeviceCallback(
-      const VideoCaptureServiceClient::SetActiveDeviceCallback& callback,
+  void OnOpenDeviceCallback(
+      const std::string& device_id,
+      const VideoCaptureServiceClient::OpenDeviceCallback& callback,
       video_capture::mojom::DeviceAccessResultCode code);
 
-  void StartVideoCaptureOnIpcThread(const VideoStreamParams& capture_format);
+  void StartVideoCaptureOnIpcThread(
+      const std::string& device_id,
+      std::shared_ptr<ReceiverImpl> receiver_impl,
+      const VideoStreamParams& capture_format);
 
-  void StopVideoCaptureOnIpcThread();
+  void StopVideoCaptureOnIpcThread(const std::string& device_id);
 
   void CreateVirtualDeviceOnIpcThread(
       const VideoDevice& video_device,
@@ -142,8 +146,10 @@
   // Entry point Mojo object for talking to the video capture service API.
   video_capture::mojom::DeviceFactoryPtr device_factory_;
 
-  // Provides interface to an open device.
-  video_capture::mojom::DevicePtr active_device_;
+  // Store a map from device ids to active devices.
+  std::map<std::string /* obfuscated device_id */,
+      video_capture::mojom::DevicePtr /* active_device */>
+          device_id_to_active_device_map_;
 
   int unique_device_counter_;
 
@@ -160,9 +166,6 @@
       std::string /* obfuscated device_id */,
       std::string /* device_id */> obfuscated_device_id_map_;
 
-  // Provides interface for receiving frames from the video capture service.
-  ReceiverImpl receiver_impl_;
-
   std::mutex vcs_connection_state_mutex_;
   bool is_connected_to_vcs_ = false;
 };
diff --git a/media_perception/proto_mojom_conversion.cc b/media_perception/proto_mojom_conversion.cc
index 7bd72f9..5a1b7a2 100644
--- a/media_perception/proto_mojom_conversion.cc
+++ b/media_perception/proto_mojom_conversion.cc
@@ -249,6 +249,16 @@
 
 namespace mri {
 
+std::vector<uint8_t> SerializeVideoStreamParamsProto(
+    const VideoStreamParams& params) {
+  const int size = params.ByteSizeLong();
+  std::vector<uint8_t> bytes(size, 0);
+
+  CHECK(params.SerializeToArray(bytes.data(), size))
+      << "Failed to serialize params proto.";
+  return bytes;
+}
+
 std::vector<uint8_t> SerializeVideoDeviceProto(const VideoDevice& device) {
   const int size = device.ByteSizeLong();
   std::vector<uint8_t> bytes(size, 0);
diff --git a/media_perception/proto_mojom_conversion.h b/media_perception/proto_mojom_conversion.h
index 62659d9..c3b8563 100644
--- a/media_perception/proto_mojom_conversion.h
+++ b/media_perception/proto_mojom_conversion.h
@@ -56,6 +56,8 @@
 
 namespace mri {
 
+std::vector<uint8_t> SerializeVideoStreamParamsProto(
+    const VideoStreamParams& params);
 std::vector<uint8_t> SerializeVideoDeviceProto(const VideoDevice& device);
 std::vector<uint8_t> SerializeSuccessStatusProto(const SuccessStatus& status);
 
diff --git a/media_perception/receiver_impl.cc b/media_perception/receiver_impl.cc
index 244a620..45789e3 100644
--- a/media_perception/receiver_impl.cc
+++ b/media_perception/receiver_impl.cc
@@ -10,8 +10,46 @@
 
 namespace mri {
 
-void ReceiverImpl::SetFrameHandler(FrameDataHandler frame_data_handler) {
-  frame_data_handler_ = std::move(frame_data_handler);
+bool ReceiverImpl::HasValidCaptureFormat() {
+  return capture_format_.width_in_pixels() > 0
+      && capture_format_.height_in_pixels() > 0;
+}
+
+void ReceiverImpl::SetCaptureFormat(const VideoStreamParams& params) {
+  capture_format_ = params;
+}
+
+bool ReceiverImpl::CaptureFormatsMatch(const VideoStreamParams& params) {
+  return capture_format_.width_in_pixels() == params.width_in_pixels() &&
+      capture_format_.height_in_pixels() == params.height_in_pixels() &&
+      capture_format_.frame_rate_in_frames_per_second() ==
+      params.frame_rate_in_frames_per_second();
+}
+
+VideoStreamParams ReceiverImpl::GetCaptureFormat() {
+  return capture_format_;
+}
+
+int ReceiverImpl::GetFrameHandlerCount() {
+  return frame_handler_map_.size();
+}
+
+int ReceiverImpl::AddFrameHandler(
+    VideoCaptureServiceClient::FrameHandler frame_handler) {
+  frame_handler_id_counter_++;
+  frame_handler_map_.insert(
+      std::make_pair(frame_handler_id_counter_, std::move(frame_handler)));
+  return frame_handler_id_counter_;
+}
+
+bool ReceiverImpl::RemoveFrameHandler(int frame_handler_id) {
+  std::map<int, VideoCaptureServiceClient::FrameHandler>::iterator it =
+      frame_handler_map_.find(frame_handler_id);
+  if (it == frame_handler_map_.end()) {
+    return false;
+  }
+  frame_handler_map_.erase(frame_handler_id);
+  return true;
 }
 
 video_capture::mojom::ReceiverPtr ReceiverImpl::CreateInterfacePtr() {
@@ -20,6 +58,7 @@
 
 void ReceiverImpl::OnNewBuffer(
     int32_t buffer_id, media::mojom::VideoBufferHandlePtr buffer_handle) {
+  LOG(INFO) << "On new buffer";
   CHECK(buffer_handle->is_shared_memory_via_raw_file_descriptor());
   std::unique_ptr<SharedMemoryProvider> shared_memory_provider =
       SharedMemoryProvider::CreateFromRawFileDescriptor(
@@ -40,18 +79,20 @@
     int32_t buffer_id, int32_t frame_feedback_id,
     video_capture::mojom::ScopedAccessPermissionPtr permission,
     media::mojom::VideoFrameInfoPtr frame_info) {
-  if (frame_data_handler_ == nullptr) {
-    LOG(ERROR) << "Frame handler is null.";
-    return;
-  }
-
   SharedMemoryProvider* incoming_buffer =
       incoming_buffer_id_to_buffer_map_.at(buffer_id).get();
-  frame_data_handler_(
-      frame_info->timestamp->microseconds,
-      static_cast<const uint8_t*>(
-          incoming_buffer->GetSharedMemoryForInProcessAccess()->memory()),
-      incoming_buffer->GetMemorySizeInBytes());
+  // Loop through all the registered frame handlers and push a frame out.
+  std::map<int, VideoCaptureServiceClient::FrameHandler>::iterator it;
+  for (it = frame_handler_map_.begin();
+       it != frame_handler_map_.end(); it++) {
+    it->second(
+        frame_info->timestamp->microseconds,
+        static_cast<const uint8_t*>(
+            incoming_buffer->GetSharedMemoryForInProcessAccess()->memory()),
+        incoming_buffer->GetMemorySizeInBytes(),
+        capture_format_.width_in_pixels(),
+        capture_format_.height_in_pixels());
+  }
 }
 
 void ReceiverImpl::OnFrameDropped(
diff --git a/media_perception/receiver_impl.h b/media_perception/receiver_impl.h
index 690061e..7de7e87 100644
--- a/media_perception/receiver_impl.h
+++ b/media_perception/receiver_impl.h
@@ -12,6 +12,7 @@
 #include <string>
 
 #include "base/logging.h"
+#include "media_perception/device_management.pb.h"
 #include "media_perception/shared_memory_provider.h"
 #include "media_perception/video_capture_service_client.h"
 #include "mojom/device_factory.mojom.h"
@@ -20,15 +21,32 @@
 
 class ReceiverImpl : public video_capture::mojom::Receiver {
  public:
-  using FrameDataHandler = std::function<void(
-      uint64_t timestamp_in_microseconds, const uint8_t* data, int data_size)>;
-  ReceiverImpl() : binding_(this) {}
+  ReceiverImpl() :
+    frame_handler_id_counter_(0),
+    binding_(this) {}
+
+  bool HasValidCaptureFormat();
+
+  void SetCaptureFormat(const VideoStreamParams& params);
+
+  VideoStreamParams GetCaptureFormat();
+
+  // Checks if the frame dimensions match the current dimensions.
+  bool CaptureFormatsMatch(const VideoStreamParams& params);
 
   // Creates a local proxy of the ReceiverPtr interface.
   video_capture::mojom::ReceiverPtr CreateInterfacePtr();
 
-  // Sets the handler that will be called when new frames come from the device.
-  void SetFrameHandler(FrameDataHandler frame_data_handler);
+  // Returns the count of active frame handlers on this receiver.
+  int GetFrameHandlerCount();
+
+  // Add a handler that will be called when new frames come from the associated
+  // device. Return value is an id for this frame handler.
+  int AddFrameHandler(VideoCaptureServiceClient::FrameHandler frame_handler);
+
+  // Removes a frame handler on this device with this id. Return value indicates
+  // if the removal was successful.
+  bool RemoveFrameHandler(int frame_handler_id);
 
   // video_capture::mojom::Receiver overrides.
   void OnNewBuffer(int32_t buffer_id,
@@ -46,12 +64,18 @@
   void OnStartedUsingGpuDecode() override;
 
  private:
-  // Frame handler for forwarding frames to the client.
-  FrameDataHandler frame_data_handler_;
+  // Incremented to create unique frame handler ids.
+  int frame_handler_id_counter_;
+
+  // Frame handler map for forwarding frames to one or more clients.
+  std::map<int, VideoCaptureServiceClient::FrameHandler> frame_handler_map_;
 
   // Binding of the Recevier interface to message pipe.
   mojo::Binding<video_capture::mojom::Receiver> binding_;
 
+  // Stores the capture format requested from the open device.
+  VideoStreamParams capture_format_;
+
   std::map<int32_t /*buffer_id*/, std::unique_ptr<SharedMemoryProvider>>
       incoming_buffer_id_to_buffer_map_;
 };
diff --git a/media_perception/video_capture_service_client.h b/media_perception/video_capture_service_client.h
index 55c891d..870f03d 100644
--- a/media_perception/video_capture_service_client.h
+++ b/media_perception/video_capture_service_client.h
@@ -14,14 +14,15 @@
 #include <utility>
 #include <vector>
 
+
+namespace mri {
+
 // Typdefs for readability. Serialized protos are passed back and forth across
 // the boundary between platform2 code and librtanalytics.so
 using SerializedVideoStreamParams = std::vector<uint8_t>;
 using SerializedVideoDevice = std::vector<uint8_t>;
 using RawPixelFormat = uint32_t;
 
-namespace mri {
-
 enum DeviceAccessResultCode {
   RESULT_UNKNOWN,
   NOT_INITIALIZED,
@@ -30,12 +31,14 @@
 };
 
 // Provides the interface definition for the rtanalytics library to interact
-// with the Chrome Video Capture Service.
+// with the Chrome Video Capture Service. Note that the
+// VideoCaptureServiceClient is thread-safe and can be shared between multiple
+// clients.
 class VideoCaptureServiceClient {
  public:
   using GetDevicesCallback = std::function<void(
       std::vector<SerializedVideoDevice>)>;
-  using SetActiveDeviceCallback = std::function<void(DeviceAccessResultCode)>;
+  using OpenDeviceCallback = std::function<void(DeviceAccessResultCode)>;
   using VirtualDeviceCallback = std::function<void(SerializedVideoDevice)>;
   using FrameHandler =
       std::function<void(uint64_t timestamp_us, const uint8_t* data,
@@ -52,15 +55,34 @@
   // Gets a list of video devices available.
   virtual void GetDevices(const GetDevicesCallback& callback) = 0;
 
-  // Sets the active device to be opened by the Video Capture Service.
-  // Return value indicates success or failure of setting the active device.
-  virtual void SetActiveDevice(const std::string& device_id,
-                               const SetActiveDeviceCallback& callback) = 0;
+  // Sets a device to be opened by the Video Capture Service with the exact
+  // device_id specified. OpenDeviceCallback provides information on the success
+  // or failure of the request.
+  virtual void OpenDevice(const std::string& device_id,
+                          const OpenDeviceCallback& callback) = 0;
 
-  // Starts video capture on the active device. Frames will be forwarded to
-  // the frame handler.
-  virtual void StartVideoCapture(
-      const SerializedVideoStreamParams& capture_format) = 0;
+  // Determines if a particular device has already started capture and if it
+  // has, fills in the |capture_format| with the current parameters used to
+  // read frames from the device.
+  virtual bool IsVideoCaptureStartedForDevice(
+      const std::string& device_id,
+      SerializedVideoStreamParams* capture_format) = 0;
+
+  // Add a frame handler for a particular device id. Return value is the handler
+  // id. Note that multiple clients can add a frame handler for a single device.
+  // AddFrameHandler will start video capture on a device if it is not already
+  // started. An return value of 0 indicates a failure to add the handler or
+  // start video capture.
+  virtual int AddFrameHandler(
+      const std::string& device_id,
+      const SerializedVideoStreamParams& capture_format,
+      FrameHandler handler) = 0;
+
+  // Remove a frame handler for a particular device by specifying the frame
+  // handler id. Video capture on a particular device will only stop when all
+  // frame handlers are removed for a particular device.
+  virtual bool RemoveFrameHandler(
+      const std::string& device_id, int frame_handler_id) = 0;
 
   // Interface for creating a virtual device with a set of parameters.
   virtual void CreateVirtualDevice(
@@ -77,19 +99,6 @@
 
   // Closes the specified virtual device.
   virtual void CloseVirtualDevice(const std::string& device_id) = 0;
-
-  // Stops video capture from the active device.
-  virtual void StopVideoCapture() = 0;
-
-  // Set the frame handler. Made virtual to support testing/mocking, clients are
-  // not expected to override this function.
-  virtual void SetFrameHandler(FrameHandler handler) {
-    frame_handler_ = std::move(handler);
-  }
-
- protected:
-  // Handler for processing input frames.
-  FrameHandler frame_handler_;
 };
 
 }  // namespace mri
diff --git a/media_perception/video_capture_service_client_impl.cc b/media_perception/video_capture_service_client_impl.cc
index 7b0a4fb..2672afc 100644
--- a/media_perception/video_capture_service_client_impl.cc
+++ b/media_perception/video_capture_service_client_impl.cc
@@ -8,6 +8,7 @@
 #include <base/single_thread_task_runner.h>
 
 #include "media_perception/device_management.pb.h"
+#include "media_perception/proto_mojom_conversion.h"
 
 namespace mri {
 
@@ -37,42 +38,89 @@
   mojo_connector_->GetDevices(callback);
 }
 
-void VideoCaptureServiceClientImpl::SetActiveDevice(
-    const std::string& device_id, const SetActiveDeviceCallback& callback) {
-  mojo_connector_->SetActiveDevice(device_id, callback);
+void VideoCaptureServiceClientImpl::OpenDevice(
+    const std::string& device_id, const OpenDeviceCallback& callback) {
+  mojo_connector_->OpenDevice(device_id, callback);
 }
 
-void VideoCaptureServiceClientImpl::StartVideoCapture(
-    const SerializedVideoStreamParams& capture_format) {
+bool VideoCaptureServiceClientImpl::IsVideoCaptureStartedForDevice(
+    const std::string& device_id,
+    SerializedVideoStreamParams* capture_format) {
+  std::lock_guard<std::mutex> lock(device_id_to_receiver_map_lock_);
+  std::map<std::string, std::shared_ptr<ReceiverImpl>>::iterator it =
+      device_id_to_receiver_map_.find(device_id);
+  bool capture_started = it != device_id_to_receiver_map_.end() &&
+      it->second->HasValidCaptureFormat();
+  if (capture_started) {
+    *capture_format = SerializeVideoStreamParamsProto(
+        it->second->GetCaptureFormat());
+  }
+  return capture_started;
+}
+
+int VideoCaptureServiceClientImpl::AddFrameHandler(
+    const std::string& device_id,
+    const SerializedVideoStreamParams& capture_format,
+    FrameHandler handler) {
+  std::lock_guard<std::mutex> lock(device_id_to_receiver_map_lock_);
   VideoStreamParams format;
   CHECK(format.ParseFromArray(capture_format.data(), capture_format.size()))
       << "Failed to deserialize mri::VideoStreamParams proto.";
 
-  requested_frame_width_ = format.width_in_pixels();
-  requested_frame_height_ = format.height_in_pixels();
-  mojo_connector_->StartVideoCapture(
-      format, std::bind(&VideoCaptureServiceClientImpl::OnNewFrameData,
-                                this, std::placeholders::_1,
-                                std::placeholders::_2, std::placeholders::_3));
-}
-
-void VideoCaptureServiceClientImpl::StopVideoCapture() {
-  mojo_connector_->StopVideoCapture();
-}
-
-void VideoCaptureServiceClientImpl::OnNewFrameData(
-    uint64_t timestamp_in_microseconds, const uint8_t* data, int data_size) {
-  if (frame_handler_ == nullptr) {
-    LOG(ERROR) << "Frame handler is null but receiving frames.";
-    return;
+  std::map<std::string, std::shared_ptr<ReceiverImpl>>::iterator it =
+      device_id_to_receiver_map_.find(device_id);
+  if (it != device_id_to_receiver_map_.end() &&
+      it->second->HasValidCaptureFormat()) {
+    LOG(INFO) << "Device with " << device_id << " already open.";
+    if (!it->second->CaptureFormatsMatch(format)) {
+      LOG(WARNING) << "Device " << device_id << " is already open but with "
+                   << "different capture formats.";
+      return 0;
+    }
+    return it->second->AddFrameHandler(std::move(handler));
   }
-  frame_handler_(timestamp_in_microseconds, data, data_size,
-                 requested_frame_width_, requested_frame_height_);
+
+  std::shared_ptr<ReceiverImpl> receiver_impl;
+  if (it != device_id_to_receiver_map_.end()) {
+    receiver_impl = it->second;
+  } else {  // Create receiver if it doesn't exist.
+    receiver_impl = std::make_shared<ReceiverImpl>();
+  }
+  receiver_impl->SetCaptureFormat(format);
+  device_id_to_receiver_map_.insert(
+      std::make_pair(device_id, receiver_impl));
+  mojo_connector_->StartVideoCapture(
+      device_id, receiver_impl, format);
+  return receiver_impl->AddFrameHandler(std::move(handler));
+}
+
+bool VideoCaptureServiceClientImpl::RemoveFrameHandler(
+    const std::string& device_id, int frame_handler_id) {
+  std::lock_guard<std::mutex> lock(device_id_to_receiver_map_lock_);
+  std::map<std::string, std::shared_ptr<ReceiverImpl>>::iterator it =
+      device_id_to_receiver_map_.find(device_id);
+
+  if (it == device_id_to_receiver_map_.end()) {
+    // Receiver does not exist. Ensure that the device is removed as well.
+    mojo_connector_->StopVideoCapture(device_id);
+    return false;
+  }
+
+  // Receiver does exist.
+  bool success = it->second->RemoveFrameHandler(frame_handler_id);
+  if (it->second->GetFrameHandlerCount() == 0) {
+    // Remove the receiver object.
+    device_id_to_receiver_map_.erase(device_id);
+    // Stop video capture on the device.
+    mojo_connector_->StopVideoCapture(device_id);
+  }
+  return success;
 }
 
 void VideoCaptureServiceClientImpl::CreateVirtualDevice(
     const SerializedVideoDevice& video_device,
     const VirtualDeviceCallback& callback) {
+  std::lock_guard<std::mutex> lock(device_id_to_producer_map_lock_);
   VideoDevice device;
   CHECK(device.ParseFromArray(video_device.data(), video_device.size()))
       << "Failed to deserialze mri::VideoDevice proto.";
@@ -80,7 +128,6 @@
   auto producer_impl = std::make_shared<ProducerImpl>();
   mojo_connector_->CreateVirtualDevice(device, producer_impl, callback);
 
-  std::lock_guard<std::mutex> lock(device_id_to_producer_map_lock_);
   device_id_to_producer_map_.insert(
       std::make_pair(device.id(), producer_impl));
 }
diff --git a/media_perception/video_capture_service_client_impl.h b/media_perception/video_capture_service_client_impl.h
index b2be5b4..7a35188 100644
--- a/media_perception/video_capture_service_client_impl.h
+++ b/media_perception/video_capture_service_client_impl.h
@@ -15,6 +15,7 @@
 
 #include "media_perception/mojo_connector.h"
 #include "media_perception/producer_impl.h"
+#include "media_perception/receiver_impl.h"
 #include "mojom/media_perception_service.mojom.h"
 
 namespace mri {
@@ -33,11 +34,17 @@
   bool Connect() override;
   bool IsConnected() override;
   void GetDevices(const GetDevicesCallback& callback) override;
-  void SetActiveDevice(const std::string& device_id,
-                       const SetActiveDeviceCallback& callback) override;
-  void StartVideoCapture(
-      const SerializedVideoStreamParams& capture_format) override;
-  void StopVideoCapture() override;
+  void OpenDevice(const std::string& device_id,
+                  const OpenDeviceCallback& callback) override;
+  bool IsVideoCaptureStartedForDevice(
+      const std::string& device_id,
+      SerializedVideoStreamParams* capture_format) override;
+  int AddFrameHandler(
+      const std::string& device_id,
+      const SerializedVideoStreamParams& capture_format,
+      FrameHandler handler) override;
+  bool RemoveFrameHandler(
+      const std::string& device_id, int frame_handler_id) override;
   void CreateVirtualDevice(const SerializedVideoDevice& video_device,
                            const VirtualDeviceCallback& callback) override;
   void PushFrameToVirtualDevice(const std::string& device_id,
@@ -49,11 +56,16 @@
   void CloseVirtualDevice(const std::string& device_id) override;
 
  private:
-  void OnNewFrameData(uint64_t timestamp_in_microseconds, const uint8_t* data,
-                      int data_size);
-
   MojoConnector* mojo_connector_;
 
+  // Stores a map of device ids to receivers for receiving frame for the correct
+  // mojo object associated with an open device.
+  std::map<std::string /*device_id*/, std::shared_ptr<ReceiverImpl>>
+      device_id_to_receiver_map_;
+
+  // Guards against concurrent changes to |device_id_to_receiver_map_|.
+  mutable std::mutex device_id_to_receiver_map_lock_;
+
   // Stores a map of device ids to producers for pushing frames to the correct
   // mojo object when PushFrameToVirtualDevice is called.
   // ProducerImpl objects provide an interface for buffer info updates of an
@@ -62,12 +74,8 @@
       device_id_to_producer_map_;
 
   // Guards against concurrent changes to |device_id_to_producer_map_|.
+  // TODO(crbug.com/918668): Remove use of locks if possible.
   mutable std::mutex device_id_to_producer_map_lock_;
-
-  // Stores the most recent requested frame width and height for incoming image
-  // frames from the open active device.
-  int requested_frame_width_;
-  int requested_frame_height_;
 };
 
 }  // namespace mri