From 6fb23da37598f7912f01a672fbc499a33fe2f895 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 13 Jul 2016 01:36:06 +0100 Subject: [PATCH] Service::CAM: Add a V4L2-based implementation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It uses the raw Video For Linux 2 API to integrate any capture device (a camera for example) into the 3DS cam:U subsystem, so that games can now take an actual picture of the user, use the AR system, or do basically anything they wants with the video stream. Conversion to RGB isn’t supported yet, so in the very likely chance your capture device doesn’t support outputting directly in RGB you will get a garbled but recognizable image resulting from the casting of YUYV data into RGB565. Only the OUT1 camera (rear left) is currently supported, every other one will still return an empty buffer. A possible improvement would be to extend support to more than one /dev/videoN device, and let the user configure which physical device corresponds to which emulated camera. Another would be to use a computer vision library to generate a shifted image and give the illusion of perspective when using both read cameras. A possible improvement would be to check for the presence of the v4l2 header instead of matching on the OS name. A dummy implementation is built on every other OS. This also adds the SetOutputFormat call. None of this has been tested on hardware yet, nor reversed from the cam:U service, everything is pure guess work based on the camera/video example from 3ds-examples. --- src/core/CMakeLists.txt | 7 + src/core/hle/service/cam/cam.cpp | 80 ++++-- src/core/hle/service/cam/cam.h | 14 + src/core/hle/service/cam/cam_u.cpp | 2 +- src/core/hle/service/cam/impl.h | 26 ++ src/core/hle/service/cam/impl_dummy.cpp | 58 ++++ src/core/hle/service/cam/impl_linux.cpp | 362 ++++++++++++++++++++++++ 7 files changed, 524 insertions(+), 25 deletions(-) create mode 100644 src/core/hle/service/cam/impl.h create mode 100644 src/core/hle/service/cam/impl_dummy.cpp create mode 100644 src/core/hle/service/cam/impl_linux.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0773339a9..3901017d3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -209,6 +209,7 @@ set(HEADERS hle/service/cam/cam_q.h hle/service/cam/cam_s.h hle/service/cam/cam_u.h + hle/service/cam/impl.h hle/service/cecd/cecd.h hle/service/cecd/cecd_s.h hle/service/cecd/cecd_u.h @@ -281,6 +282,12 @@ set(HEADERS system.h ) +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(SRCS ${SRCS} hle/service/cam/impl_linux.cpp) +else() + set(SRCS ${SRCS} hle/service/cam/impl_dummy.cpp) +endif() + create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index 9df48a650..0bd18a02f 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -5,11 +5,13 @@ #include "common/logging/log.h" #include "core/hle/kernel/event.h" +#include "core/hle/result.h" #include "core/hle/service/cam/cam.h" #include "core/hle/service/cam/cam_c.h" #include "core/hle/service/cam/cam_q.h" #include "core/hle/service/cam/cam_s.h" #include "core/hle/service/cam/cam_u.h" +#include "core/hle/service/cam/impl.h" #include "core/hle/service/service.h" namespace Service { @@ -27,10 +29,12 @@ void StartCapture(Service::Interface* self) { u8 port = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = StartCapture(port); - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, port=%d", port); } void StopCapture(Service::Interface* self) { @@ -38,10 +42,12 @@ void StopCapture(Service::Interface* self) { u8 port = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = StopCapture(port); - LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); + cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, port=%d", port); } void GetVsyncInterruptEvent(Service::Interface* self) { @@ -78,18 +84,20 @@ void SetReceiving(Service::Interface* self) { u32 image_size = cmd_buff[3]; u16 trans_unit = cmd_buff[4] & 0xFFFF; + ResultCode result = SetReceiving(dest, port, image_size, trans_unit); + Kernel::Event* completion_event = (Port)port == Port::Cam2 ? completion_event_cam2.get() : completion_event_cam1.get(); completion_event->Signal(); cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = result.raw; cmd_buff[2] = IPC::MoveHandleDesc(); cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); - LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", - dest, port, image_size, trans_unit); + LOG_DEBUG(Service_CAM, "called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", + dest, port, image_size, trans_unit); } void SetTransferLines(Service::Interface* self) { @@ -166,11 +174,13 @@ void Activate(Service::Interface* self) { u8 cam_select = cmd_buff[1] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = Activate(cam_select); - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d", - cam_select); + cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d", + cam_select); } void FlipImage(Service::Interface* self) { @@ -194,11 +204,13 @@ void SetSize(Service::Interface* self) { u8 size = cmd_buff[2] & 0xFF; u8 context = cmd_buff[3] & 0xFF; - cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = SetSize(cam_select, size, context); - LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, size=%d, context=%d", - cam_select, size, context); + cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d, size=%d, context=%d", + cam_select, size, context); } void SetFrameRate(Service::Interface* self) { @@ -214,6 +226,22 @@ void SetFrameRate(Service::Interface* self) { cam_select, frame_rate); } +void SetOutputFormat(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + u8 cam_select = cmd_buff[1] & 0xFF; + u8 output_format = cmd_buff[2] & 0xFF; + u8 context = cmd_buff[3] & 0xFF; + + ResultCode result = SetOutputFormat(cam_select, output_format, context); + + cmd_buff[0] = IPC::MakeHeader(0x25, 3, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called, cam_select=%d, output_format=%d, context=%d", + cam_select, output_format, context); +} + void GetStereoCameraCalibrationData(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); @@ -238,7 +266,7 @@ void GetStereoCameraCalibrationData(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; memcpy(&cmd_buff[2], &data, sizeof(data)); - LOG_TRACE(Service_CAM, "called"); + LOG_DEBUG(Service_CAM, "called"); } void GetSuitableY2rStandardCoefficient(Service::Interface* self) { @@ -270,19 +298,23 @@ void DriverInitialize(Service::Interface* self) { interrupt_error_event->Clear(); vsync_interrupt_error_event->Clear(); - cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = DriverInitialize(); - LOG_WARNING(Service_CAM, "(STUBBED) called"); + cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called"); } void DriverFinalize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + ResultCode result = DriverFinalize(); - LOG_WARNING(Service_CAM, "(STUBBED) called"); + cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0); + cmd_buff[1] = result.raw; + + LOG_DEBUG(Service_CAM, "called"); } void Init() { diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index 2f4923728..eed55dcde 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -373,6 +373,20 @@ void SetSize(Service::Interface* self); */ void SetFrameRate(Service::Interface* self); +/** + * Selects the output format for the listed cameras. + * + * Inputs: + * 0: 0x002500C0 + * 1: u8 Camera select (`CameraSelect` enum) + * 2: u8 Camera output format (`OutputFormat` enum) + * 3: u8 Context id (`Context` enum) + * Outputs: + * 0: 0x002500C0 + * 1: ResultCode + */ +void SetOutputFormat(Service::Interface* self); + /** * Returns calibration data relating the outside cameras to eachother, for use in AR applications. * diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp index a1070ebb2..3fce6fc50 100644 --- a/src/core/hle/service/cam/cam_u.cpp +++ b/src/core/hle/service/cam/cam_u.cpp @@ -45,7 +45,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x002200C0, nullptr, "SetEffect"}, {0x00230080, nullptr, "SetContrast"}, {0x00240080, nullptr, "SetLensCorrection"}, - {0x002500C0, nullptr, "SetOutputFormat"}, + {0x002500C0, SetOutputFormat, "SetOutputFormat"}, {0x00260140, nullptr, "SetAutoExposureWindow"}, {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"}, {0x00280080, nullptr, "SetNoiseFilter"}, diff --git a/src/core/hle/service/cam/impl.h b/src/core/hle/service/cam/impl.h new file mode 100644 index 000000000..363d8fc94 --- /dev/null +++ b/src/core/hle/service/cam/impl.h @@ -0,0 +1,26 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/hle/result.h" +#include "core/memory.h" + +namespace Service { +namespace CAM { + +ResultCode StartCapture(u8 port); +ResultCode StopCapture(u8 port); +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit); +ResultCode Activate(u8 cam_select); +ResultCode SetSize(u8 cam_select, u8 size, u8 context); +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context); +ResultCode DriverInitialize(); +ResultCode DriverFinalize(); + +} // namespace CAM + +} // namespace Service diff --git a/src/core/hle/service/cam/impl_dummy.cpp b/src/core/hle/service/cam/impl_dummy.cpp new file mode 100644 index 000000000..7ca798124 --- /dev/null +++ b/src/core/hle/service/cam/impl_dummy.cpp @@ -0,0 +1,58 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" +#include "common/logging/log.h" + +#include "core/memory.h" + +#include "core/hle/result.h" +#include "core/hle/service/cam/impl.h" + +namespace Service { +namespace CAM { + +ResultCode StartCapture(u8 port) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode StopCapture(u8 port) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode Activate(u8 cam_select) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetSize(u8 cam_select, u8 size, u8 context) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context) { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode DriverInitialize() { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +ResultCode DriverFinalize() { + LOG_ERROR(Service_CAM, "Unimplemented!"); + return UnimplementedFunction(ErrorModule::CAM); +} + +} // namespace CAM + +} // namespace Service diff --git a/src/core/hle/service/cam/impl_linux.cpp b/src/core/hle/service/cam/impl_linux.cpp new file mode 100644 index 000000000..39139e7b0 --- /dev/null +++ b/src/core/hle/service/cam/impl_linux.cpp @@ -0,0 +1,362 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "common/logging/log.h" + +#include "core/memory.h" + +#include "core/hle/result.h" +#include "core/hle/service/cam/cam.h" +#include "core/hle/service/cam/impl.h" + +namespace Service { +namespace CAM { + +// TODO(Link Mauve): make the video device configurable. +static const char *video_device = "/dev/video0"; +static const unsigned NUM_BUFFERS = 2; + +static int fd; +static enum v4l2_buf_type format_type; +static struct v4l2_format format; +static u32 width; +static u32 height; +static bool crop_image_horizontally; + +static struct { + char *memory; + size_t length; +} mapped_buffers[NUM_BUFFERS]; + +ResultCode StartCapture(u8 port) { + if (ioctl(fd, VIDIOC_STREAMON, &format_type) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_STREAMON: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode StopCapture(u8 port) { + if (ioctl(fd, VIDIOC_STREAMOFF, &format_type) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_STREAMOFF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode SetReceiving(VAddr dest, u8 port, u32 image_size, u16 trans_unit) { + // XXX: hack stereoscopy somehow. + if (!(port & 1)) + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NotSupported, ErrorLevel::Info); + + struct v4l2_buffer buf; + + std::memset(&buf, '\0', sizeof(buf)); + buf.type = format_type; + buf.memory = V4L2_MEMORY_MMAP; + + // Dequeue a buffer, at this point we can safely read from it. + // TODO(Link Mauve): this ioctl is blocking until a buffer is ready, maybe we don’t want that? + if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_DQBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + ASSERT(buf.flags & V4L2_BUF_FLAG_DONE); + + unsigned index = buf.index; + ASSERT(index < NUM_BUFFERS); + const char *memory = mapped_buffers[index].memory; + + // Not every capture device supports the size requested. + if (crop_image_horizontally) { + // There are always two bytes per pixel, both for YUYV and for RGB565. + const unsigned bytes_per_pixel = 2; + for (int y = 0; y < height; ++y) + Memory::WriteBlock(dest + (width * bytes_per_pixel * y), + static_cast(memory) + (format.fmt.pix.bytesperline * y), + width * bytes_per_pixel); + } else { + Memory::WriteBlock(dest, memory, image_size); + } + + // Enqueue back the buffer we just copied, so it will be filled again. + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +ResultCode Activate(u8 cam_select) { + struct v4l2_requestbuffers req; + struct v4l2_buffer buf; + + // TODO(Link Mauve): apparently used to deactivate them, according to 3ds-examples. + if (cam_select == 0) { + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NothingHappened, ErrorLevel::Info); + } + + // We are using the mmap interface instead of the user-pointer one because the user could + // change the target address at any frame. + std::memset(&req, '\0', sizeof(req)); + req.type = format_type; + req.memory = V4L2_MEMORY_MMAP; + req.count = NUM_BUFFERS; + + // Allocate our NUM_BUFFERS buffers. + if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_REQBUFS: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + if (req.count < NUM_BUFFERS) { + LOG_CRITICAL(Service_CAM, "wrong number of buffers!"); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + for (unsigned i = 0; i < NUM_BUFFERS; ++i) { + std::memset(&buf, '\0', sizeof(buf)); + buf.type = format_type; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + // Query the characteristics of the newly-allocated buffer. + if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QUERYBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Map the buffer so we can copy from this address whenever we get more data. + mapped_buffers[i].memory = static_cast(mmap(nullptr, buf.length, PROT_READ, + MAP_SHARED, fd, buf.m.offset)); + mapped_buffers[i].length = buf.length; + + // Enqueue the buffer to let the driver fill it whenever it gets an image. + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QBUF: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + } + + return RESULT_SUCCESS; +} + +ResultCode SetSize(u8 cam_select, u8 size, u8 context) { + switch (static_cast(size)) { + case Size::VGA: + width = 640; + height = 480; + break; + case Size::QVGA: + width = 320; + height = 240; + break; + case Size::QQVGA: + width = 160; + height = 120; + break; + case Size::CIF: + width = 352; + height = 288; + break; + case Size::QCIF: + width = 176; + height = 144; + break; + case Size::DS_LCD: + width = 256; + height = 192; + break; + case Size::DS_LCDx4: + width = 512; + height = 384; + break; + case Size::CTR_TOP_LCD: + width = 400; + height = 240; + break; + default: + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidSize, ErrorModule::CAM, + ErrorSummary::WrongArgument, ErrorLevel::Fatal); + } + + format.fmt.pix.width = width; + format.fmt.pix.height = height; + + if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_S_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + if (format.fmt.pix.width > width) { + LOG_WARNING(Service_CAM, "Got a larger format than requested (%d > %d), will crop", + format.fmt.pix.width, width); + crop_image_horizontally = true; + } else { + crop_image_horizontally = false; + } + + if (format.fmt.pix.height > height) + LOG_WARNING(Service_CAM, "Got a higher format than requested (%d > %d), will crop", + format.fmt.pix.height, height); + + return RESULT_SUCCESS; +} + +static inline const char * dump_format(uint32_t format, char out[4]) +{ +#if BYTE_ORDER == BIG_ENDIAN + format = __builtin_bswap32(format); +#endif + memcpy(out, &format, 4); + return out; +} + +ResultCode SetOutputFormat(u8 cam_select, u8 output_format, u8 context) { + u32 v4l2_format; + + switch (static_cast(output_format)) { + case OutputFormat::YUV422: + v4l2_format = V4L2_PIX_FMT_YUYV; + break; + case OutputFormat::RGB565: + v4l2_format = V4L2_PIX_FMT_RGB565; + break; + default: + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, + ErrorSummary::WrongArgument, ErrorLevel::Fatal); + } + + format.fmt.pix.pixelformat = v4l2_format; + + if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_S_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // TODO(Link Mauve): use y2r:u instead when the user asks for RGB565, since the vast majority + // of capture devices won’t give us that kind of format. + if (format.fmt.pix.pixelformat != v4l2_format) { + char buf1[4], buf2[4]; + LOG_ERROR(Service_CAM, "Wrong pixel format, asked %.4s got %.4s!", + dump_format(v4l2_format, buf1), dump_format(format.fmt.pix.pixelformat, buf2)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::NotImplemented, ErrorModule::CAM, + ErrorSummary::NotSupported, ErrorLevel::Permanent); + } + + return RESULT_SUCCESS; +} + +ResultCode DriverInitialize() { + struct v4l2_capability cap; + + // Open the chosen v4l2 device. + fd = open(video_device, O_RDWR); + if (fd < 0) { + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::NotFound, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Query the capabilities of that device. + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_QUERYCAP: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // We only support single-planar video capture devices currently, which represent the vast + // majority of camera devices. + if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { + format_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } else { + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // We are using the streaming interface. + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + LOG_CRITICAL(Service_CAM, "Not a streaming device!"); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // Retrieve the current format, which will be modified afterwards. + std::memset(&format, '\0', sizeof(format)); + format.type = format_type; + if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) { + LOG_ERROR(Service_CAM, "VIDIOC_G_FMT: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + // TODO(Link Mauve): find the default format on a 3DS and set it here. + + return RESULT_SUCCESS; +} + +ResultCode DriverFinalize() { + // TODO(Link Mauve): maybe this should be done in Activate(0) instead. + for (unsigned i = 0; i < NUM_BUFFERS; ++i) { + if (mapped_buffers[i].memory) + munmap(mapped_buffers[i].memory, mapped_buffers[i].length); + mapped_buffers[i].memory = nullptr; + } + + // Exits the API. + if (close(fd) == -1) { + LOG_ERROR(Service_CAM, "close: %s", std::strerror(errno)); + // TODO(Link Mauve): find the proper error code. + return ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CAM, + ErrorSummary::Internal, ErrorLevel::Fatal); + } + + return RESULT_SUCCESS; +} + +} // namespace CAM + +} // namespace Service