问题描述
我的程序正在使用scriptprocessorNode
重新创建AudioWorklet
。不幸的是,我没有服务器可以对其进行测试,但是由于Google Chrome Labs Audio Worklet Polyfill,我得以使其得以运行。无论如何,这个想法的目的是通过使用新的替换功能scriptprocessorNode
重新创建与原始AudioWorklet
类似的功能(因为现已弃用)。
这是代码:
scriptProcessingWorklet.js
class ScriptProcessingHandler extends AudioWorkletProcessor {
constructor(options) {
super();
this._frameSize = 128;
this._bufferSize = options.processorOptions.bufferSize;
//this._frameCount represent a number indicating how many 128 sample frames it needs to
//to match the buffer size since all of the valid buffer sizes at https://developer.mozilla.org/en-US/docs/Web/API/scriptprocessorNode
// are all divisible by 128
this._frameCount = this._bufferSize / this._frameSize;
//arrays for storing audio data frames
this._inputData = [];
this._outputData = [];
this.port.onmessage = this._processMessage.bind(this);
}
_processMessage(e) {
switch (e.data.message) {
case "PROCESS_OUTPUT":
this._processOutput(e.data.detail);
break;
}
}
_sendMessage(message,detail = null) {
if (!message) {
return;
}
this.port.postMessage({message,detail});
}
_sendinputData(frames) {
var numChannels = 0,len = 0,f = 0;
for (f = 0; f < frames.length; f++) {
numChannels = Math.max(frames[f].length,numChannels);
len += frames[f][0].length;
}
//convert audio data frames in to one array with the correct amount of channels
var channels = new Array(numChannels).fill(new Float32Array(len));
for (f = 0; f < frames.length; f++) {
for (var c = 0; c < frames[f].length; c++) {
channels[c].set(frames[f][c]);
}
}
var detail = {
numberOfChannels: numChannels,numberOfFrames: this._frameCount,length: len,channelData: channels,};
this._sendMessage("PROCESS_DATA",detail);
}
_processInput(data) {
// if array length of audio data is equal to how many frames it needs to create a sample buffer
if (this._inputData.length >= this._frameCount) {
//then extract them out of the array and process each frame into one
var extract = this._inputData.splice(0,this._frameCount);
this._sendinputData(extract);
}
//add audio data from input to array
this._inputData.push(data);
}
_processOutput(data) {
//convert audio data back to multiple 128 sample frames
var temp = new Array(data.numberOfChannels).fill(new Float32Array(this._frameSize));
var frames = new Array(data.numberOfFrames).fill(temp);
for (var f = 0; f < frames.length; f++) {
var offset = 0,end = this._frameSize;
for (var c = 0; c < frames[f].length; c++) {
var samples = data.channelData[c].slice(offset,end);
frames[f][c].set(samples);
}
offset += this._frameSize;
end += this._frameSize;
}
this._outputData = this._outputData.concat(frames);
}
//process() calls each time with 128 samples per call
process(inputs,outputs,params) {
if (!(inputs[0][0] instanceof Float32Array)) {
return true;
}
this._processInput(inputs[0]);
if (this._outputData.length > 0) {
var output = outputs[0];
var outData = this._outputData.shift();
for (var c = 0; c < output.length; c++) {
output[c].set(outData[c]);
}
}
return true;
}
}
registerProcessor("script-processing-handler",ScriptProcessingHandler);
scriptprocessorNodePollyfill.js
(function(root) {
"use strict";
if (typeof AudioWorkletNode === "undefined") {
return;
}
//constants
const bufferSizes = [128,256,512,1024,2048,4096,8192,16384];
const defaults = {
bufferSize: 2048,numberOfInputChannels: 2,numberOfOutputChannels: 2,}
//utilites
const util = {
isNumber: function(x) {
return typeof x === "number" && isFinite(x);
},assert: function(bool,msg) {
if (!bool) {
throw new Error(msg);
}
},isValidBufferSize: function(size) {
util.assert(util.isNumber(size),"scriptprocessorNodePoylyfill.buffeSize is not a number");
var index = bufferSizes.indexOf(size);
util.assert(index !== -1,"scriptprocessorNodepolyfill.bufferSize is not a valid buffer size");
},isValidChannels: function(n,t) {
util.assert(util.isNumber(n),"scriptprocessorNodepolyfill.numberOf" + t + "Channels is not a number");
util.assert(n > 0,"scriptprocessorNodepolyfill.numberOf" + t + "Channels must be at at least 1 or more channels");
}
}
//Don't worry about the Events part of this code
//AudioProcessingEvent DeFinition
//defined in https://webaudio.github.io/web-audio-api/#audioproccessingevent
class AudioProcessingEventpolyfill extends Event {
constructor(type,init) {
super(type);
this.inputBuffer = init.inputBuffer;
this.outputBuffer = init.outputBuffer;
this.playbackTime = init.playbackTime;
}
}
//main libaray
class scriptprocessorNodepolyfill /*extends EventTarget*/ {
constructor(context,options) {
//super();
var opts = Object.assign({},defaults,options || {});
util.isValidBufferSize(opts.bufferSize);
util.isValidChannels(opts.numberOfInputChannels,"Input");
util.isValidChannels(opts.numberOfOutputChannels,"Output");
this._process = null;
this._options = opts;
this.context = context;
this.bufferSize = opts.bufferSize;
this._worklet;
this._event;
//initializing AudioWorklet
context.audioWorklet.addModule("scriptProcessingWorklet.js").then(() => {
//the properties of AudioWorkletNode and options are set to the properties based on
//scriptprocessorNode defined in https://webaudio.github.io/web-audio-api/#scriptprocessornode
this._worklet = new AudioWorkletNode(context,"script-processing-handler",{
numberOfInputs: 1,numberOfOutputs: 1,outputChannelCount: [opts.numberOfOutputChannels],processorOptions: {
bufferSize: opts.bufferSize,}
});
this._worklet.channelInterpretation = "speakers";
this._worklet.channelCountMode = "explicit";
this._worklet.channelCount = opts.numberOfInputChannels;
this._worklet.port.onmessage = this._handleMessage.bind(this);
}).catch((err) => {
throw err;
});
}
//this function is what triggers the onaudioprocess event
// I have defined a class called AudioProcessingEventpolyfil that inherits Event class for this
// so it can be disptached using window.dispatchEvent()
// but i was easier to start of with a function input instead
// using the property this._process to store the function
// I will worry about Events later. One step at a time :)
async processData(data) {
//create input and output buffers
var input = this.context.createBuffer(data.numberOfChannels,data.length,this.context.sampleRate);
var output = this.context.createBuffer(this._options.numberOfOutputChannels,this.context.sampleRate);
var playbackTime = this.context.currentTime;
//this is where the AudioProcessingEventpolyfill will go
this._event = {
playbackTime: playbackTime,inputBuffer: input,outputBuffer: output,};
//fill input buffer with data sent from worklet
for (var c = 0; c < data.numberOfChannels; c++) {
input.getChannelData(c).set(data.channelData[c]);
}
//execute function with input and output buffers with playbackTime from context.currentTime
//using async/await to make sure the function is properly finished first before anything else happens
//await window.dispatchEvent("audioprocess");
await this._process(this._event);
//create an array to fill with audio data from output buffer to be sent to worklet
var channels = new Array(output.numberOfChannels);
for (var c = 0; c < channels.length; c++) {
channels[c] = output.getChannelData(c);
}
//send audio data to worklet
this._worklet.port.postMessage({
message: "PROCESS_OUTPUT",detail: {
channelData: channels,length: output.length,numberOfChannels: output.numberOfChannels,},});
//garbage-collect audio buffers since we don't need them any
//AudioWorkletGlobalScope does not define AudioBuffer in the worker enviroment
input = null;
output = null;
}
_handleMessage(e) {
switch (e.data.message) {
case "PROCESS_DATA":
this.processData(e.data.detail);
break;
}
}
connect() {
this._worklet.connect.apply(this._worklet,arguments);
}
disconnect() {
this._worklet.disconnect.apply(this._worklet,arguments);
}
addEventListener(type,fn) {
this._process = fn;
}
}
//exposed to global window scope
root.scriptprocessorNodepolyfill = scriptprocessorNodepolyfill;
root.AudioProcessingEventpolyfill = AudioProcessingEventpolyfill;
})(this);
问题是我可以使用它,但是设置bufferSize不能输出良好的声音。在测试时,将128作为bufferSize
可以很好地工作,但高于128的声音会产生这种毛刺的声音。
我知道问题是AudioWorkletProcessor.process()
一次只调用128个样本。我确实考虑过尝试通过返回true或false来使用活动源标志,但我意识到在阅读规范后返回false不会再次调用process()方法。实际上,我不是可以通过脚本再次调用以将活动标志从false转换为true。有人有什么想法吗?
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)