update plugins/publish/publish_node.cpp
This commit is contained in:
parent
b05354ff01
commit
d28cb0b6f6
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user