前端多媒体(3)—— 处理二进制数据
如果想要在前端中处理音频和视频。那你必须要对二进制数据有很好地掌握和操作能力。这篇文章主要是记录和汇总。前端处理二进制数据用到的一些方法和联系。
- Base64
- btoa
- atob
- String.fromCharCode()
- string.charCodeAt(index)
- string.chatAt(index)
- Blob
- FileReader
- ArrayBuffer, TypeArray
- createObjectURL
- revokeObjectURL
- little-endia & big-endia 字节顺序
Base64
用64个可打印字符(ASCII)表示8Bit字节数据(比如ASCII的字符为8bit)的编码方式,所以不能表示中文(JS里的中文为unicode,32位),所以你需要对中文做encodeURIComponent处理后再转为base64。
base64可以表示图片,图片本身是二进制数据。
准确的说并不是64个字符,应该是65个字符,还有一个用于算法补位的 “ = ” 字符。所以你会经常看到在base64数据里常常有一个或者两个 “ = ” 作为结尾。
64个字符如下:
A~Z =26
a~z = 26
0~9 = 10
/ = 2
total = 64
如何把一个二进制数据用64个字符表示,这是有一套算法的,具体算法如下。
- 将三个byte的数据,先后放入一个24bit的缓冲区中
- 先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的bit用0补足
- 然后,每次取出6个bit,按照其值选择
- 最后剩下两个输入数据,在编码结果后加1个“=”
- 如果最后剩下一个输入数据,编码结果后加2个“=”;
总之就是:数据按照 3个字节一组的形式进行处理,每三个字节在编码之后被转换为4个字节。
比如我们有个 "M" 字符。对应的ASCII码为77
字符 M
ASCII : 77 => 0100,1101
base64算法 : 010011, 010000, 后2个空缺补 = (最小单元为4个6bit)
base64表示 : TQ==
算法大概是这样子的,但是。有更直接的API
atob & btoa
- btoa:二进制变为base64编码 binary to ascii
- atob:base64解码为二进制:ascii to binary
b表示 binary(二进制),a表示ASCII。
btoa("M") // TQ==
这里有一个很容易混淆的概念。btoa的b代表的是binary,但是感觉传入的参数并不是二进制数据,而是文本数据?但其实并不是这样的。
window.btoa only takes a string as its argument!" That's true but the string here is only a representation of the data.Like if you try to open an image in a notepad it'll display as a string but it's still binary data. btoa's main advantage is that it doesn't care what format the string is in, it just treats it as binary. It's only incidental that in most cases that string happens to be a regular string.
参数m看起来像是一个ASCII?或者一个unicode字符。但是底层表示存储形式还是二进制,只不过他展示为一个M
解码
atob("TQ==") // M
fromCharCode() & charCodeAt(index) & chatAt(index)
这三个方法很容易被混淆。在这里特别梳理出来
fromCharCode
fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。
String.fromCharCode(97) => a
这个是一个静态方法
charCodeAt
返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。参数为对字符串中索引位子的字符做转码
'abc'.charCodeAt(0) => 97
'abc'.charCodeAt(1) => 98
这是一个实例方法
chatAt
返回的是字符串的子串
'abc'.charAt(0) => a
'abc'.charAt(1) => b
Blob
blob是对大数据块的不透明引用,名字来源SQL数据库,表示二进制对象Binary Large Object。
创建Blob对象的方法有几种,可以调用Blob构造函数,还可以使用一个已有Blob对象上的slice()
方法切出另一个Blob对象,还可以调用canvas
对象上的toBlob
方法。
来源
- 网络资源: XMLHttpRequest
- ArrrayBuffer
- IndexedDB
比如这个demo https://young-cowboy.github.io/gallery/XMLHttpRequest_responseType/response_blob.html。通过异步的形式把二进制数据下载到内存中,并且指定它的返回类型为blob。然后通过创建链接的形式指向落个这样可以把内存中的图片渲染出来。
var xhr = new XMLHttpRequest();
xhr.open('GET', './assets/blob');
xhr.responseType = 'blob';
xhr.send();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
console.log(xhr.response instanceof Blob); // true
document.getElementById('image').src = URL.createObjectURL(xhr.response); // 这里设置一个张图片
}
}
dom的连接
<img id="image" src="blob:https://young-cowboy.github.io/45eaa988-dbb7-45ce-968c-cb373cd54b2d">
这里可以看到链接是一个blob的形式。
属性
- size:上午回对象的大小,单位为字节
- 返回 Blob 对象的 MIME 类型。如果类型无法确定,则返回空字符串。
xhr.response.size // 439504
xhr.response.type // application/octet-stream
构造器
var aBlob = new Blob( array, options );
- array 是一个由
ArrayBuffer
,ArrayBufferView
,Blob
,DOMString
等对象构成的Array
,或者其他类似对象的混合体,它将会被放进Blob
. - option**s 是一个可选的Blob熟悉字典,它可能会指定如下两种属性:
type,默认值为
""
,它代表了将会被放入到blob中的数组内容的MIME类型。- endings,默认值为
"transparent",它代表包含行结束符
\n的字符串如何被输出。
它是以下两个值中的一个:"native",代表行结束符会被更改为适合宿主操作系统文件系统的惯例,或者
"transparent",
代表会保持blob中保存的结束符不变
方法
- slice: Blob.slice() 方法用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象。
读取
之前都是直接引用,但是需要读取里面的字符或者字节就必须使用 FileReader
对象。从Blob中读取内容的唯一方法是使用 FileReader
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,
var reader = new FileReader();
reader.addEventListener("loadend", function() {
//
});
reader.readAsArrayBuffer(blob);
FileReader
实例有几个方法,分别用不同的形式对去Blob的内容
- 读文本:readAsText(blob, encoding)
- 读为链接:readAsDataURL(blob): 返回一个URL
- 原始二进制数据.:readAsBinaryString(blob)
- ArrayBuffer对象以表示所读取文件的内容: readAsArrayBuffer(blob)
因为数据很大,所以读取是异步的,对应的事件有
- onabort / onerror / onload / onloadend / onloadstart / onprogress
在读取的过程中有几个状态,已经完成read后的数据,如下是实例的属性
- readyState
- EMPTY: 0
- LOADING: 1
- DONE: 2
- error 错误
- result
这里有一个例子,用来读取File的头4个字节,然后判断类型。注意:File 是 Blob 的一个扩展类
File.prototype.__proto__ === Blob.prototype //true
<input type="file" id="input">
<script type="text/javascript">
var input = document.getElementById("input");
input.addEventListener('change', function (e){
var file = this.files[0].slice(0, 4);
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener('load', function (e){
var buffer = reader.result;
var meta = view.getUint32(0, false);
// big endian 大端字节序,读取。只有读取的时候,才必须区分字节序,其他情况都不用考虑。
var view = new DataView(reader.result);
switch(meta){
case 0xffd8ffe1: alert('EXIF JPEG');break;
case 0xffd8ffe0: alert('JFIF JPEG');break;
case 0xb1bec8ed: alert('text');break;
case 0x47494638: alert('gif');break;
default: alert('unkown')
}
});
});
</script>
ArrayBuffer
ArrayBuffer表示二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据。
ArrayBuffer
就是个装着2进制数据的对象。数据源提前写入在内存中,长度也固定。需要借助类型化数组或
DataView
对象来解释原始缓冲区
TypeArray
TypedArray 只是一个概念,没有名为 TypedArray的全局属性,也没有一个直接可见的 TypedArray构造函数。相反,有许多不同的全局属性,其值是下面列出的特定元素类型的类型化数组构造函数。
其实也是数组的一种,但是底层做了优化,操作比数组效率更高。
- Int8Array() : 8位有符号整数
- Uint8Array() : 8位无符号整数
- Int16Array() : 16位有符号整数
- Uint16Array() : 16位无符号整数
- Int32Array() : 32位有符号整数
- Uint32Array() : 32位无符号整数
- Float32Array() : 32位浮点数
- Float64Array() : 64位浮点数
实例
var bytes = new Uint8Array(1024); // 创建一个1KB的缓冲区
var pattern = new Uint8Array([0, 1, 2, 3]); // 一个4字节的数据
bytes.set(pattern); // 设置到数组的开始
bytes.set(pattern, 4); // 从第4个位置中再次设置
bytes[2] === 2 // true
var ints = new Uint16Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
ints.byteLength // 20
ints.length // 10;
ints.buffer instanceof ArrayBuffer // true 指向了ArrayBuffer
var last3 = ints.subarray(ints.length - 3, ints.length); //subarray不会创建副本,他只是返回数据的一部分内容。
last3.byteLength // 6
last3.length // 3
last3.buffer === ints.buffer // true 他们的ArrayBuffer都是相同的。
last3.byteOffset // 14 从基本的缓冲区的16个字节开始。
字节顺序
我们知道在8位的数据中,一个数据块有事一个Byte。但是在12位32位或者64位的数据块中。每一个数据会里面的Byte排序是有区别的。这个区别主要是在系统和网络中存在。在系统中,主要是通过CPU架构产生的。有一个数据块低位放前面还是高位放前面是有讲究的。更多的内容和入门可以参考这篇文章:理解字节序。
如今,大多数的CPU架构采用低位优先,然而很多网络协议以及有些二进制文件格式采用高位优先的字节顺序。
只有读取的时候,才必须区分字节序,其他情况都不用考虑。
如下的代码,用来判断系统是低位优先还是高位优先。
// 如果整数 0x 00000001 在内存中表示为。 01 00 00 00
// 这说明系统为低位优先系统
// 相反在高位系统中则会表示为 00 00 00 01
var int32Array = new Int32Array([1]);
var int8Array = new Int8Array(int32Array.buffer);
var little_endian = int8Array[0] === 1;
如上,只是判断系统的字节顺序。但是在处理网络的数据的时候,数据的字节顺序通常采用的是高位优先的字节顺序。可以借助DataView解析数据
DataView
DataView视图提供了一个与平台中字节在内存中的排列顺序(字节序)无关的从 ArrayBuffer 读写多数字类型的底层接口,他可以直接操作ArrayBuffer。通过他你可以按照不同类型获取ArrayBuffer的数据
语法
new DataView(buffer [, byteOffset [, byteLength]])
- getFloat32()
- getFloat64()
- getInt16()
- getInt32()
- getInt8()
- getUint16()
- getUint32()
- getUint8()
比如这个获取文件的类型的demo中。数据是以高位有线传输的,在读取的时候需要指定的为优先的参数为false
<input type="file" id="input">
<script type="text/javascript">
var input = document.getElementById("input");
input.addEventListener('change', function (e){
var file = this.files[0].slice(0, 4);
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener('load', function (e){
var buffer = reader.result;
var meta = view.getUint32(0, false);
// big endian 大端字节序,读取。只有读取的时候,才必须区分字节序,其他情况都不用考虑。
var view = new DataView(reader.result);
switch(meta){
case 0xffd8ffe1: alert('EXIF JPEG');break;
case 0xffd8ffe0: alert('JFIF JPEG');break;
case 0xb1bec8ed: alert('text');break;
case 0x47494638: alert('gif');break;
default: alert('unkown')
}
});
});
</script>
几种数据类型的装换
ArrayBuffer to Blob
blob的构造函数
var aBlob = new Blob( array, options );
array 可以是 ArrayBuffer, ArrayBufferView, Blob, String 等对象构成的 Array
var uint8Array = new Uint8Array([97, 98, 99]) // abc
var blob = new Blob([uint8Array])
var reader = new FileReader();
reader.readAsText(blob);
reader.onload = function(e){
console.log(reader.result) // abc
}
Blob to ArrayBuffer
使用FileReader
var blob = new Blob(['abc']);
reader.readAsArrayBuffer(blob);
reader.onload = function(e){
var uint8Array = new Uint8Array(reader.result);
uint8Array.forEach(function(e){
console.log(String.fromCharCode(e)) // a b c
})
}
String to Blob
直接传入blob构造器中
var blob = new Blob(['你好'], {type: 'text/plain'});
blob.size // 6
reader.readAsText(blob, 'utf-8');
reader.onload = function(e){
console.log(reader.result)
}