在RK3568平台上使用C++结合V4L2捕获视频流,并通过RKMPP进行硬件编码后保存为MP4文件,可以按照以下步骤实现:
1. 环境准备
V4L2_13">2. V4L2视频捕获
初始化V4L2设备并配置视频捕获参数:
#include <linux/videodev2.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
int init_v4l2(const char* device, int width, int height) {
int fd = open(device, O_RDWR);
if (fd < 0) return -1;
// 设置视频格式
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 根据摄像头支持调整
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
close(fd);
return -1;
}
// 申请缓冲区
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
close(fd);
return -1;
}
// 映射内存并入队
for (int i = 0; i < req.count; ++i) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
close(fd);
return -1;
}
void* ptr = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
// 保存缓冲区指针...
ioctl(fd, VIDIOC_QBUF, &buf);
}
// 启动视频流
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
return fd;
}
RKMPP_72">3. RKMPP编码器初始化
配置RKMPP的H.264编码器:
#include <rockchip/rk_mpi.h>
MppCtx init_rkmpp_encoder(int width, int height) {
MppCtx ctx = NULL;
MppApi *mpi = NULL;
MppParam param = NULL;
mpp_create(&ctx, &mpi);
mpi->control(ctx, MPP_SET_OUTPUT_FORMAT, MPP_FMT_YUV420SP); // 输入格式需与V4L2一致
// 配置编码参数
MppEncCodecCfg codec_cfg = {0};
codec_cfg.coding = MPP_VIDEO_CodingAVC;
mpi->control(ctx, MPP_ENC_SET_CODEC_CFG, &codec_cfg);
MppEncPrepCfg prep_cfg = {0};
prep_cfg.width = width;
prep_cfg.height = height;
prep_cfg.format = MPP_FMT_YUV420SP;
mpi->control(ctx, MPP_ENC_SET_PREP_CFG, &prep_cfg);
MppEncRcCfg rc_cfg = {0};
rc_cfg.rc_mode = MPP_ENC_RC_MODE_CBR;
rc_cfg.bps_target = 4000000; // 码率4Mbps
mpi->control(ctx, MPP_ENC_SET_RC_CFG, &rc_cfg);
return ctx;
}
4. FFmpeg封装MP4文件
初始化FFmpeg用于写入MP4容器:
extern "C" {
#include <libavformat/avformat.h>
}
AVFormatContext* init_mp4_writer(const char* filename, int width, int height) {
avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, filename);
AVStream* stream = avformat_new_stream(fmt_ctx, NULL);
stream->codecpar->codec_id = AV_CODEC_ID_H264;
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codecpar->width = width;
stream->codecpar->height = height;
stream->time_base = (AVRational){1, 30};
avio_open(&fmt_ctx->pb, filename, AVIO_FLAG_WRITE);
avformat_write_header(fmt_ctx, NULL);
return fmt_ctx;
}
5. 主循环处理
捕获、编码、写入循环:
void capture_encode_save(int v4l2_fd, MppCtx encoder, AVFormatContext* mp4_ctx) {
AVStream* stream = mp4_ctx->streams[0];
int64_t pts = 0;
while (true) {
// 从V4L2捕获一帧
struct v4l2_buffer v4l2_buf = {0};
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
ioctl(v4l2_fd, VIDIOC_DQBUF, &v4l2_buf);
void* frame_data = ...; // 获取映射的缓冲区地址
// 编码(伪代码,需适配RKMPP API)
MppBuffer mpp_buf;
mpp_buffer_put(mpp_buf, frame_data, v4l2_buf.length);
mpi->encode_put_frame(encoder, mpp_buf);
MppPacket packet;
while (mpi->encode_get_packet(encoder, &packet) == MPP_OK) {
AVPacket av_pkt = {0};
av_pkt.data = mpp_packet_get_data(packet);
av_pkt.size = mpp_packet_get_length(packet);
av_pkt.pts = pts++;
av_pkt.stream_index = stream->index;
av_write_frame(mp4_ctx, &av_pkt);
}
ioctl(v4l2_fd, VIDIOC_QBUF, &v4l2_buf);
}
}
6. 资源清理
退出时释放资源:
void cleanup(int v4l2_fd, MppCtx encoder, AVFormatContext* mp4_ctx) {
// 停止V4L2
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(v4l2_fd, VIDIOC_STREAMOFF, &type);
close(v4l2_fd);
// 销毁RKMPP编码器
mpp_destroy(encoder);
// 结束MP4写入
av_write_trailer(mp4_ctx);
avio_closep(&mp4_ctx->pb);
avformat_free_context(mp4_ctx);
}
注意事项
- 格式转换:若V4L2输出格式与编码器输入格式不匹配(如YUYV转YUV420),需使用libyuv或手动转换。
- 时间戳管理:根据实际帧率生成正确的PTS/DTS。
- 关键帧标记:在AVPacket中设置
AV_PKT_FLAG_KEY
以标记关键帧。 - 错误处理:所有IOCTL和API调用需检查返回值。
通过上述步骤,可以实现RK3568上的视频捕获、硬件编码及MP4封装。实际开发中需参考RKMPP和FFmpeg的具体API文档调整代码。