Skip to content

Commit 206a5cb

Browse files
ossrs-aiwinlinvip
authored andcommitted
AI: Support G711 with PCMU/PCMA.
1 parent f890ad8 commit 206a5cb

16 files changed

+188
-40
lines changed

trunk/research/players/js/srs.page.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function update_nav() {
2424
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
2525
}
2626

27-
// Special extra params, such as auth_key.
27+
// Special extra params, such as auth_key, codec, vcodec, acodec.
2828
function user_extra_params(query, params, rtc) {
2929
var queries = params || [];
3030

@@ -124,6 +124,9 @@ function build_default_whip_whep_url(query, apiPath) {
124124
console.log('?api=x to overwrite WebRTC API(1985).');
125125
console.log('?schema=http|https to overwrite WebRTC API protocol.');
126126
console.log(`?path=xxx to overwrite default ${apiPath}`);
127+
console.log('?codec=xxx to specify video codec (alias for vcodec, e.g., h264, vp9, av1)');
128+
console.log('?vcodec=xxx to specify video codec (e.g., h264, vp9, av1)');
129+
console.log('?acodec=xxx to specify audio codec (e.g., opus, pcmu, pcma)');
127130

128131
var server = (!query.server)? window.location.hostname:query.server;
129132
var vhost = (!query.vhost)? window.location.hostname:query.vhost;

trunk/research/players/js/srs.sdk.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,15 @@ function SrsRtcWhipWhepAsync() {
4141
// camera: boolean, whether capture video from camera, default to true.
4242
// screen: boolean, whether capture video from screen, default to false.
4343
// audio: boolean, whether play audio, default to true.
44+
// vcodec: string, video codec to use (e.g., 'h264', 'vp9', 'av1'), default to undefined.
45+
// acodec: string, audio codec to use (e.g., 'opus', 'pcmu', 'pcma'), default to undefined.
4446
self.publish = async function (url, options) {
4547
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
4648
const hasAudio = options?.audio ?? true;
4749
const useCamera = options?.camera ?? true;
4850
const useScreen = options?.screen ?? false;
51+
const vcodec = options?.vcodec;
52+
const acodec = options?.acodec;
4953

5054
if (!hasAudio && !useCamera && !useScreen) throw new Error(`The camera, screen and audio can't be false at the same time`);
5155

@@ -91,6 +95,13 @@ function SrsRtcWhipWhepAsync() {
9195

9296
var offer = await self.pc.createOffer();
9397
await self.pc.setLocalDescription(offer);
98+
99+
// Filter codecs if specified
100+
if (vcodec || acodec) {
101+
offer.sdp = self.__internal.filterCodec(offer.sdp, vcodec, acodec);
102+
console.log(`Filtered codecs (vcodec=${vcodec}, acodec=${acodec}): ${offer.sdp}`);
103+
}
104+
94105
const answer = await new Promise(function (resolve, reject) {
95106
console.log(`Generated offer: ${offer.sdp}`);
96107

@@ -119,15 +130,26 @@ function SrsRtcWhipWhepAsync() {
119130
// @options The options to control playing, supports:
120131
// videoOnly: boolean, whether only play video, default to false.
121132
// audioOnly: boolean, whether only play audio, default to false.
133+
// vcodec: string, video codec to use (e.g., 'h264', 'vp9', 'av1'), default to undefined.
134+
// acodec: string, audio codec to use (e.g., 'opus', 'pcmu', 'pcma'), default to undefined.
122135
self.play = async function(url, options) {
123136
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
124137
if (options?.videoOnly && options?.audioOnly) throw new Error(`The videoOnly and audioOnly in options can't be true at the same time`);
138+
const vcodec = options?.vcodec;
139+
const acodec = options?.acodec;
125140

126141
if (!options?.videoOnly) self.pc.addTransceiver("audio", {direction: "recvonly"});
127142
if (!options?.audioOnly) self.pc.addTransceiver("video", {direction: "recvonly"});
128143

129144
var offer = await self.pc.createOffer();
130145
await self.pc.setLocalDescription(offer);
146+
147+
// Filter codecs if specified
148+
if (vcodec || acodec) {
149+
offer.sdp = self.__internal.filterCodec(offer.sdp, vcodec, acodec);
150+
console.log(`Filtered codecs (vcodec=${vcodec}, acodec=${acodec}): ${offer.sdp}`);
151+
}
152+
131153
const answer = await new Promise(function(resolve, reject) {
132154
console.log(`Generated offer: ${offer.sdp}`);
133155

@@ -199,6 +221,43 @@ function SrsRtcWhipWhepAsync() {
199221
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
200222
};
201223
},
224+
filterCodec: (sdp, vcodec, acodec) => {
225+
// Filter video codec if specified
226+
if (vcodec) {
227+
const vcodecUpper = vcodec.toUpperCase();
228+
sdp = sdp.split('\n').filter(line => {
229+
// Keep all non-video lines
230+
if (!line.startsWith('a=rtpmap:') && !line.startsWith('a=rtcp-fb:') &&
231+
!line.startsWith('a=fmtp:')) {
232+
return true;
233+
}
234+
// For video codec lines, only keep the specified codec
235+
if (line.includes('video/')) {
236+
return line.toUpperCase().includes(vcodecUpper);
237+
}
238+
return true;
239+
}).join('\n');
240+
}
241+
242+
// Filter audio codec if specified
243+
if (acodec) {
244+
const acodecUpper = acodec.toUpperCase();
245+
sdp = sdp.split('\n').filter(line => {
246+
// Keep all non-audio lines
247+
if (!line.startsWith('a=rtpmap:') && !line.startsWith('a=rtcp-fb:') &&
248+
!line.startsWith('a=fmtp:')) {
249+
return true;
250+
}
251+
// For audio codec lines, only keep the specified codec
252+
if (line.includes('audio/')) {
253+
return line.toUpperCase().includes(acodecUpper);
254+
}
255+
return true;
256+
}).join('\n');
257+
}
258+
259+
return sdp;
260+
},
202261
};
203262

204263
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack

trunk/research/players/whep.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,17 @@
125125

126126
// For example: webrtc://r.ossrs.net/live/livestream
127127
var url = $("#txt_url").val();
128+
var query = parse_query_string();
129+
130+
// Support codec parameters: codec (alias for vcodec), vcodec, acodec
131+
var vcodec = query.vcodec || query.codec;
132+
var acodec = query.acodec;
133+
128134
sdk.play(url, {
129135
videoOnly: $('#ch_videoonly').prop('checked'),
130136
audioOnly: $('#ch_audioonly').prop('checked'),
137+
vcodec: vcodec,
138+
acodec: acodec
131139
}).then(function(session){
132140
$('#sessionid').html(session.sessionid);
133141
$('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid);

trunk/research/players/whip.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,18 @@
132132

133133
// For example: webrtc://r.ossrs.net/live/livestream
134134
var url = $("#txt_url").val();
135+
var query = parse_query_string();
136+
137+
// Support codec parameters: codec (alias for vcodec), vcodec, acodec
138+
var vcodec = query.vcodec || query.codec;
139+
var acodec = query.acodec;
140+
135141
sdk.publish(url, {
136142
camera: $('#ra_camera').prop('checked'),
137143
screen: $('#ra_screen').prop('checked'),
138-
audio: $('#ch_audio').prop('checked')
144+
audio: $('#ch_audio').prop('checked'),
145+
vcodec: vcodec,
146+
acodec: acodec
139147
}).then(function(session){
140148
$('#sessionid').html(session.sessionid);
141149
$('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid);

trunk/src/app/srs_app_log.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#include <srs_kernel_utility.hpp>
2121

2222
// the max size of a line of log.
23-
#define LOG_MAX_SIZE 8192
23+
#define LOG_MAX_SIZE 65536 // 64 KB
2424

2525
// the tail append to each log.
2626
#define LOG_TAIL '\n'

trunk/src/app/srs_app_rtc_api.cpp

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,25 @@ srs_error_t SrsGoApiRtcPlay::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMe
158158
if (eip.empty()) {
159159
eip = r->query_get("candidate");
160160
}
161-
string codec = r->query_get("codec");
161+
// Support vcodec/codec (alias for vcodec) and acodec parameters
162+
string vcodec = r->query_get("vcodec");
163+
if (vcodec.empty()) {
164+
vcodec = r->query_get("codec");
165+
}
166+
string acodec = r->query_get("acodec");
162167
// For client to specifies whether encrypt by SRTP.
163168
string srtp = r->query_get("encrypt");
164169
string dtls = r->query_get("dtls");
165170

166171
srs_trace(
167-
"RTC play %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s, srtp=%s, dtls=%s",
172+
"RTC play %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, vcodec=%s, acodec=%s, srtp=%s, dtls=%s",
168173
streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app_.c_str(),
169174
ruc.req_->stream_.c_str(), remote_sdp_str.length(),
170-
eip.c_str(), codec.c_str(), srtp.c_str(), dtls.c_str());
175+
eip.c_str(), vcodec.c_str(), acodec.c_str(), srtp.c_str(), dtls.c_str());
171176

172177
ruc.eip_ = eip;
173-
ruc.codec_ = codec;
178+
ruc.vcodec_ = vcodec;
179+
ruc.acodec_ = acodec;
174180
ruc.publish_ = false;
175181
ruc.dtls_ = (dtls != "false");
176182

@@ -479,14 +485,20 @@ srs_error_t SrsGoApiRtcPublish::do_serve_http(ISrsHttpResponseWriter *w, ISrsHtt
479485
if (eip.empty()) {
480486
eip = r->query_get("candidate");
481487
}
482-
string codec = r->query_get("codec");
488+
// Support vcodec/codec (alias for vcodec) and acodec parameters
489+
string vcodec = r->query_get("vcodec");
490+
if (vcodec.empty()) {
491+
vcodec = r->query_get("codec");
492+
}
493+
string acodec = r->query_get("acodec");
483494

484-
srs_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s",
495+
srs_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, vcodec=%s, acodec=%s",
485496
streamurl.c_str(), api.c_str(), tid.c_str(), clientip.c_str(), ruc.req_->app_.c_str(), ruc.req_->stream_.c_str(),
486-
remote_sdp_str.length(), eip.c_str(), codec.c_str());
497+
remote_sdp_str.length(), eip.c_str(), vcodec.c_str(), acodec.c_str());
487498

488499
ruc.eip_ = eip;
489-
ruc.codec_ = codec;
500+
ruc.vcodec_ = vcodec;
501+
ruc.acodec_ = acodec;
490502
ruc.publish_ = true;
491503
ruc.dtls_ = ruc.srtp_ = true;
492504

@@ -776,7 +788,12 @@ srs_error_t SrsGoApiRtcWhip::do_serve_http_with(ISrsHttpResponseWriter *w, ISrsH
776788
if (eip.empty()) {
777789
eip = r->query_get("candidate");
778790
}
779-
string codec = r->query_get("codec");
791+
// Support vcodec/codec (alias for vcodec) and acodec parameters
792+
string vcodec = r->query_get("vcodec");
793+
if (vcodec.empty()) {
794+
vcodec = r->query_get("codec");
795+
}
796+
string acodec = r->query_get("acodec");
780797
string app = r->query_get("app");
781798
string stream = r->query_get("stream");
782799
string action = r->query_get("action");
@@ -815,13 +832,14 @@ srs_error_t SrsGoApiRtcWhip::do_serve_http_with(ISrsHttpResponseWriter *w, ISrsH
815832
string srtp = r->query_get("encrypt");
816833
string dtls = r->query_get("dtls");
817834

818-
srs_trace("RTC whip %s %s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s, srtp=%s, dtls=%s, ufrag=%s, pwd=%s, param=%s",
835+
srs_trace("RTC whip %s %s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, vcodec=%s, acodec=%s, srtp=%s, dtls=%s, ufrag=%s, pwd=%s, param=%s",
819836
action.c_str(), ruc->req_->get_stream_url().c_str(), clientip.c_str(), ruc->req_->app_.c_str(), ruc->req_->stream_.c_str(),
820-
remote_sdp_str.length(), eip.c_str(), codec.c_str(), srtp.c_str(), dtls.c_str(), ruc->req_->ice_ufrag_.c_str(),
837+
remote_sdp_str.length(), eip.c_str(), vcodec.c_str(), acodec.c_str(), srtp.c_str(), dtls.c_str(), ruc->req_->ice_ufrag_.c_str(),
821838
ruc->req_->ice_pwd_.c_str(), ruc->req_->param_.c_str());
822839

823840
ruc->eip_ = eip;
824-
ruc->codec_ = codec;
841+
ruc->vcodec_ = vcodec;
842+
ruc->acodec_ = acodec;
825843
ruc->publish_ = (action == "publish");
826844

827845
// For client to specifies whether encrypt by SRTP.

trunk/src/app/srs_app_rtc_conn.cpp

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,10 +3335,30 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
33353335
// Update the ruc, which is about user specified configuration.
33363336
ruc->audio_before_video_ = !nn_any_video_parsed;
33373337

3338-
// TODO: check opus format specific param
3339-
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("opus");
3338+
// Try to find audio codec based on user preference or default order
3339+
std::vector<SrsMediaPayloadType> payloads;
3340+
3341+
// If user specified audio codec, try that first
3342+
if (!ruc->acodec_.empty()) {
3343+
payloads = remote_media_desc.find_media_with_encoding_name(ruc->acodec_);
3344+
if (payloads.empty()) {
3345+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found %s audio payload type", ruc->acodec_.c_str());
3346+
}
3347+
} else {
3348+
// Default order: Opus, PCMU (G.711 μ-law), PCMA (G.711 A-law)
3349+
// Prioritize PCMU over PCMA as per Chrome SDP order
3350+
payloads = remote_media_desc.find_media_with_encoding_name("opus");
3351+
if (payloads.empty()) {
3352+
// Then try PCMU (G.711 μ-law)
3353+
payloads = remote_media_desc.find_media_with_encoding_name("PCMU");
3354+
}
3355+
if (payloads.empty()) {
3356+
// Finally try PCMA (G.711 A-law)
3357+
payloads = remote_media_desc.find_media_with_encoding_name("PCMA");
3358+
}
3359+
}
33403360
if (payloads.empty()) {
3341-
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type");
3361+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found audio payload type (opus/PCMU/PCMA)");
33423362
}
33433363

33443364
for (int j = 0; j < (int)payloads.size(); j++) {
@@ -3366,10 +3386,10 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
33663386

33673387
track_desc->type_ = "audio";
33683388
track_desc->set_codec_payload((SrsCodecPayload *)audio_payload);
3369-
// Only choose one match opus codec.
3389+
// Only choose one match audio codec.
33703390
break;
33713391
}
3372-
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdAV1) {
3392+
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->vcodec_) == SrsVideoCodecIdAV1) {
33733393
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
33743394
if (payloads.empty()) {
33753395
// Be compatible with the Chrome M96, still check the AV1X encoding name
@@ -3406,7 +3426,7 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
34063426
track_desc->set_codec_payload((SrsCodecPayload *)video_payload);
34073427
break;
34083428
}
3409-
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdVP9) {
3429+
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->vcodec_) == SrsVideoCodecIdVP9) {
34103430
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("VP9");
34113431
if (payloads.empty()) {
34123432
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid VP9 payload type");
@@ -3438,7 +3458,7 @@ srs_error_t SrsRtcPublisherNegotiator::negotiate_publish_capability(SrsRtcUserCo
34383458
track_desc->set_codec_payload((SrsCodecPayload *)video_payload);
34393459
break;
34403460
}
3441-
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdHEVC) {
3461+
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->vcodec_) == SrsVideoCodecIdHEVC) {
34423462
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
34433463
if (payloads.empty()) {
34443464
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.265 payload type");
@@ -3797,16 +3817,33 @@ srs_error_t SrsRtcPlayerNegotiator::negotiate_play_capability(SrsRtcUserConfig *
37973817
// Update the ruc, which is about user specified configuration.
37983818
ruc->audio_before_video_ = !nn_any_video_parsed;
37993819

3800-
// TODO: check opus format specific param
3801-
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("opus");
3820+
// Try to find audio tracks in source with different codec names
3821+
// Try Opus first (most common), then PCMU, then PCMA
3822+
std::vector<SrsRtcTrackDescription *> source_audio_tracks = source->get_track_desc("audio", "opus");
3823+
std::string source_audio_codec = "opus";
3824+
3825+
if (source_audio_tracks.empty()) {
3826+
source_audio_tracks = source->get_track_desc("audio", "PCMU");
3827+
source_audio_codec = "PCMU";
3828+
}
3829+
if (source_audio_tracks.empty()) {
3830+
source_audio_tracks = source->get_track_desc("audio", "PCMA");
3831+
source_audio_codec = "PCMA";
3832+
}
3833+
if (source_audio_tracks.empty()) {
3834+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no audio track in source (tried opus/PCMU/PCMA)");
3835+
}
3836+
3837+
// Try to find matching codec in remote SDP
3838+
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name(source_audio_codec);
38023839
if (payloads.empty()) {
3803-
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type");
3840+
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found %s payload type", source_audio_codec.c_str());
38043841
}
38053842

38063843
remote_payload = payloads.at(0);
3807-
track_descs = source->get_track_desc("audio", "opus");
3844+
track_descs = source_audio_tracks;
38083845
} else if (remote_media_desc.is_video()) {
3809-
SrsVideoCodecId prefer_codec = srs_video_codec_str2id(ruc->codec_);
3846+
SrsVideoCodecId prefer_codec = srs_video_codec_str2id(ruc->vcodec_);
38103847
if (prefer_codec == SrsVideoCodecIdReserved) {
38113848
// Get the source codec if not specified.
38123849
std::vector<SrsRtcTrackDescription *> source_track_descs = source->get_track_desc("video", "");

trunk/src/app/srs_app_rtc_server.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ class SrsRtcUserConfig
7070
std::string remote_sdp_str_;
7171
SrsSdp remote_sdp_;
7272
std::string eip_;
73-
std::string codec_;
73+
std::string vcodec_; // Video codec
74+
std::string acodec_; // Audio codec
7475
std::string api_;
7576

7677
// Session data.

trunk/src/app/srs_app_rtc_source.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3838,7 +3838,7 @@ srs_error_t SrsRtcFormat::on_rtp_packet(SrsRtcRecvTrack *track, bool is_audio)
38383838
SrsAudioCodecId codec_id = (SrsAudioCodecId)media->codec(false);
38393839

38403840
// Parse channels and sample rate from track description
3841-
if (codec_id == SrsAudioCodecIdOpus) {
3841+
if (codec_id == SrsAudioCodecIdOpus || codec_id == SrsAudioCodecIdPCMA || codec_id == SrsAudioCodecIdPCMU) {
38423842
SrsAudioPayload *audio_media = dynamic_cast<SrsAudioPayload *>(media);
38433843
if (!audio_media) {
38443844
return err;

trunk/src/app/srs_app_statistic.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,13 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject *obj)
178178
audio->set("codec", SrsJsonAny::str(srs_audio_codec_id2str(acodec_).c_str()));
179179
audio->set("sample_rate", SrsJsonAny::integer(srs_audio_sample_rate2number(asample_rate_)));
180180
audio->set("channel", SrsJsonAny::integer(asound_type_ + 1));
181-
audio->set("profile", SrsJsonAny::str(srs_aac_object2str(aac_object_).c_str()));
181+
182+
// G.711 codecs don't have profiles, similar to VP9/AV1
183+
if (acodec_ == SrsAudioCodecIdPCMA || acodec_ == SrsAudioCodecIdPCMU) {
184+
audio->set("profile", SrsJsonAny::null());
185+
} else {
186+
audio->set("profile", SrsJsonAny::str(srs_aac_object2str(aac_object_).c_str()));
187+
}
182188
}
183189

184190
return err;

0 commit comments

Comments
 (0)