New WebAudio-WebRTC demo.
Capture microphone input and stream it out to a peer with a processing effect applied to the audio.
The audio stream is:
o Recorded using live-audio input.
o Filtered using an HP filter with fc=1500 Hz.
o Encoded using Opus.
o Transmitted (in loopback) to remote peer using RTCPeerConnection where it is decoded.
o Finally, the received remote stream is used as source to an <audio> tag and played out locally.
Press any key to add an effect to the transmitted audio while talking.
Please note that:
o Linux is currently not supported.
o Sample rate and channel configuration must be the same for input and output sides on Windows.
o Only the Default microphone device can be used for capturing.
R=phoglund@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/1256004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@4006 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/samples/js/demos/html/webaudio-and-webrtc.html b/samples/js/demos/html/webaudio-and-webrtc.html
new file mode 100644
index 0000000..173a308
--- /dev/null
+++ b/samples/js/demos/html/webaudio-and-webrtc.html
@@ -0,0 +1,254 @@
+<!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));
+ console.log('Local ICE candidate: \n' + event.candidate.candidate);
+ }
+ }
+
+ function iceCallback2(event){
+ if (event.candidate) {
+ pc1.addIceCandidate(new RTCIceCandidate(event.candidate));
+ console.log('Remote ICE candidate: \n ' + event.candidate.candidate);
+ }
+ }
+
+ 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 <audio>
+ 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>