Skip to content

Commit 0c88ddb

Browse files
duiniuluantanqinchundonglinlinxiaozhihong
authored andcommitted
rtmp2rtc: Support RTMP-to-WebRTC conversion with HEVC. v7.0.33 (#4289)
```bash C:\Program Files\Google\Chrome\Application>"C:\Program Files\Google\Chrome\Application\chrome.exe" --enable-features=WebRtcAllowH265Receive --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled open -a "Google Chrome" --args --enable-features=WebRtcAllowH265Receive --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled ``` > Note: The latest Chrome browser (version 136) fully enables this by default, so there's no need to launch it with any extra parameters. ```bash ./objs/srs -c conf/rtmp2rtc.conf ``` ```bash ffmpeg -stream_loop -1 -re -i input.mp4 -c:v libx265 -preset fast -b:v 2000k -maxrate 2000k -bufsize 4000k -bf 0 -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv rtmp://localhost/live/livestream ``` ```bash http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream ``` ![image](https://github.com/user-attachments/assets/bdbf4c67-b7e2-4dc6-92a1-93e2c78e00fe) sendrecv offer ```bash --enable-features=WebRtcAllowH265Send,PlatformHEVCEncoderSupport,WebRtcAllowH265Receive --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled ``` sendonly offer ```bash --enable-features=WebRtcAllowH265Send,PlatformHEVCEncoderSupport ``` recvonly offer ```bash --enable-features=WebRtcAllowH265Receive --force-fieldtrials=WebRTC-Video-H26xPacketBuffer/Enabled ``` * Browser Test for supporting H265 https://webrtc.github.io/samples/src/content/peerconnection/change-codecs/ ![image](https://github.com/user-attachments/assets/174476df-a7aa-4951-9880-56328ec75065) * How to test Safari: #3441 * Debug in Safari ![image](https://github.com/user-attachments/assets/6cf94fca-e3ed-46d2-a102-a472f1699b4e) --------- Co-authored-by: chundonglinlin <[email protected]> Co-authored-by: winlin <[email protected]> Co-authored-by: john <[email protected]> --------- Co-authored-by: chundonglinlin <[email protected]> Co-authored-by: john <[email protected]>
1 parent d35d02f commit 0c88ddb

17 files changed

+1189
-145
lines changed

trunk/doc/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The changelog for SRS.
77
<a name="v7-changes"></a>
88

99
## SRS 7.0 Changelog
10+
* v7.0, 2025-05-13, Merge [#4289](https://github.com/ossrs/srs/pull/4289): rtmp2rtc: Support RTMP-to-WebRTC conversion with HEVC. v7.0.33 (#4289)
1011
* v7.0, 2025-04-30, Merge [#4308](https://github.com/ossrs/srs/pull/4308): Fix memory leaks from errors skipping resource release. v7.0.32 (#4308)
1112
* v7.0, 2025-04-26, Merge [#4292](https://github.com/ossrs/srs/pull/4309): Support custom deleter for SrsUniquePtr. v7.0.31 (#4309)
1213
* v7.0, 2025-03-21, Merge [#4292](https://github.com/ossrs/srs/pull/4292): Typo: "forked" process in log output. v7.0.30 (#4292)

trunk/src/app/srs_app_hls.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ srs_error_t SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts)
10681068

10691069
// Refresh the codec ASAP.
10701070
if (muxer->latest_vcodec() != frame->vcodec()->id) {
1071-
srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_acodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(),
1071+
srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_vcodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(),
10721072
frame->vcodec()->id, srs_video_codec_id2str(frame->vcodec()->id).c_str());
10731073
muxer->set_latest_vcodec(frame->vcodec()->id);
10741074
}

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 122 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,51 @@ bool srs_sdp_has_h264_profile(const SrsSdp& sdp, const string& profile)
25982598
return false;
25992599
}
26002600

2601+
bool srs_sdp_has_h265_profile(const SrsMediaPayloadType& payload_type, const string& profile)
2602+
{
2603+
srs_error_t err = srs_success;
2604+
2605+
if (payload_type.format_specific_param_.empty()) {
2606+
return false;
2607+
}
2608+
2609+
H265SpecificParam h265_param;
2610+
if ((err = srs_parse_h265_fmtp(payload_type.format_specific_param_, h265_param)) != srs_success) {
2611+
srs_error_reset(err);
2612+
return false;
2613+
}
2614+
2615+
if (h265_param.profile_id == profile) {
2616+
return true;
2617+
}
2618+
2619+
return false;
2620+
}
2621+
2622+
bool srs_sdp_has_h265_profile(const SrsSdp& sdp, const string& profile)
2623+
{
2624+
for (size_t i = 0; i < sdp.media_descs_.size(); ++i) {
2625+
const SrsMediaDesc& desc = sdp.media_descs_[i];
2626+
if (!desc.is_video()) {
2627+
continue;
2628+
}
2629+
2630+
std::vector<SrsMediaPayloadType> payloads = desc.find_media_with_encoding_name("H265");
2631+
if (payloads.empty()) {
2632+
continue;
2633+
}
2634+
2635+
for (std::vector<SrsMediaPayloadType>::iterator it = payloads.begin(); it != payloads.end(); ++it) {
2636+
const SrsMediaPayloadType& payload_type = *it;
2637+
if (srs_sdp_has_h265_profile(payload_type, profile)) {
2638+
return true;
2639+
}
2640+
}
2641+
}
2642+
2643+
return false;
2644+
}
2645+
26012646
srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc, SrsRtcSourceDescription* stream_desc)
26022647
{
26032648
srs_error_t err = srs_success;
@@ -3041,8 +3086,6 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
30413086

30423087
bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost);
30433088
bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost);
3044-
// TODO: FIME: Should check packetization-mode=1 also.
3045-
bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f");
30463089

30473090
SrsSharedPtr<SrsRtcSource> source;
30483091
if ((err = _srs_rtc_sources->fetch_or_create(req, source)) != srs_success) {
@@ -3083,56 +3126,87 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
30833126

30843127
remote_payload = payloads.at(0);
30853128
track_descs = source->get_track_desc("audio", "opus");
3086-
} else if (remote_media_desc.is_video() && ruc->codec_ == "av1") {
3087-
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
3088-
if (payloads.empty()) {
3089-
// Be compatible with the Chrome M96, still check the AV1X encoding name
3090-
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
3091-
payloads = remote_media_desc.find_media_with_encoding_name("AV1X");
3092-
}
3093-
if (payloads.empty()) {
3094-
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type");
3129+
} else if (remote_media_desc.is_video()) {
3130+
std::string prefer_codec = ruc->codec_;
3131+
if (prefer_codec.empty()) {
3132+
// Get the source codec if not specified.
3133+
std::vector<SrsRtcTrackDescription*> track_descs = source->get_track_desc("video", "");
3134+
if (!track_descs.empty()) {
3135+
std::string codec_name = track_descs.at(0)->media_->name_;
3136+
std::transform(codec_name.begin(), codec_name.end(), codec_name.begin(), ::tolower);
3137+
if (codec_name == "h265") {
3138+
prefer_codec = "hevc";
3139+
} else {
3140+
prefer_codec = codec_name;
3141+
}
3142+
} else {
3143+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no video track in source");
3144+
}
30953145
}
30963146

3097-
remote_payload = payloads.at(0);
3098-
track_descs = source->get_track_desc("video", "AV1");
3099-
if (track_descs.empty()) {
3100-
// Be compatible with the Chrome M96, still check the AV1X encoding name
3101-
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
3102-
track_descs = source->get_track_desc("video", "AV1X");
3103-
}
3104-
} else if (remote_media_desc.is_video() && ruc->codec_ == "hevc") {
3105-
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
3106-
if (payloads.empty()) {
3107-
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
3108-
}
3147+
if (prefer_codec == "av1") {
3148+
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
3149+
if (payloads.empty()) {
3150+
// Be compatible with the Chrome M96, still check the AV1X encoding name
3151+
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
3152+
payloads = remote_media_desc.find_media_with_encoding_name("AV1X");
3153+
}
3154+
if (payloads.empty()) {
3155+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type");
3156+
}
31093157

3110-
remote_payload = payloads.at(0);
3158+
remote_payload = payloads.at(0);
3159+
track_descs = source->get_track_desc("video", "AV1");
3160+
if (track_descs.empty()) {
3161+
// Be compatible with the Chrome M96, still check the AV1X encoding name
3162+
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
3163+
track_descs = source->get_track_desc("video", "AV1X");
3164+
}
3165+
} else if (prefer_codec == "hevc") {
3166+
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
3167+
if (payloads.empty()) {
3168+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
3169+
}
3170+
3171+
// @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1
3172+
bool has_main_profile = srs_sdp_has_h265_profile(remote_sdp, "1");
3173+
remote_payload = payloads.at(0);
3174+
3175+
for (int j = 0; j < (int)payloads.size(); j++) {
3176+
const SrsMediaPayloadType& payload = payloads.at(j);
3177+
3178+
// For H.265, we only check if profile-id=1 (Main Profile)
3179+
// Format example: level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST
3180+
if (!has_main_profile || srs_sdp_has_h265_profile(payload, "1")) {
3181+
remote_payload = payload;
3182+
break;
3183+
}
3184+
}
31113185

3112-
// TODO: FIXME: pick up a profile for HEVC.
3113-
// @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1
3186+
track_descs = source->get_track_desc("video", "H265");
3187+
} else {
3188+
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H264");
3189+
if (payloads.empty()) {
3190+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type");
3191+
}
31143192

3115-
track_descs = source->get_track_desc("video", "H265");
3116-
} else if (remote_media_desc.is_video()) {
3117-
// TODO: check opus format specific param
3118-
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H264");
3119-
if (payloads.empty()) {
3120-
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type");
3121-
}
3193+
// TODO: FIME: Should check packetization-mode=1 also.
3194+
bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f");
31223195

3123-
remote_payload = payloads.at(0);
3124-
for (int j = 0; j < (int)payloads.size(); j++) {
3125-
const SrsMediaPayloadType& payload = payloads.at(j);
3196+
remote_payload = payloads.at(0);
3197+
for (int j = 0; j < (int)payloads.size(); j++) {
3198+
const SrsMediaPayloadType& payload = payloads.at(j);
31263199

3127-
// If exists 42e01f profile, choose it; otherwise, use the first payload.
3128-
// TODO: FIME: Should check packetization-mode=1 also.
3129-
if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) {
3130-
remote_payload = payload;
3131-
break;
3200+
// If exists 42e01f profile, choose it; otherwise, use the first payload.
3201+
// TODO: FIME: Should check packetization-mode=1 also.
3202+
if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) {
3203+
remote_payload = payload;
3204+
break;
3205+
}
31323206
}
3133-
}
31343207

3135-
track_descs = source->get_track_desc("video", "H264");
3208+
track_descs = source->get_track_desc("video", "H264");
3209+
}
31363210
}
31373211

31383212
for (int j = 0; j < (int)track_descs.size(); ++j) {
@@ -3238,7 +3312,11 @@ void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid,
32383312

32393313
SrsVideoPayload* payload = (SrsVideoPayload*)track->media_;
32403314

3241-
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
3315+
if (payload->name_ == "H265") {
3316+
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type_h265());
3317+
} else {
3318+
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
3319+
}
32423320

32433321
if (track->red_) {
32443322
SrsRedPayload* red_payload = (SrsRedPayload*)track->red_;

trunk/src/app/srs_app_rtc_sdp.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,42 @@ srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264
9292
return err;
9393
}
9494

95+
srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param)
96+
{
97+
srs_error_t err = srs_success;
98+
99+
std::vector<std::string> vec = srs_string_split(fmtp, ";");
100+
for (size_t i = 0; i < vec.size(); ++i) {
101+
std::vector<std::string> kv = srs_string_split(vec[i], "=");
102+
if (kv.size() != 2) continue;
103+
104+
if (kv[0] == "level-id") {
105+
h265_param.level_id = kv[1];
106+
} else if (kv[0] == "profile-id") {
107+
h265_param.profile_id = kv[1];
108+
} else if (kv[0] == "tier-flag") {
109+
h265_param.tier_flag = kv[1];
110+
} else if (kv[0] == "tx-mode") {
111+
h265_param.tx_mode = kv[1];
112+
}
113+
}
114+
115+
if (h265_param.level_id.empty()) {
116+
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: level-id");
117+
}
118+
if (h265_param.profile_id.empty()) {
119+
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: profile-id");
120+
}
121+
if (h265_param.tier_flag.empty()) {
122+
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tier-flag");
123+
}
124+
if (h265_param.tx_mode.empty()) {
125+
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tx-mode");
126+
}
127+
128+
return err;
129+
}
130+
95131
SrsSessionInfo::SrsSessionInfo()
96132
{
97133
}

trunk/src/app/srs_app_rtc_sdp.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,16 @@ struct H264SpecificParam
9797
std::string level_asymmerty_allow;
9898
};
9999

100-
extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param);
100+
struct H265SpecificParam
101+
{
102+
std::string level_id;
103+
std::string profile_id;
104+
std::string tier_flag;
105+
std::string tx_mode;
106+
};
101107

108+
extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param);
109+
extern srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param);
102110
class SrsMediaPayloadType
103111
{
104112
public:

0 commit comments

Comments
 (0)