mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 05:00:14 +00:00
Service::CAM: Add a V4L2-based implementation.
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.
This commit is contained in:
parent
ffda82eea5
commit
6fb23da375
@ -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})
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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"},
|
||||
|
26
src/core/hle/service/cam/impl.h
Normal file
26
src/core/hle/service/cam/impl.h
Normal file
@ -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
|
58
src/core/hle/service/cam/impl_dummy.cpp
Normal file
58
src/core/hle/service/cam/impl_dummy.cpp
Normal file
@ -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
|
362
src/core/hle/service/cam/impl_linux.cpp
Normal file
362
src/core/hle/service/cam/impl_linux.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <cstring>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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<const char*>(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<char*>(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>(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<OutputFormat>(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
|
Loading…
Reference in New Issue
Block a user