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

:)
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:
The parameters for the audio-stream:
{
video:
{
frameRate: 30,
width: 640,
height: 480
},
audio:
{
echoCancellation: true,
noiseSuppression: true,
autoGainControl: false
}
}
The GainNode filter has been added via audioContext.createGain() and has received a fixed value via gainNode.gain.value
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:
Mozilla Firefox 82.0.3,
Google Chrome 86.0.4240.198,
Safari 14.0 (15610.1.28.1.9, 15610),
and also on iOS 14.2 Safari.
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

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.

Related

Imported mesh falls through the ground in BabylonJS

I'm trying to use physics (AmmoJS) in BabylonJS for an imported mesh.
For meshes I create on the fly everything works fine, but when I import a mesh it falls through the ground.
const ground = BABYLON.MeshBuilder.CreateBox("ground",
{ width: 10, height: 1, depth: 10}, scene);
ground.receiveShadows = true;
ground.checkCollisions = true;
ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground , BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, friction: 0.5, restitution: 0.5 }, scene);
BABYLON.SceneLoader.ImportMesh(["car9"], "models/", "Policecar.glb", scene, function (meshes, particleSystems, skeletons) {
for (let i in meshes) {
meshes[i].checkCollisions = true;
}
let policecar = meshes[0];
policecar.physicsImpostor = new BABYLON.PhysicsImpostor(policecar, BABYLON.PhysicsImpostor.MeshImpostor, { mass: 10, friction: 0.5, restitution: 0.5 });
policecar.position = new BABYLON.Vector3(0, 10, 0);
policecar.scaling = new BABYLON.Vector3(scale, scale, scale);
});
When I change the restition of the policecar to 0 or 1, it doesn't fall through the ground, but bounces weirdly a few times and falls on it's side. With a BoxImpostor instead of MeshImpostor it falls straight through.
Any ideas?
You have to consider that .glb-files use right handed system, while BabylonJS uses left handed system. So I would recommend to set BabylonJS to right handed and don't scale by absolute values.
scene.useRightHandedSystem = true;
...
policecar.scaling.scaleInPlace(scale);

Getting all bids from each Header bidding partners

We are implementing some header bidding partners on our wrapper using prebid. Is it possible to get all bids from each ssp.
Any help appreciated.
If you’re asking about demand, this is dependent on each SSP. For example there may be a segment pixel or placement in one SSP that will always give you a $10 bid, but that wouldnt apply to the other SSPs.
If your asking about getting data on all the bids, you may want to check out pbjs.getBidResponses() which returns an object with the ad units and bids
Heres a sample response from pbjs.getBidResponses() which can then be used however you'd need that data:
{
"div-id-one": {
"bids": [
{
"bidderCode": "appnexus",
"width": 970,
"height": 250,
"statusMessage": "Bid available",
"adId": "1293a95bb3e9615",
"mediaType": "banner",
"creative_id": 77765220,
"cpm": 0.7826,
"adUrl": "https://...",
"requestId": "57f961f3-a32b-45df-a180-9d5e53fb9070",
"responseTimestamp": 1513707536256,
"requestTimestamp": 1513707535321,
"bidder": "appnexus",
"adUnitCode": "div-id-one",
"timeToRespond": 935,
"pbLg": "0.50",
"pbMg": "0.70",
"pbHg": "0.78",
"pbAg": "0.75",
"pbDg": "0.78",
"pbCg": "0.78",
"size": "970x250",
"adserverTargeting": {
"hb_bidder": "appnexus",
"hb_adid": "1293a95bb3e9615",
"hb_pb": "0.78",
"hb_size": "970x250"
}
}
]
},
"div-id-two": {
"bids": []
}
}
Theres also a great example on prebid.org on how to output this to console.table that could be helpful as well:
var responses = pbjs.getBidResponses();
var output = [];
for (var adunit in responses) {
if (responses.hasOwnProperty(adunit)) {
var bids = responses[adunit].bids;
for (var i = 0; i < bids.length; i++) {
var b = bids[i];
output.push({
'adunit': adunit, 'adId': b.adId, 'bidder': b.bidder,
'time': b.timeToRespond, 'cpm': b.cpm, 'msg': b.statusMessage
});
}
}
}
if (output.length) {
if (console.table) {
console.table(output);
} else {
for (var j = 0; j < output.length; j++) {
console.log(output[j]);
}
}
} else {
console.warn('NO prebid responses');
}
There is also a chrome extensions called Prebid helper that do the same as console snippet but with less clicks.
However that is useful for initial setup debug. If you need to gather aggregated data on all demand partners - bids, timeouts, wins etc. You will need to run third party wrapper analytics or use analytics adapter. It's not free but it usually is priced depending on your load on the analytics server. For example https://headbidder.net/pricing
Try out the Chrome Extension called Adwizard. It's been built to debug prebid setups. Shows you all networks and bids per adunit. CPM and Size included.
https://chrome.google.com/webstore/detail/adwizard/kndnhcfdajkaickocacghchhpieogbjh/?ref=stackoverflow

issue with c8ydevicecontrol.create

the code:
this.sendOperations = function () {
var operation = {
deviceId: '12161',
com_cumulocity_model_WebCamDevice: {
name: 'take picture',
parameters: {
duration: '5s',
quality: 'HD'
}
}
};
c8yDeviceControl.create(operation);
Result:
a new operation will be created in cumulocity server, but in the meantime, the chrome brower on which the app is runing will report some errors, although it looks like the app is still runing after that:
angular.js:9997 TypeError: Cannot read property 'match' of null
at k (deviceControl.js:267)
at wrappedCallback (angular.js:11498)
at wrappedCallback (angular.js:11498)
at angular.js:11584
at Scope.$eval (angular.js:12608)
at Scope.$digest (angular.js:12420)
at Scope.$apply (angular.js:12712)
at done (angular.js:8315)
at completeRequest (angular.js:8527)
at XMLHttpRequest.xhr.onreadystatechange (angular.js:8466)
any suggestion? Thanks
D. Chen

Collision Physics P2 and Arcade not working as desired

Hi all so I have a small problem, basically I'm trying to kill a moving sprite, it does not matters if it comes from left to right or right to left, but all I want to kill is the one colliding and not the whole group, so to test go to the far right and shoot the block, you will see the new block disappear but not the one that collided is this possible I'm providing my game.js below but I created a demo that you can download and test to get a better feel of my problem please help, thanks.
Link to Demo
BasicGame.Game = function (game) {
// When a State is added to Phaser it automatically has the following properties set on it, even if they already exist:
this.game; // a reference to the currently running game (Phaser.Game)
this.add; // used to add sprites, text, groups, etc (Phaser.GameObjectFactory)
this.camera; // a reference to the game camera (Phaser.Camera)
this.cache; // the game cache (Phaser.Cache)
this.input; // the global input manager. You can access this.input.keyboard, this.input.mouse, as well from it. (Phaser.Input)
this.load; // for preloading assets (Phaser.Loader)
this.math; // lots of useful common math operations (Phaser.Math)
this.sound; // the sound manager - add a sound, play one, set-up markers, etc (Phaser.SoundManager)
this.stage; // the game stage (Phaser.Stage)
this.time; // the clock (Phaser.Time)
this.tweens; // the tween manager (Phaser.TweenManager)
this.state; // the state manager (Phaser.StateManager)
this.world; // the game world (Phaser.World)
this.particles; // the particle manager (Phaser.Particles)
this.physics; // the physics manager (Phaser.Physics)
this.rnd; // the repeatable random number generator (Phaser.RandomDataGenerator)
// You can use any of these from any function within this State.
// But do consider them as being 'reserved words', i.e. don't create a property for your own game called "world" or you'll over-write the world reference.
this.bulletTimer = 0;
};
BasicGame.Game.prototype = {
create: function () {
//Enable physics
// Set the physics system
this.game.physics.startSystem(Phaser.Physics.ARCADE);
//End of physics
// Honestly, just about anything could go here. It's YOUR game after all. Eat your heart out!
this.createBullets();
this.createTanque();
this.makeOneBloque();
this.timerBloques = this.time.events.loop(1500, this.makeBloques, this);
},
update: function () {
if(this.game.input.activePointer.isDown){
this.fireBullet();
}
this.game.physics.arcade.overlap(this.bullets, this.bloque, this.collisionBulletBloque, null, this);
},
createBullets: function() {
this.bullets = this.game.add.group();
this.bullets.enableBody = true;
this.bullets.physicsBodyType = Phaser.Physics.ARCADE;
this.bullets.createMultiple(100, 'bulletSprite');
this.bullets.setAll('anchor.x', 0.5);
this.bullets.setAll('anchor.y', 1);
this.bullets.setAll('outOfBoundsKill', true);
this.bullets.setAll('checkWorldBounds', true);
},
fireBullet: function(){
if (this.bulletTimer < this.game.time.time) {
this.bulletTimer = this.game.time.time + 1400;
this.bullet = this.bullets.getFirstExists(false);
if (this.bullet) {
this.bullet.reset(this.tanque.x, this.tanque.y - 20);
this.bullet.body.velocity.y = -800;
}
}
},
makeOneBloque: function(){
this.bloquecs = ["bloqueSprite","bloquelSprite"];
this.bloque = this.game.add.group();
for (var i = 0; i < 4; i++){
this.bloque.createMultiple(5, this.bloquecs[Math.floor(Math.random()*this.bloquecs.length)], 0, false);
}
this.bloque.enableBody = true;
this.game.physics.arcade.enable(this.bloque);
this.bloque.setAll('anchor.x', 0.5);
this.bloque.setAll('anchor.y', 0.5);
this.bloque.setAll('outOfBoundsKill', true);
this.bloque.setAll('checkWorldBounds', true);
},
makeBloques: function(){
this.bloques = this.bloque.getFirstExists(false);
if (this.bloques) {
this.bloques.reset(0, 300);
this.bloques.body.kinematic = true;
this.bloques.body.velocity.x = 500;
}
},
createTanque: function() {
this.tanqueBounds = new Phaser.Rectangle(0, 600, 1024, 150);
this.tanque = this.add.sprite(500, 700, 'tanqueSprite');
this.tanque.inputEnabled = true;
this.tanque.input.enableDrag(true);
this.tanque.anchor.setTo(0.5,0.5);
this.tanque.input.boundsRect = this.tanqueBounds;
},
collisionBulletBloque: function(bullet, bloques) {
this.bloques.kill();
this.bullet.kill();
},
quitGame: function (pointer) {
// Here you should destroy anything you no longer need.
// Stop music, delete sprites, purge caches, free resources, all that good stuff.
// Then let's go back to the main menu.
this.state.start('MainMenu');
}
};

Display getUserMediaStream live video with media stream extensions (MSE)

I am trying to display a MediaStream taken from a webcam using getUserMedia, and to relay it to a remote peer using whatever mechanism possible for it to be played (as an experiment). I am not using webRTC directly as I want control over the raw data.
The issue I encounter is that my video element displays nothing, and I don't get any errors back. I am using Chrome Version 51.0.2704.103 (64-bit) on Elementary OS (Ubuntu 14.04 based linux OS).
As a sidenote, if I record all the blobs into an array and then create a new blob and set the video's src element to URL.createObjectUrl(blob), it displays video correctly.
Here is the code I tried to accomplish this (minus the relaying, I'm just trying to play it locally):
var ms = new MediaSource();
var video = document.querySelector("video");
video.src = window.URL.createObjectURL(ms);
ms.addEventListener("sourceopen", function() {
var sourceBuffer = ms.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
navigator.getUserMedia({video: {width: 320, height: 240, framerate: 30}, audio: true}, function(stream) {
var recorder = new MediaRecorder(stream);
recorder.ondataavailable = function(event) {
var reader = new FileReader();
reader.addEventListener("loadend", function () {
var uint8Chunk = new Uint8Array(reader.result);
if (!sourceBuffer.updating) {
sourceBuffer.appendBuffer(uint8Chunk);
}
if (video.paused) video.play();
});
reader.readAsArrayBuffer(event.data);
};
recorder.start(10);
}, function(error) {
console.error(error);
});
}, false);
Here is the info I get in chrome://media-internal:
render_id: 147
player_id: 0
pipeline_state: kPlaying
event: WEBMEDIAPLAYER_CREATED
url: blob:http%3A//localhost%3A8080/e5c51dd8-5709-4e6f-9457-49ac8c34756b
found_audio_stream: true
audio_codec_name: opus
found_video_stream: true
video_codec_name: vp8
duration: unknown
audio_dds: false
audio_decoder: OpusAudioDecoder
video_dds: false
video_decoder: FFmpegVideoDecoder
Also the log:
00:00:00 00 pipeline_state kCreated
00:00:00 00 event WEBMEDIAPLAYER_CREATED
00:00:00 00 url blob:http%3A//localhost%3A8080/e5c51dd8-5709-4e6f-9457-49ac8c34756b
00:00:00 00 pipeline_state kInitDemuxer
00:00:01 603 found_audio_stream true
00:00:01 603 audio_codec_name opus
00:00:01 603 found_video_stream true
00:00:01 603 video_codec_name vp8
00:00:01 604 duration unknown
00:00:01 604 pipeline_state kInitRenderer
00:00:01 604 audio_dds false
00:00:01 604 audio_decoder OpusAudioDecoder
00:00:01 604 video_dds false
00:00:01 604 video_decoder FFmpegVideoDecoder
00:00:01 604 pipeline_state kPlaying
Update: I've tried sending the data to node and saving it to a webm file with ffmpeg (fluent-ffmpeg), and I can view the file in VLC correctly.
Update 2: After streaming it back from node, I get the following: Media segment did not contain any video coded frames, mismatching initialization segment. Therefore, MSE coded frame processing may not interoperably detect discontinuities in appended media.
. After doing some research, it appears that webm files must be segmented to work, however I have not come across a way to do this (either using ffmpeg or other tools) for live streams. Any ideas here?
A little late, but you can try it like this (in chrome):
<html>
<body>
<video class="real1" autoplay controls></video>
<video class="real2" controls></video>
<script>
const constraints = {video: {width: 320, height: 240, framerate: 30}, audio: true};
const video1 = document.querySelector('.real1');
const video2 = document.querySelector('.real2');
var mediaSource = new MediaSource();
video2.src = window.URL.createObjectURL(mediaSource);
var sourceBuffer;
mediaSource.addEventListener('sourceopen', function () {
sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs=opus,vp8');
console.log(sourceBuffer);
})
var isFirst = true;
var mediaRecorder;
var i = 0;
function handleSuccess(stream) {
video1.srcObject = stream;
mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm; codecs=opus,vp8' });
console.log(mediaRecorder.mimeType)
mediaRecorder.ondataavailable = function (e) {
var reader = new FileReader();
reader.onload = function (e) {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
}
reader.readAsArrayBuffer(e.data);
if (video2.paused) {
video2.play(0);
}
}
mediaRecorder.start(20);
}
function handleError(error) {
console.error('Reeeejected!', error);
}
navigator.mediaDevices.getUserMedia(constraints).
then(handleSuccess).catch(handleError);
</script>
</body>
</html>
I think you missed setting the same (supported) codec to both, recorder and sourceBuffer.