From 763f1473bc9c2942f2ebf4da581b953026d1c59a Mon Sep 17 00:00:00 2001 From: sladro Date: Tue, 6 Jan 2026 16:37:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8Dmp4=E6=97=A0=E6=B3=95=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/alarm/actions/clip_action.cpp | 166 +++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 6 deletions(-) diff --git a/plugins/alarm/actions/clip_action.cpp b/plugins/alarm/actions/clip_action.cpp index c48cfe9..d95ff25 100644 --- a/plugins/alarm/actions/clip_action.cpp +++ b/plugins/alarm/actions/clip_action.cpp @@ -24,6 +24,118 @@ extern "C" { namespace rk3588 { +namespace { + +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; + + // Trim trailing zeros before next start code. + 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; + + // AVCDecoderConfigurationRecord + // https://developer.apple.com/documentation/quicktime-file-format/avcdecoderconfigurationrecord + avcc_out.reserve(11 + sps_len + pps_len); + avcc_out.push_back(1); // configurationVersion + avcc_out.push_back(sps[1]); // AVCProfileIndication + avcc_out.push_back(sps[2]); // profile_compatibility + avcc_out.push_back(sps[3]); // AVCLevelIndication + avcc_out.push_back(0xFF); // 6 bits reserved + lengthSizeMinusOne(3 => 4 bytes) + avcc_out.push_back(0xE1); // 3 bits reserved + numOfSequenceParameterSets(1) + 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); // numOfPictureParameterSets + 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); + // Each NAL becomes [len_be32][nal_bytes] + size_t total = 0; + for (const auto& nal : nals) total += 4 + nal.size(); + out.reserve(total); + for (const auto& nal : nals) { + const 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; +} + +} // namespace + bool ClipAction::Init(const SimpleJson& config) { pre_sec_ = config.ValueOr("pre_sec", 5); post_sec_ = config.ValueOr("post_sec", 10); @@ -140,16 +252,42 @@ std::string ClipAction::ProcessClipFromPackets(const std::vectortime_base = AVRational{1, 1000}; + stream->avg_frame_rate = AVRational{std::max(1, fps_), 1}; + stream->r_frame_rate = stream->avg_frame_rate; stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; stream->codecpar->codec_id = cid; stream->codecpar->width = width; stream->codecpar->height = height; stream->codecpar->format = AV_PIX_FMT_YUV420P; + // MP4 requires AVCC/HVCC style extradata and length-prefixed samples. + std::vector mp4_extradata; if (!first->codec->extradata.empty()) { - stream->codecpar->extradata_size = static_cast(first->codec->extradata.size()); - stream->codecpar->extradata = static_cast(av_mallocz(first->codec->extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); - std::memcpy(stream->codecpar->extradata, first->codec->extradata.data(), first->codec->extradata.size()); + const auto& ex = first->codec->extradata; + if (cid == AV_CODEC_ID_H264) { + if (!ex.empty() && ex[0] == 1) { + mp4_extradata = ex; // AVCC + } else if (HasAnnexBStartCode(ex.data(), ex.size())) { + if (!BuildAvccFromAnnexB(ex, mp4_extradata)) { + std::cerr << "[ClipAction] failed to build AVCC from AnnexB extradata\n"; + } + } + } + } + if (cid == AV_CODEC_ID_H264 && mp4_extradata.empty()) { + // As a fallback, try extracting SPS/PPS from the first keyframe packet. + for (const auto& m : metas) { + if (!m || m->magic != EncodedVideoFrameMeta::kMagic) continue; + if (!m->pkt.key) continue; + if (!HasAnnexBStartCode(m->pkt.data.data(), m->pkt.data.size())) continue; + if (BuildAvccFromAnnexB(m->pkt.data, mp4_extradata)) break; + } + } + + if (!mp4_extradata.empty()) { + stream->codecpar->extradata_size = static_cast(mp4_extradata.size()); + stream->codecpar->extradata = static_cast(av_mallocz(mp4_extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); + std::memcpy(stream->codecpar->extradata, mp4_extradata.data(), mp4_extradata.size()); } if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) { @@ -165,12 +303,22 @@ std::string ClipAction::ProcessClipFromPackets(const std::vectorpkt.key) { + start_idx = i; + break; + } + } + int64_t first_pts = 0; bool have_first = false; int64_t last_pts = -1; const int64_t frame_dur = (fps_ > 0) ? (1000LL / std::max(1, fps_)) : 40; - for (const auto& m : metas) { + for (size_t i = start_idx; i < metas.size(); ++i) { + const auto& m = metas[i]; if (!m || m->magic != EncodedVideoFrameMeta::kMagic) continue; if (!m->codec || m->pkt.data.empty()) continue; if (m->pkt.pts_ms <= 0) continue; @@ -184,13 +332,19 @@ std::string ClipAction::ProcessClipFromPackets(const std::vector sample = m->pkt.data; + if (cid == AV_CODEC_ID_H264 && HasAnnexBStartCode(sample.data(), sample.size())) { + sample = AnnexBToLengthPrefixed(sample.data(), sample.size()); + } + if (sample.empty()) continue; + AVPacket* pkt = av_packet_alloc(); if (!pkt) continue; - if (av_new_packet(pkt, static_cast(m->pkt.data.size())) < 0) { + if (av_new_packet(pkt, static_cast(sample.size())) < 0) { av_packet_free(&pkt); continue; } - std::memcpy(pkt->data, m->pkt.data.data(), m->pkt.data.size()); + std::memcpy(pkt->data, sample.data(), sample.size()); pkt->stream_index = stream->index; pkt->pts = pts; pkt->dts = pts;