text: // Copyright 2018 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <ddk/debug.h>
  #include "src/lib/fxl/logging.h"
  
  #include "garnet/drivers/usb_video/camera_control_impl.h"
  #include "garnet/drivers/usb_video/usb-video-stream.h"
  
  namespace camera {
  
  void ControlImpl::OnFrameAvailable(
      const fuchsia::camera::FrameAvailableEvent& frame) {
    stream_->OnFrameAvailable(frame);
  }
  
  ControlImpl::ControlImpl(video::usb::UsbVideoStream* usb_video_stream,
                           fidl::InterfaceRequest<Control> control,
                           async_dispatcher_t* dispatcher,
                           fit::closure on_connection_closed)
      : binding_(this, std::move(control), dispatcher),
        usb_video_stream_(usb_video_stream) {
    binding_.set_error_handler(
        [occ = std::move(on_connection_closed)](zx_status_t status) { occ(); });
  }
  
  void ControlImpl::GetFormats(uint32_t index, GetFormatsCallback callback) {
    if (formats_->size() == 0) {
      zx_status_t status = usb_video_stream_->GetFormats(formats_);
      if (status != ZX_OK) {
        callback(std::move(formats_), formats_->size(), status);
        return;
      }
    }
  
    size_t min_index = std::max((size_t)0, std::min((size_t)index, formats_->size() - 1));
    size_t max_index =
        std::min(min_index + fuchsia::camera::MAX_FORMATS_PER_RESPONSE - 1,
                 formats_->size() - 1);
  
    callback(std::vector<fuchsia::camera::VideoFormat>(
                 &(*formats_)[min_index], &(*formats_)[max_index + 1]),
             formats_->size(), ZX_OK);
  }
  
  void ControlImpl::GetDeviceInfo(GetDeviceInfoCallback callback) {
    // This is just a conversion from the format internal to the device driver
    // to the FIDL DeviceInfo struct.
    const auto& usb_device_info = usb_video_stream_->GetDeviceInfo();
    fuchsia::camera::DeviceInfo camera_device_info;
    camera_device_info.vendor_name = usb_device_info.manufacturer;
    camera_device_info.vendor_id = usb_device_info.vendor_id;
    camera_device_info.product_name = usb_device_info.product_name;
    camera_device_info.product_id = usb_device_info.product_id;
    camera_device_info.serial_number = usb_device_info.serial_number;
  
    // TODO(CAM-11): add more capabilities based on usb description
    camera_device_info.output_capabilities =
        fuchsia::camera::CAMERA_OUTPUT_STREAM;
    camera_device_info.max_stream_count = 1;
    callback(std::move(camera_device_info));
  }
  
  void ControlImpl::CreateStream(
      fuchsia::sysmem::BufferCollectionInfo buffer_collection,
      fuchsia::camera::FrameRate frame_rate,
      fidl::InterfaceRequest<fuchsia::camera::Stream> stream,
      zx::eventpair stream_token) {
    zx_status_t status =
        usb_video_stream_->CreateStream(std::move(buffer_collection), frame_rate);
  
    if (status != ZX_OK) {
      zxlogf(ERROR, "Failed to set format. Closing channel.\n");
      binding_.Unbind();  // Close the channel on error.
      return;
    }
  
    stream_ = std::make_unique<StreamImpl>(*this, std::move(stream),
                                           std::move(stream_token));
  }
  
  void ControlImpl::StreamImpl::OnFrameAvailable(
      const fuchsia::camera::FrameAvailableEvent& frame) {
    binding_.events().OnFrameAvailable(frame);
  }
  
  void ControlImpl::StreamImpl::Start() {
    zx_status_t status = owner_.usb_video_stream_->StartStreaming();
    if (status != ZX_OK) {
      zxlogf(ERROR, "Failed to start. Closing channel.\n");
      binding_.Unbind();  // Close the channel on error.
    }
  }
  
  void ControlImpl::StreamImpl::Stop() {
    zx_status_t status = owner_.usb_video_stream_->StopStreaming();
    if (status != ZX_OK) {
      zxlogf(ERROR, "Failed to stop. Closing channel.\n");
      binding_.Unbind();  // Close the channel on error.
    }
  }
  
  void ControlImpl::StreamImpl::ReleaseFrame(uint32_t buffer_index) {
    zx_status_t status = owner_.usb_video_stream_->FrameRelease(buffer_index);
    if (status != ZX_OK) {
      zxlogf(ERROR, "Failed to release frame. Closing channel.\n");
      binding_.Unbind();  // Close the channel on error.
    }
  }
  
  void ControlImpl::ShutDownStream() {
    // This has the effect of cancelling the wait, deleting the
    // stream_token, and unbinding from the stream channel.
    stream_ = nullptr;
  }
  
  ControlImpl::StreamImpl::~StreamImpl() {
    // Doesn't matter what this returns:
    (void)owner_.usb_video_stream_->StopStreaming();
  }
  
  ControlImpl::StreamImpl::StreamImpl(
      ControlImpl& owner, fidl::InterfaceRequest<fuchsia::camera::Stream> stream,
      zx::eventpair stream_token)
      : owner_(owner),
        binding_(this, std::move(stream)),
        stream_token_(std::move(stream_token)),
        // If not triggered by the token being closed, this waiter will be
        // cancelled by the destruction of this class, so the "this" pointer will
        // be valid as long as the waiter is around.
        stream_token_waiter_(
            stream_token_.get(), ZX_EVENTPAIR_PEER_CLOSED, std::bind([this]() {
              zxlogf(
                  INFO,
                  "ControlImpl::StreamImpl::StreamImpl - "
                  "ZX_EVENTPAIR_PEER_CLOSED received, shutting down stream.\n");
              // If the peer is closed, shut down the whole
              // stream.
              owner_.ShutDownStream();
              // We just deleted ourselves. Don't do anything
              // else.
            })) {
    zx_status_t status =
        stream_token_waiter_.Begin(async_get_default_dispatcher());
    // The waiter, dispatcher and token are known to be valid, so this should
    // never fail.
    FXL_CHECK(status == ZX_OK);
    binding_.set_error_handler([this](zx_status_t status) {
      owner_.usb_video_stream_->DeactivateVideoBuffer();
    });
  }
  
  }  // namespace camera
  