宣传一下自己的qq群: (暗号:C#交流) 欢迎喜欢C#,热爱C#,正在学习C#,准备学习C#的朋友来这里互相学习交流,共同进步

群刚建,人不多,但是都是真正热爱C#的 我也是热爱C#的 希望大家可以一起交流,共同进步

最近公司需要用到web录音的功能

本人接手了这个任务

在网上找了一些资料

http://www.jsjtt.com/webkaifa/html5/2013-08-28/34.html

http://javascript.ruanyifeng.com/bom/webrtc.html

讲的都差不多

也就是怎么使用 getUserMedia

下载来的栗子也比较简单,可以直接运行

问题1:怎么上传

栗子中最后返回的是Blob数据

  1. return new Blob([dataview], { type: type })

因为对html5不熟,所以又查了一些数据

原来HTML5中使用FormData这个对象好方便

  1. var fd = new FormData();
  2. fd.append("audioData", blob);
  3. var xhr = new XMLHttpRequest();
  4. xhr.open("POST", url);
  5. xhr.send(fd);

在C#服务器端 如下代码就可以接收了

  1. public void ProcessRequest(HttpContext context)
  2. {
  3. if (context.Request.Files.Count > )
  4. {
  5. context.Request.Files[].SaveAs("d:\\1.wav");
  6. }
  7. }

问题2:文件体积太大

是的,使用上面的栗子,直接录音保存后基本上2秒就需要400K,一段20秒的录音就达到了的4M

这样的数据根本无法使用,必须想办法压缩数据

我开始尝试读每一段代码

  1. function encodeWAV(samples){
  2. var buffer = new ArrayBuffer(44 + samples.length * 2);
  3. var view = new DataView(buffer);
  4.  
  5. /* RIFF identifier */
  6. writeString(view, 0, 'RIFF');
  7. /* file length */
  8. view.setUint32(4, 32 + samples.length * 2, true);
  9. /* RIFF type */
  10. writeString(view, 8, 'WAVE');
  11. /* format chunk identifier */
  12. writeString(view, 12, 'fmt ');
  13. /* format chunk length */
  14. view.setUint32(16, 16, true);
  15. /* sample format (raw) */
  16. view.setUint16(20, 1, true);
  17. /* channel count */
  18. view.setUint16(22, 2, true);
  19. /* sample rate */
  20. view.setUint32(24, sampleRate, true);
  21. /* byte rate (sample rate * block align) */
  22. view.setUint32(28, sampleRate * 4, true);
  23. /* block align (channel count * bytes per sample) */
  24. view.setUint16(32, 4, true);
  25. /* bits per sample */
  26. view.setUint16(34, 16, true);
  27. /* data chunk identifier */
  28. writeString(view, 36, 'data');
  29. /* data chunk length */
  30. view.setUint32(40, samples.length * 2, true);
  31.  
  32. floatTo16BitPCM(view, 44, samples);
  33.  
  34. return view;
  35. }

上面的代码,就是把字节数据格式化成wav的格式的过程

所以我又去查了wav的头文件

要压缩,就要从上面三个红圈的地方入手

最简单的就是把双声道改成单声道的,

在录音的时候只需要记录一个声道就可以了

  1. // 创建声音的缓存节点,createJavaScriptNode方法的
  2. // 第二个和第三个参数指的是输入和输出都是双声道。
  3. //recorder = context.createJavaScriptNode(bufferSize, 2, 2);
  4. recorder = context.createJavaScriptNode(bufferSize, 1, 1);//这里改成1
  5.  
  6. this.node.onaudioprocess = function(e){
  7. if (!recording) return;
  8. worker.postMessage({
  9. command: 'record',
  10. buffer: [
  11. e.inputBuffer.getChannelData(0)//,
  12. //e.inputBuffer.getChannelData(1)// 这里只需要保存一个
  13. ]
  14. });
  15. }
  16.  
  17. function exportWAV(type){
  18. var bufferL = mergeBuffers(recBuffersL, recLength);
  19. //var bufferR = mergeBuffers(recBuffersR, recLength);
  20. var interleaved = interleave(bufferL);//, bufferR); //合并数据的时候去到对右声道的处理
  21. var dataview = encodeWAV(interleaved);
  22. var audioBlob = new Blob([dataview], { type: type });
  23.  
  24. this.postMessage(audioBlob);
  25. }
  26.  
  27. function interleave(inputL){//, inputR){//混合声道的时候去掉对右声道的处理
  28. var length = inputL.length ;//+ inputR.length;
  29. var result = new Float32Array(length);
  30.  
  31. var index = 0,
  32. inputIndex = 0;
  33.  
  34. while (index < length){
  35. result[index++] = inputL[inputIndex];
  36. //result[index++] = inputR[inputIndex];
  37. inputIndex++;
  38. }
  39. return result;
  40. }

然后修改一下注释,我不喜欢英文的....

  1. function encodeWAV(samples) {
  2. var dataLength = samples.length * 2;
  3. var buffer = new ArrayBuffer(44 + dataLength);
  4. var view = new DataView(buffer);
  5.  
  6. var sampleRateTmp = sampleRate;
  7. var sampleBits = 16;
  8. var channelCount = 1;
  9. var offset = 0;
  10. /* 资源交换文件标识符 */
  11. writeString(view, offset, 'RIFF'); offset += 4;
  12. /* 下个地址开始到文件尾总字节数,即文件大小-8 */
  13. view.setUint32(offset, /*32这里地方栗子中的值错了,但是不知道为什么依然可以运行成功*/ 36 + dataLength, true); offset += 4;
  14. /* WAV文件标志 */
  15. writeString(view, offset, 'WAVE'); offset += 4;
  16. /* 波形格式标志 */
  17. writeString(view, offset, 'fmt '); offset += 4;
  18. /* 过滤字节,一般为 0x10 = 16 */
  19. view.setUint32(offset, 16, true); offset += 4;
  20. /* 格式类别 (PCM形式采样数据) */
  21. view.setUint16(offset, 1, true); offset += 2;
  22. /* 通道数 */
  23. view.setUint16(offset, channelCount, true); offset += 2;
  24. /* 采样率,每秒样本数,表示每个通道的播放速度 */
  25. view.setUint32(offset, sampleRateTmp, true); offset += 4;
  26. /* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
  27. view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4;
  28. /* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
  29. view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
  30. /* 每样本数据位数 */
  31. view.setUint16(offset, sampleBits, true); offset += 2;
  32. /* 数据标识符 */
  33. writeString(view, offset, 'data'); offset += 4;
  34. /* 采样数据总数,即数据总大小-44 */
  35. view.setUint32(offset, dataLength, true); offset += 4;
  36. /* 采样数据 */
  37. floatTo16BitPCM(view, 44, samples);
  38.  
  39. return view;
  40. }

一旦把双声道变为单声道,数据直接缩小一半了

但是还不够

继续缩小体积

除了声道以外,还有一个可以缩减的地方就是采样位数 默认是16位的,我们改成8位 又可以减少一半了

  1. function encodeWAV(samples) {
  2. var sampleBits = 8;//16;//这里改成8位
  3. var dataLength = samples.length * (sampleBits / 8);
  4. var buffer = new ArrayBuffer(44 + dataLength);
  5. var view = new DataView(buffer);
  6.  
  7. var sampleRateTmp = sampleRate;
  8.  
  9. var channelCount = 1;
  10. var offset = 0;
  11. /* 资源交换文件标识符 */
  12. writeString(view, offset, 'RIFF'); offset += 4;
  13. /* 下个地址开始到文件尾总字节数,即文件大小-8 */
  14. view.setUint32(offset, /*32这里地方栗子中的值错了,但是不知道为什么依然可以运行成功*/ 36 + dataLength, true); offset += 4;
  15. /* WAV文件标志 */
  16. writeString(view, offset, 'WAVE'); offset += 4;
  17. /* 波形格式标志 */
  18. writeString(view, offset, 'fmt '); offset += 4;
  19. /* 过滤字节,一般为 0x10 = 16 */
  20. view.setUint32(offset, 16, true); offset += 4;
  21. /* 格式类别 (PCM形式采样数据) */
  22. view.setUint16(offset, 1, true); offset += 2;
  23. /* 通道数 */
  24. view.setUint16(offset, channelCount, true); offset += 2;
  25. /* 采样率,每秒样本数,表示每个通道的播放速度 */
  26. view.setUint32(offset, sampleRateTmp, true); offset += 4;
  27. /* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
  28. view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4;
  29. /* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
  30. view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
  31. /* 每样本数据位数 */
  32. view.setUint16(offset, sampleBits, true); offset += 2;
  33. /* 数据标识符 */
  34. writeString(view, offset, 'data'); offset += 4;
  35. /* 采样数据总数,即数据总大小-44 */
  36. view.setUint32(offset, dataLength, true); offset += 4;
  37. /* 采样数据 */
  38. //floatTo16BitPCM(view, 44, samples);
  39. floatTo8BitPCM(view, 44, samples);//这里改为写入8位的数据
  40. return view;
  41. }

8和16的取值范围不一样

对比一下To8和To16的方法

这里方法是我自己猜的,如果不对还望指出~~~

  1. function floatTo16BitPCM(output, offset, input) {
  2. for (var i = 0; i < input.length; i++, offset += 2) { //因为是int16所以占2个字节,所以偏移量是+2
  3. var s = Math.max(-1, Math.min(1, input[i]));
  4. output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  5. }
  6. }
  7.  
  8. function floatTo8BitPCM(output, offset, input) {
  9. for (var i = 0; i < input.length; i++, offset++) { //这里只能加1了
  10. var s = Math.max(-1, Math.min(1, input[i]));
  11. var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
  12. val = parseInt(255 / (65535 / (val + 32768))); //这里有一个转换的代码,这个是我个人猜测的,就是按比例转换
  13. output.setInt8(offset, val, true);
  14. }
  15. }

怀着忐忑的心情,启动网页...居然听的到声音~居然成功了!!!

经过这样之后又减少了一半大小

最后就是这个采样率了

网页中录音组件的采样率是44100 不知道在哪里改,查询了一些资料,未果...

所以又自己猜测了,是不是我把已经缓存的时候按照比例抛弃一些就可以模拟减少采样率的操作呢?

比如现在已经缓存的数据大小是40960 是不是我直接间隔一位抛弃一次数据,将数据大小变成20480 就可以算是采样率变成22050了呢?

同理,要编程11025只要再抛弃一半的数据?

所以我又做了如下修改

  1. function interleave(inputL, inputR) {
  2. var compression = 44100 / 11025; //计算压缩率
  3. var length = inputL.length / compression;
  4. var result = new Float32Array(length);
  5.  
  6. var index = 0,
  7. inputIndex = 0;
  8.  
  9. while (index < length) {
  10. result[index] = inputL[inputIndex];
  11. inputIndex += compression;//每次都跳过3个数据
  12. index++;
  13. }
  14. return result;
  15. }
  16.  
  17. function encodeWAV(samples) {
  18. var dataLength = samples.length;
  19. var buffer = new ArrayBuffer(44 + dataLength);
  20. var view = new DataView(buffer);
  21.  
  22. var sampleRateTmp = 11025 ;//sampleRate;//写入新的采样率
  23. var sampleBits = 8;
  24. var channelCount = 1;
  25. var offset = 0;
  26. /* 资源交换文件标识符 */
  27. writeString(view, offset, 'RIFF'); offset += 4;
  28. /* 下个地址开始到文件尾总字节数,即文件大小-8 */
  29. view.setUint32(offset, /**/ 36 + dataLength, true); offset += 4;
  30. /* WAV文件标志 */
  31. writeString(view, offset, 'WAVE'); offset += 4;
  32. /* 波形格式标志 */
  33. writeString(view, offset, 'fmt '); offset += 4;
  34. /* 过滤字节,一般为 0x10 = 16 */
  35. view.setUint32(offset, 16, true); offset += 4;
  36. /* 格式类别 (PCM形式采样数据) */
  37. view.setUint16(offset, 1, true); offset += 2;
  38. /* 通道数 */
  39. view.setUint16(offset, channelCount, true); offset += 2;
  40. /* 采样率,每秒样本数,表示每个通道的播放速度 */
  41. view.setUint32(offset, sampleRateTmp, true); offset += 4;
  42. /* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
  43. view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4;
  44. /* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
  45. view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
  46. /* 每样本数据位数 */
  47. view.setUint16(offset, sampleBits, true); offset += 2;
  48. /* 数据标识符 */
  49. writeString(view, offset, 'data'); offset += 4;
  50. /* 采样数据总数,即数据总大小-44 */
  51. view.setUint32(offset, dataLength, true); offset += 4;
  52. /* 采样数据 */
  53. floatTo16BitPCM(view, 44, samples);
  54.  
  55. return view;
  56. }

再次怀着忐忑的心情,启动网页...居然听的到声音~居然又成功了

最后把之前的代码整理封装一下

  1. (function (window) {
  2. //兼容
  3. window.URL = window.URL || window.webkitURL;
  4. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
  5.  
  6. var HZRecorder = function (stream, config) {
  7. config = config || {};
  8. config.sampleBits = config.sampleBits || 8; //采样数位 8, 16
  9. config.sampleRate = config.sampleRate || (44100 / 6); //采样率(1/6 44100)
  10.  
  11. var context = new webkitAudioContext();
  12. var audioInput = context.createMediaStreamSource(stream);
  13. var recorder = context.createJavaScriptNode(4096, 1, 1);
  14.  
  15. var audioData = {
  16. size: 0 //录音文件长度
  17. , buffer: [] //录音缓存
  18. , inputSampleRate: context.sampleRate //输入采样率
  19. , inputSampleBits: 16 //输入采样数位 8, 16
  20. , outputSampleRate: config.sampleRate //输出采样率
  21. , oututSampleBits: config.sampleBits //输出采样数位 8, 16
  22. , input: function (data) {
  23. this.buffer.push(new Float32Array(data));
  24. this.size += data.length;
  25. }
  26. , compress: function () { //合并压缩
  27. //合并
  28. var data = new Float32Array(this.size);
  29. var offset = 0;
  30. for (var i = 0; i < this.buffer.length; i++) {
  31. data.set(this.buffer[i], offset);
  32. offset += this.buffer[i].length;
  33. }
  34. //压缩
  35. var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
  36. var length = data.length / compression;
  37. var result = new Float32Array(length);
  38. var index = 0, j = 0;
  39. while (index < length) {
  40. result[index] = data[j];
  41. j += compression;
  42. index++;
  43. }
  44. return result;
  45. }
  46. , encodeWAV: function () {
  47. var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
  48. var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
  49. var bytes = this.compress();
  50. var dataLength = bytes.length * (sampleBits / 8);
  51. var buffer = new ArrayBuffer(44 + dataLength);
  52. var data = new DataView(buffer);
  53.  
  54. var channelCount = 1;//单声道
  55. var offset = 0;
  56.  
  57. var writeString = function (str) {
  58. for (var i = 0; i < str.length; i++) {
  59. data.setUint8(offset + i, str.charCodeAt(i));
  60. }
  61. }
  62.  
  63. // 资源交换文件标识符
  64. writeString('RIFF'); offset += 4;
  65. // 下个地址开始到文件尾总字节数,即文件大小-8
  66. data.setUint32(offset, 36 + dataLength, true); offset += 4;
  67. // WAV文件标志
  68. writeString('WAVE'); offset += 4;
  69. // 波形格式标志
  70. writeString('fmt '); offset += 4;
  71. // 过滤字节,一般为 0x10 = 16
  72. data.setUint32(offset, 16, true); offset += 4;
  73. // 格式类别 (PCM形式采样数据)
  74. data.setUint16(offset, 1, true); offset += 2;
  75. // 通道数
  76. data.setUint16(offset, channelCount, true); offset += 2;
  77. // 采样率,每秒样本数,表示每个通道的播放速度
  78. data.setUint32(offset, sampleRate, true); offset += 4;
  79. // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
  80. data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
  81. // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
  82. data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
  83. // 每样本数据位数
  84. data.setUint16(offset, sampleBits, true); offset += 2;
  85. // 数据标识符
  86. writeString('data'); offset += 4;
  87. // 采样数据总数,即数据总大小-44
  88. data.setUint32(offset, dataLength, true); offset += 4;
  89. // 写入采样数据
  90. if (sampleBits === 8) {
  91. for (var i = 0; i < bytes.length; i++, offset++) {
  92. var s = Math.max(-1, Math.min(1, bytes[i]));
  93. var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
  94. val = parseInt(255 / (65535 / (val + 32768)));
  95. data.setInt8(offset, val, true);
  96. }
  97. } else {
  98. for (var i = 0; i < bytes.length; i++, offset += 2) {
  99. var s = Math.max(-1, Math.min(1, bytes[i]));
  100. data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  101. }
  102. }
  103.  
  104. return new Blob([data], { type: 'audio/wav' });
  105. }
  106. };
  107.  
  108. //开始录音
  109. this.start = function () {
  110. audioInput.connect(recorder);
  111. recorder.connect(context.destination);
  112. }
  113.  
  114. //停止
  115. this.stop = function () {
  116. recorder.disconnect();
  117. }
  118.  
  119. //获取音频文件
  120. this.getBlob = function () {
  121. this.stop();
  122. return audioData.encodeWAV();
  123. }
  124.  
  125. //回放
  126. this.play = function (audio) {
  127. audio.src = window.URL.createObjectURL(this.getBlob());
  128. }
  129.  
  130. //上传
  131. this.upload = function (url, callback) {
  132. var fd = new FormData();
  133. fd.append("audioData", this.getBlob());
  134. var xhr = new XMLHttpRequest();
  135. if (callback) {
  136. xhr.upload.addEventListener("progress", function (e) {
  137. callback('uploading', e);
  138. }, false);
  139. xhr.addEventListener("load", function (e) {
  140. callback('ok', e);
  141. }, false);
  142. xhr.addEventListener("error", function (e) {
  143. callback('error', e);
  144. }, false);
  145. xhr.addEventListener("abort", function (e) {
  146. callback('cancel', e);
  147. }, false);
  148. }
  149. xhr.open("POST", url);
  150. xhr.send(fd);
  151. }
  152.  
  153. //音频采集
  154. recorder.onaudioprocess = function (e) {
  155. audioData.input(e.inputBuffer.getChannelData(0));
  156. //record(e.inputBuffer.getChannelData(0));
  157. }
  158.  
  159. };
  160. //抛出异常
  161. HZRecorder.throwError = function (message) {
  162. alert(message);
  163. throw new function () { this.toString = function () { return message; } }
  164. }
  165. //是否支持录音
  166. HZRecorder.canRecording = (navigator.getUserMedia != null);
  167. //获取录音机
  168. HZRecorder.get = function (callback, config) {
  169. if (callback) {
  170. if (navigator.getUserMedia) {
  171. navigator.getUserMedia(
  172. { audio: true } //只启用音频
  173. , function (stream) {
  174. var rec = new HZRecorder(stream, config);
  175. callback(rec);
  176. }
  177. , function (error) {
  178. switch (error.code || error.name) {
  179. case 'PERMISSION_DENIED':
  180. case 'PermissionDeniedError':
  181. HZRecorder.throwError('用户拒绝提供信息。');
  182. break;
  183. case 'NOT_SUPPORTED_ERROR':
  184. case 'NotSupportedError':
  185. HZRecorder.throwError('浏览器不支持硬件设备。');
  186. break;
  187. case 'MANDATORY_UNSATISFIED_ERROR':
  188. case 'MandatoryUnsatisfiedError':
  189. HZRecorder.throwError('无法发现指定的硬件设备。');
  190. break;
  191. default:
  192. HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name));
  193. break;
  194. }
  195. });
  196. } else {
  197. HZRecorder.throwErr('当前浏览器不支持录音功能。'); return;
  198. }
  199. }
  200. }
  201.  
  202. window.HZRecorder = HZRecorder;
  203.  
  204. })(window);

js

  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title></title>
  6. </head>
  7. <body>
  8. <div>
  9. <audio controls autoplay></audio>
  10. <input onclick="startRecording()" type="button" value="录音" />
  11. <input onclick="stopRecording()" type="button" value="停止" />
  12. <input onclick="playRecording()" type="button" value="播放" />
  13. <input onclick="uploadAudio()" type="button" value="提交" />
  14. </div>
  15.  
  16. <script type="text/javascript" src="HZRecorder.js"></script>
  17.  
  18. <script>
  19.  
  20. var recorder;
  21.  
  22. var audio = document.querySelector('audio');
  23.  
  24. function startRecording() {
  25. HZRecorder.get(function (rec) {
  26. recorder = rec;
  27. recorder.start();
  28. });
  29. }
  30.  
  31. function stopRecording() {
  32. recorder.stop();
  33. }
  34.  
  35. function playRecording() {
  36. recorder.play(audio);
  37. }
  38.  
  39. function uploadAudio() {
  40. recorder.upload("Handler1.ashx", function (state, e) {
  41. switch (state) {
  42. case 'uploading':
  43. //var percentComplete = Math.round(e.loaded * 100 / e.total) + '%';
  44. break;
  45. case 'ok':
  46. //alert(e.target.responseText);
  47. alert("上传成功");
  48. break;
  49. case 'error':
  50. alert("上传失败");
  51. break;
  52. case 'cancel':
  53. alert("上传被取消");
  54. break;
  55. }
  56. });
  57. }
  58.  
  59. </script>
  60.  
  61. </body>
  62. </html>

好了 下班了

源码下载: RecordingDemo.rar

demo默认采用 44100/6 的采样率 ,原来20秒要4M ,单声道砍一半 2M ,8位砍一半 1M, 6分之一采样率 1M/6 170K左右

微信4秒只有4K是怎么做到的?

宣传一下自己的qq群: (暗号:C#交流) 欢迎喜欢C#,热爱C#,正在学习C#,准备学习C#的朋友来这里互相学习交流,共同进步

群刚建,人不多,但是都是真正热爱C#的 我也是热爱C#的 希望大家可以一起交流,共同进步

HTML5网页录音和压缩,边猜边做..(附源码)的更多相关文章

  1. ICSharpCode.SharpZipLib 压缩、解压文件 附源码

    http://www.icsharpcode.net/opensource/sharpziplib/ 有SharpZiplib的最新版本,本文使用的版本为0.86.0.518,支持Zip, GZip, ...

  2. openlayers4 入门开发系列之批量叠加 zip 压缩 SHP 图层篇(附源码下载)

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

  3. ICSharpCode.SharpZipLi 压缩、解压文件 附源码

    http://www.icsharpcode.net/opensource/sharpziplib/ 有SharpZiplib的最新版本,本文使用的版本为0.86.0.518,支持Zip, GZip, ...

  4. html5网页录音和语音识别

    背景 在输入方式上,人们总是在追寻一种更高效,门槛更低的方式,来降低用户使用产品的学习成本.语音输入也是一种尝试较多的方式,有些直接使用语音(如微信语音聊天),有些需要将语音转化为文字(语音识别).接 ...

  5. 精选9个值得学习的 HTML5 效果【附源码】

    这里精选了一组很酷的 HTML5 效果.HTML5 是现 Web 开发领域的热点, 拥有很多让人期待已久的新特性,特别是在移动端,Web 开发人员可以借助 HTML5 强大功能轻松制作各种交互性强.效 ...

  6. 让你心动的 HTML5 & CSS3 效果【附源码下载】

    这里集合的这组 HTML5 & CSS3 效果,有的是网站开发中常用的.实用的功能,有的是先进的 Web 技术的应用演示.不管哪一种,这些案例中的技术都值得我们去探究和学习. 超炫的 HTML ...

  7. 8个前沿的 HTML5 & CSS3 效果【附源码下载】

    作为一个前沿的 Web 开发者,对于 HTML5 和 CSS3 技术或多或少都有掌握.前几年这些新技术刚萌芽的时候,开发者们已经使用它们来小试牛刀了,如今这些先进技术已经遍地开发,特别是在移动端大显身 ...

  8. HTML5与CSS3实例教程(第2版) 附源码 中文pdf扫描版

    HTML5和CSS3技术是目前整个网页的基础.<HTML5与CSS3实例教程(第2版)>共分3部分,集中讨论了HTML5和CSS3规范及其技术的使用方法.这一版全面讲解了最新的HTML5和 ...

  9. arcgis api 3.x for js 入门开发系列批量叠加 zip 压缩 SHP 图层优化篇(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

随机推荐

  1. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  2. ABP文档 - 本地化

    文档目录 本节内容: 简介 应用语言 本地化源 XML文件 注册XML本地化源 JSOn文件 注册JSON本地化源 资源文件 自定义源 获取一个本地文本 在服务端 在MVc控制器里 在MVC视图里 在 ...

  3. 标准产品+定制开发:专注打造企业OA、智慧政务云平台——山东森普软件,交付率最高的技术型软件公司

    一.公司简介山东森普信息技术有限公司(以下简称森普软件)是一家专门致力于移动互联网产品.企业管理软件定制开发的技术型企业.公司总部设在全国五大软件园之一的济南齐鲁软件园.森普SimPro是由Simpl ...

  4. logstash file输入,无输出原因与解决办法

    1.现象 很多同学在用logstash input 为file的时候,经常会出现如下问题:配置文件无误,logstash有时一直停留在等待输入的界面 2.解释 logstash作为日志分析的管道,在实 ...

  5. 编写高质量代码:改善Java程序的151个建议(第6章:枚举和注解___建议88~92)

    建议88:用枚举实现工厂方法模式更简洁 工厂方法模式(Factory Method Pattern)是" 创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其它子类" ...

  6. H3 BPM让天下没有难用的流程之功能介绍

    H3 BPM10.0功能地图如下:  图:H3 BPM 功能地图 一.流程引擎 H3  BPM 流程引擎遵循WFMC 标准的工作流引擎技术,设计可运行的流程和表单,实现工作任务在人与人.人与系统.系统 ...

  7. Visual Studio Code——Angular2 Hello World 之 2.0

    最近看到一篇用Visual Studio Code开发Angular2的文章,也是一篇入门教程,地址为:使用Visual Studio Code開發Angular 2專案.这里按部就班的做了一遍,感觉 ...

  8. 当 IDENTITY_INSERT 设置为 OFF 时,不能为表 'T_Shell' 中的标识列插入显式值。

    --允许将显示值插入表的标识列中-ON:允许 OFF:不允许set identity_insert T_shell ONset identity_insert T_Shell OFF

  9. Oracle创建表空间

    1.创建表空间 导出Oracle数据的指令:/orcl file=C:\jds.dmp owner=jds 导入Oracle数据的指令:imp zcl:/orcl file=C:\jds.dmp fu ...

  10. Linux基础介绍【第六篇】

    定时任务crond介绍 crond是什么? crond是linux系统中用来定期执行命令或指定程序任务的一种服务或软件.一般情况下,安装完CentOS5/6 linux操作系统之后,默认便会启动cro ...