使用JavaScript播放PCM16时的饱和声音

Saturated sound when playing PCM16 with JavaScript

本文关键字:声音 JavaScript 播放 PCM16 使用      更新时间:2023-09-26

我正在尝试用AudioContext对象播放原始PCM16缓冲区。声音确实在播放,但以一种非常饱和的方式播放。

问题可能来自于将无符号16位整数转换为[-1.0;1.0]浮点,但我认为我所做的没有任何问题。

我在下面做了一个最小的html样本,以便于复制。output.raw文件可以通过以下命令从MP3文件中获得:

MP3FILE=myfile.mp3
ffmpeg -i $MP3FILE -f s16le -ac 1 -acodec pcm_s16le output.raw

我使用Linux Chromium Version 38.0.2125.111 (290379) (64-bit)

谢谢你的帮助!

EDIT:直接加载PCM32(s32le)样本会出现同样的问题。

<html>
<head>
</head>
<body>
<script>
var oReq = new XMLHttpRequest();
oReq.open("GET", "output.raw", true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
  var context = new window.AudioContext();
  var pcm16Buffer = new Uint16Array(oReq.response);
  var frameCount = pcm16Buffer.length;
  var channels = 1;
  var buffer = context.createBuffer(channels, frameCount, context.sampleRate);
  for (var channel = 0; channel < channels; ++channel) {
    var channelBuffer = buffer.getChannelData(channel);
    for (var i = 0; i < frameCount; ++i) {
      channelBuffer[i] = pcm16Buffer[i] * 2 / 65535 - 1;
    }
  }
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start();
};
function run() {
  oReq.send();
}
</script>
<a href="javascript:run()">run</a>
</body>
</html>

答案:将PCM16转换为PCM32的上述循环是不正确的,因为它不能正确地转换2的补码。

您正在使用ffmpeg输出有符号的16位数据,但随后您正在通过Uint16Array类型的数组在JavaScript中读取它,该数组是无符号的16位数。这不会在文件中为您提供-32k到+32k的值,这些值被转移到0到64k;值存储在二的补码中,因此负值将显示为较大的正值。主要需要做的是将pcm16Buffer更改为Int16Array

转换为浮点可以是一个简单的

channelBuffer[i] = pcm16Buffer[i] / 32768;

Web Audio API只能呈现PCM缓冲区,因此无论音频源自何处,进程最终都必须提供该格式。首先,使用Audacity等其他工具验证您的输入文件是否正常。以下是一个输入WAV文件,它在渲染之前从中提取原始PCM缓冲区。

<html>
<head>
</head>
<body>
    <script>
        var audio_context, gain_node, source_node;
        var chosen_audio_file_url = "output_2.wav";
        // log if an error occurs
        function on_error(e) {
            console.log("ERROR - " + e);
        }
        try {
            window.AudioContext = window.AudioContext ||
            window.webkitAudioContext ||
            window.mozAudioContext ||
            window.oAudioContext ||
            window.msAudioContext;
            audio_context = new AudioContext();
            console.log("cool audio context established");
        } catch (e) {
            alert("Web Audio API is not supported by this browser");
        }

        gain_node = audio_context.createGain(); // Declare gain node
        gain_node.connect(audio_context.destination); // Connect gain node to speakers
        source_node = audio_context.createBufferSource();
        source_node.connect(gain_node);
        var request = new XMLHttpRequest();
        request.open('GET', chosen_audio_file_url, true);
        request.responseType = 'arraybuffer';
        // When loaded decode the data
        request.onload = function() {
            // decode the data
            audio_context.decodeAudioData(request.response, function(buffer) {
                console.log(chosen_audio_file_url, ' ... buffer.length ', buffer.length);
                source_node.buffer = buffer;
                source_node.start(0);
                // ---
            }, on_error);
        }
        function run() {
            request.send();
        }
    </script>
    <a href="javascript:run()">run</a>
</body>
</html>

如果您需要更多,请告诉我们

您试图从无符号转换为有符号,但您的代码没有做任何可能更改符号的事情。首先,通过减去32767将无符号的16位(0-65536)转换为带符号(-32767到32767)。然后除以32767,缩放到-1.0到1.0。此外,请确保您的除法是浮点除法。

channelBuffer[i] = (pcm16Buffer[i]-32767) / 32767.0;