在iOS11上,onaudioprocess未被调用。

22

我正在尝试在 iOS11 上的 Safari 中实现从麦克风进行音频捕获,最近已经添加了支持

然而,onaudioprocess回调从未被调用。这是一个示例页面:

<html>
    <body>
        <button onclick="doIt()">DoIt</button>
        <ul id="logMessages">
        </ul>
        <script>
            function debug(msg) {
                if (typeof msg !== 'undefined') {
                    var logList = document.getElementById('logMessages');
                    var newLogItem = document.createElement('li');
                    if (typeof  msg === 'function') {
                        msg = Function.prototype.toString(msg);
                    } else if (typeof  msg !== 'string') {
                        msg = JSON.stringify(msg);
                    }
                    var newLogText = document.createTextNode(msg);
                    newLogItem.appendChild(newLogText);
                    logList.appendChild(newLogItem);
                }
            }
            function doIt() {
                var handleSuccess = function (stream) {
                    var context = new AudioContext();
                    var input = context.createMediaStreamSource(stream)
                    var processor = context.createScriptProcessor(1024, 1, 1);

                    input.connect(processor);
                    processor.connect(context.destination);

                    processor.onaudioprocess = function (e) {
                        // Do something with the data, i.e Convert this to WAV
                        debug(e.inputBuffer);
                    };
                };

                navigator.mediaDevices.getUserMedia({audio: true, video: false})
                        .then(handleSuccess);
            }
        </script>
    </body>
</html>

在大多数平台上,当调用onaudioprocess回调函数时,您将看到项目被添加到消息列表中。然而,在iOS上,这个回调函数从未被调用。

是否有其他事情我应该尝试来在Safari的iOS 11上调用它?


我有同样的问题 =( - evtuhovdo
必须在直接响应轻拍时创建 webkitAudioContext() 或调用 .resume(),而不是在获取流时创建。请查看下面的答案,以获取您代码的完全可行版本。 - Nathan Friedly
3个回答

28

有两个问题。主要的问题是,在iOS 11上的Safari似乎会自动挂起新的AudioContext,除非这些上下文是响应于触摸事件创建的,您只能通过响应触摸事件来resume()它们。

(更新:Chrome移动版也会有这个问题,在版本70/2018年12月之后,Chrome桌面版也将有相同的限制。)

因此,您必须在获取MediaStream之前创建它,或者让用户稍后再次触摸屏幕。

你的代码中另一个问题是,在Safari中,AudioContext被称为webkitAudioContext

以下是一个可工作的版本:

<html>
<body>
<button onclick="beginAudioCapture()">Begin Audio Capture</button>
<script>
  function beginAudioCapture() {

    var AudioContext = window.AudioContext || window.webkitAudioContext;    
    var context = new AudioContext();
    var processor = context.createScriptProcessor(1024, 1, 1);
    processor.connect(context.destination);

    var handleSuccess = function (stream) {
      var input = context.createMediaStreamSource(stream);
      input.connect(processor);

      var recievedAudio = false;
      processor.onaudioprocess = function (e) {
        // This will be called multiple times per second.
        // The audio data will be in e.inputBuffer
        if (!recievedAudio) {
          recievedAudio = true;
          console.log('got audio', e);
        }
      };
    };

    navigator.mediaDevices.getUserMedia({audio: true, video: false})
      .then(handleSuccess);
  }
</script>
</body>
</html>

如果您提前设置onaudioprocess回调,那么在用户批准麦克风访问之前,您将获得空缓冲区。

顺便提一下,还有一个iOS的bug需要注意:iPod touch上的Safari(截至iOS 12.1.1)报告它没有麦克风(但实际上是有的)。因此,如果您要在该设备上请求音频,则getUserMedia会不正确地拒绝,并显示Error: Invalid constraint

值得一提的是,我在npm上维护microphone-stream包,它可以为您执行此操作并以Node.js风格的可读流形式提供音频。该包已经包含了此修复程序,因此如果您或其他人更喜欢使用它而不是原始代码,则可以使用该包。


1
是的,承诺解决被视为单独的事件,并且不算作直接用户操作,因此音频被阻止。在直接响应轻拍时创建音频上下文,然后稍后使用它。 - Nathan Friedly
2
整个混乱的原因是因为苹果不希望移动版Safari自动播放音频。因此,他们破坏了一切来防止这种情况发生。 - Nathan Friedly
1
抱歉 @nathan-friedly - 在我有机会测试并接受它作为答案之前,SO自动将赏金分配给了另一个答案。 - John Farrelly
2
即使作为对用户操作的响应,我得到了处于暂停状态的audioContext,并且必须在其创建后立即添加audioContext.resume(),这是11.0.3。 - asiop
1
@NathanFriedly 我没有直接响应用户操作实例化处理器,这可能是我不得不使用audioContext.resume的原因。 - asiop
显示剩余6条评论

4

我在iOS 11.0.1上尝试过,但不幸的是这个问题仍未得到解决。

作为一种解决方法,我想知道是否有意义用一个从缓冲区中获取流数据并每x毫秒处理一次的函数来替换ScriptProcessor。但这对功能来说是一个很大的改变。


是的,我刚刚在我的iOS 11.0.1上尝试了一下。我看过一些演示可以做到您建议的功能,但每隔X毫秒发送的"buffer"始终为空(至少在我尝试过的演示中是如此)。似乎访问麦克风字节本身才是问题所在。 - John Farrelly
1
你写的东西给了我一些想法,但不幸的是,到目前为止没有一个行得通。我还是会把它们放在这里,也许有所帮助。你提到自助餐厅为空,所以我的想法是问题可能出在getUserMedia上。尝试了{audio: true, video: true},以及navigator.getUserMedia而不是navigator.mediaDevices.getUserMedia。这些组合都没有起作用。还尝试指定设备ID,但也没有帮助。 - Daniel Wu
2
不幸的是,我不能评论Nathan的答案,但他的方法是有效的。将 processor = createScriptProcessorprocessor.connect 移动到 createMediaStremSource 之前就可以解决问题了。 - Daniel Wu
@DanielWu,你最后的评论刚好修复了我的网络应用程序,这个应用程序必须使用录音功能,所以我非常感激。你能否编辑你的答案,这样其他人就可以更容易地找到它,因为其他人都忽略了它,而在iOS Safari上似乎有很大的作用。 - velochy

2

不知道你是否在Safari设置中启用了该功能?在iOS11中,默认情况下启用,但可能你没有注意到而将其禁用。

enter image description here


感谢@damianmr的提示,但不幸的是,在Safari中启用了相机和麦克风访问权限,并且它会在我第一次访问页面时提示我允许访问。看起来一切都按照应该的方式工作,只是onaudioprocess从未被调用。 - John Farrelly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接