-
Notifications
You must be signed in to change notification settings - Fork 1
/
TrackCtrl-1.html
334 lines (279 loc) · 9.81 KB
/
TrackCtrl-1.html
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script async src="//hm.baidu.com/hm.js?f79493fc378b2235419a88daa91d5f6d"></script>
<title>Web前端JS如何控制 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据</title>
<style>
body {
padding: 50px;
}
h1 {
text-align: center;
}
section {
margin: 100px auto;
width: 1200px;
}
audio,
input[type="file"] {
width: 471px;
}
label {
position: relative;
}
.VL,
.VR,
#VLV,
#VRV,
#TBLV,
#TBRV {
position: absolute;
top: 30px;
left: 0;
}
.VR {
left: auto;
right: -22px;
}
#VLV {
top: 50px;
left: 20px;
}
#VRV {
top: 50px;
left: 150px;
}
#TBLV {
left: auto;
top: 80px;
right: 10px;
}
#TBRV {
left: 20px;
top: 80px;
right: auto;
}
meter {
width: 128px;
transform: rotate(-90deg);
}
button {
font-size: 16px;
}
</style>
</head>
<body>
<h1>Web前端JS如何控制 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据</h1>
<hr />
<section>
<label>
<button type="button" id="LBTN">左声道(L)</button>
<input type="range" id="VL" min="0" max="10" step="0.1" class="VL" value="1" />
<span id="VLV">音量:1.0</span>
<meter id="L" min="-60" low="-20" high="-6" max="3" value="-60" style="margin-left: 13px"></meter>
<span id="TBLV">峰值电平:-60</span>
</label>
添加本地视音频:<input id="file" type="file" accept="video/*,audio/*" />
<label>
<meter id="R" min="-60" low="-20" high="-6" max="3" value="-60"></meter>
<span id="TBRV">峰值电平:-60</span>
<button type="button" id="RBTN">右声道(R)</button>
<input type="range" id="VR" min="0" max="10" step="0.1" class="VR" value="1" />
<span id="VRV">音量:1.0</span>
</label>
<br /><br />
<div class="box">
<button type="button" id="SLBTN">左环绕(SL)</button>
<meter id="SL" min="-60" low="-20" high="-6" max="3" value="-60"></meter>
<!--<audio controls loop width="600">
<source src="./media/xxx.mp3" type="audio/mp3" />
</audio>-->
<video controls width="600">
<source src="./media/Project DT-5.1.mp4" type="video/mp4" />
</video>
<meter id="SR" min="-60" low="-20" high="-6" max="3" value="-60"></meter>
<button type="button" id="SRBTN">右环绕(SR)</button>
</div>
</section>
<br><br><br>
<a href="https://blog.csdn.net/muguli2008/article/details/134762971" target="_blank">🚀了解更多!</a>
<script>
// 获取本地视音频
file.addEventListener("change", function () {
const file = this.files[0];
console.log(file);
if ("video/mp4".startsWith("video")) {
// video.src = window.URL.createObjectURL(file);
audio.src = window.URL.createObjectURL(file);
} else {
audio.src = window.URL.createObjectURL(file);
}
});
// 计算音频log对数
function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
};
// 计数峰值电平
function countPeakLevel(float) {
return getBaseLog(10, float) * 20;
};
// 提取声轨并计算最大值
function countMaxValue(buffer) {
const channels = [], { numberOfChannels } = buffer;
for (let i = 0; i < numberOfChannels; i += 1) {
channels[i] = 0.0;
const channelData = buffer.getChannelData(i);
for (let j = 0; j < channelData.length; j += 1) {
(Math.abs(channelData[j]) > channels[i]) && (channels[i] = Math.abs(channelData[j]));
}
}
return channels;
};
// const audio = document.querySelector("audio");
const audio = document.querySelector("video");
let ac = null;
// 音频元数据加载后执行
audio.addEventListener("loadedmetadata", () => {
if (!ac) {
// 上下文对象 由于浏览器安全策略要求音频上下文必须在用户事件(单击、键盘按键等)中启用。这意味着,如果您尝试在没有用户事件的情况下自动播放音乐,所以在loadedmetadata元数据已加载时再执行!!
ac = new (window.AudioContext || window.webkitAudioContext)();
// 创建并获取输入源
const audioSource = ac.createMediaElementSource(audio);
// 缓冲区大小 取值为 2 的幂次方的一个常数
const bufferSize = 2048;
// 音频通道数 默认值是 2,最高能取 32
const channelCount = 4 || audioSource.channelCount;
// 创建音频处理器
const processor = ac.createScriptProcessor(bufferSize, channelCount, channelCount);
// 链接音频处理器
audioSource.connect(processor).connect(ac.destination);
// 创建通道控制器
const volumeNodeL = new GainNode(ac, { gain: 1 });
const volumeNodeR = new GainNode(ac, { gain: 1 });
const volumeNodeSL = new GainNode(ac, { gain: 1 });
const volumeNodeSR = new GainNode(ac, { gain: 1 });
// 创建通道分配器
const splitterNode = new ChannelSplitterNode(ac, {
numberOfOutputs: channelCount,
});
splitterNode.connect(volumeNodeL, 0);
splitterNode.connect(volumeNodeR, 1);
splitterNode.connect(volumeNodeSL, 2);
splitterNode.connect(volumeNodeSR, 3);
// 控制链接到输入源
audioSource.connect(splitterNode);
// 创建通道合并器
const mergerNode = new ChannelMergerNode(ac, {
numberOfInputs: channelCount,
});
volumeNodeL.connect(mergerNode, 0, 0);
volumeNodeR.connect(mergerNode, 0, 1);
volumeNodeSL.connect(mergerNode, 0, 2);
volumeNodeSR.connect(mergerNode, 0, 3);
// 通道链接到扬声器
mergerNode.connect(ac.destination);
audio.play();
// 监听音频处理器每次处理的样本帧
processor.onaudioprocess = (evt) => {
// console.log("音频通道数:", evt.inputBuffer.numberOfChannels);
//获取声轨1输入的缓冲区数据
// evt.inputBuffer.getChannelData(0);
//获取声轨1输出的缓冲区数据
// evt.outputBuffer.getChannelData(0);
const [l, r, sl, sr] = countMaxValue(evt.inputBuffer);
// 声轨1
if (volumeNodeL.gain.value && l) {
L.value = countPeakLevel(l);
TBLV.innerText = '峰值电平:' + L.value.toFixed(2);
}
// 声轨2
if (volumeNodeR.gain.value && r) {
R.value = countPeakLevel(r);
TBRV.innerText = '峰值电平:' + R.value.toFixed(2);
}
// 声轨3
if (volumeNodeSL.gain.value && sl) {
SL.value = countPeakLevel(sl);
}
// 声轨4
if (volumeNodeSR.gain.value && sr) {
SR.value = countPeakLevel(sr);
}
/*
if (volumeNodeL.gain.value) {
// 声轨1
let input = evt.inputBuffer.getChannelData(0),
len = input.length,
i = 0;
while (i < len) {
L.value = countPeakLevel(input[i++]);
TBLV.innerText = '峰值电平:' + L.value.toFixed(2);
}
};
if (volumeNodeR.gain.value) {
// 声轨2
let input = evt.inputBuffer.getChannelData(1),
len = input.length,
i = 0;
while (i < len) {
R.value = countPeakLevel(input[i++]);
TBRV.innerText = '峰值电平:' + R.value.toFixed(2);
}
};
if (volumeNodeSL.gain.value) {
// 声轨3 (为了性能,这里不实时计算,只取第1个样本帧计算一次)
let input = evt.inputBuffer.getChannelData(2);
SL.value = countPeakLevel(input[0]);
};
if (volumeNodeSR.gain.value) {
// 声轨4 (为了性能,这里不实时计算,只取中间的计算一次)
let input = evt.inputBuffer.getChannelData(3);
SR.value = countPeakLevel(input[input.length / 2]);
};
*/
};
// 静音左L声道
LBTN.onclick = function () {
volumeNodeL.gain.value = Number(!volumeNodeL.gain.value);
};
// 左L声道音量大小控制
VL.oninput = function () {
// volumeNodeL.gain.value = this.value;
volumeNodeL.gain.setValueAtTime(this.value, ac.currentTime);
VLV.innerText = '音量:' + this.value;
};
// 静音右R声道
RBTN.onclick = function () {
volumeNodeR.gain.value = Number(!volumeNodeR.gain.value);
};
// 右R声道音量大小控制
VR.oninput = function () {
volumeNodeR.gain.setValueAtTime(this.value, ac.currentTime);
VRV.innerText = '音量:' + this.value;
};
// 静音左环绕SL声道
SLBTN.onclick = function () {
volumeNodeSL.gain.value = Number(!volumeNodeSL.gain.value);
};
// 静音右环绕SR声道
SRBTN.onclick = function () {
volumeNodeSR.gain.value = Number(!volumeNodeSR.gain.value);
};
} else {
audio.play();
};
}, false);
// 监听音频播放时,激活当前播放
audio.addEventListener('play', () => {
ac.resume();
}, false);
// 监听音频暂停时,挂起当前播放
audio.addEventListener('pause', () => {
ac.suspend();
}, false);
</script>
</body>
</html>