播放 AVAudioPlayerNode 时 AVAudioEngine inputNode 的格式发生变化

2023-11-24

我将从我制作的一个简单的“游乐场”视图控制器类开始,该类演示了我的问题:

class AudioEnginePlaygroundViewController: UIViewController {
    private var audioEngine: AVAudioEngine!
    private var micTapped = false
    override func viewDidLoad() {
        super.viewDidLoad()
        configureAudioSession()
        audioEngine = AVAudioEngine()
    }

    @IBAction func toggleMicTap(_ sender: Any) {
        guard let mic = audioEngine.inputNode else {
            return
        }
        if micTapped {
            mic.removeTap(onBus: 0)
            micTapped = false
            return
        }
        stopAudioPlayback()

        let micFormat = mic.inputFormat(forBus: 0)
        print("installing tap: \(micFormat.sampleRate) -- \(micFormat.channelCount)")
        mic.installTap(onBus: 0, bufferSize: 2048, format: micFormat) { (buffer, when) in
            print("in tap completion")
            let sampleData = UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength))
        }
        micTapped = true
        startEngine()
    }

    @IBAction func playAudioFile(_ sender: Any) {
        stopAudioPlayback()
        let playerNode = AVAudioPlayerNode()

        let audioUrl = Bundle.main.url(forResource: "test_audio", withExtension: "wav")!
        let audioFile = readableAudioFileFrom(url: audioUrl)
        audioEngine.attach(playerNode)
        audioEngine.connect(playerNode, to: audioEngine.outputNode, format: audioFile.processingFormat)
        startEngine()
        playerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
        playerNode.play()
    }

    // MARK: Internal Methods

    private func configureAudioSession() {
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers, .defaultToSpeaker])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch { }
    }

    private func readableAudioFileFrom(url: URL) -> AVAudioFile {
        var audioFile: AVAudioFile!
        do {
            try audioFile = AVAudioFile(forReading: url)
        } catch { }
        return audioFile
    }

    private func startEngine() {
        guard !audioEngine.isRunning else {
            return
        }

        do {
            try audioEngine.start()
        } catch { }
    }

    private func stopAudioPlayback() {
        audioEngine.stop()
        audioEngine.reset()
    }
}

上面的 VC 有一个 AVAudioEngine 实例和两个 UIButton 操作:一个用于播放在硬编码 URL 中找到的音频文件,另一个用于切换引擎上点击的安装/删除输入节点.

我的目标是让实时麦克风点击和音频文件播放同时工作,但彼此完全排斥。也就是说,我希望无论麦克风点击的当前状态如何都能够触发播放,反之亦然。如果我在触发音频文件播放之前安装水龙头,一切都会完全按预期工作。但是,如果我先播放音频文件,然后尝试安装 Tap,则会出现以下崩溃:

[avae] AVAEInternal.h:70:_AVAE_Check: required condition is false: [AVAEGraphNode.mm:810:CreateRecordingTap: (IsFormatSampleRateAndChannelCountValid(format))]

这导致我通过上面的日志语句检查麦克风格式的数据安装Tap称呼。果然,当我在播放前安装 Tap 时,我得到的预期采样率为 44100.0,通道数为 1。但是当我先播放音频文件并then安装麦克风水龙头后,我的日志显示采样率为 0,通道数为 2,这给了我上面显示的错误。

我尝试过修改 AVAudioEngine 的启动/重置流程,尝试了 AVAudioSession 的不同类别/模式组合(请参阅我的配置音频会话方法),并尝试手动创建 Tap 格式,如下所示:

let micFormat = mic.inputFormat(forBus: 0)
var trueFormat: AVAudioFormat!
if micFormat.sampleRate == 0 {
    trueFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1)
} else {
    trueFormat = micFormat
}
print("installing tap: \(micFormat.sampleRate) -- \(micFormat.channelCount)")
mic.installTap(onBus: 0, bufferSize: 2048, format: trueFormat) { (buffer, when) in
    print("in tap completion")
    let sampleData = UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength))
}

这给了我一个类似但不同的错误:

[avae] AVAEInternal.h:70:_AVAE_Check: required condition is false: [AVAudioIONodeImpl.mm:896:SetOutputFormat: (IsFormatSampleRateAndChannelCountValid(hwFormat))]

我看不出麦克风的格式数据会根据是否播放 AVAudioPlayerNode 而变化的任何原因。


经过一番搜索,我发现了问题所在。问题出在音频引擎上输入节点单例。来自文档:

当首次访问 inputNode 时,音频引擎会按需创建一个单例。要接收输入,请从输入音频节点的输出连接另一个音频节点,或在其上创建录音水龙头。

再加上我遇到的格式问题的参考:

检查输入节点的输入格式(特别是硬件格式)是否有非零采样率和通道数,以查看输入是否已启用。

在我的 Playground 类中,触发音频文件播放的流程永远不会访问引擎的输入节点在创建“活动链”之前:

audioEngine.connect(playerNode, to: audioEngine.outputNode, format: audioFile.processingFormat)

看来你必须访问 AVAudioEngine 的输入节点如果您希望引擎在内部配置自身以进行输入,请在 start() 之前进行。即使停止()和重置()引擎也不会导致访问输入节点重新配置引擎。 (我怀疑通过手动打破活动链断开节点调用将允许内部重新配置,但我还不确定)。

因此,从代码角度来看,修复很简单:只需在实例化后立即访问引擎的输入节点,以便将引擎配置为音频输入。这是文件播放和麦克风点击一起工作的整个课程:

import UIKit

class AudioEnginePlaygroundViewController: UIViewController {
    private var audioEngine: AVAudioEngine!
    private var mic: AVAudioInputNode!
    private var micTapped = false

    override func viewDidLoad() {
        super.viewDidLoad()
        configureAudioSession()
        audioEngine = AVAudioEngine()
        mic = audioEngine.inputNode!
    }

    @IBAction func toggleMicTap(_ sender: Any) {
        if micTapped {
            mic.removeTap(onBus: 0)
            micTapped = false
            return
        }

        let micFormat = mic.inputFormat(forBus: 0)
        mic.installTap(onBus: 0, bufferSize: 2048, format: micFormat) { (buffer, when) in
            let sampleData = UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength))
        }
        micTapped = true
        startEngine()
    }

    @IBAction func playAudioFile(_ sender: Any) {
        stopAudioPlayback()
        let playerNode = AVAudioPlayerNode()

        let audioUrl = Bundle.main.url(forResource: "test_audio", withExtension: "wav")!
        let audioFile = readableAudioFileFrom(url: audioUrl)
        audioEngine.attach(playerNode)
        audioEngine.connect(playerNode, to: audioEngine.outputNode, format: audioFile.processingFormat)
        startEngine()
        playerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
        playerNode.play()
    }

    // MARK: Internal Methods

    private func configureAudioSession() {
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers, .defaultToSpeaker])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch { }
    }

    private func readableAudioFileFrom(url: URL) -> AVAudioFile {
        var audioFile: AVAudioFile!
        do {
            try audioFile = AVAudioFile(forReading: url)
        } catch { }
        return audioFile
    }

    private func startEngine() {
        guard !audioEngine.isRunning else {
            return
        }

        do {
            try audioEngine.start()
        } catch { }
    }

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

播放 AVAudioPlayerNode 时 AVAudioEngine inputNode 的格式发生变化 的相关文章

  • ios 8 核心数据崩溃

    保存时 CoreData 发生崩溃 2014 09 16 09 51 58 273 My app 2678 105246 Terminating app due to uncaught exception NSInvalidArgument
  • 如何保存 1 个 xcode 项目中的所有构建设置并在其他 xcode 项目上使用它们?

    我使用 xcode 4 5 和 cordova phonegap 来构建我的应用程序 我投入了大量时间来获取适合我的 Xcode 项目的构建设置 并且我想在我正在构建的多个应用程序上重用这些设置 我正在寻找是否有一种快速的方法来导出这些设置
  • 新的 FUITableViewDataSource - 如何使用?雨燕3

    刚刚更新到较新的 FirebaseUI Pod 有些事情发生了变化 但其中最大的变化之一是 FUI 表视图的工作方式 我让它在旧版本上运行良好 但在下面遇到了困难 并且缺乏文档 示例 self dataSource FUITableView
  • 在界面生成器/故事板中设置 UIButton 图像

    我有一个视图控制器 我在故事板中添加了一个圆形矩形按钮 该应用程序运行良好 我还使用故事板将按钮连接到 segue 我正在尝试为此按钮设置一个自定义图像以用于其开和关状态 我如何访问此按钮并设置其属性 在本例中为开和关图像 这是一个屏幕截图
  • Firebase 连接管理器应仅返回一个结果

    我正在关注位于以下位置的文档 https www firebase com docs ios guide offline capability html section connection state https www firebase
  • 如何等待 webViewDidFinishLoad 完成

    我有一个初始化 webView 的布尔条件 并在 webViewDidFinishLoad 中加载另一个委托 以便在完成完成后触发 但是 由于布尔值在条件 webViewDidFinishLoad 之前返回 因此页面永远不会完全加载 如何确
  • 使用 Push Transition 效果更改 RootViewcontroller

    在我的iOS应用程序中 我需要更改应用程序之间窗口的rootviewController 因此 当我动态更改我的rootviewcontroller时 它会在更改之前轻拂视图 但我想要的是在更改rootviewcontroller时提供平滑
  • 使用隐藏的 SFSafariViewController 获取 Safari cookie

    我正在阅读以下关于从移动网页到本机 iOS 9 应用程序对用户进行身份验证的文章 并且想知道如何最好地实现类似于下面讨论的隐藏控制器的隐藏 safari 视图控制器 https library launchkit io how ios 9
  • WebGL iOS 渲染为浮点纹理

    我正在尝试在 iOS Safari 上的 WebGL 中渲染浮点纹理 而不是在本机应用程序中 我已经设法让 iOS 读取手动 例如从 JavaScript 创建的浮点纹理 但是当我创建浮点类型的帧缓冲区并使用 GPU 渲染到其中时 它不起作
  • UIBezierPath 的起始和结束角度?

    我在 iOS 中使用如下代码编写了半圆UI贝塞尔路径 and CAShape层 clockWiseLayer CAShapeLayer alloc init CGFloat startAngle M PI 2 CGFloat endAngl
  • iOS 内存警告

    我正在尝试使用从 Parse 数据库下载的图像填充集合视图 但我收到内存警告 然后偶尔崩溃 有谁知道其他应用程序如何设法呈现这么多图像而不崩溃 有人可以告诉我如何优化我已有的东西吗 这是所有相关代码 https gist github co
  • 切换到工作区并在 Xcode 中添加 CocoaPods 后提交 git 吗?

    我刚刚在 Xcode 5 中将 CocoaPods 添加到我当前的项目中 当然 CocoaPods 创建了一个工作区 并且我已在 Xcode 中启动了该工作区 我在工作区中看到了我的项目和 Pods 项目 我的项目从第一天起就处于源代码控制
  • swift 中的负数模

    负数模如何在 swift 中工作 当我执行 1 3 时 它给出 1 但余数是 2 其中有什么问题 雨燕余数运算符 计算余数 整数除法 a b a a b b where 是截断整数除法 在你的情况下 1 3 1 1 3 3 1 0 3 1
  • iPad 3 中配备 Xcode 4.2 和 Retina 的 iOS 5.1

    我有一台装有 Mac OS X Snow Leopard 的 Mac 我可以添加 iOS 5 1 吗 使用 iPad 3 的新分辨率 我们将如何处理图像 因为如果该应用程序将在 iPhone 3GS 4 和 iPad 3 中运行 我认为我们
  • 打印附加结构(swift 4)

    我有三个 textifled 用于将数据附加到结构中 如何打印我附加的内容 现在我收到一条错误消息 import UIKit class ViewController UIViewController IBOutlet var c UITe
  • 如何在 Xcode 4 中通过一个操作归档多个目标

    我有一个包含多个目标的项目 这些目标都适用于不同的 iOS 应用程序 例如 一个用于精简版的目标 另一个用于专业版的目标 我想立即构建并归档我的所有应用程序 目前 我对每个目标都有一个方案 我用它来独立归档每个应用程序 但现在我必须开始归档
  • 如何在ios中以编程方式添加水平间距和垂直间距?

    我在 ios 8 中创建了一个应用程序 因为我有 4 个可垂直使用的标签 它应该在某些条件下更改位置 所以我已禁用自动布局并以编程方式设置约束 现在的问题是 我可以设置水平和垂直位置 宽度和高度的约束 但我找不到任何方法来添加标签之间的水平
  • 播放(非库)Apple Music 内容 - 请求失败

    我正在尝试使用以下代码播放专辑 let predicate MPMediaPropertyPredicate value 1459938538 forProperty MPMediaItemPropertyAlbumPersistentID
  • iOS 发送 iMessage 尽可能简单

    我希望能够以编程方式发送 iMessage 除了调用一个将文本发送到带有消息的号码的函数之外 无需执行任何其他操作 这两个消息都是文本框 我真的很感激一些示例代码 因为我在网上搜索过 但我发现没有任何帮助 这不适用于商业应用程序 仅适用于我
  • 在 iOS9 中观察 AVPlayerItem 的值

    我有一个应用程序使用AVPlayer玩一个AVPlayerItem 视频 来自远程 URL 在 iOS 6 8 中我一直在观察AVPlayerItem s价值loadedTimeRanges通知我playerItem已准备好供玩家播放 当观

随机推荐

  • 将图像水平居中定位并使高度为视口的 100%

    我有一张图像占据了视口的整个高度 图像高度应跨越整个视口高度 100 以便适合查看的屏幕 此处已完成 并且宽度应与高度成相对比例 正如您在我的页面中看到的 http lamininbeauty co za 页面两侧有空间 我希望图像水平居中
  • Spring Batch - 计算已处理的行数

    因此 我正在创建一个 Spring Batch 作业来读取 CSV 文件以及包含不完整数据的某些行 它检查该行不完整 并将其输出到日志 然后跳过 它工作得很好 除了在工作结束时我希望它记录它发现的不完整的行数 只是一些简单的事情 比如 发现
  • 在数据库中查找最接近的数值

    我需要找到一个 select 语句 该语句将返回与我的输入完全匹配的记录 或者如果未找到完全匹配则返回最接近的匹配 这是到目前为止我的选择声明 SELECT FROM myTable WHERE Name Test AND Size 2 A
  • 网上有什么好的 UIScrollView 教程吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 任何好的链接都将受到高度赞赏 这将转到社区维基 一些很好的示例 涵盖了基本功能 非常简单的 uiscrollview 演示 滚动 UiScrollV
  • 如何将 WPF 英寸单位转换为 Winforms 像素单位,反之亦然?

    我有一扇设计在WPF我在一个中心使用了它WinForms所有者 现在 我想移动所有者表单 目前我的WPF窗口也必须移动到窗体的中心 但我有一个问题 只有当窗口位于屏幕中心窗体的中心时 否则以与 Windows 坐标不同的形式进行操作 我只是
  • SQLite Android 数据库游标窗口分配 2048 kb 失败

    我有一个例程 每秒对 SQLite 数据库运行不同的查询多次 一段时间后我会得到错误 android database CursorWindowAllocationException Cursor window allocation of
  • UISearchController 搜索栏隐藏表视图中的第一个单元格

    我有一个带有搜索栏的桌面视图 搜索栏由 UISearchController 提供 当我将搜索栏添加到表格的标题视图时 表格的第一行被搜索栏覆盖 如何防止搜索栏隐藏第一行 我在 viewDidLoad 中有这个片段 self searchC
  • 优化使用 Between 子句的 SQL

    考虑以下 2 个表 Table A id event time Table B id start time end time 表 A 中的每条记录都映射到表 B 中的 1 条记录 这意味着表 B 没有重叠的周期 表 A 中的许多记录可以映射
  • jquery 路径点不工作

    根据我下面写的代码 我认为当我滚动到 onscrollActivate div 警报时应该出现 但它没有给我警报 div class waypoint style width 100 height 300px div document re
  • 用于隐藏和显示页面元素的 CSS 媒体查询。

    我对使用媒体查询进行编码有点陌生 我认为我已经将自己陷入了困境 使用了太多媒体查询 并且可能以错误的方式 我想做的是当屏幕或设备的宽度小于481px时隐藏一些页面元素 因此 在下面的屏幕截图中 您可能可以看到右上角的几行文本 我的问题与我使
  • 在java中,有什么方法可以检查Windows服务的状态吗? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 我正在寻找一个库 它允许我查找 Windows 服务的状态以验证该服务是否已启动并正在运行 我查看了 Sigar 库 但它是 GPL 因此我无法使用
  • TypeScript 中的 Blob

    我正在尝试使用 FileSystem API 在 TypeScript 中编写文件下载器 当我尝试创建新的 Blob 对象时 var blob Blob new Blob xhr response JSON stringify mime 我
  • Facebook 聊天插件“杀死”Pagespeed 至 33

    我已经添加了Facebook 聊天插件通过 facebook com 生成的代码 div div
  • 使用 OCR 从表格图像中提取单个字段到 Excel

    我扫描了包含表格的图像 如下图所示 我试图单独提取每个框并执行 OCR 但是当我尝试检测水平线和垂直线 然后检测框时 它返回以下图像 当我尝试执行其他转换来检测文本 侵蚀和膨胀 时 一些线条的剩余部分仍然与文本一起出现 如下所示 我无法检测
  • Java - 读取文件并拆分为多个文件

    我有一个文件 我想用 Java 读取该文件并将其拆分为n 用户输入 输出文件 这是我读取文件的方式 int n 4 BufferedReader br new BufferedReader new FileReader file csv t
  • List.IndexOf() 如何对自定义对象执行比较?

    我编写了一类帐户对象并保存静态List
  • 将二维矩阵变换为一维矩阵

    我在使用 C 中的函数将二维矩阵传递给向量 一维数组 时遇到问题 有我想要创建的代码 include
  • django.db.migrations.RenameModel 和 AutoField 序列名称

    我使用 Django 1 8 7 和 PostgreSQL 并具有以下模型 class Permission models Model name models CharField max length 255 template models
  • 查找 Android 设备的处理器速度(以 MHz 为单位)

    如何获取 Android 设备的处理器速度 以 MHz 为单位 我能够得到的速度BogoMips通过阅读 proc cpuinfo文件 如何将 BogoMips 转换为 MHz 或者是否有其他方法获得 MHz 速度 文件中具有最大处理器速度
  • 播放 AVAudioPlayerNode 时 AVAudioEngine inputNode 的格式发生变化

    我将从我制作的一个简单的 游乐场 视图控制器类开始 该类演示了我的问题 class AudioEnginePlaygroundViewController UIViewController private var audioEngine A