update plugins/publish/publish_node.cpp

This commit is contained in:
sladro 2025-12-26 12:31:14 +08:00
parent b05354ff01
commit d28cb0b6f6

View File

@ -727,11 +727,24 @@ private:
}
mk_media_init_complete(media_);
is_h265_ = (codec_id == 1);
splitter_ = mk_h264_splitter_create(&OnSplitFrame, this);
if (!splitter_) {
std::cerr << "[publish] zlm mk_h264_splitter_create failed" << "\n";
mk_media_release(media_);
media_ = nullptr;
return false;
}
std::cout << "[publish] zlm rtsp server ready: rtsp://0.0.0.0:" << port_ << "/" << app_ << "/" << stream_ << "\n";
return true;
}
void Close() {
if (splitter_) {
mk_h264_splitter_release(splitter_);
splitter_ = nullptr;
}
if (media_) {
mk_media_release(media_);
media_ = nullptr;
@ -741,23 +754,174 @@ private:
void Write(const EncodedPacket& pkt, const std::vector<uint8_t>& header, bool is_h265) {
if (!media_ || pkt.data.empty()) return;
// Feed encoder header first to provide SPS/PPS(/VPS).
if (!sent_header_ && !header.empty()) {
if (is_h265) {
mk_media_input_h265(media_, header.data(), static_cast<int>(header.size()), pkt.pts_ms, pkt.pts_ms);
} else {
mk_media_input_h264(media_, header.data(), static_cast<int>(header.size()), pkt.pts_ms, pkt.pts_ms);
}
FeedHeader(pkt.pts_ms, header, is_h265);
sent_header_ = true;
}
if (is_h265) {
mk_media_input_h265(media_, pkt.data.data(), static_cast<int>(pkt.data.size()), pkt.pts_ms, pkt.pts_ms);
} else {
mk_media_input_h264(media_, pkt.data.data(), static_cast<int>(pkt.data.size()), pkt.pts_ms, pkt.pts_ms);
}
FeedPacket(pkt.pts_ms, pkt.data, is_h265);
// NOTE: We intentionally do not send data directly via mk_media_input_h264/h265.
// Using mk_h264_splitter + mk_media_input_frame is more robust for SPS/PPS handling.
}
private:
static uint16_t ReadBe16(const uint8_t* p) {
return static_cast<uint16_t>(p[0] << 8) | static_cast<uint16_t>(p[1]);
}
static bool HasAnnexBStartCode(const uint8_t* d, size_t n) {
if (!d || n < 3) 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 std::vector<uint8_t> ConvertLengthPrefixedToAnnexB(const uint8_t* d, size_t n) {
// Best-effort conversion for AVCC-style length-prefixed NAL units (assume 4-byte length).
std::vector<uint8_t> out;
if (!d || n < 8) return out;
size_t pos = 0;
while (pos + 4 <= n) {
uint32_t len = (static_cast<uint32_t>(d[pos]) << 24) |
(static_cast<uint32_t>(d[pos + 1]) << 16) |
(static_cast<uint32_t>(d[pos + 2]) << 8) |
(static_cast<uint32_t>(d[pos + 3]));
pos += 4;
if (len == 0 || pos + len > n) break;
out.insert(out.end(), {0, 0, 0, 1});
out.insert(out.end(), d + pos, d + pos + len);
pos += len;
}
return out;
}
static std::vector<std::vector<uint8_t>> ExtractConfigFromAvcc(const std::vector<uint8_t>& avcc) {
std::vector<std::vector<uint8_t>> out;
if (avcc.size() < 7 || avcc[0] != 1) return out;
size_t pos = 5;
uint8_t num_sps = avcc[pos++] & 0x1F;
for (uint8_t i = 0; i < num_sps; ++i) {
if (pos + 2 > avcc.size()) return {};
uint16_t len = ReadBe16(&avcc[pos]);
pos += 2;
if (pos + len > avcc.size()) return {};
std::vector<uint8_t> one{0, 0, 0, 1};
one.insert(one.end(), avcc.begin() + static_cast<long>(pos), avcc.begin() + static_cast<long>(pos + len));
out.push_back(std::move(one));
pos += len;
}
if (pos + 1 > avcc.size()) return {};
uint8_t num_pps = avcc[pos++];
for (uint8_t i = 0; i < num_pps; ++i) {
if (pos + 2 > avcc.size()) return {};
uint16_t len = ReadBe16(&avcc[pos]);
pos += 2;
if (pos + len > avcc.size()) return {};
std::vector<uint8_t> one{0, 0, 0, 1};
one.insert(one.end(), avcc.begin() + static_cast<long>(pos), avcc.begin() + static_cast<long>(pos + len));
out.push_back(std::move(one));
pos += len;
}
return out;
}
static std::vector<std::vector<uint8_t>> ExtractConfigFromHvcc(const std::vector<uint8_t>& hvcc) {
std::vector<std::vector<uint8_t>> out;
if (hvcc.size() < 23 || hvcc[0] != 1) return out;
size_t pos = 22;
if (pos >= hvcc.size()) return out;
uint8_t num_arrays = hvcc[pos++];
for (uint8_t i = 0; i < num_arrays; ++i) {
if (pos + 3 > hvcc.size()) return {};
uint8_t nal_type = hvcc[pos++] & 0x3F;
uint16_t num_nalus = ReadBe16(&hvcc[pos]);
pos += 2;
for (uint16_t j = 0; j < num_nalus; ++j) {
if (pos + 2 > hvcc.size()) return {};
uint16_t len = ReadBe16(&hvcc[pos]);
pos += 2;
if (pos + len > hvcc.size()) return {};
if (nal_type == 32 || nal_type == 33 || nal_type == 34) {
std::vector<uint8_t> one{0, 0, 0, 1};
one.insert(one.end(), hvcc.begin() + static_cast<long>(pos), hvcc.begin() + static_cast<long>(pos + len));
out.push_back(std::move(one));
}
pos += len;
}
}
return out;
}
void FeedHeader(int64_t ts_ms, const std::vector<uint8_t>& header, bool is_h265) {
if (!splitter_ || header.empty()) return;
cur_ts_ms_ = ts_ms;
if (HasAnnexBStartCode(header.data(), header.size())) {
mk_h264_splitter_input_data(splitter_, reinterpret_cast<const char*>(header.data()), static_cast<int>(header.size()));
return;
}
// AVCC/HVCC extradata: extract config NALs and feed as AnnexB.
std::vector<std::vector<uint8_t>> cfg = is_h265 ? ExtractConfigFromHvcc(header) : ExtractConfigFromAvcc(header);
for (const auto& nal : cfg) {
if (nal.empty()) continue;
mk_h264_splitter_input_data(splitter_, reinterpret_cast<const char*>(nal.data()), static_cast<int>(nal.size()));
}
}
void FeedPacket(int64_t ts_ms, const std::vector<uint8_t>& data, bool /*is_h265*/) {
if (!splitter_ || data.empty()) return;
cur_ts_ms_ = ts_ms;
if (HasAnnexBStartCode(data.data(), data.size())) {
mk_h264_splitter_input_data(splitter_, reinterpret_cast<const char*>(data.data()), static_cast<int>(data.size()));
return;
}
auto annexb = ConvertLengthPrefixedToAnnexB(data.data(), data.size());
if (!annexb.empty()) {
mk_h264_splitter_input_data(splitter_, reinterpret_cast<const char*>(annexb.data()), static_cast<int>(annexb.size()));
}
}
static void API_CALL OnSplitFrame(void* user_data, mk_h264_splitter /*splitter*/, const char* frame, int size) {
auto* self = static_cast<ZlmRtspPublisher*>(user_data);
if (!self || !self->media_ || !frame || size <= 0) return;
const auto* d = reinterpret_cast<const uint8_t*>(frame);
bool has_start_code = false;
if (size >= 3 && d[0] == 0 && d[1] == 0 && d[2] == 1) {
has_start_code = true;
} else if (size >= 4 && d[0] == 0 && d[1] == 0 && d[2] == 0 && d[3] == 1) {
has_start_code = true;
}
if (!has_start_code) {
thread_local std::vector<uint8_t> prefixed;
prefixed.resize(static_cast<size_t>(size) + 4);
prefixed[0] = 0;
prefixed[1] = 0;
prefixed[2] = 0;
prefixed[3] = 1;
std::memcpy(prefixed.data() + 4, frame, static_cast<size_t>(size));
if (self->is_h265_) {
mk_media_input_h265(self->media_, prefixed.data(), static_cast<int>(prefixed.size()), self->cur_ts_ms_, self->cur_ts_ms_);
} else {
mk_media_input_h264(self->media_, prefixed.data(), static_cast<int>(prefixed.size()), self->cur_ts_ms_, self->cur_ts_ms_);
}
return;
}
if (self->is_h265_) {
mk_media_input_h265(self->media_, frame, size, self->cur_ts_ms_, self->cur_ts_ms_);
} else {
mk_media_input_h264(self->media_, frame, size, self->cur_ts_ms_, self->cur_ts_ms_);
}
}
static void EnsureZlmEnv() {
static std::once_flag once;
std::call_once(once, [] {
@ -782,6 +946,9 @@ private:
}
mk_media media_ = nullptr;
mk_h264_splitter splitter_ = nullptr;
bool is_h265_ = false;
uint64_t cur_ts_ms_ = 0;
std::string app_;
std::string stream_;
int port_ = 0;