最近在做视频监控的项目,搞了一个多星期,总结一下学到的东西,也希望可以帮到有需要的人
从整体来看,推流端大概是这么个流程:采集、处理、编码、封装、推送
如上图所示,图像采集线程和声音采集线程经过编码封装,将RTMP包写入到缓冲队列,发送线程从缓冲队列中读取RTMP包中并加上时间戳,然后送往RTMP服务器,由于一秒可能有上百个RTMP包,会造成大量的new和delete,所以实现了一个简单的内存池以减少内存分配及释放的次数
采集
分为图像和声音,采集图像用的是v4l2 API,采集声音用的是alsa API,这两个都是linux环境下的,暂时没有考虑其他环境
v4l2
流程:打开设备、检查设备能力、设置格式、设置缓冲区、读取缓冲队列
详细代码位于V4L2Source.cpp
,当然也可以看官方Demo
打开设备:首先是打开设备文件,也可以用非阻塞的方式打开,但接下来的相关IO操作也是非阻塞的
fd = open("/dev/video0", O_RDWR);
检查设备能力:通过 V4L2_CAP_VIDEO_CAPTURE
位来判断设备是否具有捕获图像的能力
v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
return false;
}
设置格式:这一步之前可能需要检查一下摄像头支持的格式,检查需要用到ioctl
函数和VIDIOC_ENUM_FMT
参数,这里略过了这一步,统一设置成了支持较多的YUYV,也叫YUY2,然后编码前再把图片转为所需格式
v4l2_format fmt;
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;
ioctl(fd, VIDIOC_S_FMT, &fmt);
if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {
return false;
}
设置缓冲区:缓冲区是一个队列,用于接收图像数据,这里用mmap
的方式,将内核空间的内存映射到用户空间,这样可以避免拷贝,这里首先需要定义一个结构体来保存返回缓冲区的位置和大小
v4l2_requestbuffers reqbuf;
v4l2_buffer buf;
reqbuf.count = 4;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &reqbuf);