Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Side by Side Diff: ppapi/examples/audio_encode/audio_encode.html

Issue 1151973003: ppapi: implement PPB_AudioEncoder (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: More Windows fixes in proxy code Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!DOCTYPE html>
2 <html>
3 <!--
4 Copyright 2015 The Chromium Authors. All rights reserved.
5 Use of this source code is governed by a BSD-style license that can be
6 found in the LICENSE file.
7 -->
8 <head>
9 <title>Audio Encoder Example</title>
10 <script type="text/javascript">
11 var plugin;
12 var track;
13 var writer;
14 var profiles;
15
16 function writeString(array, offset, string) {
17 var out = new Uint8Array(array);
18 for (var i = 0; i < string.length; i++)
19 out.set([string.charCodeAt(i)], i + offset);
20 return string.length;
21 }
22 function writeValue(array, bytes, offset, value) {
23 var out = new Uint8Array(array);
24 for (var i = 0; i < bytes; i++)
25 out.set([((value >>> (i * 8)) & 0xff)], offset + i);
26 return bytes;
27 }
28 // Writes way data.
29 var WavWriter = function() {
30 this.chunkSizeOffset = 0;
31 this.arrayBuffer = new ArrayBuffer();
32 };
33 WavWriter.prototype = {
34 writeHeader: function(format) {
35 this.arrayBuffer =
36 new ArrayBuffer(4 + 4 + 4 + 4 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4);
37 var i = 0;
38 // File header
39 i += writeString(this.arrayBuffer, i, 'RIFF');
40 i += 4; // Gap for final size.
41 i += writeString(this.arrayBuffer, i, 'WAVE');
42 // Chunk ID.
43 i += writeString(this.arrayBuffer, i, 'fmt ');
44 // Chunk length.
45 i += writeValue(this.arrayBuffer, 4, i, 16);
46 // Codec (uncompressed LPCM).
47 i += writeValue(this.arrayBuffer, 2, i, 1);
48 // Number of channels.
49 i += writeValue(this.arrayBuffer, 2, i, format.channels);
50 // Sample rate.
51 i += writeValue(this.arrayBuffer, 4, i, format.sample_rate);
52 // Average bytes per seconds (sample rate * bytes per sample)
53 i += writeValue(this.arrayBuffer, 4, i,
54 format.sample_rate * format.sample_size);
55 // Bytes per sample.
56 i += writeValue(this.arrayBuffer, 2, i,
57 format.sample_size * format.channels);
58 // Bits per sample.
59 i += writeValue(this.arrayBuffer, 2, i, format.sample_size * 8);
60
61 // Data chunk
62 i += writeString(this.arrayBuffer, i, 'data');
63 this.chunkSizeOffset = i; // Location of the chunk's size
64 },
65 writeData: function(data) {
66 var tmp = new Uint8Array(this.arrayBuffer.byteLength + data.byteLength);
67 tmp.set(new Uint8Array(this.arrayBuffer), 0);
68 tmp.set(new Uint8Array(data), this.arrayBuffer.byteLength);
69 this.arrayBuffer = tmp.buffer;
70 },
71 end: function() {
72 var out = new Uint32Array(this.arrayBuffer);
73 out.set([this.arrayBuffer.byteLength - 8], 1);
74 out.set([this.arrayBuffer.byteLength - this.chunkSizeOffset],
75 this.chunkSizeOffset / 4);
76 },
77 getSize: function() {
78 return this.arrayBuffer.byteLength;
79 },
80 getData: function() {
81 return this.arrayBuffer;
82 },
83 getExtension: function() {
84 return 'wav';
85 },
86 };
87
88 // Writes ogg data.
89 var OggWriter = function(profile) {
90 if (profile.name == 'opus')
91 this.writeHeader = this._writeOpusHeader;
92 else
93 this.writeHeader = this._writeSpeexHeader;
94 this.arrayBuffer = new ArrayBuffer();
95 this.pageSequence = 0;
96 this.bitstreamNumber = 0;
97 this.position = 0;
98 this.dataWritten = false;
99 };
100 OggWriter.prototype = {
101 _Start: 0x2,
102 _Continue: 0x1,
103 _Stop: 0x4,
104 _append: function(data) {
105 var tmp = new Uint8Array(this.arrayBuffer.byteLength + data.byteLength);
106 tmp.set(new Uint8Array(this.arrayBuffer), 0);
107 tmp.set(new Uint8Array(data), this.arrayBuffer.byteLength);
108 this.arrayBuffer = tmp.buffer;
109 },
110 _makeCRCTable: function() {
111 var crcTable = [];
112 for (var n = 0; n < 256; n++) {
113 var r = n << 24;
114 for (var i = 0; i < 8; i++) {
115 if (r & 0x80000000)
116 r = (r << 1) ^ 0x04c11db7;
117 else
118 r <<= 1;
119 }
120 crcTable[n] = r & 0xffffffff;
121 }
122 return crcTable;
123 },
124 _crc32: function(data, start, end) {
125 var crc = 0;
126 var u8data = new Uint8Array(data)
127 var crcTable = this._crcTable || (this._crcTable = this._makeCRCTable()) ;
128 for (var i = start; i < end; i++)
129 crc = (crc << 8) ^ crcTable[((crc >> 24) & 0xff) ^ u8data[i]];
130 return crc;
131 },
132 _writePage: function(flag, size, position) {
133 var pages = 1 + Math.floor(size / 255);
134 var buffer = new ArrayBuffer(27 + pages), i = 0;
135 // capture_pattern.
136 i += writeString(buffer, i, 'OggS');
137 // stream_structure_version.
138 i += writeValue(buffer, 1, i, 0);
139 // header_type_flag.
140 i += writeValue(buffer, 1, i, flag);
141 // granule_position.
142 // TODO(llandwerlin): Not writing more than 32bits for now,
143 // because Javascript doesn't have 64bits integers, this limits
144 // the duration to ~24 hours at 48kHz sampling rate.
145 i += writeValue(buffer, 4, i, position != undefined ? position : 0);
146 i += writeValue(buffer, 4, i, 0);
147 // bitstream_serial_number.
148 i += writeValue(buffer, 4, i, this.bitstreamNumber);
149 // page_sequence_number.
150 i += writeValue(buffer, 4, i, this.pageSequence++);
151 // CRC_checksum.
152 i += writeValue(buffer, 4, i, 0);
153 // number_page_segments.
154 i += writeValue(buffer, 1, i, pages);
155 // segment sizes.
156 for (var j = 0; j < (pages - 1); j++)
157 i += writeValue(buffer, 1, i, 255);
158 i += writeValue(buffer, 1, i, size % 255);
159
160 this._append(buffer);
161 },
162 _writePageChecksum: function(pageOffset) {
163 var crc = this._crc32(this.arrayBuffer, pageOffset,
164 this.arrayBuffer.byteLength);
165 writeValue(this.arrayBuffer, 4, pageOffset + 22, crc);
166 },
167 _writeOpusHeader: function(format) {
168 this.format = format;
169 var start = this.getSize();
170 var buffer = new ArrayBuffer(8 + 1 + 1 + 2 + 4 + 2 + 1), i = 0;
171 // Opus header.
172 i += writeString(buffer, i, 'OpusHead');
173 // version.
174 i += writeValue(buffer, 1, i, 1);
175 // channel count.
176 i += writeValue(buffer, 1, i, format.channels);
177 // pre-skip.
178 i += writeValue(buffer, 2, i, 0);
179 // input sample rate.
180 i += writeValue(buffer, 4, i, format.sample_rate);
181 // output gain.
182 i += writeValue(buffer, 2, i, 0);
183 // channel mapping family.
184 i += writeValue(buffer, 1, i, 0);
185
186 this._writePage(this._Start, buffer.byteLength);
187 this._append(buffer);
188 this._writePageChecksum(start);
189 this._writeOpusCommentHeader('OpusTags');
190 },
191 _writeSpeexHeader: function(format) {
192 this.format = format;
193 var start = this.getSize();
194 var buffer = new ArrayBuffer(80), i = 0;
195
196 var sampleRateToMode = function(rate) {
197 switch (rate) {
198 case 8000:
199 return 0;
200 case 16000:
201 return 1;
202 case 32000:
203 return 2;
204 }
205 return 0;
206 };
207
208 // speex_string.
209 i += writeString(buffer, i, 'Speex ');
210 // speex_version.
211 i += writeString(buffer, i, ' ');
212 // speex_version_id.
213 i += writeValue(buffer, 4, i, 0);
214 // header_size.
215 i += writeValue(buffer, 4, i, buffer.byteLength);
216 // rate.
217 i += writeValue(buffer, 4, i, format.sample_rate);
218 // mode.
219 i += writeValue(buffer, 4, i, sampleRateToMode(format.sample_rate));
220 // mode_bitstream_version.
221 i += writeValue(buffer, 4, i, 0);
222 // nb_channels.
223 i += writeValue(buffer, 4, i, format.channels);
224 // bitrate.
225 i += writeValue(buffer, 4, i, 1);
226 // frame_size.
227 i += writeValue(buffer, 4, i, format.sample_per_frame);
228 // vbr.
229 i += writeValue(buffer, 4, i, 1);
230 // frames_per_packet.
231 i += writeValue(buffer, 4, i, 1);
232 // extra_headers.
233 i += writeValue(buffer, 4, i, 0);
234
235 this._writePage(this._Start, buffer.byteLength);
236 this._append(buffer);
237 this._writePageChecksum(start);
238 this._writeCommentHeader('Speex ');
239 },
240 _writeCommentHeader: function(name) {
241 var start = this.getSize();
242 var buffer = new ArrayBuffer(8 + 4 + 8 + 4 + 4 + 13), i = 0;
243 // Opus/Speex comment header.
244 i += writeString(buffer, i, name);
245 // Vendor string.
246 i += this._writeLengthString(buffer, i, 'Chromium');
247 // User comment list length
248 i += writeValue(buffer, 4, i, 1);
249 // User comment 0 length.
250 i += this._writeLengthString(buffer, i, 'TITLE=example');
251
252 this._writePage(this._Continue, buffer.byteLength);
253 this._append(buffer);
254 this._writePageChecksum(start);
255 },
256 _writeLengthString: function(buffer, offset, str) {
257 return (writeValue(buffer, offset, 4, str.length) +
258 writeString(buffer, offset, str));
259 },
260 writeData: function(data) {
261 this.position += this.format.sample_per_frame / this.format.channels;
262 var start = this.getSize();
263 this._writePage(0, data.byteLength, this.position);
264 this._append(data);
265 this._writePageChecksum(start);
266 this.dataWritten = true;
267 },
268 end: function() {
269 this._writePage(this._Stop, 0);
270 },
271 getSize: function() {
272 return this.arrayBuffer.byteLength;
273 },
274 getData: function() {
275 return this.arrayBuffer;
276 },
277 getExtension: function() {
278 return 'ogg';
279 },
280 };
281
282 function $(id) {
283 return document.getElementById(id);
284 }
285
286 function success(stream) {
287 track = stream.getAudioTracks()[0];
288 var list = $('profileList');
289 var profile = profiles[list.selectedIndex];
290 plugin.postMessage({
291 command: 'start',
292 profile: profile.name,
293 sample_size: profile.sample_size,
294 sample_rate: profile.sample_rate,
295 track: track,
296 });
297 }
298
299 function failure(e) {
300 console.log("Error: ", e);
301 }
302
303 function cleanupDownload() {
304 var download = $('download');
305 if (!download)
306 return;
307 download.parentNode.removeChild(download);
308 }
309
310 function setDownload(data, filename) {
311 var mimeType = 'application/octet-stream';
312 var blob = new Blob([data], { type: mimeType });
313 var a = document.createElement('a');
314 a.id = "download";
315 a.download = filename;
316 a.href = window.URL.createObjectURL(blob);
317 a.textContent = 'Download';
318 a.dataset.downloadurl = [mimeType, a.download, a.href].join(':');
319 $('download-box').appendChild(a);
320 }
321
322 function startRecord() {
323 var list = $('profileList');
324 var profile = profiles[list.selectedIndex];
325 if (profile.name == 'wav')
326 writer = new WavWriter();
327 else
328 writer = new OggWriter(profile);
329 cleanupDownload();
330
331 var constraints = [];
332 if (profile.name == 'opus') {
333 // Chromium outputs 32kHz sampling rate by default. This isn't a
334 // supported sampling rate for the Opus codec. So we force the
335 // output to 48kHz. If Chromium implements the GetUserMedia
336 // audio constraints at some point, we could potentially get rid
337 // of this.
338 constraints.push({ googAudioProcessing48kHzSupport: true });
339 }
340
341 navigator.webkitGetUserMedia({ audio: { optional: constraints },
342 video: false},
343 success, failure);
344 }
345
346 function stopRecord() {
347 plugin.postMessage({
348 command: "stop"
349 });
350 track.stop();
351 writer.end();
352 setDownload(writer.getData(), 'Capture.' + writer.getExtension());
353 }
354
355 function saveBlob(blob) {
356 var blobUrl = URL.createObjectURL(blob);
357 window.location = blobUrl;
358 }
359
360 function handleMessage(msg) {
361 if (msg.data.command == 'log') {
362 console.log(msg.data.message);
363 } else if (msg.data.command == 'error') {
364 console.error(msg.data.message);
365 } else if (msg.data.command == 'data') {
366 writer.writeData(msg.data.buffer);
367 $('length').textContent = ' Size: ' + writer.getSize() + ' bytes';
368 } else if (msg.data.command == 'format') {
369 writer.writeHeader(msg.data);
370 $('length').textContent = ' Size: ' + writer.getSize() + ' bytes';
371 } else if (msg.data.command == 'supportedProfiles') {
372 profiles = [];
373 var profileList = $('profileList');
374 for (var node in profileList.childNodes)
375 profileList.remove(node);
376
377 var item = document.createElement('option');
378 item.label = 'wav';
379 profiles.push({ name: 'wav',
380 sample_rate: 0,
381 sample_size: 0,
382 sample_per_frame:
383 msg.data.profiles[0].sample_per_frame });
384 profileList.appendChild(item);
385 for (var i = 0; i < msg.data.profiles.length; i++) {
386 var item = document.createElement('option');
387 item.label = msg.data.profiles[i].name + ' - ' +
388 msg.data.profiles[i].sample_rate + 'Hz';
389 profiles.push(msg.data.profiles[i]);
390 profileList.appendChild(item);
391 }
392 }
393 }
394
395 function resetData() {
396 writer = new WavWriter();
397 $('length').textContent = ' Size: ' + writer.getSize() + ' bytes';
398 }
399
400 function initialize() {
401 plugin = $('plugin');
402 plugin.addEventListener('message', handleMessage, false);
403
404 $('start').addEventListener('click', function (e) {
405 resetData();
406 startRecord();
407 });
408 $('stop').addEventListener('click', function (e) {
409 stopRecord();
410 });
411 }
412
413 document.addEventListener('DOMContentLoaded', initialize, false);
414 </script>
415 </head>
416
417 <body>
418 <h1>Pepper Audio Encoder API Example</h1><br>
419 This example demonstrates receiving frames from an audio MediaStreamTrack and
420 encoding them using AudioEncode.<br>
421
422 <select id="profileList"></select>
423 <input type="button" id="start" value="Start Recording"/>
424 <input type="button" id="stop" value="Stop Recording"/>
425 <div id="download-box"></div>
426 <div id="length"></div>
427 <br>
428 <embed id="plugin" type="application/x-ppapi-example-audio-encode"/>
429 </body>
430 </html>
OLDNEW
« no previous file with comments | « ppapi/examples/audio_encode/audio_encode.cc ('k') | ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698