小学生
最后登录1970-1-1
在线时间 小时
注册时间2024-10-21
|
楼主 |
发表于 2024-10-22 18:04:54
|
显示全部楼层
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> /* 低级I/O */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <time.h>
#include <rockchip/rk_mpi.h> /* Rockchip 多媒体处理平台(MPP)头文件 */
/* 定义缓冲区结构体 */
struct buffer {
void *start; /* 缓冲区起始地址 */
size_t length; /* 缓冲区长度 */
};
static char *dev_name = "/dev/video11"; /* 摄像头设备名称 */
static int fd = -1; /* 设备文件描述符 */
struct buffer *buffers; /* 缓冲区数组 */
static unsigned int n_buffers; /* 缓冲区数量 */
MppCtx ctx = NULL; /* MPP 上下文 */
MppApi *mpi = NULL; /* MPP API 接口 */
/* 定义分辨率和格式 */
#define WIDTH 1920
#define HEIGHT 1080
#define PIXEL_FORMAT V4L2_PIX_FMT_NV12 /* 使用 NV12 格式 */
#define MPP_FORMAT MPP_FMT_YUV420SP /* 对应 MPP 的 NV12 格式 */
int init_camera(void);
int start_capturing(void);
int capture_frame(int *index);
int stop_capturing(void);
int uninit_camera(void);
int init_mpp(void);
int encode_frame(void *input_buffer, size_t input_size);
int deinit_mpp(void);
int main(int argc, char **argv)
{
int ret;
struct timespec start_time, end_time;
double elapsed_time;
/* 初始化摄像头 */
ret = init_camera();
if (ret < 0) {
fprintf(stderr, "初始化摄像头失败\n");
return -1;
}
/* 初始化 MPP */
ret = init_mpp();
if (ret < 0) {
fprintf(stderr, "初始化 MPP 失败\n");
return -1;
}
/* 开始视频捕获 */
ret = start_capturing();
if (ret < 0) {
fprintf(stderr, "开始捕获失败\n");
return -1;
}
/* 捕获并编码 1000 帧图像 */
for (int i = 0; i < 1000; i++) {
int buf_index;
ret = capture_frame(&buf_index);
if (ret < 0) {
fprintf(stderr, "捕获第 %d 帧失败\n", i);
break;
}
/* 开始计时(仅统计压缩部分时间) */
clock_gettime(CLOCK_MONOTONIC, &start_time);
for (int i = 0; i < 1000; i++){
ret = encode_frame(buffers[buf_index].start, buffers[buf_index].length);
if (ret < 0) {
fprintf(stderr, "编码第 %d 帧失败\n", i);
break;
}
}
/* 结束计时 */
clock_gettime(CLOCK_MONOTONIC, &end_time);
/* 计算耗时 */
elapsed_time = (end_time.tv_sec - start_time.tv_sec) +
(end_time.tv_nsec - start_time.tv_nsec) / 1e9;
printf("第 %d 帧压缩耗时 %.6f 秒\n", i, elapsed_time);
/* 重新将缓冲区入队,供下一次捕获使用 */
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
memset(&buf, 0, sizeof(buf));
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = buf_index;
buf.m.planes = planes;
buf.length = 1; /* NV12 格式有 1 个平面 */
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if (ret == -1) {
perror("重新队列缓冲区失败");
return -1;
}
}
/* 停止捕获并释放资源 */
stop_capturing();
uninit_camera();
deinit_mpp();
return 0;
}
/* 初始化摄像头设备 */
int init_camera(void)
{
struct v4l2_capability cap;
struct v4l2_format fmt;
int ret;
/* 打开摄像头设备 */
fd = open(dev_name, O_RDWR | O_NONBLOCK, 0);
if (fd == -1) {
perror("打开视频设备失败");
return -1;
}
/* 查询设备能力 */
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret == -1) {
perror("查询设备能力失败");
return -1;
}
/* 检查设备是否支持多平面视频捕获 */
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
fprintf(stderr, "设备不支持多平面视频捕获\n");
return -1;
}
/* 设置视频格式 */
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = WIDTH;
fmt.fmt.pix_mp.height = HEIGHT;
fmt.fmt.pix_mp.pixelformat = PIXEL_FORMAT; /* 使用 NV12 格式 */
fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
fmt.fmt.pix_mp.num_planes = 1; /* NV12 格式有 1 个平面 */
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if (ret == -1) {
perror("设置像素格式失败");
return -1;
}
/* 请求缓冲区 */
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4; /* 请求 4 个缓冲区 */
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req);
if (ret == -1) {
perror("请求缓冲区失败");
return -1;
}
if (req.count < 2) {
fprintf(stderr, "内存映射失败,缓冲区数量不足\n");
return -1;
}
/* 分配缓冲区 */
buffers = calloc(req.count, sizeof(*buffers));
if (!buffers) {
fprintf(stderr, "内存分配失败\n");
return -1;
}
/* 逐个缓冲区进行映射 */
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
memset(&buf, 0, sizeof(buf));
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
buf.m.planes = planes;
buf.length = 1; /* NV12 格式有 1 个平面 */
/* 查询缓冲区信息 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
if (ret == -1) {
perror("查询缓冲区失败");
return -1;
}
/* 映射缓冲区到用户空间 */
buffers[n_buffers].length = buf.m.planes[0].length;
buffers[n_buffers].start =
mmap(NULL, buf.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.planes[0].m.mem_offset);
if (MAP_FAILED == buffers[n_buffers].start) {
perror("内存映射失败");
return -1;
}
}
return 0;
}
/* 开始视频捕获 */
int start_capturing(void)
{
unsigned int i;
enum v4l2_buf_type type;
int ret;
/* 将所有缓冲区入队,准备开始捕获 */
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
memset(&buf, 0, sizeof(buf));
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.planes = planes;
buf.length = 1; /* NV12 格式有 1 个平面 */
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if (ret == -1) {
perror("VIDIOC_QBUF 失败");
return -1;
}
}
/* 启动视频流 */
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret == -1) {
perror("VIDIOC_STREAMON 失败");
return -1;
}
return 0;
}
/* 捕获一帧图像 */
int capture_frame(int *index)
{
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
fd_set fds;
struct timeval tv;
int r, ret;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* 设置超时时间 */
tv.tv_sec = 2;
tv.tv_usec = 0;
/* 等待摄像头设备准备好 */
r = select(fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
return -1;
perror("select 失败");
return -1;
}
if (0 == r) {
fprintf(stderr, "select 超时\n");
return -1;
}
memset(&buf, 0, sizeof(buf));
memset(planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.m.planes = planes;
buf.length = 1; /* NV12 格式有 1 个平面 */
/* 从队列中取出已捕获的缓冲区 */
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
if (ret == -1) {
perror("VIDIOC_DQBUF 失败");
return -1;
}
/* 返回缓冲区索引 */
*index = buf.index;
return 0;
}
/* 初始化 MPP 编码器 */
int init_mpp(void)
{
MPP_RET ret;
MppEncCfg cfg = NULL;
int quality = 100 ;
/* 创建 MPP 上下文 */
ret = mpp_create(&ctx, &mpi);
if (ret) {
fprintf(stderr, "mpp_create 失败\n");
return -1;
}
/* 初始化 MPP 上下文为编码器类型,指定编码格式为 MJPEG */
ret = mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingMJPEG);
if (ret) {
fprintf(stderr, "mpp_init 失败\n");
mpp_destroy(ctx);
ctx = NULL;
return -1;
}
/* 初始化编码器配置 */
ret = mpp_enc_cfg_init(&cfg);
if (ret) {
fprintf(stderr, "mpp_enc_cfg_init 失败\n");
mpp_destroy(ctx);
ctx = NULL;
return -1;
}
/* 计算对齐后的步幅,通常对齐到 16 字节 */
int hor_stride = (WIDTH + 15) & (~15);
int ver_stride = (HEIGHT + 15) & (~15);
/* 设置编码器配置参数 */
ret = mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FORMAT); /* 输入格式 */
ret |= mpp_enc_cfg_set_s32(cfg, "prep:width", WIDTH); /* 图像宽度 */
ret |= mpp_enc_cfg_set_s32(cfg, "prep:height", HEIGHT); /* 图像高度 */
ret |= mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", hor_stride); /* 水平步幅 */
ret |= mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", ver_stride); /* 垂直步幅 */
ret |= mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingMJPEG); /* 编码器类型 */
ret |= mpp_enc_cfg_set_s32(cfg, "prep:format",MPP_FORMAT);
if (ret) {
fprintf(stderr, "mpp_enc_cfg_set_s32 失败\n");
mpp_enc_cfg_deinit(cfg);
mpp_destroy(ctx);
ctx = NULL;
return -1;
}
/* 应用编码器配置 */
ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
if (ret) {
fprintf(stderr, "mpi->control MPP_ENC_SET_CFG 失败\n");
mpp_enc_cfg_deinit(cfg);
mpp_destroy(ctx);
ctx = NULL;
return -1;
}
/* 释放编码器配置 */
mpp_enc_cfg_deinit(cfg);
return 0;
}
/* 编码一帧图像 */
int encode_frame(void *input_buffer, size_t input_size)
{
MPP_RET ret;
MppFrame frame = NULL;
MppPacket packet = NULL;
MppBuffer frame_buffer = NULL;
/* 初始化 MppFrame */
ret = mpp_frame_init(&frame);
if (ret) {
fprintf(stderr, "mpp_frame_init 失败\n");
return -1;
}
/* 计算对齐后的步幅 */
int hor_stride = (WIDTH + 15) & (~15);
int ver_stride = (HEIGHT + 15) & (~15);
size_t frame_size = hor_stride * ver_stride * 3 / 2;
/* 分配 MppBuffer,用于存储输入帧数据 */
ret = mpp_buffer_get(NULL, &frame_buffer, frame_size);
if (ret) {
fprintf(stderr, "mpp_buffer_get 失败\n");
mpp_frame_deinit(&frame);
return -1;
}
/* 将帧缓冲区初始化为零 */
memset(mpp_buffer_get_ptr(frame_buffer), 0, frame_size);
/* 将输入数据拷贝到 MppBuffer,考虑步幅对齐 */
uint8_t *dst = (uint8_t *)mpp_buffer_get_ptr(frame_buffer);
uint8_t *src = (uint8_t *)input_buffer;
/* 复制 Y 平面 */
for (int i = 0; i < HEIGHT; i++) {
memcpy(dst + i * hor_stride, src + i * WIDTH, WIDTH);
}
/* 复制 UV 平面 */
dst = mpp_buffer_get_ptr(frame_buffer) + hor_stride * ver_stride;
src = (uint8_t *)input_buffer + WIDTH * HEIGHT;
for (int i = 0; i < HEIGHT / 2; i++) {
memcpy(dst + i * hor_stride, src + i * WIDTH, WIDTH);
}
/* 设置 MppFrame 参数 */
mpp_frame_set_buffer(frame, frame_buffer);
mpp_frame_set_fmt(frame, MPP_FORMAT); /* 图像格式 */
mpp_frame_set_width(frame, WIDTH); /* 图像宽度 */
mpp_frame_set_height(frame, HEIGHT); /* 图像高度 */
mpp_frame_set_hor_stride(frame, hor_stride); /* 水平步幅 */
mpp_frame_set_ver_stride(frame, ver_stride); /* 垂直步幅 */
/* 将帧发送到编码器 */
ret = mpi->encode_put_frame(ctx, frame);
if (ret) {
fprintf(stderr, "mpi->encode_put_frame 失败\n");
mpp_frame_deinit(&frame);
mpp_buffer_put(frame_buffer);
return -1;
}
/* 获取编码后的数据 */
ret = mpi->encode_get_packet(ctx, &packet);
if (ret) {
fprintf(stderr, "mpi->encode_get_packet 失败\n");
mpp_frame_deinit(&frame);
mpp_buffer_put(frame_buffer);
return -1;
}
if (packet) {
/* 编码后的数据处理(根据需要保存或传输) */
/* 释放 MppPacket */
mpp_packet_deinit(&packet);
}
/* 释放资源 */
mpp_frame_deinit(&frame);
mpp_buffer_put(frame_buffer);
return 0;
}
/* 释放 MPP 资源 */
int deinit_mpp(void)
{
if (ctx) {
mpp_destroy(ctx);
ctx = NULL;
}
return 0;
}
/* 停止视频捕获 */
int stop_capturing(void)
{
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
perror("VIDIOC_STREAMOFF 失败");
return 0;
}
/* 释放摄像头资源 */
int uninit_camera(void)
{
unsigned int i;
/* 解除映射缓冲区 */
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap(buffers[i].start, buffers[i].length))
perror("munmap 失败");
/* 释放缓冲区数组 */
free(buffers);
/* 关闭设备文件 */
if (fd != -1)
close(fd);
return 0;
} |
|