refactor(camera): capture UVC frames with FFmpeg
This commit is contained in:
parent
bb6c627fad
commit
8850df3983
@ -2,7 +2,18 @@ add_library(uvc_camera SHARED src/uvc_camera.cpp)
|
|||||||
|
|
||||||
target_include_directories(uvc_camera PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(uvc_camera PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
target_link_libraries(uvc_camera PUBLIC glog opencv_core opencv_imgproc cmvr_es::proto cmvr_es::device::camera_stream_encoder)
|
target_link_libraries(uvc_camera PUBLIC
|
||||||
|
glog
|
||||||
|
opencv_core
|
||||||
|
opencv_imgproc
|
||||||
|
avcodec
|
||||||
|
avdevice
|
||||||
|
avformat
|
||||||
|
avutil
|
||||||
|
swscale
|
||||||
|
cmvr_es::proto
|
||||||
|
cmvr_es::device::camera_stream_encoder
|
||||||
|
)
|
||||||
|
|
||||||
add_library(cmvr_es::device::uvc_camera ALIAS uvc_camera)
|
add_library(cmvr_es::device::uvc_camera ALIAS uvc_camera)
|
||||||
install(TARGETS uvc_camera LIBRARY DESTINATION lib)
|
install(TARGETS uvc_camera LIBRARY DESTINATION lib)
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#ifndef CMVR_ES_UVC_CAMERA_H
|
#ifndef CMVR_ES_UVC_CAMERA_H
|
||||||
#define CMVR_ES_UVC_CAMERA_H
|
#define CMVR_ES_UVC_CAMERA_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
#include "common/base/ring_buffer.h"
|
#include "common/base/ring_buffer.h"
|
||||||
#include "camera/abstract_camera.h"
|
#include "camera/abstract_camera.h"
|
||||||
#include "devices/camera/common/include/camera_stream_encoder.h"
|
#include "devices/camera/common/include/camera_stream_encoder.h"
|
||||||
@ -46,6 +49,14 @@ namespace cmvr::device {
|
|||||||
void streaming_worker_();
|
void streaming_worker_();
|
||||||
void recording_worker_();
|
void recording_worker_();
|
||||||
void cleanup_recording_resources_();
|
void cleanup_recording_resources_();
|
||||||
|
bool open_capture_();
|
||||||
|
void close_capture_();
|
||||||
|
void capture_worker_();
|
||||||
|
bool wait_for_capture_frame_(cv::Mat& frame,
|
||||||
|
uint64_t& last_sequence,
|
||||||
|
int64_t& capture_monotonic_ns,
|
||||||
|
int64_t& capture_utc_ns);
|
||||||
|
static int interrupt_capture_(void* opaque);
|
||||||
|
|
||||||
int fps_;
|
int fps_;
|
||||||
int width_;
|
int width_;
|
||||||
@ -53,7 +64,6 @@ namespace cmvr::device {
|
|||||||
int encode_width_;
|
int encode_width_;
|
||||||
int encode_height_;
|
int encode_height_;
|
||||||
std::string serial_;
|
std::string serial_;
|
||||||
cv::VideoCapture cap_;
|
|
||||||
size_t buffer_size_;
|
size_t buffer_size_;
|
||||||
std::string codec_;
|
std::string codec_;
|
||||||
bool enable_stream_timestamp_{false};
|
bool enable_stream_timestamp_{false};
|
||||||
@ -64,12 +74,22 @@ namespace cmvr::device {
|
|||||||
std::string current_video_path_;
|
std::string current_video_path_;
|
||||||
|
|
||||||
std::mutex ctrl_mtx_{};
|
std::mutex ctrl_mtx_{};
|
||||||
std::unique_ptr<cv::VideoWriter> video_writer_;
|
|
||||||
std::shared_ptr<std::thread> stream_thread_;
|
std::shared_ptr<std::thread> stream_thread_;
|
||||||
std::shared_ptr<std::thread> recording_thread_;
|
std::shared_ptr<std::thread> recording_thread_;
|
||||||
|
std::shared_ptr<std::thread> capture_thread_;
|
||||||
|
|
||||||
|
AVFormatContext* capture_format_context_ = nullptr;
|
||||||
std::mutex capture_mutex_;
|
AVCodecContext* capture_decoder_context_ = nullptr;
|
||||||
|
SwsContext* capture_sws_context_ = nullptr;
|
||||||
|
int capture_video_stream_index_ = -1;
|
||||||
|
std::atomic<bool> capture_running_{false};
|
||||||
|
std::mutex capture_frame_mutex_;
|
||||||
|
std::condition_variable capture_frame_cv_;
|
||||||
|
cv::Mat latest_capture_frame_;
|
||||||
|
uint64_t latest_capture_sequence_ = 0;
|
||||||
|
int64_t latest_capture_monotonic_ns_ = 0;
|
||||||
|
int64_t latest_capture_utc_ns_ = 0;
|
||||||
|
std::string capture_error_;
|
||||||
|
|
||||||
std::string output_path_;
|
std::string output_path_;
|
||||||
bool is_recording_ = false;
|
bool is_recording_ = false;
|
||||||
|
|||||||
@ -4,14 +4,31 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "../include/uvc_camera.h"
|
#include "../include/uvc_camera.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavdevice/avdevice.h>
|
||||||
|
#include <libavutil/error.h>
|
||||||
|
}
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace cmvr::device;
|
using namespace cmvr::device;
|
||||||
|
|
||||||
#define USE_LIST_IMAGE 1
|
#define USE_LIST_IMAGE 1
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ffmpeg_error_string(const int error_code)
|
||||||
|
{
|
||||||
|
char error_buffer[AV_ERROR_MAX_STRING_SIZE] = {};
|
||||||
|
av_strerror(error_code, error_buffer, sizeof(error_buffer));
|
||||||
|
return error_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
UVCCamera::UVCCamera(const config::UVCCameraConfig& camera):camera_(camera)
|
UVCCamera::UVCCamera(const config::UVCCameraConfig& camera):camera_(camera)
|
||||||
{
|
{
|
||||||
id_ = camera_.id();
|
id_ = camera_.id();
|
||||||
@ -53,6 +70,7 @@ UVCCamera::UVCCamera(const config::UVCCameraConfig& camera):camera_(camera)
|
|||||||
}
|
}
|
||||||
UVCCamera::~UVCCamera() {
|
UVCCamera::~UVCCamera() {
|
||||||
stop();
|
stop();
|
||||||
|
close_capture_();
|
||||||
if (stream_thread_ && stream_thread_->joinable())
|
if (stream_thread_ && stream_thread_->joinable())
|
||||||
stream_thread_->join();
|
stream_thread_->join();
|
||||||
if (recording_thread_ && recording_thread_->joinable())
|
if (recording_thread_ && recording_thread_->joinable())
|
||||||
@ -72,58 +90,29 @@ bool UVCCamera::init() {
|
|||||||
|
|
||||||
stream_frame_buffer_ = std::make_shared<SPMCRingBuffer<StreamFrameData>>(buffer_size_);
|
stream_frame_buffer_ = std::make_shared<SPMCRingBuffer<StreamFrameData>>(buffer_size_);
|
||||||
stream_frame_buffer_->clear();
|
stream_frame_buffer_->clear();
|
||||||
if (cap_.isOpened()) {
|
|
||||||
cap_.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
cap_.open(serial_, cv::CAP_V4L2);
|
// Validate the requested V4L2 mode during initialization. start() reopens
|
||||||
this_thread::sleep_for(chrono::milliseconds(100));
|
// the device and owns it for the lifetime of the camera session.
|
||||||
|
if (!open_capture_()) {
|
||||||
if (!cap_.isOpened()) {
|
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "Failed to open USB camera at index " + serial_;
|
state_.error_message = capture_error_;
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init)" << state_.error_message;
|
CMVR_LOG(ERROR) << "[UVCCamera] (init): " << state_.error_message;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
close_capture_();
|
||||||
|
|
||||||
// 设置格式为MJPG
|
|
||||||
cap_.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
|
|
||||||
|
|
||||||
if (!cap_.set(cv::CAP_PROP_FRAME_WIDTH, width_)) {
|
|
||||||
state_.is_error = true;
|
|
||||||
state_.error_message = "set width failed";
|
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set width failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
state_.width = width_;
|
|
||||||
if (!cap_.set(cv::CAP_PROP_FRAME_HEIGHT, height_)) {
|
|
||||||
state_.is_error = true;
|
|
||||||
state_.error_message = "set height failed";
|
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set height failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
state_.height = height_;
|
|
||||||
if (!cap_.set(cv::CAP_PROP_FPS, fps_)) {
|
|
||||||
state_.is_error = true;
|
|
||||||
state_.error_message = "set fps failed";
|
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set fps failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!cap_.set(cv::CAP_PROP_BUFFERSIZE, 1)) {
|
|
||||||
CMVR_LOG(WARNING) << "[UVCCamera] (init): failed to limit capture buffer size";
|
|
||||||
}
|
|
||||||
//初始化编码器
|
|
||||||
// 初始化RGB编码器(示例参数:640x480,30fps,H.264)
|
|
||||||
if (!CameraStreamEncoder::init(rgbEncoder_, codec_, encode_width_, encode_height_, fps_)) {
|
if (!CameraStreamEncoder::init(rgbEncoder_, codec_, encode_width_, encode_height_, fps_)) {
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (start): Failed to init RGB encoder!";
|
CMVR_LOG(ERROR) << "[UVCCamera] (init): Failed to init RGB encoder!";
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "Failed to init RGB encoder!";
|
state_.error_message = "Failed to init RGB encoder!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state_.fps = fps_;
|
state_.fps = fps_;
|
||||||
|
state_.width = width_;
|
||||||
|
state_.height = height_;
|
||||||
state_.is_initialized = true;
|
state_.is_initialized = true;
|
||||||
state_.is_error = false;
|
state_.is_error = false;
|
||||||
CMVR_LOG(INFO) << "[UVCCamera] (init): UVC camera initialized at index " << serial_; // 记录初始化成功日志
|
CMVR_LOG(INFO) << "[UVCCamera] (init): UVC camera initialized at " << serial_;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,42 +130,45 @@ bool UVCCamera::start() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cap_.open(serial_, cv::CAP_V4L2);
|
if (!open_capture_()) {
|
||||||
this_thread::sleep_for(chrono::milliseconds(100));
|
|
||||||
|
|
||||||
if (!cap_.isOpened()) {
|
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "Failed to open USB camera at index " + serial_;
|
state_.error_message = capture_error_;
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init)" << state_.error_message;
|
CMVR_LOG(ERROR) << "[UVCCamera] (start): " << state_.error_message;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置格式为MJPG
|
try {
|
||||||
cap_.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
|
capture_thread_ = std::make_shared<std::thread>(&UVCCamera::capture_worker_, this);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
capture_error_ = std::string("failed to start capture thread: ") + e.what();
|
||||||
|
close_capture_();
|
||||||
|
state_.is_error = true;
|
||||||
|
state_.error_message = capture_error_;
|
||||||
|
CMVR_LOG(ERROR) << "[UVCCamera] (start): " << state_.error_message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!cap_.set(cv::CAP_PROP_FRAME_WIDTH, width_)) {
|
cv::Mat first_frame;
|
||||||
state_.is_error = true;
|
uint64_t first_sequence = 0;
|
||||||
state_.error_message = "set width failed";
|
int64_t first_capture_monotonic_ns = 0;
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set width failed";
|
int64_t first_capture_utc_ns = 0;
|
||||||
return false;
|
if (!wait_for_capture_frame_(first_frame,
|
||||||
|
first_sequence,
|
||||||
|
first_capture_monotonic_ns,
|
||||||
|
first_capture_utc_ns)) {
|
||||||
|
std::string capture_error;
|
||||||
|
{
|
||||||
|
std::lock_guard capture_lock(capture_frame_mutex_);
|
||||||
|
capture_error = capture_error_;
|
||||||
}
|
}
|
||||||
state_.width = width_;
|
close_capture_();
|
||||||
if (!cap_.set(cv::CAP_PROP_FRAME_HEIGHT, height_)) {
|
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "set height failed";
|
state_.error_message = capture_error.empty()
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set height failed";
|
? "timed out waiting for the first camera frame"
|
||||||
|
: capture_error;
|
||||||
|
CMVR_LOG(ERROR) << "[UVCCamera] (start): " << state_.error_message;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state_.height = height_;
|
|
||||||
if (!cap_.set(cv::CAP_PROP_FPS, fps_)) {
|
|
||||||
state_.is_error = true;
|
|
||||||
state_.error_message = "set fps failed";
|
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (init): set fps failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!cap_.set(cv::CAP_PROP_BUFFERSIZE, 1)) {
|
|
||||||
CMVR_LOG(WARNING) << "[UVCCamera] (start): failed to limit capture buffer size";
|
|
||||||
}
|
|
||||||
state_.is_opened = true;
|
state_.is_opened = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -204,9 +196,7 @@ bool UVCCamera::stop() {
|
|||||||
is_streaming_running = false;
|
is_streaming_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cap_.isOpened()) {
|
close_capture_();
|
||||||
cap_.release();
|
|
||||||
}
|
|
||||||
state_.is_opened = false;
|
state_.is_opened = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -230,39 +220,304 @@ void UVCCamera::getRGBImage(cv::Mat& color, Rs2Intrinsics& intrinsics)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t requested_sequence = 0;
|
||||||
{
|
{
|
||||||
std::lock_guard capture_lock(capture_mutex_);
|
std::lock_guard capture_lock(capture_frame_mutex_);
|
||||||
if (!state_.is_streaming && !state_.is_recording) {
|
requested_sequence = latest_capture_sequence_;
|
||||||
cap_.release();
|
|
||||||
if (!cap_.open(serial_, cv::CAP_V4L2)) {
|
|
||||||
state_.is_error = true;
|
|
||||||
state_.error_message = "failed to reopen camera for snapshot";
|
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (getRGBImage): " << state_.error_message;
|
|
||||||
state_.is_opened = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cap_.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
|
int64_t capture_monotonic_ns = 0;
|
||||||
cap_.set(cv::CAP_PROP_FRAME_WIDTH, width_);
|
int64_t capture_utc_ns = 0;
|
||||||
cap_.set(cv::CAP_PROP_FRAME_HEIGHT, height_);
|
if (!wait_for_capture_frame_(color,
|
||||||
cap_.set(cv::CAP_PROP_FPS, fps_);
|
requested_sequence,
|
||||||
cap_.set(cv::CAP_PROP_BUFFERSIZE, 1);
|
capture_monotonic_ns,
|
||||||
|
capture_utc_ns) || color.empty()) {
|
||||||
if (!cap_.grab()) {
|
std::string capture_error;
|
||||||
state_.is_error = true;
|
{
|
||||||
state_.error_message = "failed to discard camera startup frame";
|
std::lock_guard capture_lock(capture_frame_mutex_);
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (getRGBImage): " << state_.error_message;
|
capture_error = capture_error_;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!cap_.read(color) || color.empty()) {
|
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "read color image failed";
|
state_.error_message = capture_error.empty()
|
||||||
|
? "timed out waiting for a fresh camera frame"
|
||||||
|
: capture_error;
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera] (getRGBImage): " << state_.error_message;
|
CMVR_LOG(ERROR) << "[UVCCamera] (getRGBImage): " << state_.error_message;
|
||||||
color.release();
|
color.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UVCCamera::interrupt_capture_(void* opaque)
|
||||||
|
{
|
||||||
|
const auto* camera = static_cast<const UVCCamera*>(opaque);
|
||||||
|
return camera && !camera->capture_running_.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UVCCamera::open_capture_()
|
||||||
|
{
|
||||||
|
close_capture_();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
latest_capture_frame_.release();
|
||||||
|
latest_capture_sequence_ = 0;
|
||||||
|
latest_capture_monotonic_ns_ = 0;
|
||||||
|
latest_capture_utc_ns_ = 0;
|
||||||
|
capture_error_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* input_format = av_find_input_format("v4l2");
|
||||||
|
if (!input_format) {
|
||||||
|
capture_error_ = "FFmpeg v4l2 input format is unavailable";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_format_context_ = avformat_alloc_context();
|
||||||
|
if (!capture_format_context_) {
|
||||||
|
capture_error_ = "failed to allocate FFmpeg capture context";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_running_.store(true, std::memory_order_relaxed);
|
||||||
|
capture_format_context_->interrupt_callback.callback = &UVCCamera::interrupt_capture_;
|
||||||
|
capture_format_context_->interrupt_callback.opaque = this;
|
||||||
|
capture_format_context_->flags |= AVFMT_FLAG_NOBUFFER;
|
||||||
|
|
||||||
|
AVDictionary* options = nullptr;
|
||||||
|
const std::string video_size = std::to_string(width_) + "x" + std::to_string(height_);
|
||||||
|
av_dict_set(&options, "input_format", "mjpeg", 0);
|
||||||
|
av_dict_set(&options, "video_size", video_size.c_str(), 0);
|
||||||
|
av_dict_set(&options, "framerate", std::to_string(fps_).c_str(), 0);
|
||||||
|
|
||||||
|
int result = avformat_open_input(
|
||||||
|
&capture_format_context_, serial_.c_str(), input_format, &options);
|
||||||
|
av_dict_free(&options);
|
||||||
|
if (result < 0) {
|
||||||
|
const std::string error =
|
||||||
|
"failed to open " + serial_ + ": " + ffmpeg_error_string(result);
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = avformat_find_stream_info(capture_format_context_, nullptr);
|
||||||
|
if (result < 0) {
|
||||||
|
const std::string error =
|
||||||
|
"failed to read camera stream info: " + ffmpeg_error_string(result);
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_video_stream_index_ = av_find_best_stream(
|
||||||
|
capture_format_context_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
||||||
|
if (capture_video_stream_index_ < 0) {
|
||||||
|
const std::string error = "camera has no video stream: "
|
||||||
|
+ ffmpeg_error_string(capture_video_stream_index_);
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVStream* video_stream = capture_format_context_->streams[capture_video_stream_index_];
|
||||||
|
const AVCodec* decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);
|
||||||
|
if (!decoder) {
|
||||||
|
const std::string error = "FFmpeg decoder is unavailable for camera input";
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_decoder_context_ = avcodec_alloc_context3(decoder);
|
||||||
|
if (!capture_decoder_context_) {
|
||||||
|
const std::string error = "failed to allocate camera decoder context";
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = avcodec_parameters_to_context(capture_decoder_context_, video_stream->codecpar);
|
||||||
|
if (result >= 0) {
|
||||||
|
result = avcodec_open2(capture_decoder_context_, decoder, nullptr);
|
||||||
|
}
|
||||||
|
if (result < 0) {
|
||||||
|
const std::string error =
|
||||||
|
"failed to open camera decoder: " + ffmpeg_error_string(result);
|
||||||
|
close_capture_();
|
||||||
|
capture_error_ = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMVR_LOG(INFO) << "[UVCCamera] FFmpeg capture opened"
|
||||||
|
<< ", device=" << serial_
|
||||||
|
<< ", input_codec=" << avcodec_get_name(video_stream->codecpar->codec_id)
|
||||||
|
<< ", width=" << capture_decoder_context_->width
|
||||||
|
<< ", height=" << capture_decoder_context_->height
|
||||||
|
<< ", requested_fps=" << fps_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UVCCamera::close_capture_()
|
||||||
|
{
|
||||||
|
capture_running_.store(false, std::memory_order_relaxed);
|
||||||
|
capture_frame_cv_.notify_all();
|
||||||
|
|
||||||
|
if (capture_thread_) {
|
||||||
|
if (capture_thread_->joinable()) {
|
||||||
|
capture_thread_->join();
|
||||||
|
}
|
||||||
|
capture_thread_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capture_sws_context_) {
|
||||||
|
sws_freeContext(capture_sws_context_);
|
||||||
|
capture_sws_context_ = nullptr;
|
||||||
|
}
|
||||||
|
if (capture_decoder_context_) {
|
||||||
|
avcodec_free_context(&capture_decoder_context_);
|
||||||
|
}
|
||||||
|
if (capture_format_context_) {
|
||||||
|
avformat_close_input(&capture_format_context_);
|
||||||
|
}
|
||||||
|
capture_video_stream_index_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UVCCamera::capture_worker_()
|
||||||
|
{
|
||||||
|
AVPacket* input_packet = av_packet_alloc();
|
||||||
|
AVFrame* decoded_frame = av_frame_alloc();
|
||||||
|
if (!input_packet || !decoded_frame) {
|
||||||
|
{
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "failed to allocate FFmpeg capture frame resources";
|
||||||
|
}
|
||||||
|
capture_running_.store(false, std::memory_order_relaxed);
|
||||||
|
capture_frame_cv_.notify_all();
|
||||||
|
av_packet_free(&input_packet);
|
||||||
|
av_frame_free(&decoded_frame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (capture_running_.load(std::memory_order_relaxed)) {
|
||||||
|
int result = av_read_frame(capture_format_context_, input_packet);
|
||||||
|
if (result == AVERROR(EAGAIN)) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (result < 0) {
|
||||||
|
if (capture_running_.load(std::memory_order_relaxed)) {
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "failed to read camera packet: " + ffmpeg_error_string(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_packet->stream_index != capture_video_stream_index_) {
|
||||||
|
av_packet_unref(input_packet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = avcodec_send_packet(capture_decoder_context_, input_packet);
|
||||||
|
av_packet_unref(input_packet);
|
||||||
|
if (result < 0 && result != AVERROR(EAGAIN)) {
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "failed to submit camera packet: " + ffmpeg_error_string(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (capture_running_.load(std::memory_order_relaxed)) {
|
||||||
|
result = avcodec_receive_frame(capture_decoder_context_, decoded_frame);
|
||||||
|
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result < 0) {
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "failed to decode camera frame: " + ffmpeg_error_string(result);
|
||||||
|
capture_running_.store(false, std::memory_order_relaxed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_sws_context_ = sws_getCachedContext(
|
||||||
|
capture_sws_context_,
|
||||||
|
decoded_frame->width,
|
||||||
|
decoded_frame->height,
|
||||||
|
static_cast<AVPixelFormat>(decoded_frame->format),
|
||||||
|
width_,
|
||||||
|
height_,
|
||||||
|
AV_PIX_FMT_BGR24,
|
||||||
|
SWS_BILINEAR,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
if (!capture_sws_context_) {
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "failed to create camera pixel conversion context";
|
||||||
|
capture_running_.store(false, std::memory_order_relaxed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat bgr_frame(height_, width_, CV_8UC3);
|
||||||
|
uint8_t* destination_data[] = {bgr_frame.data, nullptr, nullptr, nullptr};
|
||||||
|
int destination_linesize[] = {
|
||||||
|
static_cast<int>(bgr_frame.step[0]), 0, 0, 0
|
||||||
|
};
|
||||||
|
const int converted_rows = sws_scale(
|
||||||
|
capture_sws_context_,
|
||||||
|
decoded_frame->data,
|
||||||
|
decoded_frame->linesize,
|
||||||
|
0,
|
||||||
|
decoded_frame->height,
|
||||||
|
destination_data,
|
||||||
|
destination_linesize);
|
||||||
|
if (converted_rows != height_) {
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
capture_error_ = "camera pixel conversion returned an incomplete frame";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto capture_monotonic = std::chrono::steady_clock::now();
|
||||||
|
const auto capture_utc = std::chrono::system_clock::now();
|
||||||
|
{
|
||||||
|
std::lock_guard frame_lock(capture_frame_mutex_);
|
||||||
|
latest_capture_frame_ = std::move(bgr_frame);
|
||||||
|
++latest_capture_sequence_;
|
||||||
|
latest_capture_monotonic_ns_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
capture_monotonic.time_since_epoch()).count();
|
||||||
|
latest_capture_utc_ns_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
capture_utc.time_since_epoch()).count();
|
||||||
|
capture_error_.clear();
|
||||||
|
}
|
||||||
|
capture_frame_cv_.notify_all();
|
||||||
|
av_frame_unref(decoded_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_running_.store(false, std::memory_order_relaxed);
|
||||||
|
capture_frame_cv_.notify_all();
|
||||||
|
av_packet_free(&input_packet);
|
||||||
|
av_frame_free(&decoded_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UVCCamera::wait_for_capture_frame_(cv::Mat& frame,
|
||||||
|
uint64_t& last_sequence,
|
||||||
|
int64_t& capture_monotonic_ns,
|
||||||
|
int64_t& capture_utc_ns)
|
||||||
|
{
|
||||||
|
std::unique_lock frame_lock(capture_frame_mutex_);
|
||||||
|
const auto timeout = std::chrono::milliseconds(
|
||||||
|
std::max(500, fps_ > 0 ? 3000 / fps_ : 500));
|
||||||
|
const bool ready = capture_frame_cv_.wait_for(frame_lock, timeout, [&] {
|
||||||
|
return latest_capture_sequence_ > last_sequence
|
||||||
|
|| !capture_running_.load(std::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
if (!ready || latest_capture_sequence_ <= last_sequence || latest_capture_frame_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = latest_capture_frame_;
|
||||||
|
last_sequence = latest_capture_sequence_;
|
||||||
|
capture_monotonic_ns = latest_capture_monotonic_ns_;
|
||||||
|
capture_utc_ns = latest_capture_utc_ns_;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UVCCamera::getDepthImage(cv::Mat& depth, Rs2Intrinsics& intrinsics) {
|
void UVCCamera::getDepthImage(cv::Mat& depth, Rs2Intrinsics& intrinsics) {
|
||||||
@ -504,6 +759,9 @@ void UVCCamera::streaming_worker_() {
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
is_streaming_running = true;
|
is_streaming_running = true;
|
||||||
cv::Mat frame;
|
cv::Mat frame;
|
||||||
|
uint64_t capture_sequence = 0;
|
||||||
|
int64_t frame_capture_monotonic_ns = 0;
|
||||||
|
int64_t frame_capture_utc_ns = 0;
|
||||||
uint64_t frame_sequence = 0;
|
uint64_t frame_sequence = 0;
|
||||||
const uint64_t stream_epoch = static_cast<uint64_t>(
|
const uint64_t stream_epoch = static_cast<uint64_t>(
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
@ -523,14 +781,19 @@ void UVCCamera::streaming_worker_() {
|
|||||||
while (state_.is_streaming || state_.is_recording) {
|
while (state_.is_streaming || state_.is_recording) {
|
||||||
// 记录当前帧处理开始时间
|
// 记录当前帧处理开始时间
|
||||||
const auto frame_start_time = std::chrono::steady_clock::now();
|
const auto frame_start_time = std::chrono::steady_clock::now();
|
||||||
bool frame_read = false;
|
if (!wait_for_capture_frame_(frame,
|
||||||
|
capture_sequence,
|
||||||
|
frame_capture_monotonic_ns,
|
||||||
|
frame_capture_utc_ns) || frame.empty()) {
|
||||||
|
std::string capture_error;
|
||||||
{
|
{
|
||||||
std::lock_guard capture_lock(capture_mutex_);
|
std::lock_guard capture_lock(capture_frame_mutex_);
|
||||||
frame_read = cap_.read(frame);
|
capture_error = capture_error_;
|
||||||
}
|
}
|
||||||
if (!frame_read || frame.empty()) {
|
|
||||||
state_.is_error = true;
|
state_.is_error = true;
|
||||||
state_.error_message = "failed to read frame";
|
state_.error_message = capture_error.empty()
|
||||||
|
? "timed out waiting for camera frame"
|
||||||
|
: capture_error;
|
||||||
CMVR_LOG(ERROR) << "[UVCCamera]streaming_worker_: " << state_.error_message;
|
CMVR_LOG(ERROR) << "[UVCCamera]streaming_worker_: " << state_.error_message;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -540,7 +803,7 @@ void UVCCamera::streaming_worker_() {
|
|||||||
StreamFrameData frame_data;
|
StreamFrameData frame_data;
|
||||||
// 保存图像数据
|
// 保存图像数据
|
||||||
{
|
{
|
||||||
frame.copyTo(frame_data.rgbImage); // 执行深拷贝
|
frame_data.rgbImage = frame;
|
||||||
}
|
}
|
||||||
const auto copy_end_time = std::chrono::steady_clock::now();
|
const auto copy_end_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
@ -570,10 +833,9 @@ void UVCCamera::streaming_worker_() {
|
|||||||
const uint64_t sequence = frame_sequence++;
|
const uint64_t sequence = frame_sequence++;
|
||||||
frame_data.stream_epoch = stream_epoch;
|
frame_data.stream_epoch = stream_epoch;
|
||||||
frame_data.sequence = sequence;
|
frame_data.sequence = sequence;
|
||||||
frame_data.capture_monotonic_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
frame_data.source_frame_number = capture_sequence;
|
||||||
frame_start_time.time_since_epoch()).count();
|
frame_data.capture_monotonic_ns = frame_capture_monotonic_ns;
|
||||||
frame_data.capture_utc_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
frame_data.capture_utc_ns = frame_capture_utc_ns;
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
frame_data.pts = static_cast<int64_t>(sequence);
|
frame_data.pts = static_cast<int64_t>(sequence);
|
||||||
frame_data.dts = frame_data.pts;
|
frame_data.dts = frame_data.pts;
|
||||||
frame_data.time_base_num = 1;
|
frame_data.time_base_num = 1;
|
||||||
@ -634,17 +896,6 @@ void UVCCamera::streaming_worker_() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算从帧开始到现在的总耗时
|
|
||||||
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::steady_clock::now() - frame_start_time
|
|
||||||
).count();
|
|
||||||
|
|
||||||
// 计算需要休眠的时间(确保总耗时达到frame_interval)
|
|
||||||
int sleep_time = frame_interval - total_duration;
|
|
||||||
// 只有当需要休眠的时间为正数时才休眠
|
|
||||||
if (sleep_time > 0) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_streaming_running = false;
|
is_streaming_running = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user