基于安卓9.0系统,简单实现打开摄像头并获取视频流数据。
以下例子是C++代码,可以做成jni so库方式给安卓应用程序使用,获取到视频数据用OpenGL方式显示出来。
1、打开摄像头
摄像头设备为:/dev/video0
//打开摄像头 deviceName为/dev/video0
if ((fd = open(deviceName, O_RDWR, 0)) < 0){
return false;
}
2、查询视频设备的能力,是否具有视频输入,或者音频输入功能
VIDIOC_QUERYCAP
v4l2_capability caps;
{
if (ioctl(fd, VIDIOC_QUERYCAP, &caps) < 0) {
ALOGE("V4l2Capture::%s,failed to get device caps for %s (%d = %s)",__func__,deviceName, errno, strerror(errno));
close(fd);
return false;
}
// Report device properties
ALOGI("V4l2Capture::%s,Open Device: %s (fd=%d)",__func__ , deviceName, fd);
ALOGI(" Driver: %s", caps.driver);
ALOGI(" Card: %s", caps.card);
ALOGI(" Version: %u.%u.%u",(caps.version >> 16) & 0xFF,(caps.version >> 8) & 0xFF,(caps.version) & 0xFF);
ALOGI(" All Caps: %08X", caps.capabilities);
ALOGI(" Dev Caps: %08X", caps.device_caps);
}
3、查询或者设置视频的制式或者帧格式
VIDIOC_ENUM_FMT
v4l2_fmtdesc formatDescriptions;
formatDescriptions.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
{
for (int i=0; true; i++) {
formatDescriptions.index = i;
// query supported format
if (ioctl(fd, VIDIOC_ENUM_FMT, &formatDescriptions) == 0) {
ALOGI("V4l2Capture::%s, %2d: %s 0x%08X 0x%X",__func__,i,formatDescriptions.description,formatDescriptions.pixelformat,formatDescriptions.flags);
}else {
// No more formats available
break;
}
}
// Verify we can use this device for video capture
if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) ||
!(caps.capabilities & V4L2_CAP_STREAMING)) {
// Can't do streaming capture.
ALOGE("V4l2Capture::%s,Streaming capture not supported by %s.",__func__, deviceName);
return false;
}
}
4、设置视频采集的帧率
VIDIOC_S_PARM
struct v4l2_streamparm param;
memset(¶m, 0, sizeof(param));
param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
param.parm.capture.timeperframe.numerator = 1;
param.parm.capture.timeperframe.denominator = 30;
param.parm.capture.capturemode = getCaptureMode(fd, mWidth, mHeight);
int ret = ioctl(fd, VIDIOC_S_PARM, ¶m);
if (ret < 0) {
ALOGE("V4l2Capture::%s: VIDIOC_S_PARM Failed: %s", __func__, strerror(errno));
return false;
}
5、设置视频的帧格式,包括帧的点阵格式,宽度和高度等
VIDIOC_S_FMT
v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
format.fmt.pix_mp.pixelformat = mV4lFormat;
format.fmt.pix_mp.width = mWidth;
format.fmt.pix_mp.height = mHeight;
// TODO: Do we need to specify this?
//format.fmt.pix_mp.field = V4L2_FIELD_ALTERNATE;
format.fmt.pix_mp.num_planes = 1;
ALOGI("V4l2Capture::%s,Requesting format %c%c%c%c (0x%08X)",__func__,((char*)&format.fmt.pix.pixelformat)[0],((char*)&format.fmt.pix.pixelformat)[1],((char*)&format.fmt.pix.pixelformat)[2],((char*)&format.fmt.pix.pixelformat)[3],format.fmt.pix.pixelformat);
if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) {
ALOGE("V4l2Capture::%s,IDIOC_S_FMT: %s",__func__, strerror(errno));
}
6、查看视频的帧格式,包括帧的点阵格式,宽度和高度等
VIDIOC_G_FMT
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (ioctl(fd, VIDIOC_G_FMT, &format) == 0) {
mV4lFormat = format.fmt.pix_mp.pixelformat;
mWidth = format.fmt.pix_mp.width;
mHeight = format.fmt.pix_mp.height;
ALOGI("V4l2Capture::%s,Current output format: fmt=0x%X, %dx%d",__func__,format.fmt.pix_mp.pixelformat,format.fmt.pix_mp.width,format.fmt.pix_mp.height);
}else {
ALOGE("V4l2Capture::%s,VIDIOC_G_FMT: %s",__func__, strerror(errno));
return false;
}
7、向驱动请求申请帧缓冲区
VIDIOC_REQBUFS
这一步是比较重要的一步,申请若干个帧缓冲区,一般不少于3个。申请缓冲区已在内核空间中申请了一段内存空间。
struct v4l2_requestbuffers req; // Parameters of the device buffers
// Initiate Memory Mapping, User Pointer I/O or DMA buffer I/O
memset(&req, 0, sizeof(req));
req.count = BUFFER_NUM; // The number of buffers requested or granted
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = mMemoryType;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0){
ALOGE("V4l2Capture::%s,VIDIOC_REQBUFS: %s",__func__, strerror(errno));
}
8、查询帧缓冲区在内核空间中的长度和偏移量
VideoBuffer mBuffers[BUFFER_NUM]; // buffers
VIDIOC_QUERYBUF
内存方式为:V4L2_MEMORY_MMAP
struct v4l2_buffer buf;
struct v4l2_plane planes = { 0 };
enum v4l2_buf_type type;
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;
buf.index = i;
// Query the status of a buffer at any time after mBuffers have been allocated with the VIDIOC_REQBUFS ioctl
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
cout << "VIDIOC_QUERYBUF error" << endl;
ALOGE("V4l2Capture::%s VIDIOC_QUERYBUF error", __func__);
ALOGE("V4l2Capture::%s VIDIOC_QUERYBUF error:%s", __func__,strerror(errno));
return false;
}
//用mmap申请内存空间,将VIDIOC_REQBUFS 申请到内核空间帧缓冲区地址映射到用户空间
mBuffers[i].length = buf.m.planes->length;
mBuffers[i].offset = (size_t) buf.m.planes->m.mem_offset;
mBuffers[i].filled = 0;
mBuffers[i].invalid = 0;
mBuffers[i].start = (unsigned char*)mmap(NULL, mBuffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mBuffers[i].offset);
memset(mBuffers[i].start, 0xFF, mBuffers[i].length);
9、将申请到的帧缓冲区全部放入视频采集输出队列,以便存放采集的数据
VIDIOC_QBUF
memset(&buf, 0, sizeof(buf));
memset(&planes, 0, sizeof(planes));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = mMemoryType;
buf.m.planes = &planes;
buf.index = i;
buf.m.planes->length = mBuffers[i].length;
buf.length = 1;
buf.m.planes->m.mem_offset = mBuffers[i].offset;
// Enqueue an empty (capturing) or filled (output) buffer in the driver's incoming queue
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
cout << "start VIDIOC_QBUF error" << endl;
ALOGE("V4l2Capture::%s start VIDIOC_QBUF error", __func__);
return(-1);
}
10、开始视频流数据的采集
VIDIOC_STREAMON
// Start streaming I/O
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
cout << "VIDIOC_STREAMON error" << endl;
ALOGE("V4l2Capture::%s VIDIOC_STREAMON error", __func__);
return(-1);
} else{
cout << "VIDIOC_STREAMON success" << endl;
ALOGE("V4l2Capture::%s VIDIOC_STREAMON success", __func__);
}
11、app从视频采集输出列列出已含有采集数据的帧缓冲区,获取该帧缓冲区的原始视频数据
//最终得到的视频数据就保存在mCaptureBuf中
v4l2_buffer* mCaptureBuf[BUFFER_NUM];
VIDIOC_DQBUF
struct v4l2_plane planes={0};
memset(mCaptureBuf[index], 0, sizeof(capture_buf[index]));
memset(&planes, 0, sizeof(planes));
mCaptureBuf[index]->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
mCaptureBuf[index]->memory = V4L2_MEMORY_MMAP;
mCaptureBuf[index]->m.planes = &planes;
mCaptureBuf[index]->length = 1;
ALOGE("V4l2Capture::VIDIOC_DQBUF index: %d", index);
// Wait for a buffer to be ready
std::unique_lock <std::mutex> lock(mLock);
fill_buffer_inx = index;
if (ioctl(fd, VIDIOC_DQBUF, mCaptureBuf[index]) < 0) { //index可以一直为0
ALOGE("V4l2Capture::%s VIDIOC_DQBUF: %s", __func__,strerror(errno));
return nullptr ;
}else{
ALOGE("V4l2Capture::%s VIDIOC_DQBUF: %s",__func__, strerror(errno));
}
12、停止视频的采集
VIDIOC_STREAMOFF
// Stop the underlying video stream (automatically empties the buffer queue)
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
ALOGE("VIDIOC_STREAMOFF: %s", strerror(errno));
}
13、释放申请的视频帧缓冲区
munmap
// Tell the L4V2 driver to release our streaming buffers
if (fd >= 0)
{
ALOGI("munmap before");
for (int i = 0; i < BUFFER_NUM; i++)
munmap(mBuffers[i].start, mBuffers[i].length);
ALOGI("munmap after");
}
14、关闭视频设备文件
ALOGI("closing video device file handled %d", fd);
close(fd);
版权声明:本文为Sunxiaolin2016原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。