diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md
index 1b046a4a7..7f8364966 100644
--- a/trunk/doc/CHANGELOG.md
+++ b/trunk/doc/CHANGELOG.md
@@ -7,6 +7,7 @@ The changelog for SRS.
## SRS 7.0 Changelog
+* v7.0, 2025-07-04, Merge [#4413](https://github.com/ossrs/srs/pull/4413): RTMP2RTC: Support dual video track for bridge. v7.0.44 (#4413)
* v7.0, 2025-07-03, Merge [#4349](https://github.com/ossrs/srs/pull/4349): rtc2rtmp: Support WebRTC-to-RTMP conversion with HEVC. v7.0.43 (#4349)
* v7.0, 2025-06-04, Merge [#4310](https://github.com/ossrs/srs/pull/4310): Player: Get codec by webrtc api: pc.getStats. v7.0.42 (#4310)
* v7.0, 2025-06-04, Merge [#4325](https://github.com/ossrs/srs/pull/4325): fix bug: loop transcoding #3516. v7.0.41 (#4325)
diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp
index 7325760e7..0d5a8d944 100644
--- a/trunk/src/app/srs_app_rtc_source.cpp
+++ b/trunk/src/app/srs_app_rtc_source.cpp
@@ -467,22 +467,42 @@ void SrsRtcSource::init_for_play_before_publishing()
audio_track_desc->media_ = new SrsAudioPayload(kAudioPayloadType, "opus", kAudioSamplerate, kAudioChannel);
}
- // video track description
+ // video track descriptions - support both H.264 and H.265 for play before publishing
+ // This allows clients to choose their preferred codec during SDP negotiation
if (true) {
- SrsRtcTrackDescription* video_track_desc = new SrsRtcTrackDescription();
- stream_desc->video_track_descs_.push_back(video_track_desc);
+ // H.264 track description
+ SrsRtcTrackDescription* h264_track_desc = new SrsRtcTrackDescription();
+ stream_desc->video_track_descs_.push_back(h264_track_desc);
- video_track_desc->type_ = "video";
- video_track_desc->id_ = "video-" + srs_random_str(8);
+ h264_track_desc->type_ = "video";
+ h264_track_desc->id_ = "video-h264-" + srs_random_str(8);
- uint32_t video_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc();
- video_track_desc->ssrc_ = video_ssrc;
- video_track_desc->direction_ = "recvonly";
+ uint32_t h264_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc();
+ h264_track_desc->ssrc_ = h264_ssrc;
+ h264_track_desc->direction_ = "recvonly";
- SrsVideoPayload* video_payload = new SrsVideoPayload(kVideoPayloadType, "H264", kVideoSamplerate);
- video_track_desc->media_ = video_payload;
+ SrsVideoPayload* h264_payload = new SrsVideoPayload(kVideoPayloadType, "H264", kVideoSamplerate);
+ h264_track_desc->media_ = h264_payload;
- video_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f");
+ h264_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f");
+ }
+
+ if (true) {
+ // H.265 track description
+ SrsRtcTrackDescription* h265_track_desc = new SrsRtcTrackDescription();
+ stream_desc->video_track_descs_.push_back(h265_track_desc);
+
+ h265_track_desc->type_ = "video";
+ h265_track_desc->id_ = "video-h265-" + srs_random_str(8);
+
+ uint32_t h265_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc();
+ h265_track_desc->ssrc_ = h265_ssrc;
+ h265_track_desc->direction_ = "recvonly";
+
+ SrsVideoPayload* h265_payload = new SrsVideoPayload(KVideoPayloadTypeHevc, "H265", kVideoSamplerate);
+ h265_track_desc->media_ = h265_payload;
+
+ h265_payload->set_h265_param_desc("level-id=156;profile-id=1;tier-flag=0;tx-mode=SRST");
}
set_stream_desc(stream_desc.get());
@@ -838,10 +858,12 @@ srs_error_t SrsRtcSource::on_timer(srs_utime_t interval)
#ifdef SRS_FFMPEG_FIT
-SrsRtcRtpBuilder::SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, uint32_t assrc, uint8_t apt, uint32_t vssrc, uint8_t vpt)
+SrsRtcRtpBuilder::SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, SrsSharedPtr source)
{
- req = NULL;
bridge_ = bridge;
+ source_ = source;
+
+ req = NULL;
format = new SrsRtmpFormat();
codec_ = new SrsAudioTranscoder();
latest_codec_ = SrsAudioCodecIdForbidden;
@@ -852,10 +874,15 @@ SrsRtcRtpBuilder::SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, uint32_t assrc,
audio_sequence = 0;
video_sequence = 0;
- audio_ssrc_ = assrc;
- audio_payload_type_ = apt;
- video_ssrc_ = vssrc;
- video_payload_type_ = vpt;
+ // Initialize with default values - will be set during lazy initialization
+ audio_ssrc_ = 0;
+ audio_payload_type_ = 0;
+ video_ssrc_ = 0;
+ video_payload_type_ = 0;
+
+ // Lazy initialization flags
+ audio_initialized_ = false;
+ video_initialized_ = false;
}
SrsRtcRtpBuilder::~SrsRtcRtpBuilder()
@@ -865,6 +892,53 @@ SrsRtcRtpBuilder::~SrsRtcRtpBuilder()
srs_freep(meta);
}
+srs_error_t SrsRtcRtpBuilder::initialize_audio_track(SrsAudioCodecId codec)
+{
+ srs_error_t err = srs_success;
+
+ // Get the audio track description for the specified codec, as we will always
+ // transcode to opus for WebRTC.
+ std::string codec_name = "opus";
+ std::vector descs = source_->get_track_desc("audio", "opus");
+
+ if (!descs.empty()) {
+ // Note we must use the PT of source, see https://github.com/ossrs/srs/pull/3079
+ SrsRtcTrackDescription* track = descs.at(0);
+ audio_ssrc_ = track->ssrc_;
+ audio_payload_type_ = track->media_->pt_;
+ } else {
+ audio_payload_type_ = kAudioPayloadType;
+ }
+
+ srs_trace("RTMP2RTC: Initialize audio track for %s with codec=%s, ssrc=%u, pt=%d",
+ srs_audio_codec_id2str(codec).c_str(), codec_name.c_str(), audio_ssrc_, audio_payload_type_);
+
+ return err;
+}
+
+srs_error_t SrsRtcRtpBuilder::initialize_video_track(SrsVideoCodecId codec)
+{
+ srs_error_t err = srs_success;
+
+ // Get the video track description for the detected codec
+ std::string codec_name = srs_video_codec_id2str(codec);
+ std::vector descs = source_->get_track_desc("video", codec_name);
+
+ if (!descs.empty()) {
+ // Note we must use the PT of source, see https://github.com/ossrs/srs/pull/3079
+ SrsRtcTrackDescription* track = descs.at(0);
+ video_ssrc_ = track->ssrc_;
+ video_payload_type_ = track->media_->pt_;
+ } else {
+ video_payload_type_ = kVideoPayloadType;
+ }
+
+ srs_trace("RTMP2RTC: Initialize video track with codec=%s, ssrc=%u, pt=%d",
+ codec_name.c_str(), video_ssrc_, video_payload_type_);
+
+ return err;
+}
+
srs_error_t SrsRtcRtpBuilder::initialize(SrsRequest* r)
{
srs_error_t err = srs_success;
@@ -942,6 +1016,14 @@ srs_error_t SrsRtcRtpBuilder::on_audio(SrsSharedPtrMessage* msg)
return err;
}
+ // Initialize audio track on first packet with actual codec
+ if (!audio_initialized_) {
+ if ((err = initialize_audio_track(acodec)) != srs_success) {
+ return srs_error_wrap(err, "init audio track");
+ }
+ audio_initialized_ = true;
+ }
+
// ignore sequence header
srs_assert(format->audio);
@@ -997,10 +1079,10 @@ srs_error_t SrsRtcRtpBuilder::init_codec(SrsAudioCodecId codec)
// Update the latest codec in stream.
if (latest_codec_ == SrsAudioCodecIdForbidden) {
- srs_trace("RTMP2RTC: Init audio codec to %d(%s)", codec, srs_audio_codec_id2str(codec).c_str());
+ srs_trace("RTMP2RTC: Init audio transcoder codec to %d(%s)", codec, srs_audio_codec_id2str(codec).c_str());
} else {
- srs_trace("RTMP2RTC: Switch audio codec %d(%s) to %d(%s)", latest_codec_, srs_audio_codec_id2str(latest_codec_).c_str(),
- codec, srs_audio_codec_id2str(codec).c_str());
+ srs_trace("RTMP2RTC: Switch audio transcoder codec %d(%s) to %d(%s)", latest_codec_, srs_audio_codec_id2str(latest_codec_).c_str(),
+ codec, srs_audio_codec_id2str(codec).c_str());
}
latest_codec_ = codec;
@@ -1097,8 +1179,12 @@ srs_error_t SrsRtcRtpBuilder::on_video(SrsSharedPtrMessage* msg)
return err;
}
- if ((err = bridge_->update_codec(vcodec)) != srs_success) {
- return srs_error_wrap(err, "update codec");
+ // Initialize video track on first packet with actual codec
+ if (!video_initialized_) {
+ if ((err = initialize_video_track(vcodec)) != srs_success) {
+ return srs_error_wrap(err, "init video track");
+ }
+ video_initialized_ = true;
}
bool has_idr = false;
diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp
index 4822fbe62..535d59ef9 100644
--- a/trunk/src/app/srs_app_rtc_source.hpp
+++ b/trunk/src/app/srs_app_rtc_source.hpp
@@ -48,6 +48,8 @@ class SrsLiveSource;
const int kAudioPayloadType = 111;
// Firefox defaults as 126, Chrome is 102.
const int kVideoPayloadType = 102;
+// Chrome HEVC defaults as 49.
+const int KVideoPayloadTypeHevc = 49;
class SrsNtp
{
@@ -287,9 +289,18 @@ private:
uint32_t video_ssrc_;
uint8_t audio_payload_type_;
uint8_t video_payload_type_;
+private:
+ SrsSharedPtr source_;
+ // Lazy initialization flags
+ bool audio_initialized_;
+ bool video_initialized_;
public:
- SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, uint32_t assrc, uint8_t apt, uint32_t vssrc, uint8_t vpt);
+ SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, SrsSharedPtr source);
virtual ~SrsRtcRtpBuilder();
+private:
+ // Lazy initialization methods
+ srs_error_t initialize_audio_track(SrsAudioCodecId codec);
+ srs_error_t initialize_video_track(SrsVideoCodecId codec);
public:
virtual srs_error_t initialize(SrsRequest* r);
virtual srs_error_t on_publish();
diff --git a/trunk/src/app/srs_app_stream_bridge.cpp b/trunk/src/app/srs_app_stream_bridge.cpp
index 80e0b999b..1a06ce365 100644
--- a/trunk/src/app/srs_app_stream_bridge.cpp
+++ b/trunk/src/app/srs_app_stream_bridge.cpp
@@ -68,35 +68,8 @@ SrsFrameToRtcBridge::SrsFrameToRtcBridge(SrsSharedPtr source)
source_ = source;
#if defined(SRS_FFMPEG_FIT)
- uint32_t audio_ssrc = 0;
- uint8_t audio_payload_type = 0;
- uint32_t video_ssrc = 0;
- uint8_t video_payload_type = 0;
-
- // audio track ssrc
- if (true) {
- std::vector descs = source->get_track_desc("audio", "opus");
- if (!descs.empty()) {
- audio_ssrc = descs.at(0)->ssrc_;
- }
- // Note we must use the PT of source, see https://github.com/ossrs/srs/pull/3079
- audio_payload_type = descs.empty() ? kAudioPayloadType : descs.front()->media_->pt_;
- }
-
- // video track ssrc
- if (true) {
- std::vector descs = source->get_track_desc("video", "");
- if (!descs.empty()) {
- video_ssrc = descs.at(0)->ssrc_;
- }
- // Note we must use the PT of source, see https://github.com/ossrs/srs/pull/3079
- video_payload_type = descs.empty() ? kVideoPayloadType : descs.front()->media_->pt_;
- }
-
- rtp_builder_ = new SrsRtcRtpBuilder(this, audio_ssrc, audio_payload_type, video_ssrc, video_payload_type);
+ rtp_builder_ = new SrsRtcRtpBuilder(this, source);
#endif
-
- video_codec_id_ = SrsVideoCodecIdReserved;
}
SrsFrameToRtcBridge::~SrsFrameToRtcBridge()
@@ -158,37 +131,7 @@ srs_error_t SrsFrameToRtcBridge::on_rtp(SrsRtpPacket* pkt)
return source_->on_rtp(pkt);
}
-srs_error_t SrsFrameToRtcBridge::update_codec(SrsVideoCodecId id)
-{
- srs_error_t err = srs_success;
- if (video_codec_id_ == id) {
- return err;
- }
-
- std::vector video_track_descs = source_->get_track_desc("video", "");
- if (video_track_descs.empty()) {
- return srs_error_new(ERROR_RTC_NO_TRACK, "no track found for conversion");
- }
-
- SrsRtcTrackDescription* video_track_desc = video_track_descs.at(0);
- SrsVideoPayload* video_payload = (SrsVideoPayload*)video_track_desc->media_;
-
- if (id == SrsVideoCodecIdHEVC) {
- video_payload->name_ = "H265";
- video_payload->set_h265_param_desc("level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST");
- } else {
- video_payload->name_ = "H264";
- video_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f");
- }
-
- srs_trace("RTC: Switch video codec %d(%s) to %d(%s)", video_codec_id_, srs_video_codec_id2str(video_codec_id_).c_str(),
- id, srs_video_codec_id2str(id).c_str());
-
- video_codec_id_ = id;
-
- return err;
-}
#endif
diff --git a/trunk/src/app/srs_app_stream_bridge.hpp b/trunk/src/app/srs_app_stream_bridge.hpp
index d5391c97c..dfa4857c8 100644
--- a/trunk/src/app/srs_app_stream_bridge.hpp
+++ b/trunk/src/app/srs_app_stream_bridge.hpp
@@ -65,8 +65,6 @@ private:
#if defined(SRS_FFMPEG_FIT)
SrsRtcRtpBuilder* rtp_builder_;
#endif
-private:
- SrsVideoCodecId video_codec_id_;
public:
SrsFrameToRtcBridge(SrsSharedPtr source);
virtual ~SrsFrameToRtcBridge();
@@ -76,7 +74,6 @@ public:
virtual void on_unpublish();
virtual srs_error_t on_frame(SrsSharedPtrMessage* frame);
srs_error_t on_rtp(SrsRtpPacket* pkt);
- srs_error_t update_codec(SrsVideoCodecId id);
};
#endif
diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp
index 3824a2a3a..a836beea1 100644
--- a/trunk/src/core/srs_core_version7.hpp
+++ b/trunk/src/core/srs_core_version7.hpp
@@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
-#define VERSION_REVISION 43
+#define VERSION_REVISION 44
#endif
\ No newline at end of file