Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions trunk/3rdparty/srs-docs/doc/webrtc.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,37 @@ The streams:
* HTTP-FLV:[http://localhost:8080/live/show.flv](http://localhost:8080/players/srs_player.html?autostart=true&stream=show.flv)
* RTMP by VLC:rtmp://localhost/live/show

## AV1 Codec Support

SRS supports AV1 codec for WebRTC-to-WebRTC streaming since v4.0.91 ([#2324](https://github.com/ossrs/srs/pull/2324)).
AV1 is a royalty-free codec that saves 30-50% bandwidth compared to H.264. SRS implements AV1 as relay-only (SFU mode),
accepting AV1 streams via WHIP and forwarding to WHEP players without transcoding. AV1 streams cannot be converted to
RTMP/HLS or recorded to DVR.

To use AV1, add the `codec=av1` query parameter to WHIP/WHEP URLs:

* Publish: `http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream&codec=av1`
* Play: `http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream&codec=av1`

Browser support: Chrome/Edge M90+, Firefox (full support), Safari (decode only). Use H.264 if you need RTMP/HLS conversion,
DVR recording, or maximum compatibility.

## VP9 Codec Support

SRS supports VP9 codec for WebRTC-to-WebRTC streaming since v7.0.0 ([#4548](https://github.com/ossrs/srs/issues/4548)).
VP9 is a royalty-free codec that saves 20-40% bandwidth compared to H.264. VP9 works better than H.264/H.265 with congestion control
in WebRTC, making it ideal for keeping streams live under network fluctuations. SRS implements VP9 as relay-only (SFU mode),
accepting VP9 streams via WHIP and forwarding to WHEP players without transcoding. VP9 streams cannot be converted to
RTMP/HLS or recorded to DVR.

To use VP9, add the `codec=vp9` query parameter to WHIP/WHEP URLs:

* Publish: `http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream&codec=vp9`
* Play: `http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream&codec=vp9`

Browser support: Chrome/Edge M29+, Firefox M28+, Opera M16+. Safari does not support VP9. Use H.264 if you need RTMP/HLS conversion,
DVR recording, or Safari compatibility.

## SFU: One to One

Please use `conf/rtc.conf` as config.
Expand Down
1 change: 1 addition & 0 deletions trunk/doc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>

## SRS 7.0 Changelog
* v7.0, 2025-11-08, AI: WebRTC: Support VP9 codec for WebRTC-to-WebRTC streaming. v7.0.123 (#4548)
* v7.0, 2025-11-08, AI: API: Add audio_frames and video_frames to HTTP API. v7.0.122 (#4559)
* v7.0, 2025-11-07, AI: WHIP: Return detailed HTTP error responses with proper status codes. v7.0.121 (#4502)
* v7.0, 2025-11-07, AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
Expand Down
6 changes: 3 additions & 3 deletions trunk/src/app/srs_app_rtc_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,16 +666,16 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa
string code_str;
if (true) {
srs_error_t err = srs_success;

err = serve_http_with(w, r);
if (err == srs_success) {
return err;
}

code = srs_error_code(err);
code_str = srs_error_code_str(err);
srs_warn("WHIP: serve http for %s with err %d:%s, %s",
r->url().c_str(), code, code_str.c_str(), srs_error_desc(err).c_str());
srs_warn("WHIP: serve http for %s with err %d:%s, %s",
r->url().c_str(), code, code_str.c_str(), srs_error_desc(err).c_str());
srs_freep(err);
}

Expand Down
40 changes: 40 additions & 0 deletions trunk/src/app/srs_app_rtc_conn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3402,6 +3402,38 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
}
}

track_desc->type_ = "video";
track_desc->set_codec_payload((SrsCodecPayload *)video_payload);
break;
}
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdVP9) {
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("VP9");
if (payloads.empty()) {
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid VP9 payload type");
}

for (int j = 0; j < (int)payloads.size(); j++) {
const SrsMediaPayloadType &payload = payloads.at(j);

// Generate video payload for vp9.
SrsVideoPayload *video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_);

// TODO: FIXME: Only support some transport algorithms.
for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
const string &rtcp_fb = payload.rtcp_fb_.at(k);

if (nack_enabled) {
if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
video_payload->rtcp_fbs_.push_back(rtcp_fb);
}
}
if (twcc_enabled && remote_twcc_id) {
if (rtcp_fb == "transport-cc") {
video_payload->rtcp_fbs_.push_back(rtcp_fb);
}
}
}

track_desc->type_ = "video";
track_desc->set_codec_payload((SrsCodecPayload *)video_payload);
break;
Expand Down Expand Up @@ -3804,6 +3836,14 @@ srs_error_t SrsRtcPlayerNegotiator::negotiate_play_capability(SrsRtcUserConfig *
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
track_descs = source->get_track_desc("video", "AV1X");
}
} else if (prefer_codec == SrsVideoCodecIdVP9) {
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("VP9");
if (payloads.empty()) {
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid VP9 payload type");
}

remote_payload = payloads.at(0);
track_descs = source->get_track_desc("video", "VP9");
} else if (prefer_codec == SrsVideoCodecIdHEVC) {
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
if (payloads.empty()) {
Expand Down
9 changes: 8 additions & 1 deletion trunk/src/app/srs_app_rtc_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3851,7 +3851,7 @@ srs_error_t SrsRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
SrsAudioChannels channels = (SrsAudioChannels)audio_media->channel_;

if ((err = stat_->on_audio_info(req_, codec_id, sample_rate, channels,
SrsAacObjectTypeReserved)) != srs_success) {
SrsAacObjectTypeReserved)) != srs_success) {
return srs_error_wrap(err, "stat audio info");
}
srs_trace("RTC: parsed %s codec, sample_rate=%dHz, channels=%d",
Expand Down Expand Up @@ -3926,6 +3926,13 @@ srs_error_t SrsRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
srs_video_codec_id2str(codec_id).c_str(),
srs_hevc_profile2str(profile).c_str(),
level_id);
} else if (codec_id == SrsVideoCodecIdAV1 || codec_id == SrsVideoCodecIdVP9) {
// AV1 and VP9 are relay-only codecs for WebRTC-to-WebRTC
// No detailed profile/level parsing needed for relay mode
if ((err = stat_->on_video_info(req_, codec_id, 0, 0, 0, 0)) != srs_success) {
return srs_error_wrap(err, "stat video info");
}
srs_trace("RTC: parsed %s codec", srs_video_codec_id2str(codec_id).c_str());
}
}

Expand Down
6 changes: 6 additions & 0 deletions trunk/src/app/srs_app_statistic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,12 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject *obj)
} else if (vcodec_ == SrsVideoCodecIdHEVC) {
video->set("profile", SrsJsonAny::str(srs_hevc_profile2str(hevc_profile_).c_str()));
video->set("level", SrsJsonAny::str(srs_hevc_level2str(hevc_level_).c_str()));
} else if (vcodec_ == SrsVideoCodecIdAV1) {
video->set("profile", SrsJsonAny::null());
video->set("level", SrsJsonAny::null());
} else if (vcodec_ == SrsVideoCodecIdVP9) {
video->set("profile", SrsJsonAny::null());
video->set("level", SrsJsonAny::null());
} else {
video->set("profile", SrsJsonAny::str("Other"));
video->set("level", SrsJsonAny::str("Other"));
Expand Down
2 changes: 1 addition & 1 deletion trunk/src/core/srs_core_version7.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 122
#define VERSION_REVISION 123

#endif
4 changes: 4 additions & 0 deletions trunk/src/kernel/srs_kernel_codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ string srs_video_codec_id2str(SrsVideoCodecId codec)
return "HEVC";
case SrsVideoCodecIdAV1:
return "AV1";
case SrsVideoCodecIdVP9:
return "VP9";
case SrsVideoCodecIdReserved:
case SrsVideoCodecIdReserved1:
case SrsVideoCodecIdReserved2:
Expand All @@ -105,6 +107,8 @@ SrsVideoCodecId srs_video_codec_str2id(const std::string &codec)
return SrsVideoCodecIdHEVC;
} else if (upper_codec == "AV1") {
return SrsVideoCodecIdAV1;
} else if (upper_codec == "VP9") {
return SrsVideoCodecIdVP9;
} else if (upper_codec == "VP6") {
return SrsVideoCodecIdOn2VP6;
} else if (upper_codec == "VP6A") {
Expand Down
4 changes: 3 additions & 1 deletion trunk/src/kernel/srs_kernel_codec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ enum SrsVideoCodecId {
SrsVideoCodecIdAVC = 7,
// See page 79 at @doc https://github.com/CDN-Union/H265/blob/master/Document/video_file_format_spec_v10_1_ksyun_20170615.doc
SrsVideoCodecIdHEVC = 12,
// https://mp.weixin.qq.com/s/H3qI7zsON5sdf4oDJ9qlkg
// AV1 codec for WebRTC, https://github.com/ossrs/srs/pull/2324
SrsVideoCodecIdAV1 = 13,
// VP9 codec for WebRTC, https://github.com/ossrs/srs/pull/4565
SrsVideoCodecIdVP9 = 14,
};
std::string srs_video_codec_id2str(SrsVideoCodecId codec);
SrsVideoCodecId srs_video_codec_str2id(const std::string &codec);
Expand Down
11 changes: 5 additions & 6 deletions trunk/src/kernel/srs_kernel_error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,9 @@
XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \
XX(ERROR_NOT_IMPLEMENTED, 1099, "NotImplemented", "Feature is not implemented") \
XX(ERROR_NOT_SUPPORTED, 1100, "NotSupported", "Feature is not supported") \
XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file") \
XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file") \
XX(ERROR_SYSTEM_AUTH, 1102, "SystemAuth", "Failed to authenticate stream")


/**************************************************/
/* RTMP protocol error. */
#define SRS_ERRNO_MAP_RTMP(XX) \
Expand Down Expand Up @@ -382,7 +381,7 @@
XX(ERROR_RTSP_NO_TRACK, 5039, "RtspNoTrack", "Drop RTSP packet for track not found") \
XX(ERROR_RTSP_TOKEN_NOT_NORMAL, 5040, "RtspToken", "Invalid RTSP token state not normal") \
XX(ERROR_RTSP_REQUEST_HEADER_EOF, 5041, "RtspHeaderEof", "Invalid RTSP request for header EOF") \
XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing") \
XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing") \
XX(ERROR_RTC_INVALID_SDP, 5043, "RtcInvalidSdp", "Invalid SDP for RTC")

/**************************************************/
Expand Down Expand Up @@ -492,7 +491,7 @@ class SrsCplxError
// }
#define srs_error_new(code, fmt, ...) SrsCplxError::create(__FUNCTION__, __FILE__, __LINE__, code, fmt, ##__VA_ARGS__)

// Wrap an existing error with additional context. The error code is
// Wrap an existing error with additional context. The error code is
// preserved from the wrapped error.
//
// Example:
Expand All @@ -501,8 +500,8 @@ class SrsCplxError
// }
#define srs_error_wrap(err, fmt, ...) SrsCplxError::wrap(__FUNCTION__, __FILE__, __LINE__, err, fmt, ##__VA_ARGS__)

// Transform an error by wrapping it and changing its error code. Useful
// for converting internal errors to protocol-specific error codes (e.g.,
// Transform an error by wrapping it and changing its error code. Useful
// for converting internal errors to protocol-specific error codes (e.g.,
// HTTP, RTMP). The wrapped error chain is preserved.
//
// Example:
Expand Down
1 change: 1 addition & 0 deletions trunk/src/kernel/srs_kernel_ts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ srs_error_t SrsTsContext::encode(ISrsStreamWriter *writer, SrsTsMessage *msg, Sr
case SrsVideoCodecIdOn2VP6WithAlphaChannel:
case SrsVideoCodecIdScreenVideoVersion2:
case SrsVideoCodecIdAV1:
case SrsVideoCodecIdVP9:
vs = SrsTsStreamReserved;
break;
}
Expand Down
39 changes: 5 additions & 34 deletions trunk/src/utest/srs_utest_ai18.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using namespace std;
#include <srs_kernel_utility.hpp>
#include <srs_protocol_utility.hpp>
#include <srs_utest_ai23.hpp>
#include <srs_utest_manual_mock.hpp>
#include <sstream>

// Mock ISrsSrtSocket implementation
Expand Down Expand Up @@ -193,10 +194,11 @@ VOID TEST(UdpListenerTest, ListenAndReceivePacket)
dest_addr.sin_port = htons(port);
dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

for (int i = 0; i < 3; i++) {
for (int i = 0; i < 10; i++) {
int sent = srs_sendto(client_fd, (void *)test_data.c_str(), test_data.size(),
(sockaddr *)&dest_addr, sizeof(dest_addr), SRS_UTIME_NO_TIMEOUT);
EXPECT_EQ(sent, (int)test_data.size());
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
}

// Wait a bit for the listener to receive and process the packet
Expand Down Expand Up @@ -2186,39 +2188,8 @@ VOID TEST(ApiServerAsCandidatesTest, MajorUseScenario)
EXPECT_TRUE(candidate_ips.find("192.168.1.100") != candidate_ips.end());
}

// Mock SrsProtocolUtility implementation
MockProtocolUtility::MockProtocolUtility()
{
}

MockProtocolUtility::~MockProtocolUtility()
{
clear_ips();
}

vector<SrsIPAddress *> &MockProtocolUtility::local_ips()
{
return mock_ips_;
}

void MockProtocolUtility::add_ip(string ip, string ifname, bool is_ipv4, bool is_loopback, bool is_internet)
{
SrsIPAddress *addr = new SrsIPAddress();
addr->ip_ = ip;
addr->ifname_ = ifname;
addr->is_ipv4_ = is_ipv4;
addr->is_loopback_ = is_loopback;
addr->is_internet_ = is_internet;
mock_ips_.push_back(addr);
}

void MockProtocolUtility::clear_ips()
{
for (size_t i = 0; i < mock_ips_.size(); i++) {
srs_freep(mock_ips_[i]);
}
mock_ips_.clear();
}
// Note: MockProtocolUtility has been merged into MockProtocolUtility
// in srs_utest_manual_mock.hpp/cpp. All usages below now use MockProtocolUtility.

// Mock ISrsAppConfig for discover_candidates implementation
MockAppConfigForDiscoverCandidates::MockAppConfigForDiscoverCandidates()
Expand Down
15 changes: 2 additions & 13 deletions trunk/src/utest/srs_utest_ai18.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,8 @@ class MockHttpHooksForSrt : public ISrsHttpHooks
void clear_calls();
};

// Mock SrsProtocolUtility for testing discover_candidates
class MockProtocolUtility : public SrsProtocolUtility
{
public:
std::vector<SrsIPAddress *> mock_ips_;

public:
MockProtocolUtility();
virtual ~MockProtocolUtility();
virtual std::vector<SrsIPAddress *> &local_ips();
void add_ip(std::string ip, std::string ifname, bool is_ipv4, bool is_loopback, bool is_internet);
void clear_ips();
};
// Note: MockProtocolUtility has been merged into MockProtocolUtility
// in srs_utest_manual_mock.hpp. Use MockProtocolUtility instead.

// Mock ISrsAppConfig for testing discover_candidates
class MockAppConfigForDiscoverCandidates : public MockAppConfig
Expand Down
Loading
Loading