如何计算从波形创建的阵列列表中的可用音频频率

问题描述

我正在处理歌曲文件以创建视听视频,因此我从每一帧中提取音频信息,并且我需要知道当前数据块中正在播放哪些频率从当前帧中提取出来以操纵其他内容

A waveform example obtained with my script

我了解音频波的工作原理,也了解同一声音中的多个频率(例如,以前的图像表示在音频块末尾带有敲击声的低音)。但是我无法创建一个函数来估计该波形(存储在阵列中)中的可用频率。我的脑子现在炸了。

注意:我正在JavaScript下工作,我曾使用AudioContext从音频文件提取信息,但是我无法获得频率数据,只能得到波形。我尝试使用AnalyserNode,但是 在我看到的每个教程或文档中,AnalyserNode都需要实时重现音频以获取频率数据,并且正如我所说,我提取了代表视频每一帧的数据块,并将其在云服务器中工作。>

解决方法

我终于找到了解决方案,如果有人遇到同样的问题,我将回答我自己的问题。请记住,我只想从歌曲文件的每一帧中获取频率(然后获取频谱数据)。因此,我不得不停止寻找先前获得的样本数据的频率,以采取一些步骤返回(请参阅问题以了解最后一部分)。

我找到了AudioContext的替代品OfflineAudioContext。使用AudioContext,您需要实时播放音频文件以获取频谱数据(音调),但是使用OfflineAudioContext,您可以节省很多时间,因为它通过呈现音频文件来尽可能快地处理所有文件数据。我会说WebAudioAPI确实令人困惑,在获得预期结果后我尝试了几次,但最终还是得到了。

在下面,您将找到“最终代码”,以获取音频每一帧的频谱数据(取决于声明的fps)。此外,您还可以看到如何处理下一个数据处理视频结果中获得的数据。

Video Result(对不起,我无法将视频上传到StackOverflow,因此您需要从Google云端硬盘下载结果视频)

Javascript

它将返回每帧的光谱数据数组。

[警告]该代码与Firefox不兼容,因为它使用了OfflineAudioContext.pause()和OfflineAudioContext.resume()函数(see more details)。

let $audioCtx = new ( window.AudioContext || window.webkitAudioContext )( ) ;
let $audioOff = null ;
let $analyser = null ;
let FFT_SIZE  = 512 ;

     fetch( $data.file_url )
          .then( response    => response.arrayBuffer( ) )
          .then( arrayBuffer => $audioCtx.decodeAudioData( arrayBuffer ) )
          .then( async ( audioBuffer ) => {
            window.$audioBuffer = audioBuffer ;
            return new Promise( ( resolve,reject ) => {
              $audioOff  = new window.OfflineAudioContext( 2,audioBuffer.length,audioBuffer.sampleRate ) ;
              $analyser  = $audioOff.createAnalyser( ) ;
              $analyser  . fftSize = FFT_SIZE ;
              $analyser  . smoothingTimeConstant = $data.fps === 24 ? 0.16 : $data.fps === 29 ? 0.24 : 0.48 ;
              $analyser  . connect( $audioOff.destination ) ;
              var source = $audioOff.createBufferSource( ) ;
                  source . buffer = audioBuffer ;
                  source . connect( $analyser ) ;
              var __data = [ ] ;
              var fps    = $data.fps || 24 ;
              var index  = 0.4 ;
              var length = Math.ceil( audioBuffer.duration * fps ) ;
              var time   = ( ( 1 / fps ) ) ;
              var onSuspend = ( ) => {
                return new Promise( ( res,rej ) => {
                  index  += 1 ;
                  var raw = new Uint8Array( $analyser.frequencyBinCount ) ;
                  $analyser.getByteFrequencyData ( raw ) ;
                  __data.push( raw ) ;
                  if( index < length ) {
                    if( time * ( index + 1 ) < audioBuffer.duration ) 
                      { $audioOff.suspend( time * ( index + 1 ) ).then( onSuspend ) ; }
                    $audioOff.resume( ) ;
                  } return res( 'OK' ) ;
                } ) ;
              } ;
              $audioOff.suspend( time * ( index + 1 ) ).then( onSuspend ) ;
              source.start( 0 ) ;
              console.log( 'Decoding Audio-Spectrum...' ) ;
              $audioOff.startRendering( ).then( ( ) => {
                console.log( '[✔] Audio-Spectrum Decoded!' ) ;
                return resolve( __data ) ;
              } ).catch( ( err ) => {
                console.log( 'Rendering failed: ' + err ) ;
                throw { error : 'Get audio data error',message : err } ;
              } ) ;
            } ) ;
          } )
          .then( async ( spectrumData ) => {
            /* DO SOMETHING WITH SPECTRUM DATA */
            /* spectrumData[ 0 ] is the first frame,depending of established fps */
            /* spectrumData[ 1 ] = 2nd frame ... */
          } ) ;