#include #include #include #include #include #include #include #include #include #include #include #include #include "utils/logger.h" #include "node.h" #include "hw/hw_factory.h" #include "media/encoded_video_meta.h" #if defined(RK3588_ENABLE_FFMPEG) extern "C" { #include #include #include #include #include } #define HAS_FFMPEG 1 #else #define HAS_FFMPEG 0 #endif #if defined(RK3588_ENABLE_MPP) extern "C" { #include #include #include } #define HAS_MPP 1 #else #define HAS_MPP 0 #endif namespace rk3588 { namespace { inline int Align16(int v) { return (v + 15) & ~15; } std::shared_ptr TryGetEncodedMeta(const FramePtr& frame) { if (!frame || !frame->user_meta) return nullptr; auto meta = std::static_pointer_cast(frame->user_meta); if (!meta || meta->magic != EncodedVideoFrameMeta::kMagic) return nullptr; if (!meta->codec || meta->pkt.data.empty() || meta->pkt.pts_ms <= 0) return nullptr; return meta; } #if HAS_FFMPEG static bool HasAnnexBStartCode(const uint8_t* d, size_t n) { if (!d || n < 4) return false; for (size_t i = 0; i + 3 < n; ++i) { if (d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 1) return true; if (i + 4 < n && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1) return true; } return false; } static size_t FindStartCode(const uint8_t* d, size_t n, size_t from, size_t* sc_len) { for (size_t i = from; i + 3 < n; ++i) { if (d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 1) { if (sc_len) *sc_len = 3; return i; } if (i + 4 < n && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1) { if (sc_len) *sc_len = 4; return i; } } if (sc_len) *sc_len = 0; return n; } static std::vector> SplitAnnexBNals(const uint8_t* d, size_t n) { std::vector> out; if (!d || n < 4) return out; size_t pos = 0; while (true) { size_t sc_len = 0; size_t sc = FindStartCode(d, n, pos, &sc_len); if (sc >= n) break; size_t nal_start = sc + sc_len; size_t next_sc = FindStartCode(d, n, nal_start, nullptr); size_t nal_end = (next_sc >= n) ? n : next_sc; while (nal_end > nal_start && d[nal_end - 1] == 0) --nal_end; if (nal_end > nal_start) { out.emplace_back(d + nal_start, d + nal_end); } pos = nal_end; } return out; } static bool BuildAvccFromAnnexB(const std::vector& annexb, std::vector& avcc_out) { avcc_out.clear(); if (annexb.empty()) return false; auto nals = SplitAnnexBNals(annexb.data(), annexb.size()); const uint8_t* sps = nullptr; size_t sps_len = 0; const uint8_t* pps = nullptr; size_t pps_len = 0; for (const auto& nal : nals) { if (nal.empty()) continue; const uint8_t t = nal[0] & 0x1F; if (t == 7 && !sps) { sps = nal.data(); sps_len = nal.size(); } else if (t == 8 && !pps) { pps = nal.data(); pps_len = nal.size(); } } if (!sps || sps_len < 4 || !pps || pps_len < 1) return false; avcc_out.reserve(11 + sps_len + pps_len); avcc_out.push_back(1); avcc_out.push_back(sps[1]); avcc_out.push_back(sps[2]); avcc_out.push_back(sps[3]); avcc_out.push_back(0xFF); avcc_out.push_back(0xE1); avcc_out.push_back(static_cast((sps_len >> 8) & 0xFF)); avcc_out.push_back(static_cast((sps_len) & 0xFF)); avcc_out.insert(avcc_out.end(), sps, sps + sps_len); avcc_out.push_back(1); avcc_out.push_back(static_cast((pps_len >> 8) & 0xFF)); avcc_out.push_back(static_cast((pps_len) & 0xFF)); avcc_out.insert(avcc_out.end(), pps, pps + pps_len); return true; } static std::vector AnnexBToLengthPrefixed(const uint8_t* d, size_t n) { std::vector out; auto nals = SplitAnnexBNals(d, n); size_t total = 0; for (const auto& nal : nals) total += 4 + nal.size(); out.reserve(total); for (const auto& nal : nals) { uint32_t len = static_cast(nal.size()); out.push_back(static_cast((len >> 24) & 0xFF)); out.push_back(static_cast((len >> 16) & 0xFF)); out.push_back(static_cast((len >> 8) & 0xFF)); out.push_back(static_cast((len) & 0xFF)); out.insert(out.end(), nal.begin(), nal.end()); } return out; } #endif bool SafeLocalTime(std::time_t t, std::tm& out) { #if defined(_WIN32) return localtime_s(&out, &t) == 0; #elif defined(__unix__) || defined(__APPLE__) return localtime_r(&t, &out) != nullptr; #else static std::mutex mu; std::lock_guard lock(mu); std::tm* p = std::localtime(&t); if (!p) return false; out = *p; return true; #endif } std::string FormatTime(const std::string& pattern) { auto now = std::chrono::system_clock::now(); auto time_t_now = std::chrono::system_clock::to_time_t(now); std::tm tm{}; if (!SafeLocalTime(time_t_now, tm)) { LogWarn("[storage] localtime failed while formatting filename"); } std::ostringstream oss; for (size_t i = 0; i < pattern.size(); ++i) { if (pattern[i] == '%' && i + 1 < pattern.size()) { char spec = pattern[++i]; switch (spec) { case 'Y': oss << std::put_time(&tm, "%Y"); break; case 'm': oss << std::put_time(&tm, "%m"); break; case 'd': oss << std::put_time(&tm, "%d"); break; case 'H': oss << std::put_time(&tm, "%H"); break; case 'M': oss << std::put_time(&tm, "%M"); break; case 'S': oss << std::put_time(&tm, "%S"); break; default: oss << '%' << spec; break; } } else { oss << pattern[i]; } } return oss.str(); } } // namespace #if HAS_MPP class MppStorageEncoder { public: ~MppStorageEncoder() { Shutdown(); } bool Init(int width, int height, PixelFormat fmt, const std::string& codec, int fps, int bitrate_kbps) { width_ = width; height_ = height; hor_stride_ = Align16(width); ver_stride_ = Align16(height); fps_ = fps > 0 ? fps : 25; bitrate_bps_ = (bitrate_kbps > 0 ? bitrate_kbps : 2000) * 1000; if (fmt == PixelFormat::NV12) { mpp_fmt_ = MPP_FMT_YUV420SP; } else if (fmt == PixelFormat::YUV420) { mpp_fmt_ = MPP_FMT_YUV420P; } else { LogError("[storage] unsupported pixel format for MPP encoder"); return false; } coding_ = (codec == "h265" || codec == "hevc") ? MPP_VIDEO_CodingHEVC : MPP_VIDEO_CodingAVC; if (mpp_create(&ctx_, &mpi_) != MPP_OK) return false; if (mpp_init(ctx_, MPP_CTX_ENC, coding_) != MPP_OK) return false; if (mpp_enc_cfg_init(&cfg_) != MPP_OK) return false; if (mpi_->control(ctx_, MPP_ENC_GET_CFG, cfg_) != MPP_OK) return false; mpp_enc_cfg_set_s32(cfg_, "prep:width", width_); mpp_enc_cfg_set_s32(cfg_, "prep:height", height_); mpp_enc_cfg_set_s32(cfg_, "prep:hor_stride", hor_stride_); mpp_enc_cfg_set_s32(cfg_, "prep:ver_stride", ver_stride_); mpp_enc_cfg_set_s32(cfg_, "prep:format", mpp_fmt_); mpp_enc_cfg_set_s32(cfg_, "rc:mode", MPP_ENC_RC_MODE_CBR); mpp_enc_cfg_set_s32(cfg_, "rc:gop", fps_ * 2); mpp_enc_cfg_set_s32(cfg_, "rc:fps_in_num", fps_); mpp_enc_cfg_set_s32(cfg_, "rc:fps_in_denorm", 1); mpp_enc_cfg_set_s32(cfg_, "rc:fps_out_num", fps_); mpp_enc_cfg_set_s32(cfg_, "rc:fps_out_denorm", 1); mpp_enc_cfg_set_s32(cfg_, "rc:bps_target", bitrate_bps_); mpp_enc_cfg_set_s32(cfg_, "rc:bps_max", bitrate_bps_ * 3 / 2); mpp_enc_cfg_set_s32(cfg_, "rc:bps_min", bitrate_bps_ / 2); mpp_enc_cfg_set_s32(cfg_, "codec:type", coding_); if (mpi_->control(ctx_, MPP_ENC_SET_CFG, cfg_) != MPP_OK) return false; MppEncHeaderMode header_mode = MPP_ENC_HEADER_MODE_EACH_IDR; mpi_->control(ctx_, MPP_ENC_SET_HEADER_MODE, &header_mode); if (mpp_buffer_group_get_internal(&frm_grp_, MPP_BUFFER_TYPE_DRM, NULL) != MPP_OK) { mpp_buffer_group_get_internal(&frm_grp_, MPP_BUFFER_TYPE_NORMAL, NULL); } // Get header MppPacket hdr = nullptr; if (mpi_->control(ctx_, MPP_ENC_GET_HDR_SYNC, &hdr) == MPP_OK && hdr) { auto* data = static_cast(mpp_packet_get_pos(hdr)); size_t len = mpp_packet_get_length(hdr); header_.assign(data, data + len); mpp_packet_deinit(&hdr); } initialized_ = true; return true; } void Shutdown() { if (frm_grp_) { mpp_buffer_group_put(frm_grp_); frm_grp_ = nullptr; } if (cfg_) { mpp_enc_cfg_deinit(cfg_); cfg_ = nullptr; } if (ctx_) { if (mpi_) mpi_->reset(ctx_); mpp_destroy(ctx_); ctx_ = nullptr; mpi_ = nullptr; } initialized_ = false; } bool Encode(const FramePtr& frame, std::vector& out, bool& is_key) { out.clear(); is_key = false; if (!initialized_ || !frame) return false; size_t frame_size = static_cast(hor_stride_) * ver_stride_ * 3 / 2; MppBuffer buf = nullptr; if (frame->DmaFd() >= 0 && frame->data_size > 0) { MppBufferInfo info{}; info.type = MPP_BUFFER_TYPE_EXT_DMA; info.size = frame->data_size; info.fd = frame->DmaFd(); mpp_buffer_import(&buf, &info); } if (!buf) { if (!frame->data) return false; if (mpp_buffer_get(frm_grp_, &buf, frame_size) != MPP_OK) return false; auto* dst = static_cast(mpp_buffer_get_ptr(buf)); std::memset(dst, 0, frame_size); // Copy Y plane for (int row = 0; row < height_; ++row) { const uint8_t* src = frame->planes[0].data ? frame->planes[0].data + row * frame->planes[0].stride : frame->data + row * frame->width; std::memcpy(dst + row * hor_stride_, src, width_); } // Copy UV plane uint8_t* dst_uv = dst + hor_stride_ * ver_stride_; int uv_rows = height_ / 2; for (int row = 0; row < uv_rows; ++row) { const uint8_t* src = frame->planes[1].data ? frame->planes[1].data + row * frame->planes[1].stride : frame->data + width_ * height_ + row * width_; std::memcpy(dst_uv + row * hor_stride_, src, width_); } } MppFrame mpp_frame = nullptr; mpp_frame_init(&mpp_frame); mpp_frame_set_width(mpp_frame, width_); mpp_frame_set_height(mpp_frame, height_); mpp_frame_set_hor_stride(mpp_frame, hor_stride_); mpp_frame_set_ver_stride(mpp_frame, ver_stride_); mpp_frame_set_fmt(mpp_frame, mpp_fmt_); mpp_frame_set_pts(mpp_frame, frame->pts); mpp_frame_set_buffer(mpp_frame, buf); if (mpi_->encode_put_frame(ctx_, mpp_frame) != MPP_OK) { mpp_frame_deinit(&mpp_frame); mpp_buffer_put(buf); return false; } mpp_frame_deinit(&mpp_frame); MppPacket packet = nullptr; while (true) { MPP_RET ret = mpi_->encode_get_packet(ctx_, &packet); if (ret == MPP_ERR_TIMEOUT) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); continue; } if (ret != MPP_OK || !packet) break; auto* pos = static_cast(mpp_packet_get_pos(packet)); size_t len = mpp_packet_get_length(packet); out.assign(pos, pos + len); RK_U32 flag = mpp_packet_get_flag(packet); is_key = (flag & 0x08) != 0; mpp_packet_deinit(&packet); break; } mpp_buffer_put(buf); return !out.empty(); } const std::vector& Header() const { return header_; } bool IsH265() const { return coding_ == MPP_VIDEO_CodingHEVC; } private: MppCtx ctx_ = nullptr; MppApi* mpi_ = nullptr; MppEncCfg cfg_ = nullptr; MppBufferGroup frm_grp_ = nullptr; MppCodingType coding_ = MPP_VIDEO_CodingAVC; MppFrameFormat mpp_fmt_ = MPP_FMT_YUV420SP; int width_ = 0, height_ = 0; int hor_stride_ = 0, ver_stride_ = 0; int fps_ = 25; int bitrate_bps_ = 2000000; std::vector header_; bool initialized_ = false; }; #endif class StorageNode : public INode { public: std::string Id() const override { return id_; } std::string Type() const override { return "storage"; } bool Init(const SimpleJson& config, const NodeContext& ctx) override { id_ = config.ValueOr("id", "storage"); mode_ = config.ValueOr("mode", "continuous"); format_ = config.ValueOr("format", "mp4"); codec_ = config.ValueOr("codec", "h264"); segment_sec_ = config.ValueOr("segment_sec", 300); base_path_ = config.ValueOr("path", "/rec"); filename_pattern_ = config.ValueOr("filename_pattern", "%Y%m%d/%H%M%S"); fps_ = config.ValueOr("fps", 25); bitrate_kbps_ = config.ValueOr("bitrate_kbps", 2000); reuse_encoded_meta_ = config.ValueOr("reuse_encoded_meta", true); platform_ = config.ValueOr("platform", ""); hw_platform_ = config.ValueOr("hw_platform", ""); input_queue_ = ctx.input_queue; if (!input_queue_) { LogError("[storage] no input queue"); return false; } try { std::filesystem::create_directories(base_path_); } catch (const std::exception& e) { LogError(std::string("[storage] failed to create directory: ") + e.what()); return false; } LogInfo("[storage] initialized, path=" + base_path_ + " segment=" + std::to_string(segment_sec_) + "s format=" + format_); return true; } bool Start() override { LogInfo("[storage] started"); return true; } void Stop() override { if (input_queue_) input_queue_->Stop(); CloseCurrentFile(); if (encoder_) encoder_->Close(); encoder_.reset(); encoder_ready_ = false; encoder_header_.clear(); LogInfo("[storage] stopped, recorded " + std::to_string(total_frames_) + " frames"); } void Drain() override { std::lock_guard lock(mu_); CloseCurrentFile(); } bool UpdateConfig(const SimpleJson& new_config) override { const std::string new_id = new_config.ValueOr("id", id_); if (!new_id.empty() && new_id != id_) return false; // We only support a safe subset of updates; unsupported changes trigger graph rebuild. const std::string new_mode = new_config.ValueOr("mode", mode_); const std::string new_format = new_config.ValueOr("format", format_); const std::string new_codec = new_config.ValueOr("codec", codec_); if (new_mode != mode_ || new_format != format_ || new_codec != codec_) { return false; } int new_segment = new_config.ValueOr("segment_sec", segment_sec_); std::string new_path = new_config.ValueOr("path", base_path_); std::string new_pattern = new_config.ValueOr("filename_pattern", filename_pattern_); int new_fps = new_config.ValueOr("fps", fps_); int new_bitrate = new_config.ValueOr("bitrate_kbps", bitrate_kbps_); { std::lock_guard lock(mu_); segment_sec_ = new_segment; base_path_ = std::move(new_path); filename_pattern_ = std::move(new_pattern); fps_ = new_fps; bitrate_kbps_ = new_bitrate; try { std::filesystem::create_directories(base_path_); } catch (const std::exception& e) { LogWarn(std::string("[storage] create_directories failed during UpdateConfig: ") + e.what()); return false; } catch (...) { LogWarn("[storage] create_directories failed during UpdateConfig: unknown error"); return false; } // Apply on next segment. CloseCurrentFile(); if (encoder_) encoder_->Close(); encoder_.reset(); encoder_ready_ = false; encoder_header_.clear(); } return true; } NodeStatus Process(FramePtr frame) override { if (!frame) return NodeStatus::DROP; std::lock_guard lock(mu_); ProcessFrame(frame); ++total_frames_; return NodeStatus::OK; } private: static std::string PixelFormatToString(PixelFormat fmt) { switch (fmt) { case PixelFormat::NV12: return "nv12"; case PixelFormat::YUV420: return "yuv420"; case PixelFormat::RGB: return "rgb"; case PixelFormat::BGR: return "bgr"; default: return "unknown"; } } void ProcessFrame(FramePtr frame) { // Check if we need to start a new segment auto now = std::chrono::steady_clock::now(); bool need_new_segment = false; if (!file_open_) { need_new_segment = true; } else if (segment_sec_ > 0) { auto elapsed = std::chrono::duration_cast( now - segment_start_); if (elapsed.count() >= segment_sec_) { need_new_segment = true; } } if (need_new_segment) { CloseCurrentFile(); OpenNewFile(frame); } WriteFrame(frame); } void OpenNewFile(FramePtr frame) { #if HAS_FFMPEG auto meta = reuse_encoded_meta_ ? TryGetEncodedMeta(frame) : nullptr; using_encoded_meta_ = (meta != nullptr); segment_base_pts_ms_ = using_encoded_meta_ ? meta->pkt.pts_ms : 0; last_pts90k_ = -1; std::string filename = FormatTime(filename_pattern_) + "." + format_; current_path_ = base_path_ + "/" + filename; try { std::filesystem::path p(current_path_); if (p.has_parent_path()) { std::filesystem::create_directories(p.parent_path()); } } catch (const std::exception& e) { LogWarn(std::string("[storage] failed to create directories for segment: ") + e.what()); } catch (...) { LogWarn("[storage] failed to create directories for segment: unknown error"); } const char* fmt_name = format_ == "ts" ? "mpegts" : "mp4"; if (avformat_alloc_output_context2(&fmt_ctx_, nullptr, fmt_name, current_path_.c_str()) < 0) { LogError("[storage] failed to create output context"); return; } AVCodecID codec_id = AV_CODEC_ID_H264; if (using_encoded_meta_ && meta && meta->codec) { codec_id = (meta->codec->codec == VideoCodec::H265) ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264; } else { codec_id = (codec_ == "h265" || codec_ == "hevc") ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264; } stream_ = avformat_new_stream(fmt_ctx_, nullptr); if (!stream_) { avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; return; } if (using_encoded_meta_) { stream_->time_base = AVRational{1, 90000}; const int fps_eff = (meta && meta->codec && meta->codec->fps > 0) ? meta->codec->fps : fps_; stream_->avg_frame_rate = AVRational{std::max(1, fps_eff), 1}; stream_->r_frame_rate = stream_->avg_frame_rate; } else { stream_->time_base = AVRational{1, fps_}; } stream_->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; stream_->codecpar->codec_id = codec_id; stream_->codecpar->width = (using_encoded_meta_ && meta && meta->codec && meta->codec->width > 0) ? meta->codec->width : frame->width; stream_->codecpar->height = (using_encoded_meta_ && meta && meta->codec && meta->codec->height > 0) ? meta->codec->height : frame->height; stream_->codecpar->format = AV_PIX_FMT_YUV420P; if (!using_encoded_meta_) { if (!encoder_ready_) { SimpleJson::Object enc_obj; enc_obj["backend"] = SimpleJson(std::string("mpp")); enc_obj["codec"] = SimpleJson(codec_); enc_obj["fps"] = SimpleJson(static_cast(fps_)); enc_obj["gop"] = SimpleJson(static_cast(std::max(1, fps_) * 2)); enc_obj["bitrate_kbps"] = SimpleJson(static_cast(bitrate_kbps_)); enc_obj["width"] = SimpleJson(static_cast(frame->width)); enc_obj["height"] = SimpleJson(static_cast(frame->height)); enc_obj["pixel_format"] = SimpleJson(PixelFormatToString(frame->format)); if (!platform_.empty()) { enc_obj["platform"] = SimpleJson(platform_); } if (!hw_platform_.empty()) { enc_obj["hw_platform"] = SimpleJson(hw_platform_); } SimpleJson enc_cfg(std::move(enc_obj)); if (!encoder_) { encoder_ = HwFactory::CreateEncoder(enc_cfg); } if (!encoder_ || encoder_->Open(enc_cfg).Failed()) { encoder_.reset(); } else { encoder_header_ = encoder_->ExtraData(); encoder_ready_ = true; } } if (encoder_ready_ && !encoder_header_.empty()) { stream_->codecpar->extradata_size = static_cast(encoder_header_.size()); stream_->codecpar->extradata = static_cast( av_mallocz(encoder_header_.size() + AV_INPUT_BUFFER_PADDING_SIZE)); std::memcpy(stream_->codecpar->extradata, encoder_header_.data(), encoder_header_.size()); } } if (using_encoded_meta_ && meta && meta->codec && !meta->codec->extradata.empty()) { std::vector ex = meta->codec->extradata; const bool is_mp4 = (format_ != "ts"); // MP4 prefers AVCC/HVCC. Do a best-effort conversion for H264 AnnexB extradata. if (is_mp4 && codec_id == AV_CODEC_ID_H264) { if (!(ex.size() >= 1 && ex[0] == 1) && HasAnnexBStartCode(ex.data(), ex.size())) { std::vector avcc; if (BuildAvccFromAnnexB(ex, avcc)) { ex = std::move(avcc); } } } stream_->codecpar->extradata_size = static_cast(ex.size()); stream_->codecpar->extradata = static_cast( av_mallocz(ex.size() + AV_INPUT_BUFFER_PADDING_SIZE)); std::memcpy(stream_->codecpar->extradata, ex.data(), ex.size()); } if (!(fmt_ctx_->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&fmt_ctx_->pb, current_path_.c_str(), AVIO_FLAG_WRITE) < 0) { avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; return; } } if (avformat_write_header(fmt_ctx_, nullptr) < 0) { if (fmt_ctx_->pb) avio_closep(&fmt_ctx_->pb); avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; return; } file_open_ = true; segment_start_ = std::chrono::steady_clock::now(); segment_frames_ = 0; LogInfo("[storage] opened: " + current_path_); #else LogError("[storage] FFmpeg not enabled"); #endif } void WriteFrame(FramePtr frame) { #if HAS_FFMPEG if (!file_open_ || !fmt_ctx_ || !stream_) return; if (using_encoded_meta_) { auto meta = TryGetEncodedMeta(frame); if (!meta || !meta->codec) return; const bool is_mp4 = (format_ != "ts"); std::vector sample = meta->pkt.data; if (is_mp4 && stream_->codecpar->codec_id == AV_CODEC_ID_H264 && HasAnnexBStartCode(sample.data(), sample.size())) { sample = AnnexBToLengthPrefixed(sample.data(), sample.size()); } if (sample.empty()) return; const int fps_eff = (meta->codec->fps > 0) ? meta->codec->fps : fps_; const int64_t frame_dur = fps_eff > 0 ? (90000 / std::max(1, fps_eff)) : 0; int64_t rel_ms = meta->pkt.pts_ms - segment_base_pts_ms_; if (rel_ms < 0) rel_ms = 0; int64_t pts90k = rel_ms * 90; if (last_pts90k_ >= 0 && pts90k <= last_pts90k_) { pts90k = last_pts90k_ + (frame_dur > 0 ? frame_dur : 1); } last_pts90k_ = pts90k; AVPacket* pkt = av_packet_alloc(); if (!pkt) return; if (av_new_packet(pkt, static_cast(sample.size())) < 0) { av_packet_free(&pkt); return; } std::memcpy(pkt->data, sample.data(), sample.size()); pkt->stream_index = stream_->index; pkt->flags = meta->pkt.key ? AV_PKT_FLAG_KEY : 0; pkt->pts = pts90k; pkt->dts = pts90k; pkt->duration = frame_dur; (void)av_interleaved_write_frame(fmt_ctx_, pkt); av_packet_free(&pkt); ++segment_frames_; return; } #endif #if HAS_FFMPEG if (!file_open_ || !fmt_ctx_ || !encoder_ || !encoder_ready_) return; if (encoder_->Send(frame).Failed()) return; while (true) { auto out = encoder_->Receive(); if (!out.Ok()) break; auto pkt_out = std::move(out.Value()); if (pkt_out.data.empty()) continue; AVPacket* pkt = av_packet_alloc(); if (av_new_packet(pkt, static_cast(pkt_out.data.size())) < 0) { av_packet_free(&pkt); continue; } std::memcpy(pkt->data, pkt_out.data.data(), pkt_out.data.size()); pkt->stream_index = stream_->index; pkt->flags = pkt_out.keyframe ? AV_PKT_FLAG_KEY : 0; pkt->pts = segment_frames_; pkt->dts = segment_frames_; pkt->duration = 1; av_packet_rescale_ts(pkt, AVRational{1, fps_}, stream_->time_base); av_interleaved_write_frame(fmt_ctx_, pkt); av_packet_free(&pkt); ++segment_frames_; } #else (void)frame; #endif } void CloseCurrentFile() { #if HAS_FFMPEG if (!file_open_ || !fmt_ctx_) return; av_write_trailer(fmt_ctx_); if (!(fmt_ctx_->oformat->flags & AVFMT_NOFILE)) { avio_closep(&fmt_ctx_->pb); } avformat_free_context(fmt_ctx_); fmt_ctx_ = nullptr; stream_ = nullptr; file_open_ = false; using_encoded_meta_ = false; segment_base_pts_ms_ = 0; last_pts90k_ = -1; LogInfo("[storage] closed: " + current_path_ + " (" + std::to_string(segment_frames_) + " frames)"); #endif } std::string id_; std::string mode_; std::string format_; std::string codec_; int segment_sec_ = 300; std::string base_path_; std::string filename_pattern_; int fps_ = 25; int bitrate_kbps_ = 2000; bool reuse_encoded_meta_ = true; std::string platform_; std::string hw_platform_; std::mutex mu_; std::shared_ptr> input_queue_; uint64_t total_frames_ = 0; // Current file state bool file_open_ = false; std::string current_path_; std::chrono::steady_clock::time_point segment_start_; int64_t segment_frames_ = 0; bool using_encoded_meta_ = false; int64_t segment_base_pts_ms_ = 0; int64_t last_pts90k_ = -1; #if HAS_FFMPEG AVFormatContext* fmt_ctx_ = nullptr; AVStream* stream_ = nullptr; #endif std::shared_ptr encoder_; bool encoder_ready_ = false; std::vector encoder_header_; }; REGISTER_NODE(StorageNode, "storage"); } // namespace rk3588