如何生成HTML5视频音量图表?

时间:2017-12-30 07:08:10

标签: javascript html5-video html5-audio web-audio-api

给出了30s的简单网络视频:

<video src="my-video.mp4"></video>

我如何生成其音量水平图?

volume|
 level|    ******
      |   *      *                           **
      |  *        *                         *  **
      |**          *      ***              *    
      |             ** * *   *            *
      +---------------*-*-----************------+--- time
      0                                        30s
          video is             and quiet 
          loud here            here

注意:

  • 简单的JavaScript,请。没有图书馆。

1 个答案:

答案 0 :(得分:4)

根据用途,有几种方法可以做到这一点。

为了准确,您可以使用常规音量和单位进行测量,例如RMS,LUFS / LKFS(K加权,响度),dBFS(满量程dB)等等

简单朴素的方法是绘制波形的峰值。您只会对正值感兴趣。要获得峰值,您将检测两点之间的方向,并在方向从向上变为向下时记录第一个点(p0> p1)。

对于所有方法,您最终可以应用某种形式的平滑,例如加权移动平均(example)或通用平滑算法,以消除小峰值和变化,在RMS,dB等情况下,您将使用窗口大小,可与bin平滑(每段平均)结合使用。

要绘制图形,您将获得当前样本的值,假设它被标准化并将其绘制为线条或指向由绘图区域高度缩放的画布。

关于加载源数据的小型讨论

解决评论中的一些问题;这些只是在我的头脑中提供一些指示 -

由于Web Audio API无法自行进行流式处理,因此您必须将整个文件加载到内存中并将音轨解码为缓冲区。

  • 优点:工作(分析部分),数据最终准备时的快速分析,适用于较小的文件,如果缓存,则可以使用URL而无需重新下载
  • 缺点:较长的初始加载时间/较差的UX,可能的内存占用/不适合大文件,音频是&#34;分离的&#34;从视频同步,强制重用URL * ,如果没有大型和/或缓存,文件将不得不再次下载/流式传输,目前在某些浏览器/版本中引起问题(请参阅例子如下)。

* 总是可以选择将下载的视频存储为IndexedDB中的blob(含义),并使用带有该blob的Object-URL在视频元素中流式传输(可能要求MSE正常工作,我自己也没有尝试过。

流式传输时的绘图:

  • 优点:内存/资源便宜
  • 缺点:在整个文件播放完毕之前,剧情无法完整显示,用户可能会跳过/跳转部分,可能无法完成

侧面加载低质量单声道纯音频文件:

  • 优点:音频可以独立于视频文件加载到内存中,从而产生足够的近似级别使用
  • 缺点:可以延迟视频的初始加载,可能在视频启动前未及时准备好,需要提前进行额外处理

服务器端绘图:

  • 优点:可以在上传时绘制,可以存储在请求视频时作为元数据提供的原始绘图数据,低带宽,视频启动时的数据就绪(假设数据代表时间段的平均值)。
  • 缺点:要求服务器上的基础设施可以分离,分析和生成绘图数据,具体取决于数据的存储方式可能需要修改数据库。

我可能会遗漏或错过一些观点,但它应该给出一般的想法......

实施例

此示例测量每个样本的给定窗口大小的常规dB。窗口大小越大,结果越平滑,但也需要更多时间来计算。

请注意,为简单起见,此示例中像素位置确定dB窗口范围。这可能会产生不均匀的间隙/重叠,这取决于影响当前样本值的缓冲区大小,但是应该用于此处演示的目的。同样为了简单起见,我将dB读数除以40,这里有点任意数字(ABS仅用于绘图和我的大脑工作方式(?)在深夜/清晨我做了这个:))

我在顶部添加了红色的bin / segment-smoothing,以更好地显示与自动调平等相关的长期音频变化。

我在这里使用音频源但你可以插入视频源,只要它包含可以解码的音轨格式(aac,mp3,ogg等)。

除此之外,这个例子只是一个例子。它不是生产代码,所以要把它当作它的价值。根据需要进行调整。

(出于某种原因,音频不会在Firefox v58beta中播放,但它会描绘。音频在Chrome中播放,FF58dev)。

&#13;
&#13;
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)
}
&#13;
body {background:#536375}
#c {border:1px solid;background:#7b8ca0}
&#13;
<canvas id=c width=640 height=300></canvas><br>
<audio style="display:none" src="//dl.dropboxusercontent.com/s/a6s1qq4lnwj46uj/testaudiobyk3n_lo.mp3" controls></audio>
&#13;
&#13;
&#13;