在 Chrome 中使用 JavaScript 将音频录制为 .wav

2024-02-19

我正在构建一个网页,用于记录用户设备中的音频,并将其发送到 Microsoft 的认知语音服务以进行语音到文本的转换。到目前为止,我已经能够创建和播放用 JavaScript 制作的 .ogg 文件,但我需要获取 .wav 格式的文件。

斑点类型audio/wav不能依赖,因为并非所有浏览器都支持它(至少我的浏览器不支持)。 Blob 被发送到 Django 服务器并由其存储。当我尝试使用 PySoundFile 打开这些文件时,出现错误:File contains data in an unknown format。这些斑点是用new Blob(chunks, { type: 'audio/ogg; codecs=opus' })并保存使用django.db.FileField https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.FileField。 Blob 块来自MediaRecorder.ondataavailable.

更新: 我放弃使用媒体记录器 https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder并选择了脚本处理器节点 https://developer.mozilla.org/en-US/docs/Web/API/ScriptProcessorNode反而。同样,Firefox 可以工作,但 Chrome 不能。 Chrome 似乎在音频末尾获取了一小部分,并在音频长度内重复了这一部分。这是我使用的代码,它基于 Matt Diamond 的工作github.com/mattdiamond/Recorderjs https://github.com/mattdiamond/Recorderjs。使用他的作品的演示可以在webaudiodemos.appspot.com/AudioRecorder/index.html https://webaudiodemos.appspot.com/AudioRecorder/index.html,它对我来说适用于 Firefox 和 Chrome。另外,我原来的代码在一个类中,但我不想包含整个类。如果我在翻译中犯了任何语法错误,我深表歉意。

let recBuffers = [[], []];
let recLength = 0;
let numChannels = 2;
let listening = false;
let timeout = null;
let constraints = {
    audio: true
};
let failedToGetUserMedia = false;

if (navigator.getUserMedia) {
    navigator.getUserMedia(constraints, (stream) => {
        init(stream);
    }, (err) => {
        alert('Unable to access audio.\n\n' + err);
        console.log('The following error occurred: ' + err);
        failedToGetUserMedia = true;
    });
}
else if (navigator.mediaDevices.getUserMedia) {
    navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
        init(stream);
    }).catch((err) => {
        alert('Unable to access audio.\n\n' + err);
        console.log('The following error occurred: ' + err);
        failedToGetUserMedia = true;
    });
}
else failedToGetUserMedia = true;

function beginRecording() {
    recBuffers = [[], []];
    recLength = 0;
    listening = true;
    timeout = setTimeout(() => {
        endRecording();
    }, maxTime);
}

function endRecording() {
    clearTimeout(timeout);
    timeout = null;
    exportWAV();
}

function init(stream) {
    let audioContext = new AudioContext();
    let source = audioContext.createMediaStreamSource(stream);
    let context = source.context;
    let node = (context.createScriptProcessor || context.createJavaScriptNode).call(context, 4096, numChannels, numChannels);
    node.onaudioprocess = (e) => {
        if (!listening) return;

        for (var i = 0; i < numChannels; i++) {
            recBuffers[i].push(e.inputBuffer.getChannelData(i));
        }

        recLength += recBuffers[0][0].length;
    }
    source.connect(node);
    node.connect(context.destination);
}

function mergeBuffers(buffers, len) {
    let result = new Float32Array(len);
    let offset = 0;
    for (var i = 0; i < buffers.length; i++) {
        result.set(buffers[i], offset);
        offset += buffers[i].length;
    }
    return result;
}

function interleave(inputL, inputR) {
    let len = inputL.length + inputR.length;
    let result = new Float32Array(len);

    let index = 0;
    let inputIndex = 0;

    while (index < len) {
        result[index++] = inputL[inputIndex];
        result[index++] = inputR[inputIndex];
        inputIndex++;
    }

    return result;
}

function exportWAV() {
    let buffers = [];
    for (var i = 0; i < numChannels; i++) {
        buffers.push(mergeBuffers(recBuffers[i], recLength));
    }

    let interleaved = numChannels == 2 ? interleave(buffers[0], buffers[1]) : buffers[0];
    let dataView = encodeWAV(interleaved);
    let blob = new Blob([ dataView ], { type: 'audio/wav' });
    blob.name = Math.floor((new Date()).getTime() / 1000) + '.wav';

    listening = false;

    return blob;
}

function floatTo16BitPCM(output, offset, input){
    for (var i = 0; i < input.length; i++, offset+=2){
        var s = Math.max(-1, Math.min(1, input[i]));
        output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
    }
}

function writeString(view, offset, string){
    for (var i = 0; i < string.length; i++) {
        view.setUint8(offset + i, string.charCodeAt(i));
    }
}

function encodeWAV(samples){
    var buffer = new ArrayBuffer(44 + samples.length * 2);
    var view = new DataView(buffer);

    /* RIFF identifier */
    writeString(view, 0, 'RIFF');
    /* file length */
    view.setUint32(4, 36 + samples.length * 2, true);
    /* RIFF type */
    writeString(view, 8, 'WAVE');
    /* format chunk identifier */
    writeString(view, 12, 'fmt ');
    /* format chunk length */
    view.setUint32(16, 16, true);
    /* sample format (raw) */
    view.setUint16(20, 1, true);
    /* channel count */
    view.setUint16(22, numChannels, true);
    /* sample rate */
    view.setUint32(24, context.sampleRate, true);
    /* byte rate (sample rate * block align) */
    view.setUint32(28, context.sampleRate * 4, true);
    /* block align (channel count * bytes per sample) */
    view.setUint16(32, numChannels * 2, true);
    /* bits per sample */
    view.setUint16(34, 16, true);
    /* data chunk identifier */
    writeString(view, 36, 'data');
    /* data chunk length */
    view.setUint32(40, samples.length * 2, true);

    floatTo16BitPCM(view, 44, samples);

    return view;
}

if (!failedToGetUserMedia) beginRecording();

更新: 我已经确认,当 Chrome 缓冲区的值作为 Firefox 上交错的输入提供时,输出与 Chrome 的输出相同。这表明 Chrome 没有用正确的值填充 recBuffers。事实上,当我在 Chrome 上查看 recBuffers 时,每个通道都充满了交替列表。例如:

recBuffers = [[
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1]
], [
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8]
]]

当然,实际值是不同的。这只是一个例子来说明这一点。


最初,我使用 MediaRecorder 获取音频并从所述音频创建一个类型为 Blobaudio/wav。这在 Chrome 中不起作用,但在 Firefox 中却可以。我放弃了这一点并开始使用 ScriptProcessorNode。同样,它适用于 Firefox,但不适用于 Chrome。经过一些调试后,很明显在 Chrome 上,recBuffers 被交替列表填满了。我仍然不确定为什么会发生这种情况,但我的猜测是范围界定或缓存之类的东西,因为扩展语法解决了它。将 onaudioprocess 中的一行更改为this.recBuffers[i].push(e.inputBuffer.getChannelData(i)); to this.recBuffers[i].push([...e.inputBuffer.getChannelData(i)]); worked.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Chrome 中使用 JavaScript 将音频录制为 .wav 的相关文章

  • 了解设置 JQuery 变量

    了解设置 JQuery 变量 最近 我通过在 StackOverflow 上遇到的另一个问题寻找帮助 了解到如何设置 JQuery 变量 如下所示 您可以通过简单地调用变量来创建输入字段 并且锚变量似乎也定义了样式 var clicked
  • 如何重置使用 JavaScript 更改的 CSS 属性?

    我的导航按钮的宽度从 100px 增加到 150px 当鼠标悬停在 nav li hover width 150px 但是使用 javascript 我已经做到了 无论选择哪个选项 宽度都将继续为 150px 当选择每个选项时 它会使其他选
  • 使用模数按字母顺序对列表进行排序

    我在获取元素列表并按字母顺序对它们进行排序方面没有任何问题 但我很难理解如何使用模数来做到这一点 更新 这是按我的方式工作的代码 但是 我更喜欢下面提供的答案的可重用性 因此接受了该答案
  • Meteor:应用程序无法在 0.9.1.1 版本上运行

    出现类似错误 Error TypeError undefined is not a function evaluating Template create anonymous function iron dynamic template j
  • 如何监听 jQuery AJAX 请求?

    以下两种实现 ajaxRequest 1 2 的方法应该是等效的 话说回来 为什么验证回调已执行的单元测试 3 在 1 中成功而在 2 中失败 我应该如何重写测试 3 来监视 2 中的成功回调 如果我尝试stub jQuery ajax使用
  • 可以使用 jQuery 或 Javascript 将图片的特定部分用作链接吗?

    我有这个想法 将图片 而不是文本 的各个部分链接到不同的页面或网站 并且我想在不实际创建不同的照片并将它们彼此靠近的情况下完成 这样看起来就像是一张完整的图片 这里有人知道如何使用 JavaScript 的变体 例如 jQuery 或纯 J
  • Node.js:如何在检索数据(块)时关闭响应/请求

    我正在用 node js 构建一个应用程序 它加载多个页面并分析内容 因为 node js 发送块 所以我可以分析这些块 如果一个块包含例如索引 nofollow 我想关闭该连接并继续其余部分 var host example com to
  • 为什么是 javascript:history.go(-1);无法在移动设备上工作?

    首先 一些背景 我有一个向用户呈现搜索页面 html 表单 的应用程序 填写标准并单击 搜索 按钮后 结果将显示在标准部分下方 在结果列表中 您可以通过单击将您带到新页面的链接来查看单个结果的详细信息 在详细信息页面中 我添加了一个 返回结
  • Meteor - 从客户端取消服务器方法

    我正在通过服务器方法执行数据库计数 用户可以选择他们希望如何执行计数 然后调用该方法 我的问题是 计数可能需要一些时间 并且用户可能会在方法运行时改变主意并请求不同的计数 有什么方法可以取消调用的方法并运行新的计数吗 我认为 this un
  • 跟踪用户何时点击浏览器上的后退按钮

    是否可以检测用户何时单击浏览器的后退按钮 我有一个 Ajax 应用程序 如果我可以检测到用户何时单击后退按钮 我可以显示适当的数据 任何使用 PHP JavaScript 的解决方案都是优选的 任何语言的解决方案都可以 只需要我可以翻译成
  • 在 webpack 2.x 中使用 autoprefixer 和 postcss

    如何使用autoprefixer使用 webpack 2 x 以前 它曾经是这样的 module loaders test scss loader style css sass postcss postcss gt return autop
  • Babel 7 Jest Core JS“TypeError:wks不是函数”

    将我的项目升级到 Babel 7 后 通过 Jest 运行测试会抛出以下错误 测试在 Babel 6 中运行没有任何问题 但在 Babel 7 中失败并出现以下错误 TypeError wks is not a function at Ob
  • Electron - 为什么在关闭事件时将 BrowserWindow 实例设置为 null

    The 电子文档 https electronjs org docs api browser window 提供以下代码示例来创建新窗口 const BrowserWindow require electron let win new Br
  • Javascript 数组到 VBScript

    我有一个使用 Javascript 构建的对象数组 我需要使用 VBScript 读取它 如下例所示 我找不到在 VbScript 代码中循环遍历数组的方法myArray object 这个例子是我的问题的简化 我无法更改页面的默认语言 这
  • 模块构建失败(来自 ./node_modules/babel-loader/lib/index.js)Vue Js

    我从 GitHub 下载了一个我和我的朋友正在开发的项目 但是当我尝试运行时 npm run serve 我收到这个错误 src main js 中的错误 Module build failed from node modules babe
  • 如何获取给定 DOM 元素的所有定义的 CSS 选择器?

    如何使用 jQuery 获取给定 DOM 元素的所有定义的 CSS 选择器 定义后 我的意思是在应用于任何样式表的所有 CSS 选择器document 在某种程度上 这类似于 FireBug 实现的功能 其中显示所选 DOM 元素的所有应用
  • 有没有办法阻止 prettier / prettier-now 将函数参数分解为新行

    当使用 prettier prettier now 在保存时进行格式化时 当一个函数包装另一个函数时 它会中断到一个新行 我想知道是否有办法阻止这种行为 例如 期望的输出 app get campgrounds id catchAsync
  • Javascript转换时区问题

    我在转换当前时区的日期时间时遇到问题 我从服务器收到此日期字符串 格式为 2015 10 09T08 00 00 这是中部时间 但是当我使用 GMT 5 中的 new Date strDate 转换此日期时间时 它返回给我的信息如下 这是不
  • 为什么 jquery 没有检测到单选按钮未被选中的情况? [复制]

    这个问题在这里已经有答案了 可能的重复 JQuery radioButton change 在取消选择期间不会触发 https stackoverflow com questions 5176803 jquery radiobutton c
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成

随机推荐

  • 反应本机本地通知

    我是 React Native 的新手 需要实现一个功能 应用程序需要每天在特定时间向用户发送通知 每天要显示的数据存储在客户端的json文件中 不会改变 通知已按计划进行 鉴于我希望有一种方法可以从应用程序本身触发通知 有谁知道无需将应用
  • 访问相机和照片库

    在我的 iOS 应用程序中 我有一个 ImageView 和两个用于打开相机和照片库的按钮 当我单击其中一个按钮时 应用程序将关闭 我在我的设备上运行应用程序 而不是模拟器 我的代码需要更改什么 class PhotoViewControl
  • 保留 std::set 或 std::unordered_set 上的插入顺序

    在将其标记为重复之前 我已经here https stackoverflow com questions 1098175 a stdmap that keep track of the order of insertion here htt
  • PDO bindColumn 和 PDO::FETCH_BOUND —— 强制还是可选?

    在我们的 PHP 代码的许多地方 如果重要的话可以使用 postgres 我们有这样的东西 q SELECT DISTINCT a id FROM alarms current a entities e installations i q
  • jQuery AJAX 响应设置 Cookie 标头

    我有一个使用 REST API 的项目 在这里 当我发送登录请求时 他们会以包含一些数据的 JSON 形式向我发送响应 与响应标头中的内容一起 Access Control Allow Origin Connection keep aliv
  • 在不同的元素上使用相同的控制器来引用同一个对象

    我想如果我打了一巴掌ng controller GeneralInfoCtrl 在我的 DOM 中的多个元素上 它们会共享相同的内容 scope 或者至少双向绑定不起作用 我想要这样做的原因是因为我在 HTML 的不同部分有不同的只读视图和
  • SVG 不从父级继承值

    所以这似乎是一个错误今天出现在 Chrome 中 显然还有 Firefox 我有一个 SVG 包裹在一个跨度内 我也尝试过 div 和对象标签 这似乎并不重要 并且该跨度具有指定的高度和宽度 昨天 子元素会根据其父元素的完整尺寸适当调整自身
  • 是否有一种算法可以从数组中提取二重奏中的值并对它们进行操作? [复制]

    这个问题在这里已经有答案了 我有一个像这样的数组 1 2 3 4 5 6 7 8 9 我想在二重唱中获得这样的项目 1 2 做一些操作 2 3 做一些操作 3 4 做一些操作 4 5 做一些操作 5 6 做一些操作 6 7 做一些操作 7
  • 从 Samsung Tizen TV 应用程序启动 AppStore

    我正在尝试在基于 Javascript 的 Tizen TV 应用程序中实现一个按钮 单击该按钮后 它应该打开另一个应用程序的 App Store 页面 我指的是这些文件 https developer tizen org sites de
  • UIWebView 日志消息 iOS 7 [重复]

    这个问题在这里已经有答案了 我正在开发一个使用网络浏览器的应用程序 在构建它后我收到了以下消息 UITextField webView called This method is no longer supported with the n
  • 如果模板尚未使用某种类型实例化,是否可能会触发编译器/链接器错误?

    后续问题 转换为指向模板的指针是否会实例化该模板 https stackoverflow com questions 8379002 does a casting to a pointer to a template instantiate
  • Vim:如何转到声明(类、方法、函数、变量等)

    现在我正在处理一个使用许多类 方法 函数 变量等的文件 是否可以转到所有这些的声明 请考虑到其中一些声明位于同一个文件中 但其他声明位于其他文件中 这些文件可能未打开 并且您不知道声明在哪里 但它们确实存在 如果声明在目录中为上一级 会发生
  • 在线性时间内旋转数组的算法

    如何旋转整数数组i使用次数swap仅在线性时间内起作用 您可以使用reverse 帮助器在线性时间内完成此操作 rotate array of size size by n positions void rotate int array i
  • Android 获取所有可用存储设备

    是否有任何功能 方法可以通过名称检测手机的各种不同存储空间 我的意思是检测所有内部 外部存储 如果您的设备上有多个可用存储 如何检测是否有可用的SD卡 提前致谢 我不知道其他类型的存储设备 我只知道Android的内部存储和外部存储设备 现
  • 如何使用不同元素的事件监听器管理不同的事件?

    document ready function var player 1 listener myVid 1 0 f player 1 listener addEvent ready ready var player 2 listener m
  • 如何在内存中保存位图

    跟进将位图资源存储在静态变量中 https stackoverflow com questions 8814455 storing a bitmap resource in a static variable 似乎存储一个静态引用andro
  • 更新查询结果错误

    我有一个名为company emp 在该表中 我有 6 列与员工相关 empid ename dob doj 我还有另一张桌子叫bday 我只有 2 列 恩皮德和多布 我有这样的疑问 select empid dob from compan
  • 显示带有故事板的一次性登录屏幕的最佳实践

    我在这里看到过类似的问题 但没有明确的答案 因此 我有一个模式登录视图 其中包含经典的用户名 密码表单 Facebook 登录按钮和注册按钮 我希望在用户第一次启动应用程序时显示这些按钮 据我发现 有两种方法可以实现这一点 但都有缺点 在
  • 命令字符串中带有 & 符号的 PowerShell Invoke-Expression

    我试图将带有包含 符号的字符串的变量传递到 Invoke Expression 中 它告诉我必须将其放在引号中并将其作为字符串传递 我尝试了转义和使用原始字符串以及变量中的字符串与 和 组合的多种组合 但均无济于事 我该怎么做 这是代码 s
  • 在 Chrome 中使用 JavaScript 将音频录制为 .wav

    我正在构建一个网页 用于记录用户设备中的音频 并将其发送到 Microsoft 的认知语音服务以进行语音到文本的转换 到目前为止 我已经能够创建和播放用 JavaScript 制作的 ogg 文件 但我需要获取 wav 格式的文件 斑点类型