fix(camera): prevent double free in UVC recording

This commit is contained in:
linbo 2026-08-12 10:21:57 +08:00
parent e94db8b66c
commit 5f4e3897ac
2 changed files with 33 additions and 39 deletions

View File

@ -45,6 +45,7 @@ namespace cmvr::device {
private: private:
void streaming_worker_(); void streaming_worker_();
void recording_worker_(); void recording_worker_();
void cleanup_recording_resources_();
int fps_; int fps_;
int width_; int width_;

View File

@ -181,7 +181,7 @@ bool UVCCamera::start() {
bool UVCCamera::stop() { bool UVCCamera::stop() {
//先停止录制再关闭摄像头 //先停止录制再关闭摄像头
if (state_.is_recording) { if (state_.is_recording || recording_thread_ || packet_ || format_context_ || stream_) {
stopRecording(); stopRecording();
} }
std::lock_guard lock(ctrl_mtx_); std::lock_guard lock(ctrl_mtx_);
@ -278,6 +278,22 @@ void UVCCamera::getRGBDImages(cv::Mat &color, cv::Mat &depth, Rs2Intrinsics& int
depth.release(); depth.release();
} }
void UVCCamera::cleanup_recording_resources_() {
if (packet_) {
av_packet_free(&packet_);
}
if (format_context_) {
if (!(format_context_->oformat->flags & AVFMT_NOFILE) && format_context_->pb) {
avio_closep(&format_context_->pb);
}
avformat_free_context(format_context_);
format_context_ = nullptr;
}
stream_ = nullptr;
}
void UVCCamera::startRecording(const std::string &video_path) { void UVCCamera::startRecording(const std::string &video_path) {
std::lock_guard lock(ctrl_mtx_); std::lock_guard lock(ctrl_mtx_);
clear_error_(); clear_error_();
@ -300,18 +316,6 @@ void UVCCamera::startRecording(const std::string &video_path) {
return; return;
} }
auto cleanup_recording_resources = [this]() {
if (packet_) av_packet_free(&packet_);
if (stream_ && stream_->codecpar->extradata) av_free(stream_->codecpar->extradata);
if (format_context_) {
if (!(format_context_->oformat->flags & AVFMT_NOFILE) && format_context_->pb) avio_closep(&format_context_->pb);
avformat_free_context(format_context_);
}
stream_ = nullptr;
format_context_ = nullptr;
state_.is_recording = false;
};
try { try {
current_video_path_ = video_path; current_video_path_ = video_path;
std::string temp_path = current_video_path_ + ".temp"; // 临时文件 std::string temp_path = current_video_path_ + ".temp"; // 临时文件
@ -348,7 +352,7 @@ void UVCCamera::startRecording(const std::string &video_path) {
state_.is_error = true; state_.is_error = true;
state_.error_message = "failed to create video stream"; state_.error_message = "failed to create video stream";
CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message;
cleanup_recording_resources(); cleanup_recording_resources_();
return; return;
} }
stream_ = stream; stream_ = stream;
@ -366,7 +370,7 @@ void UVCCamera::startRecording(const std::string &video_path) {
state_.is_error = true; state_.is_error = true;
state_.error_message = "failed to allocate extradata"; state_.error_message = "failed to allocate extradata";
CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message;
cleanup_recording_resources(); cleanup_recording_resources_();
return; return;
} }
memcpy(codecpar->extradata, rgbEncoder_->codec_context->extradata, rgbEncoder_->codec_context->extradata_size); memcpy(codecpar->extradata, rgbEncoder_->codec_context->extradata, rgbEncoder_->codec_context->extradata_size);
@ -379,7 +383,7 @@ void UVCCamera::startRecording(const std::string &video_path) {
state_.is_error = true; state_.is_error = true;
state_.error_message = "failed to open output file: " + temp_path; state_.error_message = "failed to open output file: " + temp_path;
CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message;
cleanup_recording_resources(); cleanup_recording_resources_();
return; return;
} }
} }
@ -389,7 +393,7 @@ void UVCCamera::startRecording(const std::string &video_path) {
state_.is_error = true; state_.is_error = true;
state_.error_message = "failed to write file header"; state_.error_message = "failed to write file header";
CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message;
cleanup_recording_resources(); cleanup_recording_resources_();
return; return;
} }
@ -399,7 +403,7 @@ void UVCCamera::startRecording(const std::string &video_path) {
state_.is_error = true; state_.is_error = true;
state_.error_message = "failed to allocate AVPacket"; state_.error_message = "failed to allocate AVPacket";
CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (startRecording): " << state_.error_message;
cleanup_recording_resources(); cleanup_recording_resources_();
return; return;
} }
//不在录像也不在流传输,但是采集线程没有退出时。 //不在录像也不在流传输,但是采集线程没有退出时。
@ -432,7 +436,8 @@ void UVCCamera::startRecording(const std::string &video_path) {
recording_thread_ = make_shared<thread>(&UVCCamera::recording_worker_, this); recording_thread_ = make_shared<thread>(&UVCCamera::recording_worker_, this);
} catch (const std::exception& e) { } catch (const std::exception& e) {
cleanup_recording_resources(); cleanup_recording_resources_();
state_.is_recording = false;
state_.is_error = true; state_.is_error = true;
state_.error_message = "[UVCCamera] (startRecording): " + std::string(e.what()); state_.error_message = "[UVCCamera] (startRecording): " + std::string(e.what());
CMVR_LOG(ERROR) << state_.error_message; CMVR_LOG(ERROR) << state_.error_message;
@ -448,36 +453,24 @@ void UVCCamera::stopRecording() {
CMVR_LOG(ERROR) << "[UVCCamera] (stopRecording): " << state_.error_message; CMVR_LOG(ERROR) << "[UVCCamera] (stopRecording): " << state_.error_message;
return; return;
} }
if (!state_.is_recording) { const bool has_recording_resources =
recording_thread_ || packet_ || format_context_ || stream_;
if (!state_.is_recording && !has_recording_resources) {
CMVR_LOG(WARNING) << "[UVCCamera] (stopRecording): not recording"; CMVR_LOG(WARNING) << "[UVCCamera] (stopRecording): not recording";
return; return;
} }
// 1. 停止录像线程 // 1. 停止录像线程
state_.is_recording = false; state_.is_recording = false;
if (recording_thread_ && recording_thread_->joinable()) { if (recording_thread_) {
if (recording_thread_->joinable()) {
recording_thread_->join(); recording_thread_->join();
}
recording_thread_.reset(); recording_thread_.reset();
} }
// 2. 清理FFmpeg资源 // 2. 清理FFmpeg资源
if (packet_) { cleanup_recording_resources_();
av_packet_free(&packet_);
packet_ = nullptr;
}
if (stream_ && stream_->codecpar->extradata) {
av_free(stream_->codecpar->extradata);
stream_->codecpar->extradata = nullptr;
stream_->codecpar->extradata_size = 0;
}
if (format_context_) {
if (!(format_context_->oformat->flags & AVFMT_NOFILE) && format_context_->pb) {
avio_closep(&format_context_->pb); // 关闭文件
}
avformat_free_context(format_context_); // 释放格式上下文
format_context_ = nullptr;
}
stream_ = nullptr;
// 3. 重命名临时文件为目标文件 // 3. 重命名临时文件为目标文件
std::string temp_path = current_video_path_ + ".temp"; std::string temp_path = current_video_path_ + ".temp";