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

Posted

技术标签:

【中文标题】将Java代码重写为JS - 从字节创建音频?【英文标题】:rewriting Java code to JS - creating an audio from bytes? 【发布时间】:2017-08-24 21:17:15 【问题描述】:

我正在尝试将我发现用 Java 编写的一些(非常简单的)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 to produce the sound from the bytes,但这是否允许我控制此处的其他信息(即采样率等)?换句话说:在 JS 中最简单、最准确的方法是什么?

更新

我一直在尝试复制我在this answer 中找到的内容。这是我的代码的相关部分:

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

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

【问题讨论】:

更新:我尝试在这个解决方案中工作***.com/questions/24151121/…,但我得到了Uncaught (in promise) DOMException: Unable to decode audio data,我真的不了解将字节转换为声音的基础知识以了解原因 我想你可能正在寻找网络音频 api [AudioBuffer](developer.mozilla.org/en-US/docs/Web/API/AudioBuffer)。我没有足够的经验来提供完整的答案,但我想我会发表评论以防万一。 应该var buffer = new Uint8Array( bytes.length )var buffer = new ArrayBuffer( bytes.length ) 吗?为什么需要Uint8Arraybytes 是什么? 【参考方案1】:

如果bytesArrayBuffer,则无需创建Uint8Array。您可以将ArrayBuffer bytes 作为参数传递给AudioContext.decodeAudioData(),它返回一个Promise,将.then() 链接到.decodeAudioData(),以play 函数作为参数调用。

javascript at stacksn-ps,<input type="file"> 元素用于接受音频文件的上传,FileReader.prototype.readAsArrayBuffer()File 对象创建ArrayBuffer,并传递给playByteArray

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])" />

【讨论】:

我没有加载任何音频文件,我正在根据计算创建一个字节序列。反正我自己解决了。首先,当我发现它是 16 位声音时,我设法产生了声音,我实际上需要使用 32FloatArray(2 个 16 位通道)。我不得不做一个额外的步骤,从那时起声音就被扭曲了,在一位知识渊博的朋友的帮助下,我们一起找出了计算本身的一些问题(但这不是实际产生声音的重点)。 无论如何,我很感激你付出的努力,所以+1你的回答 @yuvi 您可以在答案中发布您的解决方案吗?见***.com/help/self-answer 是的,我打算这个周末有空的时候去做【参考方案2】:

我自己解决了。我对MDN docs explaining AudioBuffer 进行了更多阅读,并意识到两件重要的事情:

    我不需要对AudioData 进行解码(因为我自己创建数据,所以没有要解码的内容)。实际上,我从我正在复制的答案中吸取了这一点,回想起来,这完全没有必要。 由于我使用的是 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 创建一个通道单声道文件,或者是否有必要\更好。

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

【讨论】:

8000采样率输入如何调整这段代码?

以上是关于将Java代码重写为JS - 从字节创建音频?的主要内容,如果未能解决你的问题,请参考以下文章

为 HTML5 从设备中的线路创建实时音频流

将音频样本的字节数组更改为频率

从音频字节创建 wav 文件

使用 AUHAL 音频单元将字节写入音频文件

Java - 将 16 位有符号 pcm 音频数据数组转换为双精度数组

将短数组从音频记录转换为字节数组而不降低音频质量?