1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198 |
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x17
x17
x18
x18
x17
x18
x18
x17
x18
x18
x17
x18
x18
x1
x29
x17
x18
x18
x17
x19
x19
x17
x18
x18
x17
x18
x18
x17
x18
x18
x1
x23
x23
x1
x23
x23
x23
x1
x17
x20
x20
x20
x20
x17
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x5
x1
x5
x5
x5
x1
x5
x5
x1
x5
x7
x7
x7
x7
x7
x7
x7
x7
x5
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x1
x25
x25
x25
x27
x27
x1
x47
x25
x27
x27
x1
x45
x25
x27
x27
x1
x43
x25
x29
x29
x1
x39
x25
x33
x33
x35
x33
x41
x41
x25
x31
x31
x31
x31
x31
x31
x31
x33
x33
x31
x25
x41
x49
x49
x49
x49
x41
x1
x33
x25 |
|
/**
* @file Encoding/decoding functions for audio packet
*
* More detail of packet protocol, see `designs/packet-protocol.md`
*
* Note: It tested only for monaural audio, multi channels is unsupported.
*
* @author aKuad
*/
import { ONE_FRAME_SAMPLES, ONE_SAMPLE_BYTES } from "../AUDIO_PARAM.js";
import { int16_to_uint8_little_endian, uint8_to_int16_little_endian } from "../util/int16bytes_conv.js";
import { dbfs_float } from "../util/dbfs.js";
import { typeof_detail } from "../util/typeof_detail.js";
/**
* Packet type ID of audio packet
*/
export const AUDIO_PACKET_TYPE_ID = 0x10;
/**
* Packet type ID of silent audio packet
*/
export const SILENT_AUDIO_PACKET_TYPE_ID = 0x11;
/**
* Create audio packet
*
* @param {Float32Array} audio_pcm Audio PCM
* @param {string} lane_name Lane name of view in mixer-client
* @param {Uint8Array} ext_bytes User's custom external data
* @param {number} silent_threshold_dbfs Under this dBFS input will be ignored
* @returns {Uint8Array} Encoded packet
*
* @throws {TypeError} If `audio_pcm` is not `Float32Array`
* @throws {TypeError} If `lane_name` is not `string`
* @throws {TypeError} If `ext_bytes` is not `Uint8Array`
* @throws {TypeError} If `silent_threshold_dbfs` is not `number`
* @throws {RangeError} If `lane_name` is empty `string`
* @throws {RangeError} If `lane_name` has non ascii or control ascii characters
* @throws {RangeError} If `lane_name` has over 3 characters
* @throws {RangeError} If `ext_bytes` has over 255 bytes
* @throws {RangeError} If `silent_threshold_dbfs` is positive value
*/
export function packet_audio_encode(audio_pcm, lane_name, ext_bytes = new Uint8Array(0), silent_threshold_dbfs = -Infinity) {
// Arguments type checking
if(!(audio_pcm instanceof Float32Array)) {
throw new TypeError(`audio_pcm must be Float32Array, but got ${typeof_detail(audio_pcm)}`);
}
if(typeof lane_name !== "string") {
throw new TypeError(`lane_name must be string, but got ${typeof_detail(lane_name)}`);
}
if(!(ext_bytes instanceof Uint8Array)) {
throw new TypeError(`ext_bytes must be Uint8Array, but got ${typeof_detail(ext_bytes)}`);
}
if(typeof silent_threshold_dbfs !== "number") {
throw new TypeError(`silent_threshold_dbfs must be number, but got ${typeof_detail(silent_threshold_dbfs)}`);
}
// Arguments range checking
if(lane_name.length === 0) {
throw new RangeError("lane_name can't be empty string");
}
if(!(/^[\x20-\x7F]*$/.test(lane_name))) {
throw new RangeError("For lane_name, non ascii or control ascii characters are not allowed");
}
if(lane_name.length > 3) {
throw new RangeError(`For lane_name, over 3 characters string is not allowed, but got ${lane_name.length} characters`);
}
if(ext_bytes.length > 255) {
throw new RangeError(`For ext_bytes, over 255 bytes data is not allowed, but got ${ext_bytes.length} bytes`);
}
if(silent_threshold_dbfs > 0) {
throw new RangeError(`silent_threshold_dbfs must be 0 or negative value, but got ${silent_threshold_dbfs}`);
}
const audio_data_int16t = Int16Array.from(audio_pcm, e => e*32767); // *32767: float32(-1, 1) to int16(-32768, 32767)
const audio_data_uint8t = int16_to_uint8_little_endian(audio_data_int16t);
const lane_name_adj3 = (lane_name + " ").slice(0, 3);
const text_encoder = new TextEncoder();
const lane_name_uint8t = text_encoder.encode(lane_name_adj3);
if(dbfs_float(audio_pcm) <= silent_threshold_dbfs) {
return Uint8Array.of(SILENT_AUDIO_PACKET_TYPE_ID, ...lane_name_uint8t, ext_bytes.length, ...ext_bytes);
} else {
return Uint8Array.of(AUDIO_PACKET_TYPE_ID, ...lane_name_uint8t, ext_bytes.length, ...ext_bytes, ...audio_data_uint8t);
}
}
/**
* Data structure of decoded audio packet
*
* @typedef {Object} AudioData
* @property {Float32Array} audio_pcm Audio PCM
* @property {string} lane_name Lane name of view in mixer-client
* @property {Uint8Array} ext_bytes User's custom external data
*/
/**
* Unpack audio packet
*
* Note: About raises, see reference of `is_audio_packet`.
*
* @param {Uint8Array} raw_packet Encoded packet
* @returns {AudioData} Decoded data - Audio PCM, lane name and external data
*/
export function packet_audio_decode(raw_packet) {
is_audio_packet(raw_packet, true);
const lane_name_uint8t = raw_packet.slice(1, 4);
const text_decoder = new TextDecoder();
const lane_name = text_decoder.decode(lane_name_uint8t);
const ext_bytes_len = raw_packet[4];
const ext_bytes = raw_packet.slice(5, 5 + ext_bytes_len); // +1 for length byte\
if(raw_packet[0] == SILENT_AUDIO_PACKET_TYPE_ID) {
const audio_data_float32t = new Float32Array(ONE_FRAME_SAMPLES);
return {audio_pcm: audio_data_float32t, lane_name: lane_name, ext_bytes: ext_bytes};
} else {
const audio_data_uint8t = raw_packet.slice(5 + ext_bytes_len);
const audio_data_int16t = uint8_to_int16_little_endian(audio_data_uint8t);
const audio_data_float32t = Float32Array.from(audio_data_int16t, e => e/32767); // /32767: int16(-32768, 32767) to float32(-1, 1)
return {audio_pcm: audio_data_float32t, lane_name: lane_name, ext_bytes: ext_bytes};
}
}
/**
* Verify the packet is audio packet
*
* @param {Uint8Array} raw_packet Packet to verify
* @param {boolean} throw_on_invalid Toggle behavior if packet is invalid, true: raise exception, false: return false
* @returns {boolean} It is an audio packet: true, otherwise: false (if throw_on_invalid === true, error will be thrown)
*
* @throws {TypeError} If `raw_packet` is not `Uint8Array`
* @throws {RangeError} If `raw_packet` is an empty array
* @throws {RangeError} If `raw_packet` has not an audio packet or silent audio packet type ID
* @throws {RangeError} If `raw_packet` is too short bytes as audio packet
* @throws {RangeError} If `raw_packet` is too long bytes as audio packet
* @throws {RangeError} If `raw_packet` is too long bytes as silent audio packet
*/
export function is_audio_packet(raw_packet, throw_on_invalid = false) {
try {
// Arguments type checking
if(!(raw_packet instanceof Uint8Array)) {
throw new TypeError(`raw_packet must be Uint8Array, but got ${typeof_detail(raw_packet)}`);
}
// Packet content availability checking
if(raw_packet.length === 0) {
throw new RangeError("Empty array passed");
}
// Packet type ID checking
if(raw_packet[0] !== AUDIO_PACKET_TYPE_ID && raw_packet[0] !== SILENT_AUDIO_PACKET_TYPE_ID) {
throw new RangeError(`It has not an audio packet or silent audio packet type ID - should be ${AUDIO_PACKET_TYPE_ID} or ${SILENT_AUDIO_PACKET_TYPE_ID}, but got ${raw_packet[0]}`);
}
// Packet length checking
if(raw_packet.length < 5) {
throw new RangeError("Too short bytes received, external bytes length field missing");
}
// Packet length checking (for [non]silent pattern)
if(raw_packet[0] === AUDIO_PACKET_TYPE_ID) {
const EXPECTED_LENGTH = 5 + raw_packet[4] + ONE_FRAME_SAMPLES * ONE_SAMPLE_BYTES;
if (raw_packet.length < EXPECTED_LENGTH) {
throw new RangeError(`Too short bytes as audio packet - expected ${EXPECTED_LENGTH}, but got ${raw_packet.length}`);
} else if(raw_packet.length > EXPECTED_LENGTH) {
throw new RangeError(`Too long bytes as audio packet - expected ${EXPECTED_LENGTH}, but got ${raw_packet.length}`);
}
} else if(raw_packet[0] === SILENT_AUDIO_PACKET_TYPE_ID) {
const EXPECTED_LENGTH = 5 + raw_packet[4];
// // Error of RangeError("Too short bytes as silent audio packet") will be matched as
// // RangeError("Too short bytes received, external bytes length missing")
// if (raw_packet.length < EXPECTED_LENGTH) {
// throw new RangeError("Too short bytes as silent audio packet");
// }
if (raw_packet.length > EXPECTED_LENGTH) {
throw new RangeError(`Too long bytes as silent audio packet - expected ${EXPECTED_LENGTH}, but got ${raw_packet.length}`);
}
}
} catch(e) {
if(throw_on_invalid) {
throw e;
} else {
return false;
}
}
return true;
}
|