将Java代码重写为JS - 从字节创建音频?

2022-09-04 22:30:14

我试图将一些(非常简单的)Android代码重写为一个静态的HTML5应用程序(我不需要服务器来做任何事情,我想保持这种方式)。我在Web开发方面拥有广泛的背景,但对Java有基本的了解,在Android开发方面的知识甚至更少。

该应用程序的唯一功能是获取一些数字并将其转换为字节的音频啁啾声。我绝对没有问题将数学逻辑翻译成JS。我遇到麻烦的地方是当它真正产生声音时。以下是原始代码的相关部分:

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

// later in the code:

AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STATIC);

// some math, and then:

track.write(sound, 0, sound.length); // sound is an array of bytes

如何在JS中执行此操作?我可以使用dataURI从字节中产生声音,但这是否允许我控制这里的其他信息(即采样率等)?换句话说:在JS中执行此操作的最简单,最准确的方法是什么

更新

我一直试图复制我在这个答案中发现的东西。这是我代码的相关部分:

window.onload = init;
var context;    // Audio context
var buf;        // Audio buffer

function init() {
if (!window.AudioContext) {
    if (!window.webkitAudioContext) {
        alert("Your browser does not support any AudioContext and cannot play back this audio.");
        return;
    }
        window.AudioContext = window.webkitAudioContext;
    }

    context = new AudioContext();
}

function playByteArray( bytes ) {
    var buffer = new Uint8Array( bytes.length );
    buffer.set( new Uint8Array(bytes), 0 );

    context.decodeAudioData(buffer.buffer, play);
}

function play( audioBuffer ) {
    var source    = context.createBufferSource();
    source.buffer = audioBuffer;
    source.connect( context.destination );
    source.start(0);
}

但是,当我运行此程序时,我收到此错误:

Uncaught (in promise) DOMException: Unable to decode audio data

我觉得这很不寻常,因为这是一个如此普遍的错误,它设法漂亮地告诉我到底出了什么问题。更令人惊讶的是,当我逐步调试此内容时,即使错误链从该行开始(预期),它实际上在jQuery文件中(3.2.1,未压缩)中运行了几行,在错误之前会经过第5208,5195,5191,5219,5223和最后的5015行。我不知道为什么jQuery与它有任何关系,并且该错误使我不知道该尝试什么。有什么想法吗?context.decodeAudioData(buffer.buffer, play);


答案 1

如果 是,则无需创建 .您可以将参数作为参数传递给 AudioContext.decodeAudioData(),后者返回一个 , chain to ,以函数作为参数进行调用。bytesArrayBufferUint8ArrayArrayBufferbytesPromise.then().decodeAudioData()play

在 stacksnippets, element 用于接受音频文件的上传, 从对象创建, 它被传递给 .javascript<input type="file">FileReader.prototype.readAsArrayBuffer()ArrayBufferFileplayByteArray

window.onload = init;
var context; // Audio context
var buf; // Audio buffer
var reader = new FileReader(); // to create `ArrayBuffer` from `File`

function init() {
  if (!window.AudioContext) {
    if (!window.webkitAudioContext) {
      alert("Your browser does not support any AudioContext and cannot play back this audio.");
      return;
    }
    window.AudioContext = window.webkitAudioContext;
  }

  context = new AudioContext();
}

function handleFile(file) {
  console.log(file);
  reader.onload = function() {
    console.log(reader.result instanceof ArrayBuffer);
    playByteArray(reader.result); // pass `ArrayBuffer` to `playByteArray`
  }
  reader.readAsArrayBuffer(file);
};

function playByteArray(bytes) {
  context.decodeAudioData(bytes)
  .then(play)
  .catch(function(err) {
    console.error(err);
  });
}

function play(audioBuffer) {
  var source = context.createBufferSource();
  source.buffer = audioBuffer;
  source.connect(context.destination);
  source.start(0);
}
<input type="file" accepts="audio/*" onchange="handleFile(this.files[0])" />

答案 2

我自己解决了这个问题。我阅读了更多关于解释AudioBuffer的MDN文档,并意识到两件重要的事情:

  1. 我不需要解码AudioData(因为我是自己创建数据的,所以没有什么可解码的)。实际上,我从我正在复制的答案中吸取了这一点,回想起来,这完全是不必要的。
  2. 由于我正在使用16位PCM立体声,这意味着我需要使用Float32Array(2个通道,每个16位)。

当然,我仍然对一些导致声音失真的计算有问题,但就产生声音本身而言,我最终做了这个非常简单的解决方案:

function playBytes(bytes) {
    var floats = new Float32Array(bytes.length);

    bytes.forEach(function( sample, i ) {
        floats[i] = sample / 32767;
    });

    var buffer = context.createBuffer(1, floats.length, 48000),
        source = context.createBufferSource();

    buffer.getChannelData(0).set(floats);
    source.buffer = buffer;
    source.connect(context.destination);
    source.start(0);
}

我可能会进一步优化它 - 32767部分应该在此之前发生,例如,在我计算数据的部分。另外,我正在创建一个具有两个通道的Float32Array,然后输出其中一个,因为我真的不需要两个通道。我不知道是否有一种方法可以使用Int16Array创建一个通道单声道文件,或者这是否有必要\更好。

无论如何,基本上就是这样。它实际上只是最基本的解决方案,我对如何正确处理数据的理解很少。希望这对那里的任何人都有帮助。