Linux中,使用C++获取网络摄像头视频流的方式【附带源码示例】
在Linux中,使用C++获取网络摄像头视频流通常可以通过以下两种方式实现:
-
使用Video4Linux2 (V4L2) API
V4L2是Linux内核提供的用于视频设备(如摄像头)的API,支持直接访问摄像头硬件并获取视频流。 -
使用FFmpeg库
FFmpeg是一个功能强大的多媒体处理库,支持从摄像头设备(如/dev/video0
)或网络流(如RTSP)获取视频流。
下面分别介绍这两种方式的实现方法。
方法一:使用Video4Linux2 (V4L2) API
1. 安装依赖
确保系统已安装V4L2开发库:
sudo apt-get install libv4l-dev
2. 示例代码
以下代码展示了如何使用V4L2 API从摄像头获取视频流并保存为YUV格式的文件。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <cstring>
#define DEVICE "/dev/video0"
#define WIDTH 640
#define HEIGHT 480
#define OUTPUT_FILE "output.yuv"
int main() {
int fd = open(DEVICE, O_RDWR);
if (fd == -1) {
std::cerr << "Failed to open device: " << DEVICE << std::endl;
return -1;
}
// 设置视频格式
v4l2_format format = {};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = WIDTH;
format.fmt.pix.height = HEIGHT;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV格式
format.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) {
std::cerr << "Failed to set video format" << std::endl;
close(fd);
return -1;
}
// 申请缓冲区
v4l2_requestbuffers req = {};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
std::cerr << "Failed to request buffers" << std::endl;
close(fd);
return -1;
}
// 映射缓冲区
v4l2_buffer buffer = {};
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = 0;
if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) == -1) {
std::cerr << "Failed to query buffer" << std::endl;
close(fd);
return -1;
}
void* bufferStart = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer.m.offset);
if (bufferStart == MAP_FAILED) {
std::cerr << "Failed to map buffer" << std::endl;
close(fd);
return -1;
}
// 开始捕获
if (ioctl(fd, VIDIOC_STREAMON, &buffer.type) == -1) {
std::cerr << "Failed to start streaming" << std::endl;
close(fd);
return -1;
}
// 打开输出文件
FILE* outputFile = fopen(OUTPUT_FILE, "wb");
if (!outputFile) {
std::cerr << "Failed to open output file" << std::endl;
close(fd);
return -1;
}
// 捕获帧并写入文件
for (int i = 0; i < 100; ++i) { // 捕获100帧
if (ioctl(fd, VIDIOC_QBUF, &buffer) == -1) {
std::cerr << "Failed to enqueue buffer" << std::endl;
break;
}
if (ioctl(fd, VIDIOC_DQBUF, &buffer) == -1) {
std::cerr << "Failed to dequeue buffer" << std::endl;
break;
}
fwrite(bufferStart, buffer.bytesused, 1, outputFile);
}
// 停止捕获
ioctl(fd, VIDIOC_STREAMOFF, &buffer.type);
// 释放资源
munmap(bufferStart, buffer.length);
fclose(outputFile);
close(fd);
std::cout << "Video capture completed. Saved to " << OUTPUT_FILE << std::endl;
return 0;
}
3. 编译与运行
编译代码:
g++ -o v4l2_capture v4l2_capture.cpp
运行程序:
./v4l2_capture
方法二:使用FFmpeg库
1. 安装FFmpeg开发库
确保已安装FFmpeg开发库:
sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswscale-dev
2. 示例代码
以下代码展示了如何使用FFmpeg从摄像头获取视频流并解码为帧。
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#include <iostream>
int main() {
av_register_all();
avformat_network_init();
AVFormatContext* formatContext = avformat_alloc_context();
if (!formatContext) {
std::cerr << "Failed to allocate format context" << std::endl;
return -1;
}
// 打开摄像头设备
const char* device = "/dev/video0";
if (avformat_open_input(&formatContext, device, nullptr, nullptr) != 0) {
std::cerr << "Failed to open device: " << device << std::endl;
return -1;
}
// 获取流信息
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
std::cerr << "Failed to find stream information" << std::endl;
return -1;
}
// 查找视频流
int videoStreamIndex = -1;
for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
if (videoStreamIndex == -1) {
std::cerr << "Could not find video stream" << std::endl;
return -1;
}
// 获取解码器
AVCodecParameters* codecParameters = formatContext->streams[videoStreamIndex]->codecpar;
AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id);
if (!codec) {
std::cerr << "Unsupported codec" << std::endl;
return -1;
}
// 创建解码器上下文
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {
std::cerr << "Failed to allocate codec context" << std::endl;
return -1;
}
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {
std::cerr << "Failed to copy codec parameters to context" << std::endl;
return -1;
}
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
std::cerr << "Failed to open codec" << std::endl;
return -1;
}
// 分配帧和包
AVFrame* frame = av_frame_alloc();
AVPacket* packet = av_packet_alloc();
if (!frame || !packet) {
std::cerr << "Failed to allocate frame or packet" << std::endl;
return -1;
}
// 读取帧
while (av_read_frame(formatContext, packet) >= 0) {
if (packet->stream_index == videoStreamIndex) {
if (avcodec_send_packet(codecContext, packet) == 0) {
while (avcodec_receive_frame(codecContext, frame) == 0) {
// 处理解码后的帧(frame)
std::cout << "Decoded frame: " << frame->pts << std::endl;
}
}
}
av_packet_unref(packet);
}
// 释放资源
av_frame_free(&frame);
av_packet_free(&packet);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
avformat_free_context(formatContext);
return 0;
}
3. 编译与运行
编译代码:
g++ -o ffmpeg_capture ffmpeg_capture.cpp -lavformat -lavcodec -lavutil -lswscale
运行程序:
./ffmpeg_capture
总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
V4L2 | 直接访问硬件,低延迟 | 代码复杂,功能有限 | 本地摄像头访问 |
FFmpeg | 功能强大,支持多种格式和协议 | 依赖库较大,延迟稍高 | 网络流或复杂视频处理 |
根据需求选择合适的方式:
- 如果需要直接访问摄像头硬件,推荐使用 V4L2。
- 如果需要支持多种格式或网络流,推荐使用 FFmpeg。