264 lines
7.2 KiB
C++
264 lines
7.2 KiB
C++
// 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
|