npm install
npm run serve
npm run build
npm run test:unit
npm run lint
Vue+ Nodejs+Travis-CI
Home Page: https://pkjy.github.io
npm install
npm run serve
npm run build
npm run test:unit
npm run lint
前端实现录音有两种方式,一种是使用MediaRecorder,另一种是使用WebRTC的getUserMedia结合AudioContext,MediaRecorder出现得比较早,只不过Safari/Edge等浏览器一直没有实现,所以兼容性不是很好,而WebRTC已经得到了所有主流浏览器的支持,如Safari 11起就支持了。所以我们用WebRTC的方式进行录制。
利用AudioContext播放声音的使用,我已经在《Chrome 66禁止声音自动播放之后》做过介绍,本篇我们会继续用到AudioContext的API.
为实现录音功能,我们先从播放本地文件音乐说起,因为有些API是通用的。
播放音频可以使用audio标签,也可以使用AudioContext,audio标签需要一个url,它可以是一个远程的http协议的url,也可以是一个本地的blob协议的url,怎么创建一个本地的url呢?
提供一个file input上传控件让用户选择本地的文件和一个audio标签准备来播放。当用户选择文件后会触发onchange事件,在onchange回调里面就可以拿到文件的内容,如下代码所示:
这里使用一个FileReader读取文件,读取为ArrayBuffer即原始的二进制内容,把它打印如下所示:
可以用这个ArrayBuffer实例化一个Uint8Array就能读取它里面的内容,Uint8Array数组里面的每个元素都是一个无符号整型8位数字,即0 ~ 255,相当于每1个字节的0101内容就读取为一个整数。
这个arrayBuffer可以转成一个blob,然后用这个blob生成一个url,如下代码所示:
主要利用URL.createObjectURL这个API生成一个blob的url,这个url打印出来是这样的:
blob:null/c2df9f4d-a19d-4016-9fb6-b4899bac630d
然后丢给audio标签就能播放了,作用相当于一个远程的http的url.
在使用ArrayBuffer生成blob对象的时候可以指定文件类型或者叫mime类型,如下代码所示:
这个mime可以通过file input的files[0].type得到,而files[0]是一个File实例,File有mime类型,而Blob也有,因为File是继承于Blob的,两者是同根的。所以在上面实现代码里面其实不需要读取为ArrayBuffer然后再封装成一个Blob,直接使用File就行了,如下代码所示:
而使用AudioContext需要拿到文件的内容,然后手动进行音频解码才能播放。
使用AudioContext怎么播放声音呢,我们来分析一下它的模型,如下图所示:
我们拿到一个ArrayBuffer之后,使用AudioContext的decodeAudioData进行解码,生成一个AudioBuffer实例,把它做为AudioBufferSourceNode对象的buffer属性,这个Node继承于AudioNode,它还有connect和start两个方法,start是开始播放,而在开始播放之前,需要调一下connect,把这个Node连结到audioContext.destination即扬声器设备。代码如下所示:
把解码后的audioBuffer打印出来,如下图所示:
他有几个对开发人员可见的属性,包括音频时长,声道数量和采样率。从打印的结果可以知道播放的音频是2声道,采样率为44.1k Hz,时长为196.8s。关于声音这些属性的意义可见《从Chrome源码看audio/video流媒体实现一》.
从上面的代码可以看到,利用AudioContext处理声音有一个很重要的枢纽元素AudioNode,上面使用的是AudioBufferSourceNode,它的数据来源于一个解码好的完整的buffer。其它继承于AudioNode的还有GainNode:用于设置音量、BiquadFilterNode:用于滤波、ScriptProcessorNode:提供了一个onaudioprocess的回调让你分析处理音频数据、MediaStreamAudioSourceNode:用于连接麦克风设备,等等。这些结点可以用装饰者模式,一层层connect,如上面代码使用到的bufferSourceNode可以先connect到gainNode,再由gainNode connect到扬声器,就能调整音量了。
这些节点都是使用audioContext的工厂函数创建的,如调createGainNode就可以创建一个gainNode.
说了这么多就是为了录音做准备,录音需要用到ScriptProcessorNode.
上面播放音乐的来源是本地音频文件,而录音的来源是麦克风,为了能够获取调起麦克风并获取数据,需要使用WebRTC的getUserMedia,如下代码所示;
在调用getUserMedia的时候指定需要录制音频,如果同时需要录制视频那么再加一个video: true就可以了,也可以指定录制的格式:
调用的时候,浏览器会弹一个框,询问用户是否允许使用用麦克风:
如果用户点了拒绝,那么会抛异常,在catch里面可以捕获到,而如果一切顺序的话,将会返回一个MediaStream对象:
它是音频流的抽象,把这个流用来初始化一个MediaStreamAudioSourceNode对象,然后把这个节点connect连接到一个JavascriptProcessorNode,在它的onaudioprocess里面获取到音频数据,然后保存起来,就得到录音的数据。
如果想直接把录的音直接播放出来的话,那么只要把它connect到扬声器就行了,如下代码所示:
但一边录一边播的话,如果没用耳机的话容易产生回音,这里不要播放了。
为了获取录到的音的数据,我们把它connect到一个javascriptProcessorNode,为此先创建一个实例:
这里是使用createScriptProcessor创建的对象,需要传三个参数:一个是缓冲区大小,通常设定为4kB,另外两个是输入和输出频道数量,这里设定为双声道。它里面有两个缓冲区,一个是输入inputBuffer,另一个是输出outputBuffer,它们是AudioBuffer实例。可以在onaudioprocess回调里面获取到inputBuffer的数据,处理之后,然后放到outputBuffer,如下图所示:
例如我们可以把第1步播放本音频用到的bufferSourceNode连接到jsNode,然后jsNode再连接到扬声器,就能在process回调里面分批处理声音的数据,如降噪。当扬声器把4kB的outputBuffer消费完之后,就会触发process回调。所以process回调是不断触发的。
在录音的例子里,是要把mediaNode连接到这个jsNode,进而拿到录音的数据,把这些数据不断地push到一个数组,直到录音终止了。如下代码所示:
我们把inputBuffer打印出来,可以看到每一段大概是0.09s:
也就是说每隔0.09秒就会触发一次。接下来的工作就是在process回调里面把录音的数据持续地保存起来,如下代码所示,分别获取到左声道和右声道的数据:
打印出来可以看到它是一个Float32Array,即数组里的每个数字都是32位的单精度浮点数,如下图所示:
这里有个问题,录音的数据到底表示的是什么呢,它是采样采来的表示声音的强弱,声波被麦克风转换为不同强度的电流信号,这些数字就代表了信号的强弱。它的取值范围是[-1, 1],表示一个相对比例。
然后不断地push到一个array里面:
最后加一个停止录音的按钮,并响应操作:
把保存的数据打印出来是这样的:
是一个普通数组里面有很多个Float32Array,接下来它们合成一个单个Float32Array:
那为什么一开始不直接就弄成一个单个的,因为这种Array不太方便扩容。一开始不知道数组总的长度,因为不确定要录多长,所以等结束录音的时候再合并一下比较方便。
然后把左右声道的数据合并一下,wav格式存储的时候并不是先放左声道再放右声道的,而是一个左声道数据,一个右声道数据交叉放的,如下代码所示:
最后创建一个wav文件,首先写入wav的头部信息,包括设置声道、采样率、位声等,如下代码所示:
接下来写入录音数据,我们准备写入16位位深即用16位二进制表示声音的强弱,16位表示的范围是 [-32768, +32767],最大值是32767即0x7FFF,录音数据的取值范围是[-1, 1],表示相对比例,用这个比例乘以最大值就是实际要存储的值。如下代码所示:
最后,再用第1点提到的生成一个本地播放的blob url就能够播放刚刚录的音了,如下代码所示:
或者是把blob使用FormData进行上传。
整一个录音的实现基本就结束了,代码参考了一个录音库RecordRTC。
回顾一下,整体的流程是这样的:
先调用webRTC的getUserMediaStream获取音频流,用这个流初始化一个mediaNode,把它connect连接到一个jsNode,在jsNode的process回调里面不断地获取到录音的数据,停止录音后,把这些数据合并换算成16位的整型数据,并写入wav头部信息生成一个wav音频文件的内存buffer,把这个buffer封装成Blob文件,生成一个url,就能够在本地播放,或者是借助FormData进行上传。这个过程理解了就不是很复杂了。
本篇涉及到了WebRTC和AudioContext的API,重点介绍了AudioContext整体的模型,并且知道了音频数据实际上就是声音强弱的记录,存储的时候通过乘以16位整数的最大值换算成16位位深的表示。同时可利用blob和URL.createObjectURL生成一个本地数据的blob链接。
RecordRTC录音库最后面还使用了webworker进行合并左右声道数据和生成wav文件,可进一步提高效率,避免录音文件太大后面处理的时候卡住了。
音频下载
Javascript在数据的处理上一直不是强项,比如数字不分整型和浮点数统一使用了64位浮点数,如果涉及到二进制运算则显得非常无力,在数据传输上也非常浪费带宽。在ES6针对Javascript二进制数据处理上的无力引入了原始缓冲区ArrayBuffer,并且还提供了多种位数的int类型数组以及数据视图来处理数据。
用一个 Int8 的确定类型数组来分离存放 8 位二进制字节。
用一个无符号的 Int16 数组来分离存放 16 位二进制字节,这样如果是一个无符号的整数也能处理。
甚至可以在相同基础的 buffer 上使用不同的 view,同样的操作不同的 view 会给你不同的结果。
比如,如果我们在这个 ArrayBuffer 中从 Int8 view 里获取了元素 0 和 1,在 Uint16 view 中元素 0 会返回给我们不同的值,尽管它们包含的是完全相同的二进制字节。
在这种方式中,ArrayBuffer 基本上扮演了一个原生内存的角色,它模拟了像 C 语言才有的那种直接访问内存的方式。
你可能想知道为什么我们不让程序直接访问内存,而是添加了这种抽象层。直接访问内存将导致一些安全漏洞。
更多ArrayBuffer的基础介绍可参考 《JavaScript 标准参考教程》-阮一峰-二进制数组
一个整型数字写成2进制之后以书写习惯来说左边是高位,右边是低位。举个例子,int8的5使用二进制是这么表示的:
0000 0101
最左边的0是符号位,表示非负数,101则是5的二进制写法。
负数无法使用非负数的规则来表示,比如-1用非负数的规则表示是:
1000 0001
考虑到-1如果加上1值应该为0,那么该值加上1却会变成-2:
1000 0010
也就是在计算方式上出现问题了,所以二进制是采用了补码的方式来表示,补码可以完全不考虑符号位,它的规则如下:
按这种规则,-1的绝对值二进制是:
0000 0001
取反后是:
1111 1110
再加1则是:
1111 1111
我们再尝试给它加上1,看看值是多少:
1 0000 0000
明显高位溢出了,于是将高位多余的1截掉变成:
0000 0000
它的值如我们所料是0,这就是补码。uint8由于没有正负之分,它的所有值都是非负数,所以就不需要考虑补码。
首先需要了解一下int16用二进制是如何表示的,以3850为例子,它的二进制表示方式是这样子的:
00001111 00001010
int16占了两个字节,因此它被截成两断,我们把左边的字节称为高位字节,右边的字节称为低位字节。在内存中它是如何存放的呢?建立一个Int8Array来看一下:
var buffer = new ArrayBuffer(2);
var int16Array = new Int16Array(buffer);
var int8Array = new Int8Array(buffer);
int16Array[0] = 3850;
// Int16Array [ 3850 ]
console.log(int16Array);
// Int8Array [ 10, 15 ]
console.log(int8Array);
从运行结果看,3850被截断成两个int8值:
int8Array[0] = 10 = 00001010
int8Array[1] = 15 = 00001111
看起来很反人类是吧?高位字节不是放在左边下标为0的字节上,却放在右边下标为1的字节上,跟我们的书写习惯反过来了!
事实上,数据在内存中存储并没有硬性规则高位字节必须放在左边(虽然它更符合人类阅读习惯),具体实现也是根据当前CPU的实现。大多数计算机是以高位优先的顺序存放数据(即高位在左,低位在右),但基于Intel CPU的计算机则是反过来以低位优先存放数据,我的本子就是Intel CPU的,因此我的运行结果就是Int8Array [ 10, 15 ],或许换个电脑就会变成Int8Array [ 15, 10 ]。
那么当数据溢出截断又是怎么处理的?尝试着给一个int8字节赋值3850,看看最终得到的值是什么:
var int8Array = new Int8Array([3850]);
//Int8Array [ 10 ]
console.log(int8Array);
很明显,它把15抛弃了,也就是保存了低位字节,抛弃高位。事实上,溢出处理在各种CPU上都是保留低位能保留的字节,把高位的截断,这个与数据存储是按高位优先还是低位优先没什么关系。
请先看以下示例代码在我机子上跑的情况:
var buffer = new ArrayBuffer(2);
var view = new DataView(buffer);
var int16Array = new Int16Array(buffer);
view.setInt8(0, 10);
// 10
console.log(view.getInt8(0))
// 2560 用二进制表示是:00001010 00000000,
console.log(view.getInt16(0));
int16Array[0] = 10;
// 2560!
console.log(view.getInt16(0));
仔细看结果,很明显,view使用的是高位优先的读写方式,而TypeArray使用的是低位优先的读写方式。在内存中两个字节的数据在setInt8(0, 10)的时候会变成
00001010 00000000
按照我计算机的读写规则应该是低位优先,也就是实际上这里应该被解读成
00000000 00001010
也就是得到10,但实际上使用view却得到了2560,也就是高位优先。
DataView默认是使用big-endian方式,也就是高位优先进行读写。但在读写多字节数据的时候,可以通过传入值为true的little-endian参数来要求使用低位优先规则读写数据。
1 《JavaScript 标准参考教程》-阮一峰-二进制数组
2 缓冲数组以及数据视图
3 通俗漫画介绍 ArrayBuffers 和 SharedArrayBuffers
一直觉得写博客是一件非常需要精力的事,从大学那会自学开始,就有尝试过在githubfront-end-skills、csdnpkjy上记录过一些Demo的编程过程和一些翻译,读书笔记。很多时候一篇简单的文章下来,花费的时间绝对是要比单做一些Demo要高出几倍。
其次也先后考虑过文章入数据库或者完全静态Markdown到本地。当然本地输出Markdown也是我入门Node的首次尝试,虽然没有完全利用起来(简单了写了一个Markdown内容转为所需JSON文件的小脚本)。
在几年的学习中,陆陆续续发现了很多优质的文章,也都收藏了起来,有网页收藏夹形式的,也有各网站自带的收藏功能,不过这些内容一来是比较分散,不方便归类;再者不知道是什么缘由,有部分的文章会过期或者被删除掉了。(pocket有在用,但是文章链接本身是不可靠的,所以未能解决上面提到的一些问题)
因此为了更好的更永久的保存下来经过自己确认过的好文章,并且统一一个入口来进行管理,在此决定用issue记录博客的内容,也有很多把博客放在issue里的大佬,这里还看到一份收录博客相关相关工具炒鸡多的issue博客系统、文档生成。有体验过语雀,个人博客,最终选择。使用起来确实很舒服,不过毕竟是商业的东西,心里总觉得不踏实。目前有图床并且能写md就已经很满足我的要求了,剩下的就是把以前零零散散看到的深度好文都给换到这个新家来喽,当然这也意味着放弃了流量,毕竟csdn,掘金等技术网站在流量上还是很有优势的。
将音频数字化,其实就是将声音数字化。最常见的方式是透过脉冲编码调制PCM(Pulse Code Modulation) 。运作原理如下。首先我们考虑声音经过麦克风,转换成一连串电压变化的信号。要将这样的信号转为 PCM 格式的方法,是使用三个参数来表示声音,它们是:声道数、采样位数和采样频率。
采样频率:即取样频率,指每秒钟取得声音样本的次数。采样频率越高,声音的质量也就越好,声音的还原也就越真实,但同时它占的资源比较多。由于人耳的分辨率很有限,太高的频率并不能分辨出来。在16位声卡中有22KHz、44KHz等几级,其中,22KHz相当于普通FM广播的音质,44KHz已相当于CD音质了,目前的常用采样频率都不超过48KHz。
采样位数:即采样值或取样值(就是将采样样本幅度量化)。它是用来衡量声音波动变化的一个参数,也可以说是声卡的分辨率。它的数值越大,分辨率也就越高,所发出声音的能力越强。
声道数:很好理解,有单声道和立体声之分,单声道的声音只能使用一个喇叭发声(有的也处理成两个喇叭输出同一个声道的声音),立体声的pcm可以使两个喇叭都发声(一般左右声道有分工) ,更能感受到空间效果。
图中的黑色曲线表示的是pcm文件录制的自然界的声波,红色曲线表示的是pcm文件输出的声波,横坐标便是采样频率;纵坐标便是采样位数。这几幅图中的格子从左到右,逐渐加密,先是加大横坐标的密度,然后加大纵坐标的密度。显然,当横坐标的单位越小即两个采样时刻的间隔越小,则越有利于保持原始声音的真实情况,换句话说,采样的频率越大则音质越有保证;同理,当纵坐标的单位越小则越有利于音质的提高,即采样的位数越大越好。
计算机中采样位数一般有8位和16位之分,但有一点请大家注意,8位不是说把纵坐标分成8份,而是分成2的8次方即256份; 同理16位是把纵坐标分成2的16次方65536份;如下图:
存储量=(采样频率X采样位数X声道)X时间/8(单位:字节数)
更多坑在standardized-audio-context这里面可以看到
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first
在没有用户交互的情况下,直接进行音频的播放是被浏览器禁止的。可以在有交互的时候就开始音频的播放,然后采取禁音的方法来处理。
G711的打包周期分为10ms,20ms,30ms,sample rate是8000,速率是64kbit/s
64kbits,意味着每秒发送64000比特
那么10ms发送= 64000 * (10/1000) = 640 比特 = 80 字节
那么10ms的包 = 80字节
20ms = 160 字节
30ms = 240 字节
那么64kbits 如何来的?
因为采样周期为8000,那么意味着1秒8000个采样,每次采样站8个比特
那么 8000*8 = 64000bit/s = 64kbit/s
接下来计算timestamp的增长量,公式如下:
两帧之间RTP timestamp的增量 = 时钟频率 / 帧率
同样以10ms的G711举例:
10ms的G711帧率 = 100,意味着每秒发送100帧
那么10ms的timestamp增量 = 8000/100 = 80
20ms的timestamp增量 = 160
30ms的timestamp增量 = 240
m=audio 1234 RTP/AVP 0
a=rtpmap:0 pcma/8000/1
a=framerate:25
c=IN IP4 172.18.168.45
1.m=是媒体级会话的开始处,audio:媒体类型 ; 1234:端口号 ;RTP/AVP:传输协议 ;0:rtp头中的payload格式
2.a=rtpmap:证明是动态绑定的进一步说明 ;0:rtp头中的payload格式;pcma:编码名 ;8000:采样频率;1:单声道
注意:g711有两种编码类型,另一种是pcmu
3.a=framerate:25 指1s播放几个rtp包,单位帧每秒,倒数为一个rtp包承载的数据播放的时间,单位s
8000/25=320 表示每个时间戳增量值 每个rtp包的g711数据大小
4.c=:媒体链接信息;IN:网络类型一般为IN;IP4:地址类型一般为IP4;后面是IP地址(注意是VLC所在的IP地址,不是发送方的IP)
更多sdp介绍参考
websocket可以通过binaryType
属性来选择数据传输的二进制格式,blob
会返回Blob
类型的原始二进制数据,arraybuffer
会返回ArrayBuffer
类型的二进制数据。binaryType参考文档
Blob
是一个类文件(file-like)对象,与File
api结合使用。例如文件地址可以通过URL.createObjectURL()
来转换成一个本地的url地址,包括audio
和video
,他们除了src属性之外,还有srcObject
属性,可以直接将Blob
地址传给srcObject
属性,同样也能正常播放音频与视频。ArrayBuffer
是一个提供给js处理二进制数据的数据类型,ArrayBuffer
本身是只读的,里面包含了各种类型的TypedArray
,是支持直接修改数据的,或者通过DataView
视图来修改ArrayBuffer
。完整的TypedArray参考MDN文档。FileReader.readAsArrayBuffer()
把数据读为ArrayBuffer
。Buffer是V8引擎上的扩展,这意味着它有其固有的一些限制。Buffer实际上是对内存的直接分配,这意味着这多少受制于你在低级计算机语言方面的经验。JavaScript的其他数据类型都把存储数据的复杂性进行了抽象,而Buffer与它们不同,它提供的是内存的直接操作。创建了一个Buffer后,它的大小就固定了。如果你需要添加更多的数据,就必须把老的Buffer复制到一个更大的Buffer中。虽然有些特性看起来让人沮丧,但它们让Buffer能够在服务器上快速地处理大量的数据操作。这是一个特意的设计选择,为了性能而牺牲了一些程序员的开发便利。 -《Node即学即用》4.3.3 Buffer
简单来说,Buffer是Node提供给我们直接操作二进制数据的类。
大多数人都知道,计算机的工作原理是操作“开”和“关”状态。因为只有这样两种状态,所以我们称此为二元状态。计算机的所有东西都建立在此基础上,这就说明了为什么在计算机上操作时,直接操作二进制通常是最快的方法。要做更复杂的事情时,我们把比特(bit,每一位表示一个二元状态)集合成8个一组,称之为8位组(octet),也就是通常所说的字节(byte)。1这样我们就能表示除了0、1外的其他数字了。-《Node即学即用》4.3.3 Buffer
8位bit = 1个byte
利用8位字节,我们就可以表示从0到255间的所有数字了。
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | = 0 |
- | - | - | - | - | - | - | - | |
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | = 255 |
- | - | - | - | - | - | - | - | |
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | |
1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | =149 |
转化的算法就不多加赘述了,高中都有学.. 比如149 = 1x2^7+0x2^6+0x2^5+1x2^4+0x2^3+1x2^2+0x2^1+1x2^0
但是8个0和1组成的字符串来描述实在是不方便,所以**十六进制(HEX)**表示法便流行起来。二进制表示法的基数是2,因此每个数字(0或1)只有两种状态。十六进制使用的基数是16,每一位能够表示0到F种状态,其中字母A到F(或对应的小写字母)对应代表10到15。这样只需要2个字母就能表示整个字节了。最右的位表示1,往左一位就表示16。如果我们要表示数字149,就等价于(16 x 9)+(5 x 1),也就是十六进制的95。
十六进制转换到十进制:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
十六位进制法计数:
16 | 1 | |
---|---|---|
0 | 0 | = 0 |
16 | 1 | |
---|---|---|
F | F | = 255 |
16 | 1 | |
---|---|---|
9 | 5 | = 149 |
在JavaScript中,用一个十六进制值表示数字时,需要在其十六进制数值前添加0x标记。比如,0x95表示十进制数字149。在Node里,你会常常看见console.log()输出,或是在Node命令行中,Buffer值是采用十六进制表示的。下面表格就演示了如何用Buffer保存3个字符(比如RGB颜色值)。
> new Buffer([255,0,149]);
<Buffer ff 00 95>
>
Node中创建Buffer默认是UTF-8编码的。
Base64编码是基于64个字符A-Z,a-z,0-9,+,/的编码方式,因为2的6次方正好为64,所以就用6bit就可以表示出64个字符,eg:000000对应A,000001对应B。
**BASE64 的编码原理:**都是按字符串长度,以每 3 个 字符(1Byte=8bit)为一组,然后针对每组,首先获取每个字符的 ASCII 编码(字符'a'=97=01100001),然后将 ASCII 编码转换成 8 bit 的二进制,得到一组 3 * 8=24 bit 的字节。然后再将这 24 bit 划分为 4 个 6 bit 的字节,并在每个 6 bit 的字节前面都填两个高位 0,得到 4 个 8 bit 的字节,然后将这 4 个 8 bit 的字节转换成十进制,对照 BASE64 编码表 (下表),得到对应编码后的字符。
注:1. 要求被编码字符是8bit的,所以须在ASCII编码范围内,\u0000-\u00ff,中文就不行。2. 如果被编码字符长度不是3的倍数的时候,则都用0代替,对应的输出字符为“=”
Base64编码本质上是一种将二进制数据转成文本数据的方案。对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在A--Z,a--z,0--9,+,/ 这64个字符中找到对应的字符,最终得到一个文本字符串。基本规则如下几点:
解码原理是将4个字节转换成3个字节.先读入4个6位(用或运算),每次左移6位,再右移3次,每次8位,这样就还原了。
1、字符长度为能被3整除时:比如“Tom” :
T | o | m | ||
---|---|---|---|---|
ASCII: | 84 | 111 | 109 | |
8bit字节: | 01010100 | 01101111 | 01101101 | |
6bit字节: | 010101 | 000110 | 111101 | 101101 |
十进制: | 21 | 6 | 61 | 45 |
对应编码: | V | G | 9 | t |
所以,“Tom”的 BASE64 编码结果为 VG9t。
2、字符串长度不能被3整除时,比如“Lucy”:
L | u | c | y | |||||
---|---|---|---|---|---|---|---|---|
ASCII: | 76 | 117 | 99 | 121 | ||||
8bit字节: | 01001100 | 01110101 | 01100011 | 01111001 | 00000000 | 00000000 | ||
6bit字节: | 010011 | 000111 | 010101 | 100011 | 011110 | 010000 | 000000 | 000000 |
十进制: | 19 | 7 | 21 | 35 | 30 | 16 | 异常 | 异常 |
对应编码: | T | H | V | j | e | Q | = | = |
因为4个Base编码为一组,最后再补上'='补齐,即:THVjeQ==
ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。
ASCII码表可参考《标准表》
实现简单的数据加密,使用户一眼望去完全看不出真实数据内容,base64算法的复杂程度要小,效率要高相对较高。
Base64编码的主要的作用不在于安全性,而在于让内容能在各个网关间无错的传输,这才是Base64编码的核心作用。
在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。
Base64 编码在URL中的应用:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.