This PR adds G.711 (PCMU/PCMA) audio codec support for WebRTC in SRS, enabling relay-only streaming of G.711 audio between WebRTC clients via WHIP/WHEP. G.711 is a widely-used, royalty-free audio codec with excellent compatibility across VoIP systems, IP cameras, and legacy telephony equipment. Fixes #4075 Many IP cameras, VoIP systems, and IoT devices use G.711 (PCMU/PCMA) as their default audio codec. Previously, SRS only supported Opus for WebRTC audio, requiring transcoding or rejecting G.711 streams entirely. This PR enables direct relay of G.711 audio streams in WebRTC, similar to how VP9/AV1 video codecs are supported. Enhanced WHIP/WHEP players with URL-based codec selection: ``` # Audio codec only http://localhost:8080/players/whip.html?acodec=pcmu http://localhost:8080/players/whip.html?acodec=pcma # Video + audio codecs http://localhost:8080/players/whip.html?vcodec=vp9&acodec=pcmu http://localhost:8080/players/whep.html?vcodec=h264&acodec=pcma # Backward compatible (codec = vcodec) http://localhost:8080/players/whip.html?codec=vp9 ``` Testing ```bash # Build and run unit tests cd trunk make utest -j && ./objs/srs_utest # Test with WHIP player # 1. Start SRS server ./objs/srs -c conf/rtc.conf # 2. Open WHIP publisher with PCMU audio http://localhost:8080/players/whip.html?acodec=pcmu # 3. Open WHEP player to receive stream http://localhost:8080/players/whep.html ``` ## Related Issues - Fixes #4075 - WebRTC G.711A Audio Codec Support - Related to #4548 - VP9 codec support (similar relay-only pattern)
186 lines
6.9 KiB
JavaScript
Executable File
186 lines
6.9 KiB
JavaScript
Executable File
|
|
// to query the swf anti cache.
|
|
function srs_get_version_code() { return "1.33"; }
|
|
|
|
/**
|
|
* player specified size.
|
|
*/
|
|
function srs_get_player_modal() { return 740; }
|
|
function srs_get_player_width() { return srs_get_player_modal() - 30; }
|
|
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
|
|
|
|
/**
|
|
* update the navigator, add same query string.
|
|
*/
|
|
function update_nav() {
|
|
$("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
|
|
$("#nav_rtc_player").attr("href", "rtc_player.html" + window.location.search);
|
|
$("#nav_rtc_publisher").attr("href", "rtc_publisher.html" + window.location.search);
|
|
$("#nav_whip").attr("href", "whip.html" + window.location.search);
|
|
$("#nav_whep").attr("href", "whep.html" + window.location.search);
|
|
$("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
|
|
$("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
|
|
$("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
|
|
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
|
|
}
|
|
|
|
// Special extra params, such as auth_key, codec, vcodec, acodec.
|
|
function user_extra_params(query, params, rtc) {
|
|
var queries = params || [];
|
|
|
|
for (var key in query.user_query) {
|
|
if (key === 'app' || key === 'autostart' || key === 'dir'
|
|
|| key === 'filename' || key === 'host' || key === 'hostname'
|
|
|| key === 'http_port' || key === 'pathname' || key === 'port'
|
|
|| key === 'server' || key === 'stream' || key === 'buffer'
|
|
|| key === 'schema' || key === 'vhost' || key === 'api'
|
|
|| key === 'path'
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (query[key]) {
|
|
queries.push(key + '=' + query[key]);
|
|
}
|
|
}
|
|
|
|
return queries;
|
|
}
|
|
|
|
function is_default_port(schema, port) {
|
|
return (schema === 'http' && port === 80)
|
|
|| (schema === 'https' && port === 443)
|
|
|| (schema === 'webrtc' && port === 1985)
|
|
|| (schema === 'rtmp' && port === 1935);
|
|
}
|
|
|
|
/**
|
|
@param server the ip of server. default to window.location.hostname
|
|
@param vhost the vhost of HTTP-FLV. default to window.location.hostname
|
|
@param port the port of HTTP-FLV. default to 1935
|
|
@param app the app of HTTP-FLV. default to live.
|
|
@param stream the stream of HTTP-FLV. default to livestream.flv
|
|
*/
|
|
function build_default_flv_url() {
|
|
var query = parse_query_string();
|
|
|
|
var schema = (!query.schema)? "http":query.schema;
|
|
var server = (!query.server)? window.location.hostname:query.server;
|
|
var port = (!query.port)? (schema==="http"? 8080:1935) : Number(query.port);
|
|
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
|
|
var app = (!query.app)? "live":query.app;
|
|
var stream = (!query.stream)? "livestream.flv":query.stream;
|
|
|
|
var queries = [];
|
|
if (server !== vhost && vhost !== "__defaultVhost__") {
|
|
queries.push("vhost=" + vhost);
|
|
}
|
|
queries = user_extra_params(query, queries);
|
|
|
|
var uri = schema + "://" + server;
|
|
if (!is_default_port(schema, port)) {
|
|
uri += ":" + port;
|
|
}
|
|
uri += "/" + app + "/" + stream + "?" + queries.join('&');
|
|
while (uri.indexOf("?") === uri.length - 1) {
|
|
uri = uri.slice(0, uri.length - 1);
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
function build_default_rtc_url(query) {
|
|
// The format for query string to overwrite configs of server.
|
|
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
|
|
console.log('?api=x to overwrite WebRTC API(1985).');
|
|
console.log('?schema=http|https to overwrite WebRTC API protocol.');
|
|
|
|
var server = (!query.server)? window.location.hostname:query.server;
|
|
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
|
|
var app = (!query.app)? "live":query.app;
|
|
var stream = (!query.stream)? "livestream":query.stream;
|
|
var api = query.api? ':'+query.api : '';
|
|
|
|
var queries = [];
|
|
if (server !== vhost && vhost !== "__defaultVhost__") {
|
|
queries.push("vhost=" + vhost);
|
|
}
|
|
if (query.schema && window.location.protocol !== query.schema + ':') {
|
|
queries.push('schema=' + query.schema);
|
|
}
|
|
queries = user_extra_params(query, queries, true);
|
|
|
|
var uri = "webrtc://" + server + api + "/" + app + "/" + stream + "?" + queries.join('&');
|
|
while (uri.lastIndexOf("?") === uri.length - 1) {
|
|
uri = uri.slice(0, uri.length - 1);
|
|
}
|
|
|
|
return uri;
|
|
};
|
|
|
|
function build_default_whip_whep_url(query, apiPath) {
|
|
// The format for query string to overwrite configs of server.
|
|
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
|
|
console.log('?api=x to overwrite WebRTC API(1985).');
|
|
console.log('?schema=http|https to overwrite WebRTC API protocol.');
|
|
console.log(`?path=xxx to overwrite default ${apiPath}`);
|
|
console.log('?codec=xxx to specify video codec (alias for vcodec, e.g., h264, vp9, av1)');
|
|
console.log('?vcodec=xxx to specify video codec (e.g., h264, vp9, av1)');
|
|
console.log('?acodec=xxx to specify audio codec (e.g., opus, pcmu, pcma)');
|
|
|
|
var server = (!query.server)? window.location.hostname:query.server;
|
|
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
|
|
var app = (!query.app)? "live":query.app;
|
|
var stream = (!query.stream)? "livestream":query.stream;
|
|
var api = ':' + (query.api || (window.location.protocol === 'http:' ? '1985' : '1990'));
|
|
const realApiPath = query.path || apiPath;
|
|
|
|
var queries = [];
|
|
if (server !== vhost && vhost !== "__defaultVhost__") {
|
|
queries.push("vhost=" + vhost);
|
|
}
|
|
if (query.schema && window.location.protocol !== query.schema + ':') {
|
|
queries.push('schema=' + query.schema);
|
|
}
|
|
queries = user_extra_params(query, queries, true);
|
|
|
|
var uri = window.location.protocol + "//" + server + api + realApiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&');
|
|
while (uri.lastIndexOf("?") === uri.length - 1) {
|
|
uri = uri.slice(0, uri.length - 1);
|
|
}
|
|
while (uri.lastIndexOf("&") === uri.length - 1) {
|
|
uri = uri.slice(0, uri.length - 1);
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* initialize the page.
|
|
* @param flv_url the div id contains the flv stream url to play
|
|
* @param hls_url the div id contains the hls stream url to play
|
|
* @param modal_player the div id contains the modal player
|
|
*/
|
|
function srs_init_flv(flv_url, modal_player) {
|
|
update_nav();
|
|
if (flv_url) {
|
|
$(flv_url).val(build_default_flv_url());
|
|
}
|
|
if (modal_player) {
|
|
$(modal_player).width(srs_get_player_modal() + "px");
|
|
$(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
|
|
}
|
|
}
|
|
function srs_init_rtc(id, query) {
|
|
update_nav();
|
|
$(id).val(build_default_rtc_url(query));
|
|
}
|
|
function srs_init_whip(id, query) {
|
|
update_nav();
|
|
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whip/'));
|
|
}
|
|
function srs_init_whep(id, query) {
|
|
update_nav();
|
|
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whep/'));
|
|
}
|