From ceb78bd54cc2e187dd9f3e0293a88540e9adf872 Mon Sep 17 00:00:00 2001 From: weishao Date: Wed, 20 Aug 2025 18:28:40 +0800 Subject: [PATCH] feat: add H.265 profile parameters support for WebRTC SDP negotiation --- ext-codec/H265.cpp | 106 ++++++++++++++++++++++++++++++++++++++++++++- webrtc/Sdp.cpp | 14 +++++- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/ext-codec/H265.cpp b/ext-codec/H265.cpp index b01bf46c..cf400dfa 100644 --- a/ext-codec/H265.cpp +++ b/ext-codec/H265.cpp @@ -216,6 +216,108 @@ void H265Track::insertConfigFrame(const Frame::Ptr &frame) { } } +class BitReader { +public: + BitReader(const uint8_t* data, size_t size) : _data(data), _size(size), _bitPos(0) {} + + uint32_t readBits(int n) { + uint32_t result = 0; + for (int i = 0; i < n; i++) { + if (_bitPos >= _size * 8) throw std::runtime_error("Out of range"); + int bytePos = _bitPos / 8; + int bitOffset = 7 - (_bitPos % 8); + result = (result << 1) | ((_data[bytePos] >> bitOffset) & 0x01); + _bitPos++; + } + return result; + } + + void skipBits(int n) { + _bitPos += n; + if (_bitPos > _size * 8) throw std::runtime_error("Skip out of range"); + } + +private: + const uint8_t* _data; + size_t _size; + size_t _bitPos; +}; + +struct HevcProfileInfo { + int profile_id = -1; // profile-id + int level_id = -1; // level-id + int tier_flag = -1; // tier-flag +}; + +// 移除 00 00 03 防竞争字节 +std::vector removeEmulationPrevention(const uint8_t *data, size_t size) { + std::vector out; + out.reserve(size); + for (size_t i = 0; i < size; i++) { + if (i + 2 < size && data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x03) { + out.push_back(0x00); + out.push_back(0x00); + i += 2; // skip 0x00 0x00 0x03 + } else { + out.push_back(data[i]); + } + } + return out; +} + +// 从 VPS 或 SPS 里提取 profile/level/tier 信息 +HevcProfileInfo parse_hevc_profile_tier_level(const uint8_t *nalu, size_t size) { + // 去掉起始码 (00 00 01 或 00 00 00 01) + size_t offset = 0; + if (size > 4 && nalu[0] == 0x00 && nalu[1] == 0x00) { + if (nalu[2] == 0x01) + offset = 3; + else if (nalu[2] == 0x00 && nalu[3] == 0x01) + offset = 4; + } + + auto rbsp = removeEmulationPrevention(nalu + offset, size - offset); + BitReader br(rbsp.data(), rbsp.size()); + + // ---- NALU header ---- + br.skipBits(1 + 6 + 6 + 3); // forbidden_zero_bit + nal_unit_type + nuh_layer_id + nuh_temporal_id_plus1 + + // VPS 和 SPS 都包含 profile_tier_level() + // 先解析最少需要的部分 + + // vps_video_parameter_set_id 或 sps_video_parameter_set_id (略过) + br.readBits(4); + + // sps 里还有 sps_max_sub_layers_minus1 + uint32_t max_sub_layers_minus1 = br.readBits(3); + // temporal_id_nesting_flag + br.readBits(1); + + // ---- profile_tier_level ---- + HevcProfileInfo info; + uint32_t profile_space = br.readBits(2); // general_profile_space + info.tier_flag = br.readBits(1); // general_tier_flag + info.profile_id = br.readBits(5); // general_profile_idc + + // general_profile_compatibility_flag[32] + for (int i = 0; i < 32; i++) + br.readBits(1); + + // general_progressive_source_flag 等 (跳过) + br.readBits(1); // progressive_source_flag + br.readBits(1); // interlaced_source_flag + br.readBits(1); // non_packed_constraint_flag + br.readBits(1); // frame_only_constraint_flag + + // general_reserved_zero_44bits + br.skipBits(44); + + // general_level_idc (8 bits) + info.level_id = br.readBits(8); + + return info; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** @@ -248,7 +350,9 @@ public: _printer << "b=AS:" << bitrate << "\r\n"; } _printer << "a=rtpmap:" << payload_type << " " << getCodecName(CodecH265) << "/" << 90000 << "\r\n"; - _printer << "a=fmtp:" << payload_type << " "; + + auto info = parse_hevc_profile_tier_level((uint8_t *)strSPS.data(), strSPS.size()); + _printer << "a=fmtp:" << payload_type << " level-id=" << info.level_id << "; profile-id=" << info.profile_id << "; tier-flag=" << info.tier_flag << "; "; _printer << "sprop-vps="; _printer << encodeBase64(strVPS) << "; "; _printer << "sprop-sps="; diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index 40fe8e46..358b5aa6 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -1845,7 +1845,8 @@ void RtcConfigure::setPlayRtspInfo(const string &sdp) { } } -static const string kProfile { "profile-level-id" }; +static const string kH264Profile { "profile-level-id" }; +static const string kH265Profile { "profile-id" }; static const string kMode { "packetization-mode" }; bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const { @@ -1860,7 +1861,7 @@ bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { // h264时,profile-level-id [AUTO-TRANSLATED:94a5f360] // When h264, profile-level-id - if (strcasecmp(_rtsp_video_plan->fmtp[kProfile].data(), const_cast(plan).fmtp[kProfile].data())) { + if (strcasecmp(_rtsp_video_plan->fmtp[kH264Profile].data(), const_cast(plan).fmtp[kH264Profile].data())) { // profile-level-id 不匹配 [AUTO-TRANSLATED:814ec4c4] // profile-level-id does not match return false; @@ -1868,6 +1869,15 @@ bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) return true; } + if (_rtsp_video_plan && codec == CodecH265 && getCodecId(_rtsp_video_plan->codec) == CodecH265) { + // h265时,profile-id + if (strcasecmp(_rtsp_video_plan->fmtp[kH265Profile].data(), const_cast(plan).fmtp[kH265Profile].data())) { + // profile-id 不匹配 + return false; + } + return true; + } + return true; }