Permalink
Switch branches/tags
Nothing to show
Find file Copy path
0ce1318 Mar 19, 2014
1 contributor

Users who have contributed to this file

265 lines (226 sloc) 7.96 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Audio effects with WebAudio in WebRTC</title>
<script type="text/javascript" src="../../base/adapter.js"></script>
<script>
var audioElement;
var buttonStart;
var buttonStop;
var localStream;
var pc1, pc2;
var display;
var webAudio;
// WebAudio helper class which takes care of the WebAudio related parts.
function WebAudio() {
this.context = new webkitAudioContext();
this.soundBuffer = null;
}
WebAudio.prototype.start = function() {
this.filter = this.context.createBiquadFilter();
this.filter.type = this.filter.HIGHPASS;
this.filter.frequency.value = 1500;
}
WebAudio.prototype.applyFilter = function(stream) {
this.mic = this.context.createMediaStreamSource(stream);
this.mic.connect(this.filter);
this.peer = this.context.createMediaStreamDestination();
this.filter.connect(this.peer);
return this.peer.stream;
}
WebAudio.prototype.renderLocally = function(enabled) {
if (enabled) {
this.mic.connect(this.context.destination);
} else {
this.mic.disconnect(0);
this.mic.connect(this.filter);
}
}
WebAudio.prototype.stop = function() {
this.mic.disconnect(0);
this.filter.disconnect(0);
mic = null;
peer = null;
}
WebAudio.prototype.addEffect = function() {
var effect = this.context.createBufferSource();
effect.buffer = this.soundBuffer;
if (this.peer) {
effect.connect(this.peer);
effect.start(0);
}
}
WebAudio.prototype.loadCompleted = function() {
this.soundBuffer = this.context.createBuffer(this.request.response, true);
}
WebAudio.prototype.loadSound = function(url) {
this.request = new XMLHttpRequest();
this.request.open('GET', url, true);
this.request.responseType = 'arraybuffer';
this.request.onload = this.loadCompleted.bind(this);
this.request.send();
}
// Global methods.
function trace(txt) {
display.innerHTML += txt + "<br>";
}
function logEvent(e) {
console.log(e.type + ':' + e.target + ':' + e.target.id + ':muted=' +
e.target.muted);
}
$ = function(id) {
return document.getElementById(id);
};
function start() {
webAudio.start();
var constraints = {audio:true, video:false};
getUserMedia(constraints, gotStream, gotStreamFailed);
buttonStart.disabled = true;
buttonStop.disabled = false;
}
function stop() {
webAudio.stop();
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
buttonStart.enabled = true;
buttonStop.enabled = false;
localStream.stop();
}
function gotStream(stream) {
audioTracks = stream.getAudioTracks();
if (audioTracks.length == 1) {
console.log('gotStream({audio:true, video:false})');
var filteredStream = webAudio.applyFilter(stream);
var servers = null;
pc1 = new webkitRTCPeerConnection(servers);
console.log('Created local peer connection object pc1');
pc1.onicecandidate = iceCallback1;
pc2 = new webkitRTCPeerConnection(servers);
console.log('Created remote peer connection object pc2');
pc2.onicecandidate = iceCallback2;
pc2.onaddstream = gotRemoteStream;
pc1.addStream(filteredStream);
pc1.createOffer(gotDescription1);
stream.onended = function() {
console.log('stream.onended');
buttonStart.disabled = false;
buttonStop.disabled = true;
};
localStream = stream;
} else {
alert('The media stream contains an invalid amount of audio tracks.');
stream.stop();
}
}
function gotStreamFailed(error) {
buttonStart.disabled = false;
buttonStop.disabled = true;
alert('Failed to get access to local media. Error code: ' + error.code);
}
function forceOpus(sdp) {
// Remove all other codecs (not the video codecs though).
sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
'm=audio $1 RTP/SAVPF 111\r\n');
sdp = sdp.replace(/a=rtpmap:(?!111)\d{1,3} (?!VP8|red|ulpfec).*\r\n/g, '');
return sdp;
}
function gotDescription1(desc){
console.log('Offer from pc1 \n' + desc.sdp);
var modifiedOffer = new RTCSessionDescription({type: 'offer',
sdp: forceOpus(desc.sdp)});
pc1.setLocalDescription(modifiedOffer);
console.log('Offer from pc1 \n' + modifiedOffer.sdp);
pc2.setRemoteDescription(modifiedOffer);
pc2.createAnswer(gotDescription2);
}
function gotDescription2(desc){
pc2.setLocalDescription(desc);
console.log('Answer from pc2 \n' + desc.sdp);
pc1.setRemoteDescription(desc);
}
function gotRemoteStream(e){
attachMediaStream(audioElement, e.stream);
}
function iceCallback1(event){
if (event.candidate) {
pc2.addIceCandidate(new RTCIceCandidate(event.candidate),
onAddIceCandidateSuccess, onAddIceCandidateError);
console.log('Local ICE candidate: \n' + event.candidate.candidate);
}
}
function iceCallback2(event){
if (event.candidate) {
pc1.addIceCandidate(new RTCIceCandidate(event.candidate),
onAddIceCandidateSuccess, onAddIceCandidateError);
console.log('Remote ICE candidate: \n ' + event.candidate.candidate);
}
}
function onAddIceCandidateSuccess() {
trace("AddIceCandidate success.");
}
function onAddIceCandidateError(error) {
trace("Failed to add Ice Candidate: " + error.toString());
}
function handleKeyDown(event) {
var keyCode = event.keyCode;
webAudio.addEffect();
}
function doMix(checkbox) {
webAudio.renderLocally(checkbox.checked);
}
function onload() {
webAudio = new WebAudio();
webAudio.loadSound('../sounds/Shamisen-C4.wav');
audioElement = $('audio');
buttonStart = $('start');
buttonStop = $('stop');
display = $('display');
document.addEventListener('keydown', handleKeyDown, false);
buttonStart.enabled = true;
buttonStop.disabled = true;
}
</script>
</head>
<body onload='onload()'>
<h2>Capture microphone input and stream it out to a peer with a processing
effect applied to the audio.</h2>
<p>The audio stream is: <br><br>
o Recorded using <a href="http://www.html5audio.org/2012/09/live-audio-input-comes-to-googles-chrome-canary.html"
title="Live audio input comes to Google's Chrome Canary">live-audio
input.</a><br>
o Filtered using an HP filter with fc=1500 Hz.<br>
o Encoded using <a href="http://www.opus-codec.org/" title="Opus Codec">
Opus.</a><br>
o Transmitted (in loopback) to remote peer using
<a href="http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcpeerconnection-interface"
title="RTCPeerConnection Interface">RTCPeerConnection</a> where it is decoded.<br>
o Finally, the received remote stream is used as source to an &lt;audio&gt;
tag and played out locally.<br>
<br>Press any key to add an effect to the transmitted audio while talking.
</p>
<p>Please note that: <br><br>
o Linux is currently not supported.<br>
o Sample rate and channel configuration must be the same for input and
output sides on Windows.<br>
o Only the Default microphone device can be used for capturing.
</p>
<p>For more information, see <a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/webrtc-integration.html"
title="Example 3: Capture microphone input and stream it out to a peer with a processing effect applied to the audio">
WebRTC integration with the Web Audio API.</a>
</p>
<style>
button {
font: 14px sans-serif;
padding: 8px;
}
</style>
<audio id="audio" autoplay controls></audio><br><br>
<button id="start" onclick="start()">Start</button>
<button id="stop" onclick="stop()">Stop</button><br><br>
Add local audio to output:<input id="mix" type="checkbox" onclick="doMix(this);"><br><br>
<pre id="display"></pre>
</body>
</html>