| // Copyright 2022 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 "src/camera/drivers/usb_video/video_frame.h" | |
| #include <lib/ddk/debug.h> | |
| #include <lib/fit/function.h> | |
| #include <lib/fzl/vmo-pool.h> | |
| #include <memory> | |
| #include <usb/usb-request.h> | |
| #include <usb/usb.h> | |
| namespace camera::usb_video { | |
| // Only keep the first 11 bits of the USB SOF (Start of Frame) values. | |
| // The payload header SOF values only have 11 bits before wrapping around, | |
| // whereas the XHCI host returns 64 bits. | |
| static constexpr uint16_t USB_SOF_MASK = 0x7FF; | |
| zx::result<fuchsia::camera::FrameAvailableEvent> VideoFrame::Release() { | |
| if (bytes_ == 0) { | |
| zxlogf(WARNING, "VideoFrame::Release: bytes == 0"); | |
| return zx::error(ZX_ERR_BAD_STATE); | |
| } | |
| fuchsia::camera::FrameAvailableEvent event; | |
| event.metadata.timestamp = capture_time_; | |
| if (!buffer_.valid()) { | |
| event.frame_status = fuchsia::camera::FrameStatus::ERROR_BUFFER_FULL; | |
| zxlogf(WARNING, "VideoFrame::Release: !buffer_.valid()"); | |
| return zx::ok(event); | |
| } | |
| event.buffer_id = buffer_.ReleaseWriteLockAndGetIndex(); | |
| zxlogf(DEBUG, "buffer_.ReleaseWriteLockAndGetIndex got %u", event.buffer_id); | |
| // If we were writing to a buffer (we have to complete it) | |
| // If we had a writing error: | |
| if (error_) { | |
| event.frame_status = fuchsia::camera::FrameStatus::ERROR_FRAME; | |
| } | |
| return zx::ok(event); | |
| } | |
| // static | |
| zx::result<VideoFrame::PayloadHeader> VideoFrame::PayloadHeader::ParseHeader(usb_request_t* req) { | |
| if (req->response.status != ZX_OK) { | |
| failure_count_++; | |
| if (failure_count_ == 1 || (failure_count_) % 10 == 0) { | |
| zxlogf(ERROR, "usb request failed %lu times: %d", failure_count_.load(), | |
| req->response.status); | |
| } | |
| return zx::error(req->response.status); | |
| } | |
| usb_video_vs_payload_header pheader; | |
| size_t len = usb_request_copy_from(req, &pheader, sizeof(pheader), 0); | |
| if (len != sizeof(usb_video_vs_payload_header) || pheader.bHeaderLength > req->response.actual) { | |
| zxlogf(ERROR, "got invalid header bHeaderLength %u data length %lu", pheader.bHeaderLength, | |
| req->response.actual); | |
| return zx::error(ZX_ERR_INTERNAL); | |
| } | |
| return zx::ok(PayloadHeader{.header = pheader}); | |
| } | |
| bool VideoFrame::IsPayloadComplete(usb_request_t* req) { | |
| if (usb_state_.EndpointType() == USB_ENDPOINT_ISOCHRONOUS) { | |
| return true; | |
| } | |
| bulk_payload_bytes_ += req->response.actual; | |
| // A payload is complete when we've received enough bytes to reach the max | |
| // payload size, or fewer bytes than what we requested. | |
| if (bulk_payload_bytes_ >= usb_state_.MaxPayloadTransferSize() || | |
| req->response.actual < usb_state_.RequestSize()) { | |
| bulk_payload_bytes_ = 0; | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Extracts the payload data from the usb request response, | |
| // and stores it in the video buffer. | |
| zx_status_t VideoFrame::ProcessPayload(usb_request_t* req) { | |
| // Empty responses should be ignored. | |
| if (req->response.actual == 0 || req->response.status != ZX_OK) { | |
| return ZX_OK; | |
| } | |
| uint32_t header_offset = 0; | |
| // Read the header for the first request | |
| if (HasPayloadHeader()) { | |
| auto header_result = PayloadHeader::ParseHeader(req); | |
| if (header_result.is_error()) { | |
| zxlogf(ERROR, "payload header has error"); | |
| return ZX_ERR_INTERNAL; | |
| } | |
| if (header_result->error()) { | |
| // Only print the error message for the first erroneous payload of the | |
| // frame. | |
| if (!error_) { | |
| zxlogf(ERROR, "payload of frame had an error bit set"); | |
| error_ = true; | |
| } | |
| } | |
| eof_ = header_result->eof(); | |
| // Check if this should be a new frame. This should not happen if | |
| // we are detecting the eof flag correctly. | |
| // if fid == -1, that just means this is the first payload of the frame. | |
| if (fid_ != -1 && fid_ != header_result->fid()) { | |
| zxlogf(DEBUG, "new frame detected"); | |
| return ZX_ERR_NEXT; | |
| } | |
| fid_ = header_result->fid(); // set fid, if it is the first payload. | |
| header_offset = header_result->length(); | |
| ParseHeaderTimestamps(req); | |
| } | |
| zx_status_t status = ZX_ERR_INTERNAL; | |
| if (!error_) { | |
| status = CopyData(req, header_offset); | |
| if (status != ZX_OK) { | |
| error_ = true; // Don't return yet, we may have just finished a frame. | |
| } | |
| } | |
| if (IsPayloadComplete(req) && eof_ == true) { | |
| return ZX_ERR_STOP; | |
| } | |
| return status; | |
| } | |
| zx_status_t VideoFrame::CopyData(usb_request_t* req, uint32_t data_offset) { | |
| if (!buffer_.valid()) { | |
| // There was no space in the video buffer when the frame's first payload | |
| // header was parsed. | |
| return ZX_ERR_BAD_STATE; | |
| } | |
| // Copy the data into the video buffer. | |
| uint32_t data_size = static_cast<uint32_t>(req->response.actual) - data_offset; | |
| if (bytes_ + data_size > usb_state_.MaxFrameSize()) { | |
| zxlogf(ERROR, "invalid data size %u, cur frame bytes %u, frame size %u", data_size, bytes_, | |
| usb_state_.MaxFrameSize()); | |
| error_ = true; | |
| return ZX_ERR_IO_INVALID; | |
| } | |
| // Append the data to the end of the current frame. | |
| uint64_t avail = buffer_.size() - bytes_; | |
| ZX_DEBUG_ASSERT(avail >= data_size); | |
| uint8_t* dst = reinterpret_cast<uint8_t*>(buffer_.virtual_address()) + bytes_; | |
| // TODO (fxb/63635): Decide what to do here | |
| [[maybe_unused]] auto copy_result = usb_request_copy_from(req, dst, data_size, data_offset); | |
| bytes_ += data_size; | |
| return ZX_OK; | |
| } | |
| static inline double device_clock_to_ms(uint32_t clock_reading, uint32_t clock_frequency_hz) { | |
| return clock_frequency_hz != 0 ? clock_reading * 1000.0 / clock_frequency_hz : 0; | |
| } | |
| void VideoFrame::ParseHeaderTimestamps(usb_request_t* req) { | |
| // TODO(jocelyndang): handle other formats, the timestamp offset is variable. | |
| usb_video_vs_uncompressed_payload_header header = {}; | |
| [[maybe_unused]] auto result = | |
| usb_request_copy_from(req, &header, sizeof(usb_video_vs_uncompressed_payload_header), 0); | |
| // PTS should stay the same for payloads of the same frame, | |
| // but it's probably not a critical error if they're different. | |
| if (header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_PTS) { | |
| uint32_t new_pts = header.dwPresentationTime; | |
| // Use the first seen PTS value. | |
| if (pts_ == 0) { | |
| pts_ = new_pts; | |
| } else if (new_pts != pts_) { | |
| zxlogf(ERROR, "#%u: PTS changed between payloads, from %u to %u", frame_number_, pts_, | |
| new_pts); | |
| } | |
| } | |
| if (header.bmHeaderInfo & USB_VIDEO_VS_PAYLOAD_HEADER_SCR) { | |
| uint32_t new_stc = header.scrSourceTimeClock; | |
| uint16_t new_sof = header.scrSourceClockSOFCounter; | |
| // The USB Video Class Spec 1.1 suggests that updated SCR values may | |
| // be provided per payload of a frame. Only use the first seen value. | |
| if (stc_ == 0) { | |
| stc_ = new_stc; | |
| device_sof_ = new_sof; | |
| } | |
| } | |
| // The device might not support header timestamps. | |
| if (pts_ == 0 || stc_ == 0) { | |
| return; | |
| } | |
| // Already calculated the capture time for the frame. | |
| if (capture_time_ != 0) { | |
| return; | |
| } | |
| // Calculate the capture time. This uses the method detailed in the | |
| // USB Video Class 1.5 FAQ, Section 2.7 Audio and Video Stream | |
| // Synchronization. | |
| // | |
| // Event Available Timestamps | |
| // ------------------------ ---------------------------------- | |
| // raw frame capture starts - PTS in device clock units | |
| // raw frame capture ends - STC in device clock units, device SOF | |
| // driver receives frame - host monotonic timestamp, host SOF | |
| // | |
| // TODO(jocelyndang): revisit this. This may be slightly inaccurate for | |
| // devices implementing the 1.1 version of the spec, which states that a | |
| // payload's SOF number is not required to match the 'current' frame number. | |
| // Get the current host SOF value and host monotonic timestamp. | |
| // TODO(fxbug.dev/104503): Use the IRQ timestamp instead of the system timestamp | |
| // for more accuracy. | |
| zx_time_t host_complete_time_ns = zx_clock_get_monotonic(); | |
| // Calculate the difference between when raw frame capture starts and ends. | |
| uint32_t device_delay = stc_ - pts_; | |
| double device_delay_ms = device_clock_to_ms(device_delay, usb_state_.ClockFrequencyHz()); | |
| // Calculate the delay caused by USB transport and processing. This will be | |
| // the time between raw frame capture ending and the driver receiving the | |
| // frame | |
| // | |
| // SOF (Start of Frame) values are transmitted by the USB host every | |
| // millisecond. | |
| // We want the difference between the SOF values of when frame capture | |
| // completed (device_sof) and when we received the frame (host_sof). | |
| // | |
| // Since the device SOF value only has 11 bits and wraps around, we should | |
| // discard the higher bits of the result. The delay is expected to be | |
| // less than 2^11 ms. | |
| uint16_t transport_delay_ms = (host_sof_ - device_sof_) & USB_SOF_MASK; | |
| // Time between when raw frame capture starts and the driver receiving the | |
| // frame. | |
| double total_video_delay = device_delay_ms + transport_delay_ms; | |
| // Start of raw frame capture as zx_time_t (nanoseconds). | |
| zx_time_t capture_start_ns = host_complete_time_ns - ZX_MSEC(total_video_delay); | |
| // The capture time is specified in the camera interface as the midpoint of | |
| // the capture operation, not including USB transport time. | |
| capture_time_ = capture_start_ns + ZX_MSEC(device_delay_ms) / 2; | |
| } | |
| } // namespace camera::usb_video |