给出一个30s的简单网络视频:
我怎样才能生成其音量水平图?
volume|
level| ******
| * * **
| * * * **
|** * *** *
| ** * * * *
+---------------*-*-----************------+--- time
0 30s
video is and quiet
loud here here
注意:
请简单的JavaScript。没有图书馆。
答案
根据用途的不同,有几种方法可以做到这一点。
为了准确,您可以测量传统的音量和单位,如RMS,LUFS / LKFS(K加权,响度),dBFS(满量程dB)等等。
简单朴素的方法是绘制波形的峰值。您只会对正值感兴趣。要获得峰值,您将检测两点之间的方向,并在方向从向上变为向下时记录第一个点(p0> p1)。
对于所有方法,您最终可以应用某种形式的平滑,例如加权移动平均值(example)或通用平滑算法,以消除小峰值和变化,在RMS,dB等情况下,您将使用可与之组合的窗口大小bin-smoothing(每段平均值)。
要绘制图形,您将获得当前样本的值,假设它被标准化并将其绘制为线条或指向由绘图区域高度缩放的画布。
Mini-discussion as to loading the source data
解决评论中的一些问题;这些只是我的头脑中给出一些指示 -
由于Web Audio API无法自行进行流式传输,因此您必须将整个文件加载到内存中并将音轨解码为缓冲区。
优点:工作(分析部分),数据最终准备时的快速分析,适用于较小的文件,如果缓存,可以使用URL而无需重新下载
缺点:较长的初始加载时间/较差的UX,可能的内存占用/不适合大文件,音频与视频同步“分离”,强制重用URL *,如果大和/或缓存不到位,文件将必须再次下载/流式传输,目前在某些浏览器/版本中会出现问题(请参阅下面的示例)。
*:总是可以选择将下载的视频作为blob存储在IndexedDB中(具有其含义),并使用带有该blob的Object-URL在视频元素中流式传输(可能需要MSE正常工作,没有尝试过自己) 。
在流式传输时绘图:
优点:内存/资源便宜
缺点:在整个文件播放完毕之前,情节无法完整显示,用户可能会跳过/跳转部分,可能无法完成
侧面加载低质量单声道纯音频文件:
优点:音频可以独立于视频文件加载到内存中,从而产生足够接近水平使用的近似值
缺点:可以延迟视频的初始加载,可能在视频启动前未及时准备好,需要提前进行额外处理
服务器端绘图:
优点:可以在上传时绘制,可以存储在请求视频时作为元数据提供的原始绘图数据,低带宽,视频启动时的数据就绪(假设数据表示时间段上的平均值)。
缺点:需要服务器上的基础设施,可以分离,分析和生成绘图数据,具体取决于数据的存储方式可能需要修改数据库。
我可能会遗漏或错过一些观点,但它应该给出一般的想法......
例
该示例测量每个样本的给定窗口大小的常规dB。窗口大小越大,结果越平滑,但也需要更多时间来计算。
注意,为简单起见,在该示例中,像素位置确定dB窗口范围。这可能会产生不均匀的间隙/重叠,这取决于影响当前样本值的缓冲区大小,但是应该用于此处演示的目的。同样为了简单起见,我将dB读数除以40,这里有点任意数字(ABS仅用于绘图和我的大脑工作方式(?)在深夜/清晨我做了这个:)) 。
我在顶部添加了红色的bin / segment-smoothing,以更好地显示与自动调平等相关的长期音频变化。
我在这里使用音频源但你可以插入一个视频源,只要它包含一个可以解码的音轨格式(aac,mp3,ogg等)。
除此之外,这个例子只是一个例子。这不是生产代码所以把它当作它的价值。根据需要进行调整。
(出于某种原因,音频将无法在Firefox v58beta中播放,但它会绘制。音频在Chrome,FF58dev中播放)。
var ctx = c.getContext("2d"), ref, audio;
var actx = new (AudioContext || webkitAudioContext)();
var url = "//dl.dropboxusercontent.com/s/a6s1qq4lnwj46uj/testaudiobyk3n_lo.mp3";
ctx.font = "20px sans-serif";
ctx.fillText("Loading and processing...", 10, 50);
ctx.fillStyle = "#001730";
// Load audio
fetch(url, {mode: "cors"})
.then(function(resp) {return resp.arrayBuffer()})
.then(actx.decodeAudioData.bind(actx))
.then(function(buffer) {
// Get data from channel 0 (you will want to measure all/avg.)
var channel = buffer.getChannelData(0);
// dB per window + Plot
var points = [0];
ctx.clearRect(0, 0, c.width, c.height);
ctx.moveTo(x, c.height);
for(var x = 1, i, v; x < c.width; x++) {
i = ((x / c.width) * channel.length)|0; // get index in buffer based on x
v = Math.abs(dB(channel, i, 8820)) / 40; // 200ms window, normalize
ctx.lineTo(x, c.height * v);
points.push(v);
}
ctx.fill();
// smooth using bins
var bins = 40; // segments
var range = (c.width / bins)|0;
var sum;
ctx.beginPath();
ctx.moveTo(0,c.height);
for(x = 0, v; x < points.length; x++) {
for(v = 0, i = 0; i < range; i++) {
v += points[x++];
}
sum = v / range;
ctx.lineTo(x - (range>>1), sum * c.height); //-r/2 to compensate visually
}
ctx.lineWidth = 2;
ctx.strokeStyle = "#c00";
ctx.stroke();
// for audio / progressbar only
c.style.backgroundImage = "url(" + c.toDataURL() + ")";
c.width = c.width;
ctx.fillStyle = "#c00";
audio = document.querySelector("audio");
audio.onplay = start;
audio.onended = stop;
audio.style.display = "block";
});
// calculates RMS per window and returns dB
function dB(buffer, pos, winSize) {
for(var rms, sum = 0, v, i = pos - winSize; i <= pos; i++) {
v = i < 0 ? 0 : buffer[i];
sum += v * v;
}
rms = Math.sqrt(sum / winSize); // corrected!
return 20 * Math.log10(rms);
}
// for progress bar (audio)
function start() {if (!ref) ref = requestAnimationFrame(progress)}
function stop() {cancelAnimationFrame(ref);ref=null}
function progress() {
var x = audio.currentTime / audio.duration * c.width;
ctx.clearRect(0,0,c.width,c.height);
ctx.fillRect(x-1,0,2,c.height);
ref = requestAnimationFrame(progress)
}
body {background:#536375}
#c {border:1px solid;background:#7b8ca0}
以上就是本文的全部内容,希望对大家的学习有所帮助,本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。