blob: 8b311826d44e14e6f5b0c6b945e8fd42085065ce [file] [log] [blame]
// 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 <ddk/protocol/usb.h>
#include <fbl/vector.h>
#include <stdlib.h>
#include <usb/usb.h>
#include <zircon/hw/usb/video.h>
#include "garnet/drivers/usb_video/usb-video-stream.h"
#include "garnet/drivers/usb_video/uvc_format.h"
namespace video {
namespace usb {
// 8 bits for each RGB.
static constexpr uint32_t MJPEG_BITS_PER_PIXEL = 24;
static constexpr uint32_t NANOSECS_IN_SEC = 1e9;
UvcPixelFormat guid_to_pixel_format(uint8_t guid[GUID_LENGTH]) {
struct {
uint8_t guid[GUID_LENGTH];
UvcPixelFormat pixel_format;
} GUID_LUT[] = {
{USB_VIDEO_GUID_YUY2_VALUE, UvcPixelFormat::YUY2},
{USB_VIDEO_GUID_NV12_VALUE, UvcPixelFormat::NV12},
{USB_VIDEO_GUID_M420_VALUE, UvcPixelFormat::M420},
{USB_VIDEO_GUID_I420_VALUE, UvcPixelFormat::I420},
};
for (const auto& g : GUID_LUT) {
if (memcmp(g.guid, guid, GUID_LENGTH) == 0) {
return g.pixel_format;
}
}
return UvcPixelFormat::INVALID;
}
fuchsia::camera::VideoFormat ToFidl(const UvcFormat& format_in) {
fuchsia::camera::VideoFormat ret = {
.format.width = format_in.width,
.format.height = format_in.height,
.format.layers = 1,
// The frame descriptor frame interval is expressed in 100ns units.
// e.g. a frame interval of 333333 is equivalent to 30fps (1e7 / 333333).
.rate.frames_per_sec_numerator =
NANOSECS_IN_SEC / 100, // static_cast<uint32_t>(1e7),
.rate.frames_per_sec_denominator = format_in.default_frame_interval};
ret.format.planes[0].bytes_per_row = format_in.stride;
// Convert Pixel Format:
switch (format_in.pixel_format) {
case UvcPixelFormat::BGRA32:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::BGRA32;
break;
case UvcPixelFormat::I420:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::I420;
break;
case UvcPixelFormat::M420:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::M420;
break;
case UvcPixelFormat::NV12:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::NV12;
break;
case UvcPixelFormat::YUY2:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::YUY2;
break;
case UvcPixelFormat::MJPEG:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::MJPEG;
break;
default:
ret.format.pixel_format.type = fuchsia::sysmem::PixelFormatType::INVALID;
}
return ret;
}
bool Compare(const fuchsia::camera::VideoFormat& vf, const UvcFormat& uf) {
fuchsia::camera::VideoFormat uvf = ToFidl(uf);
bool has_equal_frame_rate =
(static_cast<uint64_t>(vf.rate.frames_per_sec_numerator) *
uvf.rate.frames_per_sec_denominator) ==
(static_cast<uint64_t>(vf.rate.frames_per_sec_numerator) *
uvf.rate.frames_per_sec_denominator);
for (uint32_t i = 0; i < uvf.format.planes.size(); i++) {
if (vf.format.planes[i].byte_offset != uvf.format.planes[i].byte_offset ||
vf.format.planes[i].bytes_per_row !=
uvf.format.planes[i].bytes_per_row) {
return false;
}
}
if (fidl::Equals(vf.format.pixel_format, uvf.format.pixel_format) &&
vf.format.width == uvf.format.width &&
vf.format.height == uvf.format.height && has_equal_frame_rate) {
return true;
}
return false;
}
// Parses the payload format descriptor and any corresponding frame descriptors.
// The result is stored in out_format.
zx_status_t UvcFormatList::ParseUsbDescriptor(
usb_video_vc_desc_header* format_desc, usb_desc_iter_t* iter) {
uint8_t want_frame_type = 0;
int want_num_frame_descs = 0;
// These fields are shared between all frame descriptions:
uint8_t format_index, default_frame_index;
UvcPixelFormat pixel_format;
uint8_t bits_per_pixel;
switch (format_desc->bDescriptorSubtype) {
case USB_VIDEO_VS_FORMAT_UNCOMPRESSED: {
usb_video_vs_uncompressed_format_desc* uncompressed_desc =
(usb_video_vs_uncompressed_format_desc*)format_desc;
zxlogf(TRACE,
"USB_VIDEO_VS_FORMAT_UNCOMPRESSED bNumFrameDescriptors %u "
"bBitsPerPixel %u\n",
uncompressed_desc->bNumFrameDescriptors,
uncompressed_desc->bBitsPerPixel);
want_frame_type = USB_VIDEO_VS_FRAME_UNCOMPRESSED;
format_index = uncompressed_desc->bFormatIndex;
pixel_format = guid_to_pixel_format(uncompressed_desc->guidFormat);
bits_per_pixel = uncompressed_desc->bBitsPerPixel;
want_num_frame_descs = uncompressed_desc->bNumFrameDescriptors;
default_frame_index = uncompressed_desc->bDefaultFrameIndex;
break;
}
case USB_VIDEO_VS_FORMAT_MJPEG: {
usb_video_vs_mjpeg_format_desc* mjpeg_desc =
(usb_video_vs_mjpeg_format_desc*)format_desc;
zxlogf(TRACE,
"USB_VIDEO_VS_FORMAT_MJPEG bNumFrameDescriptors %u bmFlags %d\n",
mjpeg_desc->bNumFrameDescriptors, mjpeg_desc->bmFlags);
want_frame_type = USB_VIDEO_VS_FRAME_MJPEG;
format_index = mjpeg_desc->bFormatIndex;
pixel_format = UvcPixelFormat::MJPEG;
bits_per_pixel = MJPEG_BITS_PER_PIXEL;
want_num_frame_descs = mjpeg_desc->bNumFrameDescriptors;
default_frame_index = mjpeg_desc->bDefaultFrameIndex;
break;
}
// TODO(jocelyndang): handle other formats.
default:
zxlogf(ERROR, "unsupported format bDescriptorSubtype %u\n",
format_desc->bDescriptorSubtype);
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AllocChecker ac;
// TODO(garratt): add case for format with no frame_desc
formats_.reserve(formats_.size() + want_num_frame_descs, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// The format descriptor mut be immediately followed by its frame descriptors,
// if any.
int num_frame_descs_found = 0;
usb_descriptor_header_t* header;
while ((header = usb_desc_iter_peek(iter)) != NULL &&
header->bDescriptorType == USB_VIDEO_CS_INTERFACE &&
num_frame_descs_found < want_num_frame_descs) {
usb_video_vc_desc_header* format_desc = (usb_video_vc_desc_header*)header;
if (format_desc->bDescriptorSubtype != want_frame_type) {
break;
}
switch (format_desc->bDescriptorSubtype) {
case USB_VIDEO_VS_FRAME_UNCOMPRESSED:
case USB_VIDEO_VS_FRAME_MJPEG: {
usb_video_vs_frame_desc* desc = (usb_video_vs_frame_desc*)header;
// Intervals are specified in 100 ns units.
double framesPerSec = 1 / (desc->dwDefaultFrameInterval * 100 / 1e9);
zxlogf(
TRACE, "%s (%u x %u) %.2f frames / sec\n",
format_desc->bDescriptorSubtype == USB_VIDEO_VS_FRAME_UNCOMPRESSED
? "USB_VIDEO_VS_FRAME_UNCOMPRESSED"
: "USB_VIDEO_VS_FRAME_MJPEG",
desc->wWidth, desc->wHeight, framesPerSec);
video::usb::UvcFormat frame_desc = {
.format_index = format_index,
.pixel_format = pixel_format,
.bits_per_pixel = bits_per_pixel,
.default_frame_index = default_frame_index,
.frame_index = desc->bFrameIndex,
.default_frame_interval = desc->dwDefaultFrameInterval,
.width = desc->wWidth,
.height = desc->wHeight,
.stride = desc->dwMaxVideoFrameBufferSize / desc->wHeight};
formats_.push_back(frame_desc);
} break;
default:
zxlogf(ERROR, "unhandled frame type: %u\n",
format_desc->bDescriptorSubtype);
return ZX_ERR_NOT_SUPPORTED;
}
header = usb_desc_iter_next(iter);
num_frame_descs_found++;
}
if (num_frame_descs_found != want_num_frame_descs) {
zxlogf(ERROR, "missing %u frame descriptors\n",
want_num_frame_descs - num_frame_descs_found);
return ZX_ERR_INTERNAL;
}
// TODO(jocelyndang); parse still image frame and color matching descriptors.
return ZX_OK;
}
} // namespace usb
} // namespace video