開發ASP.NET MVC 在線錄音錄像(音視頻錄制並上傳)


最近有個在線招聘錄音的開發需求,需要在招聘網站上讓招聘者上傳錄音和視頻。

找到兩個不錯的javascript開源,可以在除了IE以外的瀏覽器運行。

https://github.com/mattdiamond/Recorderjs

https://github.com/muaz-khan/RecordRTC

核心算法如下:

Bit rate = (sampling rate) × (bit depth) × (number of channels)

舉例說明,CD音質的Bit rate就是:

sampling rate =44.1 kHz

bit depth =16bit

通道(左聲道+右聲道) = 2

44100 × 16 × 2 = 1411200 bits per second = 1411.2 kbit/s

注意,以上單位是位!不是字節! 

開發中有一個坑點,就是音頻的采樣率比較高,造成音頻的比特率較大,文件隨之很大,找了一些資料,只能減少一半:

http://stackoverflow.com/questions/16296645/decrease-bitrate-on-wav-file-created-with-recorderjs/26245260#26245260

 

Webrtc的音頻編解碼采用iLIBC/iSAC/G722/PCM16/RED/AVT編解碼技術

在iSAC編碼下,采樣率是16khz,24khz,32khz;(默認為16khz)
在iLBC編碼(Internet Low Bitrate Codec)下,采樣頻率:8khz;20ms幀比特率為15.2kbps

在iLBC下能最大化的減少比特率.但在這兩個js庫中查不到使用了什么編碼。

 

嘗試1:

window.AudioContext = window.AudioContext || window.webkitAudioContext;

var audioContext = new AudioContext();

audioContext.sampleRate = 16000;

//發現是無法設置AudioContext 的采樣率,這是一個操作系統指定的不可改屬性
console.log(audioContext.sampleRate);

 

嘗試2:成功。方法是通過將wav轉為mp3:

https://github.com/remusnegrota/Recorderjs/tree/Recorder.js-For-Mp3 

 

使用Recorderjs的例子在:examples/example_simple_exportwav

MVC工程是使用RecordRTC。

 

一些預備知識:

碼率:Bit Rate,指視頻或音頻文件在單位時間內使用的數據流量,該參數的單位通常是Kbps,也就是千比特每秒。通常2000kbps~3000kbps就已經足以將畫質效果表現到極致了。碼率參數與視頻文件最終體積大小有直接性的關系。 (編碼碼率---軟件)

  混合碼率:Overall Bit Rate,指視頻文件中視頻和音頻混合后的整體平均碼率。一般描述一個視頻文件的碼率都是指OBR,如新浪播客允許的OBR上限為523Kbps。 

  固定碼率:Constant Bit Rate,指的是編碼器的輸出碼率(或者解碼器的輸入碼率)應該是固定制(常數)。CBR不適合高清晰度視頻的編碼,因為CBR將導致沒有足夠的碼率應對復雜多變內容部分進行編碼(從而導致畫質下降),同時在簡單的內容部分會浪費一些碼率。 

  可變碼率:Variable Bit Rate,編碼器的輸出碼率(或者解碼器的輸入碼率)可以根據編碼器的輸入源信號的負責度自適應的調整,目的是達到保持輸出質量保持不變而不是保持輸出碼率保持不變。VBR編碼會消耗較多的計算時間,但可以更好的利用有限的存儲空間:用比較多的碼率對復雜度高的段進行編碼,用比較少的碼率對復雜度低的段進行編碼。總之需要清晰度高且體積小的視頻,選擇VBR是明智的選擇。 

  平均碼率:Average Bit Rate,指音頻或視頻的平均碼率,可以簡單的認為等於文件大小除以播放時間。在音頻編碼方面與CBR基本相同,會按照設定的目標碼率進行編碼。但當編碼器認為“適當”的時候,會使用高於目標碼率的數值來進行編碼以保證更好的質量。 

  幀率:Frame Rate,是用於測量畫面顯示幀數的量度。所謂的測量單位為每秒顯示幀數(Frames per Second,縮寫:FPS)。如電影的幀率一般是25fps和29.97fps,而第一人稱射擊游戲等要求畫面極為順暢的特殊場合,則需要30fps以上的效果,高於60fps就沒有必要了。 

  采樣率:每秒從連續信號中提取並組成離散信號的采樣個數,它用赫茲(Hz)來表示。一般音樂CD的采樣率是44100Hz,所以視頻編碼中的音頻采樣率保持在這個級別就完全足夠了,通常視頻轉換器也將這個采樣率作為默認設置。(芯片采樣次數---硬件,得到的是原始波形文件pcm) 

  Single Pass:在編碼的時候只進行一次運算,直接生成經過編碼的視頻文件。 

  Two Pass:需要運算兩次,可以理解為先進行一次全局的計算,收集畫面信息,並將這些信息記錄到信息文件。第二次才根據采集的信息,正式進行壓縮,生成壓縮文件。 

  Single pass模式編碼較簡單,編碼速度較快,但是最終質量不如Two pass模式好,對於視頻源本身畫質就不佳的編碼過程可以采用。Two pass通過第一次運算的信息采集,可以讓需要高碼率的運動畫面可以分配更的碼率來保證畫面質量。而對於不包含太多運動信息的靜態畫面,則可以消減分配的碼率。Twopass模式可以在影片容量與畫面質量之間找到最佳平衡點。所以要求畫面清晰的視頻,肯定要選擇Two Pass,只是編碼速度慘不忍睹。

  封裝格式:多媒體封裝格式也稱多媒體容器 (Multimedia Container),它不同於H.264、 AAC這類編碼格式,它只是為多媒體編碼提供了一個“外殼”,也就是所謂的視頻格式。如MP4、AVI、MKV、FLV、WMA等。 

  畫面比例:Aspect Ratio,指視頻畫面寬和高的比例。常見的比例有16:9和4:3。電視媒體有嚴格的視頻制式要求,視頻比例和幀數都是固定的,而網絡傳播的視頻比例則較為自由。一般DVD和BD電影的視頻比例大多是寬屏或者超寬屏。在視頻編碼過程中一定要注意畫面比例是否正確,不然就會出現畫面拉伸變形。 

  分辨率:指視頻寬高的像素數值,單位為Px。通常視頻分辨率的數值寬高比要等於畫面比例,不然視頻文件就會產生黑邊。標准1080P的分辨率為1920×1080,幀率為60fps,也就是真高清。而最常見的網絡傳播的1080P高清片幀率通常為23.976 fps。

 

 

什么是 采樣率 和 比特率? 16bit/44.1kHz、24bit/48kHz、24bit/192kHz 分別代表什么?
 

簡單來講,采樣率和比特率就像是坐標軸上的橫縱坐標。 橫坐標的采樣率表示了每秒鍾的采樣次數。而聲音的位數就表示每個取樣的數據量,數據量越大,回放的聲音越准確。

 

簡單來講,采樣率和比特率就像是坐標軸上的橫縱坐標。 
    橫坐標的采樣率表示了每秒鍾的采樣次數。 
    縱坐標的比特率表示了用數字量來量化模擬量的時候的精度,即當前的模擬量用多少位的2進制數對其編碼表示。

以電話為例,每秒3000次取樣,每個取樣是7比特,那么電話的比特率是21000。而CD是每秒44100次取樣,兩個聲道,每個取樣是13位PCM編碼,所以CD的比特率是44100*2*13=1146600。

 

1G容量用480Mbps傳有多快,一想,這還不簡單,480Mbps多快,用1024M除下不就得了,后來發現這么做不對,我將"480Mbps"誤解為480兆/秒。事實上"480MBPS"應為480兆比特/秒或480兆位/秒,它等於60兆/秒.要是傳1G容量應該是1024M/60=17秒。
    一張700M的光盤容量為700MB = 1024*700KB = 716800KB
    立體聲16位44.1KHZ聲音的比特率為 44100*16*2=1411200bps
    1411200/8/1024=172.26KB
/s
    716800/172.26=4161.15s
    4151.15/60=69.3525min

 

采樣率

采樣率實際上是指當將聲音儲存至計算機中,必須經過一個錄音轉換的過程,轉換些什么呢?就是把聲音這種模擬信號轉成計算機可以辨識的數字信號,在轉換過程中將聲波的波形以微分方式切開成許多單位,再把每個切開的聲波以一個數值來代表該單位的一個量,以此方式完成采樣的工作,而在單位時間內切開的數量便是所謂的采樣頻率,說明白些,就是模擬轉數字時每秒對聲波采樣的數量,像是CD音樂的標准采樣頻率為44.1KHz,這也是目前聲卡與計算機作業間最常用的采樣頻率。

另外,在單位時間內采樣的數量越多就會越接近原始的模擬信號,在將數字信號還原成模擬信號時也就越能接近真實的原始聲音;相對的越高的采樣率,資料的大小就越大,反之則越小,當然也就越不真實了。數字數據量的大小與聲道數、采樣率、音質分辨率有着密不可分的關系。

前面提到CD音樂的采樣率為44.1KHz,而在計算機上的DVD音效則為48KHz (經聲卡轉換) ,一般的電台FM廣播為32KHz,其它的音效則因不同的應用有不同的采樣率,像是以網絡會議之類的應用就不要使用高的采樣率,否則在傳遞這些聲音數據時會是一件十分痛苦的事。

當然,目前比較盛行的高清碟的采樣率就相當的高,達到了192kHz。而目前的聲卡,絕大多數都可以支持44.1kHz、48kHz、96kHz,高端產品可支持192kHz甚至更高。

 

采樣率Vs.比特率 samping rate Vs bit rate - 阿英 - Mr.Right

上圖中 16bits 對應 2^16 = 65536個電平, 20*log10(65536)  = 96.3296dB

 

比特率

聲波在轉為數字的過程中不是只有采樣率會影響原始聲音的完整性,另一個亦具有舉足輕重的參數——量化精度(比特率),也是相當的重要。一般來說,音質分辨率就是大家常說的bit數。目前,絕大多數的聲卡都已經可以支持24bit的量化精度。

那么,什么是量化精度呢?前面曾說明采樣頻率,它是針對每秒鍾所采樣的數量,而量化精度則是對於聲波的“振幅”進行切割,形成類似階梯的度量單位。所以,如果說采樣頻率是對聲波水平進行的X軸切割,那么量化精度則是對Y軸的切割,切割的數量是以最大振幅切成2的n次方計算,n就是bit數。

舉個例子,如果是8bit,那么在振幅方面的采樣就有256階,若是16bit,則振幅的計量單位便會成為65536階,越多的階數就越能精確描述每個采樣的振幅高度。如此,也就越接近原始聲波的“能量”,在還原的過程序也就越接近原始的聲音了。

另外,bit的數目還決定了聲波振幅的范圍(即動態范圍,最大音量與最小音量的差距)。如果這個位數越大,則能夠表示的數值越大,描述波形更精確。每一個Bit的數據可以記錄約等於6dB動態的信號。一般來說,16Bit可以提供最大96dB的動態范圍(加高頻顫動后只有92dB)。每增加一個Bit的量化精度,這個值就增加6dB。因此,我們可以推斷出20Bit可以達到120dB的動態范圍,而24Bit則可以提供高達144dB的動態范圍。

那么,動態范圍大了,會有什么好處呢?動態范圍是指系統的輸出噪音功率和最大不失真音量功率的比值,這個值越大,則系統可以承受很高的動態。比如1812序曲中的炮聲,如果系統動態過小,高於動態范圍的信號將被削波(Clipping, 高於0dB的溢出信號將被砍掉,會導致噼里啪啦的聲音)。

視圖代碼:

@{
    ViewBag.Title = "Index";
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>錄像並上傳</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <link rel="stylesheet" href="/css/style.css">

        <style>
            audio {
                vertical-align: bottom;
                width: 10em;
            }

            video { vertical-align: top;max-width: 100%; }

            input {
                border: 1px solid #d9d9d9;
                border-radius: 1px;
                font-size: 2em;
                margin: .2em;
                width: 30%;
            }

            p, .inner { padding: 1em; }

            li {
                border-bottom: 1px solid rgb(189, 189, 189);
                border-left: 1px solid rgb(189, 189, 189);
                padding: .5em;
            }

            label {
                display: inline-block;
                width: 8em;
            }
        </style>
        <script>
            document.createElement('article');
            document.createElement('footer');
        </script>
        
        <!-- script used for audio/video/gif recording -->
        <script src="/js/RecordRTC.js"> </script>
    </head>

    <body>
        <article>          
        
            <section class="experiment">  
             
                <p style="text-align:center;">
                    <video id="preview" controls style="border: 1px solid rgb(15, 158, 238); height: 240px; width: 320px;"></video> 
                </p>
                <hr />

                <button id="record">錄像</button>
                <button id="stop" disabled>停止並上傳</button>
                <button id="delete" disabled>從服務器刪除錄像</button>

                <div id="container" style="padding:1em 2em;"></div>
            </section>
            
            
            
            <script>
                // PostBlob方法使用 XHR2 和 FormData 來發送提交視頻文件 ,IE不支持
                // 錄制blob並發送到服務器
                function PostBlob(blob, fileType, fileName) {
                    // FormData
                    var formData = new FormData();
                    //文件名鍵值對
                    formData.append(fileType + '-filename', fileName);
                    //blob鍵值對
                    formData.append(fileType + '-blob', blob);

                    // progress-bar 進度條
                    var hr = document.createElement('hr');
                    container.appendChild(hr);
                    var strPercentage = document.createElement('strPercentage');
                    strPercentage.id = 'percentage';
                    strPercentage.innerHTML = fileType + ' 上傳進度: ';
                    container.appendChild(strPercentage);
                    var progress = document.createElement('progress');
                    container.appendChild(progress);

                    // POST the Blob using XHR2
                    xhr('/Home/PostRecordedAudioVideo', formData, progress, percentage,
                        function (fName) //回調
                        {
                            container.appendChild(document.createElement('hr'));
                            var mediaElement = document.createElement(fileType);

                            var source = document.createElement('source');
                            source.src = location.href + 'uploads/' + fName.replace(/"/g, '');

                            if (fileType == 'video') source.type = 'video/webm; codecs="vp8, vorbis"';
                            if (fileType == 'audio') source.type = !!navigator.mozGetUserMedia ? 'audio/ogg' : 'audio/wav';

                            mediaElement.appendChild(source);

                            mediaElement.controls = true;
                            container.appendChild(mediaElement);
                            mediaElement.play();

                            progress.parentNode.removeChild(progress);
                            strPercentage.parentNode.removeChild(strPercentage);
                            hr.parentNode.removeChild(hr);
                        }
                    ); //xhr 結束
                }//PostBlob 方法結束

                var record = document.getElementById('record');
                var stop = document.getElementById('stop');
                var deleteFiles = document.getElementById('delete');

                var audio = document.querySelector('audio');

                var recordVideo = document.getElementById('record-video');
                var preview = document.getElementById('preview');

                var container = document.getElementById('container');

                // 如果只想用chrome錄制音頻,可以設置
                //  "isFirefox=true"
                var isFirefox = !!navigator.mozGetUserMedia;

                //******** 錄音函數 開始 ********
                var recordAudio, recordVideo;
                record.onclick = function () {
                    record.disabled = true;

                    //提示用戶需要權限去使用像攝像頭或麥克風之類的媒體設備.如果用戶提供了這個權限
                    //successCallback函數會被調用,且接收一個LocalMediaStream 對象作為參數.
                    //語法:navigator.getUserMedia ( constraints, successCallback, errorCallback );

                    var mediaConstraints = {
                        audio: {
                            mandatory: {
                                echoCancellation: true,
                                googAutoGainControl: false,
                                googNoiseSuppression: false,
                                googHighpassFilter: false
                            },
                            optional: [{
                                googAudioMirroring: false
                            }]
                        },
                        video: true
                    };

                    navigator.getUserMedia(
                       mediaConstraints,
                    //successCallback函數被調用
                    function (stream) {
                        preview.src = window.URL.createObjectURL(stream);
                        preview.muted = true;//如果正在錄制的時候進行預覽不靜音播放會產生循環回音
                        preview.play();

                        // var legalBufferValues = [256, 512, 1024, 2048, 4096, 8192, 16384];
                        // sample-rates in at least the range 22050 to 96000.
                        recordAudio = RecordRTC(stream, {
                            //bufferSize: 256,
                            //sampleRate: 22050, //設置22050后,音軌長度增加一倍
                            //audioBitsPerSecond: 21000,//無效
                            //bitsPerSecond: 64000 , //無效
                            //recorderType: StereoAudioRecorder,
                            numberOfAudioChannels: 1, //單聲道(左聲道)這個將減少wav文件一半大小
                            onAudioProcessStarted: function () {
                                if (!isFirefox) {
                                    recordVideo.startRecording();
                                }
                            }
                        });

                        if (isFirefox) {
                            recordAudio.startRecording();
                        }

                        if (!isFirefox) {
                            
                            recordVideo = RecordRTC(stream, {
                            //recordVideo = WhammyRecorder(stream, {
                                    type: 'video'
                            });
                            recordAudio.startRecording();
                        }

                        stop.disabled = false;
                    },

                    //errorCallback函數被調用
                    function (error) {
                        alert(JSON.stringify(error, null, '\t'));
                    });
                };
                //******** 錄音函數 結束 ********

                //******** 結束錄音函數 開始 ********

                var fileName;
                stop.onclick = function () {
                    record.disabled = false;
                    stop.disabled = true;

                    preview.src = '';

                    fileName = Math.round(Math.random() * 99999999) + 99999999;

                    if (!isFirefox) {
                        recordAudio.stopRecording(function () {
                            PostBlob(recordAudio.getBlob(), 'audio', fileName + '.wav');
                        });
                    } else {
                        recordAudio.stopRecording(function (url) {
                            preview.src = url;
                            PostBlob(recordAudio.getBlob(), 'video', fileName + '.webm');
                        });
                    }

                    if (!isFirefox) {
                        recordVideo.stopRecording(function () {
                            PostBlob(recordVideo.getBlob(), 'video', fileName + '.webm');
                        });
                    }

                    deleteFiles.disabled = false;
                };
                //******** 結束錄音函數 結束 ********


                deleteFiles.onclick = function () {
                    deleteAudioVideoFiles();
                };

                function deleteAudioVideoFiles() {
                    deleteFiles.disabled = true;
                    if (!fileName) return;
                    var formData = new FormData();
                    formData.append('delete-file', fileName);
                    xhr('/Home/DeleteFile', formData, null, null, function (response) {
                        console.log(response);
                    });
                    fileName = null;
                    container.innerHTML = '';
                }

                function xhr(url, data, progress, percentage, callback) {
                    var request = new XMLHttpRequest();
                    request.onreadystatechange = function () {
                        if (request.readyState == 4 && request.status == 200) {
                            callback(request.responseText);
                        }
                    };

                    if (url.indexOf('/Home/DeleteFile') == -1) {
                        request.upload.onloadstart = function () {
                            percentage.innerHTML = 'Upload started...';
                        };

                        request.upload.onprogress = function (event) {
                            progress.max = event.total;
                            progress.value = event.loaded;
                            percentage.innerHTML = 'Upload Progress ' + Math.round(event.loaded / event.total * 100) + "%";
                        };

                        request.upload.onload = function () {
                            percentage.innerHTML = 'Saved!';
                        };
                    }

                    request.open('POST', url);
                    request.send(data);
                }

                window.onbeforeunload = function () {
                    if (!!fileName) {
                        deleteAudioVideoFiles();
                        return 'It seems that you\'ve not deleted audio/video files from the server.';
                    }
                };
            </script>
            
            
 
        </article>

    
        
        <footer>
            <p>
 
            </p>
        </footer>
    
        <!-- commits.js is useless for you! -->
        <script src="/js/commits.js" async> </script>
    </body>
</html>

 

控制器:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebTalk.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult PostRecordedAudioVideo()
        {
            foreach (string upload in Request.Files)
            {
                var path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
                var file = Request.Files[upload];
                if (file == null) continue;

                file.SaveAs(Path.Combine(path, Request.Form[0]));
            }
            return Json(Request.Form[0]);
        }

        [HttpPost]
        public ActionResult DeleteFile()
        {
            var fileUrl = AppDomain.CurrentDomain.BaseDirectory + "uploads/" + Request.Form["delete-file"];
            new FileInfo(fileUrl + ".wav").Delete();
            new FileInfo(fileUrl + ".webm").Delete();
            return Json(true);
        }

        public ActionResult About()
        {
            return View();
        }
    }
}

 

一個優化文件大小的做法:

http://www.cnblogs.com/blqw/p/3782420.html

 

修改后js

//兼容
window.URL = window.URL || window.webkitURL;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

var Storage = {};

if (typeof AudioContext !== 'undefined') {
    Storage.AudioContext = AudioContext;
} else if (typeof webkitAudioContext !== 'undefined') {
    Storage.AudioContext = webkitAudioContext;
}

function HZRecorder(stream, config) {
    config = config || {};
    config.sampleBits = config.sampleBits || 8;
    config.sampleRate = config.sampleRate || (44100 / 6);
    var channelCount = 1;//單聲道
    var self = this;

    if (!Storage.AudioContextConstructor) {
        Storage.AudioContextConstructor = new Storage.AudioContext();
    }

    var context = Storage.AudioContextConstructor;
    var audioInput = context.createMediaStreamSource(stream);
    var recorder;
    if (context.createJavaScriptNode) {
        recorder = context.createJavaScriptNode(4096, channelCount, channelCount);
    } else if (context.createScriptProcessor) {
        recorder = context.createScriptProcessor(4096, channelCount, channelCount);
    } else {
        throw 'WebAudio API has no support on this browser.';
    }

    recorder.onaudioprocess = function (e) {
        audioData.input(e.inputBuffer.getChannelData(0));
    }

    var audioData = {
        size: 0,
        buffer: [],
        inputSampleRate: context.sampleRate,
        inputSampleBit: 16,
        outputSampleRate: config.sampleRate,
        outputSampleBit: config.sampleBits,
        input: function (data) {
            this.buffer.push(new Float32Array(data));
            this.size += data.length;
        },
        compress: function () {
            var data = new Float32Array(this.size);
            var offset = 0;
            for (var i = 0; i < this.buffer.length; i++) {
                data.set(this.buffer[i], offset);
                offset += this.buffer[i].length;
            }

            //壓縮
            var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
            var length = data.length / compression;
            var result = new Float32Array(length);
            var index = 0, j = 0;
            while (index < length) {
                result[index] = data[j];
                j += compression;
                index++;
            }
            console.log(this.inputSampleRate);
            console.log(this.outputSampleRate);
            console.log(this.compression);

            return result;
        },
        encodeWAV: function () {
            var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
            var sampleBits = Math.min(this.inputSampleBit, this.outputSampleBit);
            var bytes = this.compress();
            var dataLength = bytes.length * (sampleBits / 8);
            var buffer = new ArrayBuffer(44 + dataLength);
            var data = new DataView(buffer);

            var writeString = function (offset, str) {
                for (var i = 0; i < str.length; i++) {
                    data.setUint8(offset + i, str.charCodeAt(i));
                }
            }

                //資源交換文件標識符
                writeString(0, 'RIFF');
                //下個地址開始到文件尾總字節數
                data.setUint32(4, 36 + dataLength, true);
                //WAV文件標識
                writeString(8, 'WAVE');
                //波形格式標識
                writeString(12, 'fmt ');
                //過濾字節,一般為0x10=16
                data.setUint32(16, 16, true);
                //格式類別(PCM形式采樣數據)
                data.setUint16(20, 1, true);
                //通道數
                data.setUint16(22, channelCount, true);
                //采樣率,每秒樣本數,表示每個通達奧的播放速度
                data.setUint32(24, sampleRate, true);
                //波形數據傳輸率(每秒平均字節數)
                data.setUint32(28, channelCount * sampleRate * (sampleBits / 8), true);
                //快數據調整數 采樣一次占用字節數
                data.setUint16(32, channelCount * (sampleBits / 8), true);
                //每樣本數據位數
                data.setUint16(34, sampleBits, true);
                //數據標識符
                writeString(36, 'data');
                //采樣數據總數
                data.setUint32(40, dataLength, true);
            var offset = 44;
            if (sampleBits === 8) {
                for (var i = 0; i < bytes.length; i++, offset++) {
                    var s = Math.max(-1, Math.min(1, bytes[i]));
                    var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
                    val = parseInt(255 / (65535 / (val + 32768)));
                    data.setInt8(offset, val, true);
                }
            } else {
                for (var i = 0; i < bytes.length; i++, offset += 2) {
                    var s = Math.max(-1, Math.min(1, bytes[i]));
                    data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
                }
            }

            return new Blob([data], { type: 'audio/wavv' });
        }
    };

    this.start = function () {
        audioInput.connect(recorder);
        recorder.connect(context.destination);
    }

    this.stop = function () {
        recorder.disconnect();
    }

    this.getBlob = function () {
        this.stop();
        return audioData.encodeWAV();
    }

    var returnObject = {
        start: start,
        stop: stop,
        getBlob: getBlob,
        blob: null,
        bufferSize: 0,
        sampleRate: 0,
        buffer: null,
        view: null
    };

    return returnObject;
}
View Code

 

 

完成代碼下載: source code download

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM