blob: cdcf8cf13e76a97f7bde9904b3a06f0ea7509a4f [file] [log] [blame]
// 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