Andrei Costenco
Andrei Costenco

Reputation: 1

WebRTC AGC (Automatic Gain Control): can it really be disabled?

:)

I've installed AppRTC (https://github.com/webrtc/apprtc) to a separate server to try out more flexible control of the WebRTC parameters.

The main task is to disable Automatic Gain Control (AGC).

The following steps have been performed:

  1. The parameters for the audio-stream:
{
    video: 
    {
        frameRate: 30,
        width: 640,
        height: 480
    }, 
    audio: 
    {
        echoCancellation: true, 
        noiseSuppression: true, 
        autoGainControl: false
    }
}
  1. The GainNode filter has been added via audioContext.createGain() and has received a fixed value via gainNode.gain.value

  2. To be able to test the AGC absence - a graphical audio meter has been added using audioContext.createScriptProcessor(...).onaudioprocess


The problem is that in fact the AGC is not disabled and Gain still remains dynamic.

During a long monotone loud sound the analyzer drops to a significantly lower value after 5-6 seconds.

And after 5-6 seconds of silence gets back to previous range.


All this has been tested on macOs Catalina 10.15.7, in the following browsers:


The question: is there a functioning possibility to turn off AGC and to follow that not only "by hearing" but also by the meter values?

The full code of the gain fixation method:

src/web_app/html/index_template.html

    var loadingParams = {
      errorMessages: [],
      isLoopback: false,
      warningMessages: [],

      roomId: '101',
      roomLink: 'https://www.xxxx.com/r/101',

//      mediaConstraints: {"audio": true, "video": true},
      mediaConstraints: {video: {frameRate: 30, width: 640, height: 480}, audio: {echoCancellation: true, noiseSuppression: true, autoGainControl: false}},
      offerOptions: {},
      peerConnectionConfig: {"bundlePolicy": "max-bundle", "iceServers": [{"urls": ["turn:www.xxxx.com:3478?transport=udp", "turn:www.xxxx.com:3478?transport=tcp"], "username": "demo", "credential": "demo"}, {"urls": ["stun:www.xxxx.com:3478"], "username": "demo", "credential": "demo"}], "rtcpMuxPolicy": "require"},
      peerConnectionConstraints: {"optional": []},
      iceServerRequestUrl: 'https://www.xxxx.com//v1alpha/iceconfig?key=',
      iceServerTransports: '',
      wssUrl: 'wss://www.xxxx.com:8089/ws',
      wssPostUrl: 'https://www.xxxx.com:8089',
      bypassJoinConfirmation: false,
      versionInfo: {"time": "Wed Sep 23 12:49:00 2020 +0200", "gitHash": "78600dbe205774c115cf481a091387d928c99d6a", "branch": "master"},
    };

src/web_app/js/appcontroller.js

AppController.prototype.gainStream = function (stream, gainValue) {

                        var max_level_L = 0;
                        var old_level_L = 0;

                        var cnvs = document.createElement('canvas');
                        cnvs.style.cssText = 'position:fixed;width:320px;height:30px;z-index:100;background:#000';
                        document.body.appendChild(cnvs);
                        var cnvs_cntxt = cnvs.getContext("2d");

  var videoTracks = stream.getVideoTracks();

  var context = new AudioContext();
  var mediaStreamSource = context.createMediaStreamSource(stream);
  var mediaStreamDestination = context.createMediaStreamDestination();
  var gainNode = context.createGain();

                        var javascriptNode = context.createScriptProcessor(1024, 1, 1);


  mediaStreamSource.connect(gainNode);
        mediaStreamSource.connect(javascriptNode);
  gainNode.connect(mediaStreamDestination);
        javascriptNode.connect(mediaStreamDestination);

                        javascriptNode.onaudioprocess = function(event){

                                var inpt_L = event.inputBuffer.getChannelData(0);
                                var instant_L = 0.0;

                                var sum_L = 0.0;
                                for(var i = 0; i < inpt_L.length; ++i) {
                                        sum_L += inpt_L[i] * inpt_L[i];
                                }
                                instant_L = Math.sqrt(sum_L / inpt_L.length);
                                max_level_L = Math.max(max_level_L, instant_L);
                                instant_L = Math.max( instant_L, old_level_L -0.008 );
                                old_level_L = instant_L;

                                cnvs_cntxt.clearRect(0, 0, cnvs.width, cnvs.height);
                                cnvs_cntxt.fillStyle = '#00ff00';
                                cnvs_cntxt.fillRect(10,10,(cnvs.width-20)*(instant_L/max_level_L),(cnvs.height-20)); // x,y,w,h

                        }

  gainNode.gain.value = gainValue;

  var controlledStream = mediaStreamDestination.stream;
  for (const videoTrack of videoTracks) {
    controlledStream.addTrack(videoTrack);
  }

  return controlledStream;
};

AppController.prototype.onLocalStreamAdded_ = function(stream) {
  trace('User has granted access to local media.');
  this.localStream_ = this.gainStream(stream, 100);
  this.infoBox_.getLocalTrackIds(this.localStream_);

  if (!this.roomSelection_) {
    this.attachLocalStream_();
  }
};

Thank you!

Best Regards, Andrei Costenco

Upvotes: 0

Views: 2170

Answers (1)

chrisguttandin
chrisguttandin

Reputation: 9116

I think the problem you ran into is that echoCancellation and noiseSuppression do modify the signal as well. Since you mentioned that you are using a long monotone sound to test your code it could very well be that the noiseSuppression algorithm tries to reduce that "noise".

Unfortunately there is no way to tell why the signal was modified. You have to trust the browser here that it actually has switched off the gain control and that all remaining modifications come from the other two algorithms.

If you don't want to fully trust the browser you could also experiment a bit by using other sounds to run your tests. It should not be "noisy" but it's difficult to say what get's detected by the browser as noise and what doesn't.

Upvotes: 2

Related Questions