Files
2024-12-03 16:57:11 +08:00

384 lines
12 KiB
C

/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "linux_camera.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include "../../../utilities/include/sample_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
static int camera_capture_callback_set(struct camera_t* camera, vcamera_frame_get pcallback)
{
if (camera->state_ == CAMERA_SATTE_CAP) {
SLOGW("Set capture callback failed");
return -1;
}
camera->pcallback_ = pcallback;
return 0;
}
static void* camera_capture_thread(void* param)
{
int Ret = -1;
camera_t* camera = (camera_t*)param;
struct v4l2_buffer EnQueueBuf;
struct v4l2_buffer DeQueueBuf;
SLOGI("Start capture");
while (camera->state_ == CAMERA_SATTE_CAP) {
memset(&DeQueueBuf, 0, sizeof(struct v4l2_buffer));
DeQueueBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
DeQueueBuf.memory = V4L2_MEMORY_MMAP;
memset(&EnQueueBuf, 0, sizeof(struct v4l2_buffer));
EnQueueBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
EnQueueBuf.memory = V4L2_MEMORY_MMAP;
// Retrieve data from the queue
Ret = ioctl(camera->dev_fd_, VIDIOC_DQBUF, &DeQueueBuf);
if (Ret) {
perror("Dequeue fail");
break;
}
camera->pcallback_(camera->pcamera_buffer_[DeQueueBuf.index].pstart, camera->width_, camera->height_,
camera->pcamera_buffer_[DeQueueBuf.index].length, camera->ctx_);
// Re-queue the data
EnQueueBuf.index = DeQueueBuf.index;
if (ioctl(camera->dev_fd_, VIDIOC_QBUF, &EnQueueBuf)) {
perror("Enqueue fail");
break;
}
}
SLOGI("Stop capture");
return NULL;
}
static int camera_capture_start(struct camera_t* camera)
{
SLOGI("Start capture thread");
if (!camera->pcallback_) {
SLOGW("Capture callback not set, start faild");
return -1;
}
if (camera->state_ != CAMERA_SATTE_OPEN) {
SLOGW("Camera not open, start faild");
return -1;
}
camera->state_ = CAMERA_SATTE_CAP;
pthread_create(&camera->capture_thread_id_, NULL, camera_capture_thread, camera);
return 0;
}
static int camera_capture_stop(struct camera_t* camera)
{
SLOGI("Stop capture thread");
camera->state_ = CAMERA_SATTE_OPEN;
pthread_join(camera->capture_thread_id_, NULL);
SLOGI("Capture thread stop");
return 0;
}
static void camera_set_ctx(struct camera_t* camera, void* ctx)
{
camera->ctx_ = ctx;
}
int camera_open_from(camera_t* camera)
{
int Ret = -1;
if (camera == NULL) return -1;
/* Check whether the camera is already open or in an error state */
SLOGI("Open camera %s...", camera->dev_name_);
if (camera->state_ != CAMERA_STATE_CLOSE) {
SLOGE("Error: camera was open or meet error, now state is: %d", camera->state_);
goto ErrorHandle;
}
/* Open the camera device */
if ((camera->dev_fd_ = open(camera->dev_name_, O_RDWR, 0)) == -1) {
SLOGE("Open camera %s faild.", camera->dev_name_);
goto ErrorHandle;
}
SLOGI("Open device successful.");
/* Set the capture source */
struct v4l2_input Input;
Input.index = 0;
if (ioctl(camera->dev_fd_, VIDIOC_S_INPUT, &Input)) {
perror("VIDIOC_S_INPUT");
}
/* Retrieve camera information */
struct v4l2_capability Cap;
Ret = ioctl(camera->dev_fd_, VIDIOC_QUERYCAP, &Cap);
if (Ret) {
perror("Get device info error");
goto ErrorHandle;
}
SLOGI("Get device info successful.");
SLOGI("Driver name: %s", Cap.driver);
SLOGI("Card name: %s", Cap.card);
SLOGI("Bus info: %s", Cap.bus_info);
struct v4l2_format TvFmt;
struct v4l2_fmtdesc FmtDesc;
struct v4l2_streamparm StreamParam;
memset(&FmtDesc, 0, sizeof(FmtDesc));
FmtDesc.index = 0;
FmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* Enumerate supported frame formats */
SLOGI("Support format:");
while (ioctl(camera->dev_fd_, VIDIOC_ENUM_FMT, &FmtDesc) == 0) {
FmtDesc.index++;
SLOGI("Pixel format = %c%c%c%c", (FmtDesc.pixelformat & 0xFF), ((FmtDesc.pixelformat >> 8) & 0xFF),
((FmtDesc.pixelformat >> 16) & 0xFF), (FmtDesc.pixelformat >> 24));
SLOGI("Description = %s ", FmtDesc.description);
}
/* Determine if the device is a device that can capture video */
if (!(Cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE)) {
SLOGE("The Current device is not a video capture device");
goto ErrorHandle;
}
/* Determine if video streaming is supported */
if (!(Cap.capabilities & V4L2_CAP_STREAMING)) {
SLOGE("The Current device does not support streaming i/o");
goto ErrorHandle;
}
/* Get video stream information */
memset(&StreamParam, 0, sizeof(struct v4l2_streamparm));
StreamParam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
Ret = ioctl(camera->dev_fd_, VIDIOC_G_PARM, &StreamParam);
if (Ret) {
perror("Get stream info failed");
goto ErrorHandle;
}
SLOGI("Before setting stream params:");
SLOGI("Capability: %u", StreamParam.parm.capture.capability);
SLOGI("Capturemode: %u", StreamParam.parm.capture.capturemode);
SLOGI("Extendedmode: %u", StreamParam.parm.capture.extendedmode);
SLOGI("Timeperframe denominator: %u", StreamParam.parm.capture.timeperframe.denominator);
SLOGI("Timeperframe numerator: %u", StreamParam.parm.capture.timeperframe.numerator);
/* Set frame rate denominator and numerator */
StreamParam.parm.capture.timeperframe.denominator = camera->capture_fps_;
StreamParam.parm.capture.timeperframe.numerator = 1;
if (ioctl(camera->dev_fd_, VIDIOC_S_PARM, &StreamParam) == -1) {
perror("Unable to set fps");
goto ErrorHandle;
}
/* Get video stream information */
Ret = ioctl(camera->dev_fd_, VIDIOC_G_PARM, &StreamParam);
if (Ret) {
perror("Get stream info failed");
goto ErrorHandle;
}
SLOGI("After setting stream params:");
SLOGI("Capability: %u", StreamParam.parm.capture.capability);
SLOGI("Capturemode: %u", StreamParam.parm.capture.capturemode);
SLOGI("Extendedmode: %u", StreamParam.parm.capture.extendedmode);
SLOGI("Timeperframe denominator: %u", StreamParam.parm.capture.timeperframe.denominator);
SLOGI("Timeperframe numerator: %u", StreamParam.parm.capture.timeperframe.numerator);
/* Set automatic exposure */
struct v4l2_control Ctrl;
Ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE;
Ret = ioctl(camera->dev_fd_, VIDIOC_G_CTRL, &Ctrl);
if (Ret) {
perror("Set exposure failed");
// goto ErrorHandle;
}
/* Set capture format */
TvFmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
TvFmt.fmt.pix.width = camera->width_;
TvFmt.fmt.pix.height = camera->height_;
TvFmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
TvFmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(camera->dev_fd_, VIDIOC_S_FMT, &TvFmt) < 0) {
perror("Unable set VIDIOC_S_FMT");
goto ErrorHandle;
}
if (ioctl(camera->dev_fd_, VIDIOC_G_FMT, &TvFmt) < 0) {
perror("Get VIDIOC_S_FMT error");
goto ErrorHandle;
}
SLOGI("After setting frame params:");
SLOGI("Width: %d, height: %d", TvFmt.fmt.pix.width, TvFmt.fmt.pix.height);
SLOGI("Pixel format = %c%c%c%c", (TvFmt.fmt.pix.pixelformat & 0xFF), ((TvFmt.fmt.pix.pixelformat >> 8) & 0xFF),
((TvFmt.fmt.pix.pixelformat >> 16) & 0xFF), (TvFmt.fmt.pix.pixelformat >> 24));
camera->width_ = TvFmt.fmt.pix.width;
camera->height_ = TvFmt.fmt.pix.height;
/* Request V4L2 driver to allocate memory */
struct v4l2_requestbuffers ReqBuffer;
memset(&ReqBuffer, 0, sizeof(ReqBuffer));
ReqBuffer.count = camera->buffer_cnt_;
ReqBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ReqBuffer.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(camera->dev_fd_, VIDIOC_REQBUFS, &ReqBuffer)) {
perror("Fail to ioctl 'VIDIOC_REQBUFS'");
goto ErrorHandle;
}
camera->buffer_cnt_ = ReqBuffer.count;
SLOGI("Buffer count: %d", camera->buffer_cnt_);
camera->pcamera_buffer_ = (CAMERA_Buffer_t*)calloc(camera->buffer_cnt_, sizeof(CAMERA_Buffer_t));
if (camera->pcamera_buffer_ == NULL) {
SLOGE("Malloc buffer failed");
goto ErrorHandle;
}
/* Map kernel buffer to user space */
struct v4l2_buffer Buf;
for (int i = 0; i < camera->buffer_cnt_; i++) {
memset(&Buf, 0, sizeof(Buf));
Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
Buf.memory = V4L2_MEMORY_MMAP;
Buf.index = i;
if (-1 == ioctl(camera->dev_fd_, VIDIOC_QUERYBUF, &Buf)) {
perror("Fail to ioctl : VIDIOC_QUERYBUF");
goto ErrorHandle;
}
camera->pcamera_buffer_[i].length = Buf.length;
camera->pcamera_buffer_[i].pstart =
mmap(NULL, Buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camera->dev_fd_, Buf.m.offset);
if (MAP_FAILED == camera->pcamera_buffer_[i].pstart) {
perror("Fail to mmap");
goto ErrorHandle;
}
if (ioctl(camera->dev_fd_, VIDIOC_QBUF, &Buf)) {
perror("Unable to queue buffer");
goto ErrorHandle;
}
}
/* Start capturing */
enum v4l2_buf_type BufType;
BufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(camera->dev_fd_, VIDIOC_STREAMON, &BufType) < 0) {
perror("VIDIOC_STREAMON error");
goto ErrorHandle;
}
camera->state_ = CAMERA_SATTE_OPEN;
camera->camera_capture_callback_set = camera_capture_callback_set;
camera->camera_capture_start = camera_capture_start;
camera->camera_capture_stop = camera_capture_stop;
camera->camera_set_ctx = camera_set_ctx;
camera->is_alloc_ = 0;
return 0;
ErrorHandle:
SLOGE("Camera open meet error, now handle it");
close(camera->dev_fd_);
free(camera->pcamera_buffer_);
return -1;
}
camera_t* camera_open(const char* pdev_name, int width, int height, int fps)
{
int Ret = -1;
camera_t* camera = (camera_t*)malloc(sizeof(camera_t));
if (camera == NULL) return NULL;
memset(camera, 0, sizeof(camera_t));
camera->buffer_cnt_ = CONFIG_CAPTURE_BUF_CNT;
int CopyLen = strlen(pdev_name);
if (CopyLen > CONFIG_DEVNAME_LEN - 1) {
SLOGE("Error: device name length over limit: %d", CopyLen);
goto ErrorHandle;
}
memset(camera->dev_name_, 0, CONFIG_DEVNAME_LEN);
memcpy(camera->dev_name_, pdev_name, CopyLen);
camera->width_ = width;
camera->height_ = height;
camera->capture_fps_ = fps;
Ret = camera_open_from(camera);
if (Ret) {
goto ErrorHandle;
}
camera->is_alloc_ = 1;
return camera;
ErrorHandle:
SLOGE("Camera open meet error, now handle it");
free(camera);
return NULL;
}
int camera_close(camera_t* camera)
{
if (camera == NULL) return -1;
if (camera->state_ == CAMERA_SATTE_OPEN || camera->state_ == CAMERA_SATTE_CAP) {
SLOGI("Close camera %s", camera->dev_name_);
} else {
SLOGW("Skip close camera progress");
return 0;
}
for (int i = 0; i < CONFIG_CAPTURE_BUF_CNT; i++) {
munmap(camera->pcamera_buffer_[i].pstart, camera->pcamera_buffer_[i].length);
camera->pcamera_buffer_[i].length = 0;
}
free(camera->pcamera_buffer_);
camera->pcamera_buffer_ = NULL;
int Off = 1;
if (ioctl(camera->dev_fd_, VIDIOC_STREAMOFF, &Off)) {
perror("Stop camera fail");
return -1;
}
close(camera->dev_fd_);
if (camera->is_alloc_) free(camera);
SLOGI("camera closed");
return 0;
}