博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
开发ASP.NET MVC 在线录音录像(音视频录制并上传)
阅读量:4135 次
发布时间:2019-05-25

本文共 12074 字,大约阅读时间需要 40 分钟。

最近有个在线招聘录音的开发需求,需要在招聘网站上让招聘者上传录音和视频。

找到两个不错的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;}            录像并上传        


 

控制器:

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

 

 

完成代码下载:

 

你可能感兴趣的文章
Servlet进阶和JSP基础
查看>>
servlet中的cookie和session
查看>>
过滤器及JSP九大隐式对象
查看>>
软件(项目)的分层
查看>>
菜单树
查看>>
Servlet的生命周期
查看>>
JAVA八大经典书籍,你看过几本?
查看>>
《读书笔记》—–书单推荐
查看>>
JAVA数据类型
查看>>
【Python】学习笔记——-6.2、使用第三方模块
查看>>
【Python】学习笔记——-7.0、面向对象编程
查看>>
【Python】学习笔记——-7.2、访问限制
查看>>
【Python】学习笔记——-7.3、继承和多态
查看>>
【Python】学习笔记——-7.5、实例属性和类属性
查看>>
git中文安装教程
查看>>
虚拟机 CentOS7/RedHat7/OracleLinux7 配置静态IP地址 Ping 物理机和互联网
查看>>
Jackson Tree Model Example
查看>>
常用js收集
查看>>
如何防止sql注入
查看>>
springmvc传值
查看>>