#include "rtsp_grabber.h" #include #include #include #include extern "C" { #include #include #include #include #include } #define LOG_ERR(msg) std::cerr << "[ERR] " << msg << std::endl #define LOG_INF(msg) std::cerr << "[INF] " << msg << std::endl struct RtspFrameGrabberImpl { AVFormatContext *fmt_ctx = nullptr; AVCodecContext *dec_ctx = nullptr; int video_stream_index = -1; AVFrame *decoded_frame = nullptr; AVFrame *sws_frame = nullptr; SwsContext *sws_ctx = nullptr; AVCodecContext *jpeg_enc_ctx = nullptr; AVPacket *enc_pkt = nullptr; AVFrame *latest_frame_copy = nullptr; int width = 0; int height = 0; AVPixelFormat src_pix_fmt = AV_PIX_FMT_NONE; std::atomic stop_flag{false}; std::atomic started{false}; std::atomic ready{false}; std::thread th; int reconnect_delay_ms = 1000; }; static void free_impl(RtspFrameGrabberImpl *impl) { if (!impl) return; if (impl->th.joinable()) { impl->stop_flag = true; impl->th.join(); } if (impl->enc_pkt) av_packet_free(&impl->enc_pkt); if (impl->jpeg_enc_ctx) avcodec_free_context(&impl->jpeg_enc_ctx); if (impl->latest_frame_copy) av_frame_free(&impl->latest_frame_copy); if (impl->sws_frame) av_frame_free(&impl->sws_frame); if (impl->decoded_frame) av_frame_free(&impl->decoded_frame); if (impl->sws_ctx) sws_freeContext(impl->sws_ctx); if (impl->dec_ctx) avcodec_free_context(&impl->dec_ctx); if (impl->fmt_ctx) avformat_close_input(&impl->fmt_ctx); delete impl; } static AVFrame *avframe_deep_copy(const AVFrame *src) { if (!src) return nullptr; AVFrame *dst = av_frame_alloc(); dst->format = src->format; dst->width = src->width; dst->height = src->height; av_frame_get_buffer(dst, 0); av_frame_copy(dst, src); av_frame_copy_props(dst, src); return dst; } static bool init_jpeg_encoder(RtspFrameGrabberImpl *impl, int width, int height, AVPixelFormat pix_fmt) { if (!impl || impl->jpeg_enc_ctx) return true; const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); if (!codec) return false; AVCodecContext *c = avcodec_alloc_context3(codec); c->pix_fmt = pix_fmt; c->width = width; c->height = height; c->time_base = AVRational{1, 25}; // ⭐ 最高质量 c->qmin = 1; c->qmax = 2; c->compression_level = 0; if (avcodec_open2(c, codec, nullptr) < 0) { avcodec_free_context(&c); return false; } impl->jpeg_enc_ctx = c; impl->enc_pkt = av_packet_alloc(); return true; } static bool encode_frame_to_jpeg(RtspFrameGrabberImpl *impl, AVFrame *frame, const std::string &filename) { if (!impl || !impl->jpeg_enc_ctx || !frame) return false; AVCodecContext *enc = impl->jpeg_enc_ctx; AVPacket *pkt = impl->enc_pkt; av_packet_unref(pkt); if (avcodec_send_frame(enc, frame) < 0) return false; if (avcodec_receive_packet(enc, pkt) < 0) return false; FILE *f = fopen(filename.c_str(), "wb"); if (!f) return false; fwrite(pkt->data, 1, pkt->size, f); fclose(f); av_packet_unref(pkt); return true; } // -------- RtspFrameGrabber Methods -------- RtspFrameGrabber::RtspFrameGrabber(const std::string &rtsp_url) : rtsp_url_(rtsp_url) { avformat_network_init(); opaque_impl_ = new RtspFrameGrabberImpl(); } RtspFrameGrabber::~RtspFrameGrabber() { stop(); free_impl(reinterpret_cast(opaque_impl_)); opaque_impl_ = nullptr; } bool RtspFrameGrabber::start() { RtspFrameGrabberImpl *impl = reinterpret_cast(opaque_impl_); if (!impl || impl->started) return false; impl->stop_flag = false; impl->th = std::thread([this, impl]() { this->decode_loop(); }); impl->started = true; return true; } void RtspFrameGrabber::stop() { RtspFrameGrabberImpl *impl = reinterpret_cast(opaque_impl_); if (!impl) return; impl->stop_flag = true; if (impl->th.joinable()) impl->th.join(); impl->started = false; impl->ready = false; } bool RtspFrameGrabber::is_ready() const { RtspFrameGrabberImpl *impl = reinterpret_cast(opaque_impl_); return impl && impl->ready; } bool RtspFrameGrabber::capture_jpeg(const std::string &filename) { RtspFrameGrabberImpl *impl = reinterpret_cast(opaque_impl_); if (!impl) return false; std::lock_guard lk(frame_mutex_); if (!impl->latest_frame_copy) return false; int w = impl->latest_frame_copy->width; int h = impl->latest_frame_copy->height; AVPixelFormat pf = (AVPixelFormat)impl->latest_frame_copy->format; if (!init_jpeg_encoder(impl, w, h, pf)) return false; return encode_frame_to_jpeg(impl, impl->latest_frame_copy, filename); } void RtspFrameGrabber::decode_loop() { RtspFrameGrabberImpl *impl = reinterpret_cast(opaque_impl_); if (!impl) return; AVDictionary *opts = nullptr; av_dict_set(&opts, "rtsp_transport", "tcp", 0); av_dict_set(&opts, "stimeout", "2000000", 0); av_dict_set(&opts, "fflags", "nobuffer", 0); av_dict_set(&opts, "flags", "low_delay", 0); av_dict_set(&opts, "max_delay", "0", 0); av_dict_set(&opts, "buffer_size", "102400", 0); AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUVJ420P; #if LIBAVUTIL_VERSION_MAJOR >= 57 dst_pix_fmt = AV_PIX_FMT_YUV420P; #endif while (!impl->stop_flag) { AVFormatContext *fmt_ctx = nullptr; int ret = avformat_open_input(&fmt_ctx, rtsp_url_.c_str(), nullptr, &opts); if (ret != 0) { std::this_thread::sleep_for( std::chrono::milliseconds(impl->reconnect_delay_ms)); continue; } impl->fmt_ctx = fmt_ctx; impl->reconnect_delay_ms = 1000; if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) { avformat_close_input(&impl->fmt_ctx); continue; } int video_index = -1; for (unsigned i = 0; i < fmt_ctx->nb_streams; ++i) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_index = i; break; } } if (video_index < 0) { avformat_close_input(&impl->fmt_ctx); continue; } impl->video_stream_index = video_index; AVCodecParameters *codecpar = fmt_ctx->streams[video_index]->codecpar; const AVCodec *dec = avcodec_find_decoder(codecpar->codec_id); if (!dec) { avformat_close_input(&impl->fmt_ctx); continue; } AVCodecContext *dec_ctx = avcodec_alloc_context3(dec); avcodec_parameters_to_context(dec_ctx, codecpar); if (avcodec_open2(dec_ctx, dec, nullptr) < 0) { avcodec_free_context(&dec_ctx); avformat_close_input(&impl->fmt_ctx); continue; } impl->dec_ctx = dec_ctx; impl->decoded_frame = av_frame_alloc(); impl->width = dec_ctx->width; impl->height = dec_ctx->height; impl->src_pix_fmt = dec_ctx->pix_fmt; impl->sws_frame = av_frame_alloc(); impl->sws_frame->format = dst_pix_fmt; impl->sws_frame->width = impl->width; impl->sws_frame->height = impl->height; av_frame_get_buffer(impl->sws_frame, 32); impl->sws_ctx = sws_getContext(impl->width, impl->height, impl->src_pix_fmt, impl->width, impl->height, (AVPixelFormat)impl->sws_frame->format, SWS_BICUBIC, nullptr, nullptr, nullptr); init_jpeg_encoder(impl, impl->width, impl->height, (AVPixelFormat)impl->sws_frame->format); impl->ready = true; // LOG_INF("connected and decoding: " << rtsp_url_); AVPacket *pkt = av_packet_alloc(); while (!impl->stop_flag) { int r = av_read_frame(fmt_ctx, pkt); if (r < 0) { av_packet_unref(pkt); impl->ready = false; break; } if (pkt->stream_index != impl->video_stream_index) { av_packet_unref(pkt); continue; } if (avcodec_send_packet(dec_ctx, pkt) < 0) { av_packet_unref(pkt); continue; } while (!impl->stop_flag) { r = avcodec_receive_frame(dec_ctx, impl->decoded_frame); if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) break; sws_scale(impl->sws_ctx, impl->decoded_frame->data, impl->decoded_frame->linesize, 0, impl->height, impl->sws_frame->data, impl->sws_frame->linesize); std::lock_guard lk(frame_mutex_); if (impl->latest_frame_copy) av_frame_free(&impl->latest_frame_copy); impl->latest_frame_copy = avframe_deep_copy(impl->sws_frame); } av_packet_unref(pkt); } av_packet_free(&pkt); impl->ready = false; if (impl->sws_ctx) { sws_freeContext(impl->sws_ctx); impl->sws_ctx = nullptr; } if (impl->sws_frame) { av_frame_free(&impl->sws_frame); impl->sws_frame = nullptr; } if (impl->decoded_frame) { av_frame_free(&impl->decoded_frame); impl->decoded_frame = nullptr; } if (impl->dec_ctx) { avcodec_free_context(&impl->dec_ctx); impl->dec_ctx = nullptr; } if (impl->fmt_ctx) { avformat_close_input(&impl->fmt_ctx); impl->fmt_ctx = nullptr; } if (!impl->stop_flag) std::this_thread::sleep_for( std::chrono::milliseconds(impl->reconnect_delay_ms)); } impl->ready = false; LOG_INF("decode thread exit for: " << rtsp_url_); } RtspManager::~RtspManager() { shutdown(); } void RtspManager::configure( const std::unordered_map &side_sources) { std::lock_guard lk(mgr_mutex_); // 删除不再需要的grabber for (auto it = side_grabbers_.begin(); it != side_grabbers_.end();) { auto f = side_sources.find(it->first); if (f == side_sources.end() || f->second != it->second->get_rtsp_url()) { try { it->second->stop(); } catch (...) { // 忽略异常,避免影响主程序 } it = side_grabbers_.erase(it); } else { ++it; } } // 新增grabber for (const auto &kv : side_sources) { int sid = kv.first; const std::string &url = kv.second; if (side_grabbers_.find(sid) != side_grabbers_.end()) continue; try { auto g = std::make_unique(url); if (!g) { continue; } // start失败直接跳过 if (!g->start()) { continue; } side_grabbers_.emplace(sid, std::move(g)); } catch (...) { // 捕获所有异常,避免configure影响系统稳定 continue; } } } void RtspManager::shutdown() { std::lock_guard lk(mgr_mutex_); for (auto &kv : side_grabbers_) { kv.second->stop(); } side_grabbers_.clear(); } std::string RtspManager::capture_side_frame(int source_id) { if (!is_ready(source_id)) return ""; std::lock_guard lk(mgr_mutex_); auto it = side_grabbers_.find(source_id); if (it == side_grabbers_.end()) return ""; std::filesystem::create_directories("./temp"); auto now = std::chrono::system_clock::now(); std::time_t t = std::chrono::system_clock::to_time_t(now); std::ostringstream oss; oss << "side_" << source_id << "_" << t << ".jpg"; std::string name = oss.str(); std::string path = "./temp/" + name; if (!it->second->capture_jpeg(path)) return ""; return name; } bool RtspManager::is_ready(int source_id) const { std::lock_guard lk(mgr_mutex_); auto it = side_grabbers_.find(source_id); return it != side_grabbers_.end() && it->second->is_ready(); }