cmvr-es/cmvr-es/common/media/ffmpeg/camera_capture.cpp

264 lines
7.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// CameraCapture.cpp
#include "common/media/ffmpeg/camera_capture.h"
#include "common/base/logging/logger.h"
#include <thread>
#include <chrono>
namespace ffmpeg {
CameraCapture::CameraCapture() {
packet_ = av_packet_alloc();
frame_ = av_frame_alloc();
rgb_frame_ = av_frame_alloc();
}
CameraCapture::~CameraCapture() {
stop_capture();
cleanup();
if (packet_) av_packet_free(&packet_);
if (frame_) av_frame_free(&frame_);
if (rgb_frame_) av_frame_free(&rgb_frame_);
if (sws_ctx_) sws_freeContext(sws_ctx_);
}
int CameraCapture::initialize(const Config& config) {
config_ = config;
// 初始化设备
int ret = init_device();
if (ret < 0) {
CMVR_LOG(ERROR) << "初始化设备失败";
return ret;
}
// 初始化解码器
ret = init_decoder();
if (ret < 0) {
CMVR_LOG(ERROR) << "初始化解码器失败";
return ret;
}
// 初始化SWS上下文
ret = init_sws_context();
if (ret < 0) {
CMVR_LOG(ERROR) << "初始化SWS上下文失败";
return ret;
}
// 分配RGB帧缓冲区(用于转换)
rgb_frame_->width = config_.width;
rgb_frame_->height = config_.height;
rgb_frame_->format = AV_PIX_FMT_BGR24; // OpenCV使用BGR格式
ret = av_frame_get_buffer(rgb_frame_, 0);
if (ret < 0) {
CMVR_LOG(ERROR) << "分配RGB帧缓冲区失败";
return ret;
}
CMVR_LOG(INFO) << "摄像头初始化成功: " << config_.width << "x" << config_.height
<< "@" << config_.fps << "fps";
return 0;
}
int CameraCapture::init_device() {
// AVInputFormat* input_fmt = nullptr;
std::string device_path;
#ifdef _WIN32
auto input_fmt = av_find_input_format("dshow");
device_path = "video=" + config_.device_name;
#else
auto input_fmt = av_find_input_format("v4l2");
device_path = config_.device_name;
#endif
if (!input_fmt) {
CMVR_LOG(ERROR) << "找不到输入格式";
return -1;
}
// 设置摄像头参数
AVDictionary* options = nullptr;
av_dict_set(&options, "framerate", std::to_string(config_.fps).c_str(), 0);
av_dict_set(&options, "video_size", (std::to_string(config_.width) + "x" + std::to_string(config_.height)).c_str(), 0);
// 打开摄像头
int ret = avformat_open_input(&fmt_ctx_, device_path.c_str(), input_fmt, &options);
av_dict_free(&options);
if (ret < 0) {
char err_buf[1024];
av_strerror(ret, err_buf, sizeof(err_buf));
CMVR_LOG(ERROR) << "打开摄像头失败: " << err_buf;
return ret;
}
// 查找流信息
ret = avformat_find_stream_info(fmt_ctx_, nullptr);
if (ret < 0) {
CMVR_LOG(ERROR) << "查找流信息失败";
return ret;
}
// 查找视频流
video_stream_index_ = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_stream_index_ < 0) {
CMVR_LOG(ERROR) << "找不到视频流";
return video_stream_index_;
}
return 0;
}
int CameraCapture::init_decoder() {
AVStream* stream = fmt_ctx_->streams[video_stream_index_];
const AVCodec* decoder = avcodec_find_decoder(stream->codecpar->codec_id);
if (!decoder) {
CMVR_LOG(ERROR) << "找不到解码器";
return -1;
}
decoder_ctx_ = avcodec_alloc_context3(decoder);
if (!decoder_ctx_) {
CMVR_LOG(ERROR) << "分配解码器上下文失败";
return -1;
}
// 复制参数到解码器上下文
int ret = avcodec_parameters_to_context(decoder_ctx_, stream->codecpar);
if (ret < 0) {
CMVR_LOG(ERROR) << "复制解码器参数失败";
return ret;
}
// 打开解码器
ret = avcodec_open2(decoder_ctx_, decoder, nullptr);
if (ret < 0) {
CMVR_LOG(ERROR) << "打开解码器失败";
return ret;
}
return 0;
}
int CameraCapture::init_sws_context() {
// 获取解码器输出的像素格式
AVPixelFormat src_format = decoder_ctx_->pix_fmt;
sws_ctx_ = sws_getContext(
decoder_ctx_->width, decoder_ctx_->height, src_format,
config_.width, config_.height, AV_PIX_FMT_BGR24, // 转换为BGR24给OpenCV
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (!sws_ctx_) {
CMVR_LOG(ERROR) << "创建SWS上下文失败";
return -1;
}
return 0;
}
cv::Mat CameraCapture::avframe_to_cvmat(AVFrame* frame) {
// 确保RGB帧可写
av_frame_make_writable(rgb_frame_);
// 转换到BGR24
sws_scale(sws_ctx_,
frame->data, frame->linesize, 0, frame->height,
rgb_frame_->data, rgb_frame_->linesize);
// 创建cv::Mat(BGR格式)
cv::Mat image(config_.height, config_.width, CV_8UC3);
// 复制数据(由于linesize可能不等于width*3,需要逐行复制)
for (int i = 0; i < config_.height; i++) {
memcpy(image.data + i * image.step,
rgb_frame_->data[0] + i * rgb_frame_->linesize[0],
config_.width * 3);
}
return image;
}
int CameraCapture::start_capture(FrameCallback callback) {
if (!callback || !fmt_ctx_ || !decoder_ctx_) {
CMVR_LOG(ERROR) << "参数无效或未初始化";
return -1;
}
is_capturing_ = true;
frame_count_ = 0;
CMVR_LOG(INFO) << "开始采集...";
while (is_capturing_) {
// 读取数据包
int ret = av_read_frame(fmt_ctx_, packet_);
if (ret < 0) {
if (ret == AVERROR(EAGAIN)) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
CMVR_LOG(ERROR) << "读取帧失败: " << ret;
break;
}
// 只处理视频流
if (packet_->stream_index == video_stream_index_) {
// 发送数据包到解码器
ret = avcodec_send_packet(decoder_ctx_, packet_);
if (ret < 0 && ret != AVERROR(EAGAIN)) {
CMVR_LOG(ERROR) << "发送数据包到解码器失败: " << ret;
av_packet_unref(packet_);
continue;
}
// 接收解码后的帧
while (ret >= 0) {
ret = avcodec_receive_frame(decoder_ctx_, frame_);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
CMVR_LOG(ERROR) << "接收解码帧失败: " << ret;
break;
}
// 设置PTS
frame_->pts = frame_count_;
// 转换为cv::Mat(用于显示或OpenCV处理)
cv::Mat rgb_image = avframe_to_cvmat(frame_);
// 回调:同时返回AVFrame和cv::Mat
callback(frame_, rgb_image, frame_count_++);
}
}
av_packet_unref(packet_);
}
return 0;
}
void CameraCapture::stop_capture() {
is_capturing_ = false;
}
void CameraCapture::cleanup() {
if (decoder_ctx_) {
avcodec_close(decoder_ctx_);
avcodec_free_context(&decoder_ctx_);
}
if (fmt_ctx_) {
avformat_close_input(&fmt_ctx_);
avformat_free_context(fmt_ctx_);
fmt_ctx_ = nullptr;
}
}
} // namespace ffmpeg