Index: ppapi/examples/audio_encode/audio_encode.html |
diff --git a/ppapi/examples/audio_encode/audio_encode.html b/ppapi/examples/audio_encode/audio_encode.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b21053e0d1175a7e52d01cb3e3d77d9e3fcd548a |
--- /dev/null |
+++ b/ppapi/examples/audio_encode/audio_encode.html |
@@ -0,0 +1,430 @@ |
+<!DOCTYPE html> |
+<html> |
+ <!-- |
+ Copyright 2015 The Chromium Authors. All rights reserved. |
+ Use of this source code is governed by a BSD-style license that can be |
+ found in the LICENSE file. |
+ --> |
+<head> |
+ <title>Audio Encoder Example</title> |
+ <script type="text/javascript"> |
+ var plugin; |
+ var track; |
+ var writer; |
+ var profiles; |
+ |
+ function writeString(array, offset, string) { |
+ var out = new Uint8Array(array); |
+ for (var i = 0; i < string.length; i++) |
+ out.set([string.charCodeAt(i)], i + offset); |
+ return string.length; |
+ } |
+ function writeValue(array, bytes, offset, value) { |
+ var out = new Uint8Array(array); |
+ for (var i = 0; i < bytes; i++) |
+ out.set([((value >>> (i * 8)) & 0xff)], offset + i); |
+ return bytes; |
+ } |
+ // Writes way data. |
+ var WavWriter = function() { |
+ this.chunkSizeOffset = 0; |
+ this.arrayBuffer = new ArrayBuffer(); |
+ }; |
+ WavWriter.prototype = { |
+ writeHeader: function(format) { |
+ this.arrayBuffer = |
+ new ArrayBuffer(4 + 4 + 4 + 4 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4); |
+ var i = 0; |
+ // File header |
+ i += writeString(this.arrayBuffer, i, 'RIFF'); |
+ i += 4; // Gap for final size. |
+ i += writeString(this.arrayBuffer, i, 'WAVE'); |
+ // Chunk ID. |
+ i += writeString(this.arrayBuffer, i, 'fmt '); |
+ // Chunk length. |
+ i += writeValue(this.arrayBuffer, 4, i, 16); |
+ // Codec (uncompressed LPCM). |
+ i += writeValue(this.arrayBuffer, 2, i, 1); |
+ // Number of channels. |
+ i += writeValue(this.arrayBuffer, 2, i, format.channels); |
+ // Sample rate. |
+ i += writeValue(this.arrayBuffer, 4, i, format.sample_rate); |
+ // Average bytes per seconds (sample rate * bytes per sample) |
+ i += writeValue(this.arrayBuffer, 4, i, |
+ format.sample_rate * format.sample_size); |
+ // Bytes per sample. |
+ i += writeValue(this.arrayBuffer, 2, i, |
+ format.sample_size * format.channels); |
+ // Bits per sample. |
+ i += writeValue(this.arrayBuffer, 2, i, format.sample_size * 8); |
+ |
+ // Data chunk |
+ i += writeString(this.arrayBuffer, i, 'data'); |
+ this.chunkSizeOffset = i; // Location of the chunk's size |
+ }, |
+ writeData: function(data) { |
+ var tmp = new Uint8Array(this.arrayBuffer.byteLength + data.byteLength); |
+ tmp.set(new Uint8Array(this.arrayBuffer), 0); |
+ tmp.set(new Uint8Array(data), this.arrayBuffer.byteLength); |
+ this.arrayBuffer = tmp.buffer; |
+ }, |
+ end: function() { |
+ var out = new Uint32Array(this.arrayBuffer); |
+ out.set([this.arrayBuffer.byteLength - 8], 1); |
+ out.set([this.arrayBuffer.byteLength - this.chunkSizeOffset], |
+ this.chunkSizeOffset / 4); |
+ }, |
+ getSize: function() { |
+ return this.arrayBuffer.byteLength; |
+ }, |
+ getData: function() { |
+ return this.arrayBuffer; |
+ }, |
+ getExtension: function() { |
+ return 'wav'; |
+ }, |
+ }; |
+ |
+ // Writes ogg data. |
+ var OggWriter = function(profile) { |
+ if (profile.name == 'opus') |
+ this.writeHeader = this._writeOpusHeader; |
+ else |
+ this.writeHeader = this._writeSpeexHeader; |
+ this.arrayBuffer = new ArrayBuffer(); |
+ this.pageSequence = 0; |
+ this.bitstreamNumber = 0; |
+ this.position = 0; |
+ this.dataWritten = false; |
+ }; |
+ OggWriter.prototype = { |
+ _Start: 0x2, |
+ _Continue: 0x1, |
+ _Stop: 0x4, |
+ _append: function(data) { |
+ var tmp = new Uint8Array(this.arrayBuffer.byteLength + data.byteLength); |
+ tmp.set(new Uint8Array(this.arrayBuffer), 0); |
+ tmp.set(new Uint8Array(data), this.arrayBuffer.byteLength); |
+ this.arrayBuffer = tmp.buffer; |
+ }, |
+ _makeCRCTable: function() { |
+ var crcTable = []; |
+ for (var n = 0; n < 256; n++) { |
+ var r = n << 24; |
+ for (var i = 0; i < 8; i++) { |
+ if (r & 0x80000000) |
+ r = (r << 1) ^ 0x04c11db7; |
+ else |
+ r <<= 1; |
+ } |
+ crcTable[n] = r & 0xffffffff; |
+ } |
+ return crcTable; |
+ }, |
+ _crc32: function(data, start, end) { |
+ var crc = 0; |
+ var u8data = new Uint8Array(data) |
+ var crcTable = this._crcTable || (this._crcTable = this._makeCRCTable()); |
+ for (var i = start; i < end; i++) |
+ crc = (crc << 8) ^ crcTable[((crc >> 24) & 0xff) ^ u8data[i]]; |
+ return crc; |
+ }, |
+ _writePage: function(flag, size, position) { |
+ var pages = 1 + Math.floor(size / 255); |
+ var buffer = new ArrayBuffer(27 + pages), i = 0; |
+ // capture_pattern. |
+ i += writeString(buffer, i, 'OggS'); |
+ // stream_structure_version. |
+ i += writeValue(buffer, 1, i, 0); |
+ // header_type_flag. |
+ i += writeValue(buffer, 1, i, flag); |
+ // granule_position. |
+ // TODO(llandwerlin): Not writing more than 32bits for now, |
+ // because Javascript doesn't have 64bits integers, this limits |
+ // the duration to ~24 hours at 48kHz sampling rate. |
+ i += writeValue(buffer, 4, i, position != undefined ? position : 0); |
+ i += writeValue(buffer, 4, i, 0); |
+ // bitstream_serial_number. |
+ i += writeValue(buffer, 4, i, this.bitstreamNumber); |
+ // page_sequence_number. |
+ i += writeValue(buffer, 4, i, this.pageSequence++); |
+ // CRC_checksum. |
+ i += writeValue(buffer, 4, i, 0); |
+ // number_page_segments. |
+ i += writeValue(buffer, 1, i, pages); |
+ // segment sizes. |
+ for (var j = 0; j < (pages - 1); j++) |
+ i += writeValue(buffer, 1, i, 255); |
+ i += writeValue(buffer, 1, i, size % 255); |
+ |
+ this._append(buffer); |
+ }, |
+ _writePageChecksum: function(pageOffset) { |
+ var crc = this._crc32(this.arrayBuffer, pageOffset, |
+ this.arrayBuffer.byteLength); |
+ writeValue(this.arrayBuffer, 4, pageOffset + 22, crc); |
+ }, |
+ _writeOpusHeader: function(format) { |
+ this.format = format; |
+ var start = this.getSize(); |
+ var buffer = new ArrayBuffer(8 + 1 + 1 + 2 + 4 + 2 + 1), i = 0; |
+ // Opus header. |
+ i += writeString(buffer, i, 'OpusHead'); |
+ // version. |
+ i += writeValue(buffer, 1, i, 1); |
+ // channel count. |
+ i += writeValue(buffer, 1, i, format.channels); |
+ // pre-skip. |
+ i += writeValue(buffer, 2, i, 0); |
+ // input sample rate. |
+ i += writeValue(buffer, 4, i, format.sample_rate); |
+ // output gain. |
+ i += writeValue(buffer, 2, i, 0); |
+ // channel mapping family. |
+ i += writeValue(buffer, 1, i, 0); |
+ |
+ this._writePage(this._Start, buffer.byteLength); |
+ this._append(buffer); |
+ this._writePageChecksum(start); |
+ this._writeOpusCommentHeader('OpusTags'); |
+ }, |
+ _writeSpeexHeader: function(format) { |
+ this.format = format; |
+ var start = this.getSize(); |
+ var buffer = new ArrayBuffer(80), i = 0; |
+ |
+ var sampleRateToMode = function(rate) { |
+ switch (rate) { |
+ case 8000: |
+ return 0; |
+ case 16000: |
+ return 1; |
+ case 32000: |
+ return 2; |
+ } |
+ return 0; |
+ }; |
+ |
+ // speex_string. |
+ i += writeString(buffer, i, 'Speex '); |
+ // speex_version. |
+ i += writeString(buffer, i, ' '); |
+ // speex_version_id. |
+ i += writeValue(buffer, 4, i, 0); |
+ // header_size. |
+ i += writeValue(buffer, 4, i, buffer.byteLength); |
+ // rate. |
+ i += writeValue(buffer, 4, i, format.sample_rate); |
+ // mode. |
+ i += writeValue(buffer, 4, i, sampleRateToMode(format.sample_rate)); |
+ // mode_bitstream_version. |
+ i += writeValue(buffer, 4, i, 0); |
+ // nb_channels. |
+ i += writeValue(buffer, 4, i, format.channels); |
+ // bitrate. |
+ i += writeValue(buffer, 4, i, 1); |
+ // frame_size. |
+ i += writeValue(buffer, 4, i, format.sample_per_frame); |
+ // vbr. |
+ i += writeValue(buffer, 4, i, 1); |
+ // frames_per_packet. |
+ i += writeValue(buffer, 4, i, 1); |
+ // extra_headers. |
+ i += writeValue(buffer, 4, i, 0); |
+ |
+ this._writePage(this._Start, buffer.byteLength); |
+ this._append(buffer); |
+ this._writePageChecksum(start); |
+ this._writeCommentHeader('Speex '); |
+ }, |
+ _writeCommentHeader: function(name) { |
+ var start = this.getSize(); |
+ var buffer = new ArrayBuffer(8 + 4 + 8 + 4 + 4 + 13), i = 0; |
+ // Opus/Speex comment header. |
+ i += writeString(buffer, i, name); |
+ // Vendor string. |
+ i += this._writeLengthString(buffer, i, 'Chromium'); |
+ // User comment list length |
+ i += writeValue(buffer, 4, i, 1); |
+ // User comment 0 length. |
+ i += this._writeLengthString(buffer, i, 'TITLE=example'); |
+ |
+ this._writePage(this._Continue, buffer.byteLength); |
+ this._append(buffer); |
+ this._writePageChecksum(start); |
+ }, |
+ _writeLengthString: function(buffer, offset, str) { |
+ return (writeValue(buffer, offset, 4, str.length) + |
+ writeString(buffer, offset, str)); |
+ }, |
+ writeData: function(data) { |
+ this.position += this.format.sample_per_frame / this.format.channels; |
+ var start = this.getSize(); |
+ this._writePage(0, data.byteLength, this.position); |
+ this._append(data); |
+ this._writePageChecksum(start); |
+ this.dataWritten = true; |
+ }, |
+ end: function() { |
+ this._writePage(this._Stop, 0); |
+ }, |
+ getSize: function() { |
+ return this.arrayBuffer.byteLength; |
+ }, |
+ getData: function() { |
+ return this.arrayBuffer; |
+ }, |
+ getExtension: function() { |
+ return 'ogg'; |
+ }, |
+ }; |
+ |
+ function $(id) { |
+ return document.getElementById(id); |
+ } |
+ |
+ function success(stream) { |
+ track = stream.getAudioTracks()[0]; |
+ var list = $('profileList'); |
+ var profile = profiles[list.selectedIndex]; |
+ plugin.postMessage({ |
+ command: 'start', |
+ profile: profile.name, |
+ sample_size: profile.sample_size, |
+ sample_rate: profile.sample_rate, |
+ track: track, |
+ }); |
+ } |
+ |
+ function failure(e) { |
+ console.log("Error: ", e); |
+ } |
+ |
+ function cleanupDownload() { |
+ var download = $('download'); |
+ if (!download) |
+ return; |
+ download.parentNode.removeChild(download); |
+ } |
+ |
+ function setDownload(data, filename) { |
+ var mimeType = 'application/octet-stream'; |
+ var blob = new Blob([data], { type: mimeType }); |
+ var a = document.createElement('a'); |
+ a.id = "download"; |
+ a.download = filename; |
+ a.href = window.URL.createObjectURL(blob); |
+ a.textContent = 'Download'; |
+ a.dataset.downloadurl = [mimeType, a.download, a.href].join(':'); |
+ $('download-box').appendChild(a); |
+ } |
+ |
+ function startRecord() { |
+ var list = $('profileList'); |
+ var profile = profiles[list.selectedIndex]; |
+ if (profile.name == 'wav') |
+ writer = new WavWriter(); |
+ else |
+ writer = new OggWriter(profile); |
+ cleanupDownload(); |
+ |
+ var constraints = []; |
+ if (profile.name == 'opus') { |
+ // Chromium outputs 32kHz sampling rate by default. This isn't a |
+ // supported sampling rate for the Opus codec. So we force the |
+ // output to 48kHz. If Chromium implements the GetUserMedia |
+ // audio constraints at some point, we could potentially get rid |
+ // of this. |
+ constraints.push({ googAudioProcessing48kHzSupport: true }); |
+ } |
+ |
+ navigator.webkitGetUserMedia({ audio: { optional: constraints }, |
+ video: false}, |
+ success, failure); |
+ } |
+ |
+ function stopRecord() { |
+ plugin.postMessage({ |
+ command: "stop" |
+ }); |
+ track.stop(); |
+ writer.end(); |
+ setDownload(writer.getData(), 'Capture.' + writer.getExtension()); |
+ } |
+ |
+ function saveBlob(blob) { |
+ var blobUrl = URL.createObjectURL(blob); |
+ window.location = blobUrl; |
+ } |
+ |
+ function handleMessage(msg) { |
+ if (msg.data.command == 'log') { |
+ console.log(msg.data.message); |
+ } else if (msg.data.command == 'error') { |
+ console.error(msg.data.message); |
+ } else if (msg.data.command == 'data') { |
+ writer.writeData(msg.data.buffer); |
+ $('length').textContent = ' Size: ' + writer.getSize() + ' bytes'; |
+ } else if (msg.data.command == 'format') { |
+ writer.writeHeader(msg.data); |
+ $('length').textContent = ' Size: ' + writer.getSize() + ' bytes'; |
+ } else if (msg.data.command == 'supportedProfiles') { |
+ profiles = []; |
+ var profileList = $('profileList'); |
+ for (var node in profileList.childNodes) |
+ profileList.remove(node); |
+ |
+ var item = document.createElement('option'); |
+ item.label = 'wav'; |
+ profiles.push({ name: 'wav', |
+ sample_rate: 0, |
+ sample_size: 0, |
+ sample_per_frame: |
+ msg.data.profiles[0].sample_per_frame }); |
+ profileList.appendChild(item); |
+ for (var i = 0; i < msg.data.profiles.length; i++) { |
+ var item = document.createElement('option'); |
+ item.label = msg.data.profiles[i].name + ' - ' + |
+ msg.data.profiles[i].sample_rate + 'Hz'; |
+ profiles.push(msg.data.profiles[i]); |
+ profileList.appendChild(item); |
+ } |
+ } |
+ } |
+ |
+ function resetData() { |
+ writer = new WavWriter(); |
+ $('length').textContent = ' Size: ' + writer.getSize() + ' bytes'; |
+ } |
+ |
+ function initialize() { |
+ plugin = $('plugin'); |
+ plugin.addEventListener('message', handleMessage, false); |
+ |
+ $('start').addEventListener('click', function (e) { |
+ resetData(); |
+ startRecord(); |
+ }); |
+ $('stop').addEventListener('click', function (e) { |
+ stopRecord(); |
+ }); |
+ } |
+ |
+ document.addEventListener('DOMContentLoaded', initialize, false); |
+ </script> |
+</head> |
+ |
+<body> |
+ <h1>Pepper Audio Encoder API Example</h1><br> |
+ This example demonstrates receiving frames from an audio MediaStreamTrack and |
+ encoding them using AudioEncode.<br> |
+ |
+ <select id="profileList"></select> |
+ <input type="button" id="start" value="Start Recording"/> |
+ <input type="button" id="stop" value="Stop Recording"/> |
+ <div id="download-box"></div> |
+ <div id="length"></div> |
+ <br> |
+ <embed id="plugin" type="application/x-ppapi-example-audio-encode"/> |
+</body> |
+</html> |