iOS语音转换SDK相关记录

前言:

在开发iOS ASR 语音转文字SDK中遇到一系列问题,途中尝试解决方案及技术要点进行记录和学习积累 AVAudioSession 相关文章可参考

一、基础:

基础实现部分不再详细堆叠(网上文章较多),以下是主要技术要点和知识点

  • websocket(如果需要通过后台网络进行TTS相关语音转换)
  • 语音录制相关基础知识 采样率、通道(声到)、声音位数(采样精度)、编码格式(wav,mp3等)
  • 录音基础设置相关 AVAudioSession, 系统声音相关设置,包括硬件(话筒、耳机)之间的切换和优化
  • 录音设备单元相关 AudioComponent,AudioUnit等相关输入输出设置
  • 无限录制转换注意对内存进行控制
  • 容易引发崩溃的点

二、出现的问题和对应分析解决:

  • 如果APP中集成了其他语音类SDK,在使用的时候会影响我方SDK,主要影响点:

    • AVAudioSession 相关设置,这个是全局设置。设置后也可能会影响app内其他语音类SDK(要想简单完美解决这种最好的方式当然是整个语音模块都自己实现,不过复杂平台app显然不可能)

      objectivec 复制代码
              [session setCategory:AVAudioSessionCategoryPlayAndRecord
                           withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker |
                                        AVAudioSessionCategoryOptionAllowBluetooth |
                                        AVAudioSessionCategoryOptionOverrideMutedMicrophoneInterruption
                                 error:&error];

      上面代码设置后,在使用activate 就会全局改变AVAudioSession的设置。可能对app内其他使用同样AVAudioSession的SDK造成影响。这里只记录其他语音SDK设置这个对我方造成的影响

      1.情景一:前面有个语音SDK执行语音播报,播报完成后立马呼起我方语音SDK。此时无论对方AVAudioSession,只要调起我方SDK,我们直接按上面重新设置session。此时如果前面没有戴耳机是通过设备mic呼出,这时候启动我方语音SDK效果正常。这时对方一定也是设置了 AVAudioSessionCategoryOptionDefaultToSpeaker,我们这个也是defaultToSpeaker,系统没有发生设备切换。但是如果此时呼出后我们戴的是耳机就会出现另一种状况,会发现我方SDK启动较慢,而且1秒多后才能正常录音,这个原因就是我们这里发生了设备切换。而且代码中监控设备切换做了重新停止和再开启的原因。

      2.情景二: 当前有个语音SDK正在进行长播报,此时我方要启动我方语音SDK。产品要求,不能影响当前播报,我方语音可以正常呼起对话框,然后正常说话讲语音转为文字。这个情况再调我方语音SDK上面同样操作也会出现问题,开启后因为重新设置和activate 会直接中断播报。

      以上2种情况可以合并解决:1、首先判断AVAudioSession是否是激活状态。(并不能直接调用isActive,实际项目中根本没有这个方法)或者使用以下判断。

      objectivec 复制代码
      // 初始化时注册通知
      - (void)setupAudioSessionObserver {
          [[NSNotificationCenter defaultCenter] addObserver:self
                                                   selector:@selector(handleAudioInterruption:)
                                                       name:AVAudioSessionInterruptionNotification
                                                     object:nil];
      }
      
      // 记录当前激活状态的变量
      @property (nonatomic, assign) BOOL isAudioSessionActive;
      
      // 通知回调:处理激活/中断事件
      - (void)handleAudioInterruption:(NSNotification *)notification {
          NSDictionary *userInfo = notification.userInfo;
          AVAudioSessionInterruptionType type = [userInfo[AVAudioSessionInterruptionTypeKey] integerValue];
      
          if (type == AVAudioSessionInterruptionTypeBegan) {
              // 会话被中断(变为未激活)
              self.isAudioSessionActive = NO;
          } else if (type == AVAudioSessionInterruptionTypeEnded) {
              // 中断结束(可能恢复激活)
              AVAudioSessionInterruptionOptions options = [userInfo[AVAudioSessionInterruptionOptionKey] integerValue];
              if (options & AVAudioSessionInterruptionOptionShouldResume) {
                  // 允许恢复激活
                  self.isAudioSessionActive = YES;
              }
          }
      }

      2、存储当前 session 的category 和 options。根据已有的的category,option 添加设置自己需要的 category,option(注意不要改原始的,自己重新定义一个,这个无论active和不是active 都会生效),如果要去不打断播报就设置mode为AVAudioSessionModeVoiceChat,同时注意 options 中要有mix。3、这里很重要,如果是active 就不要再设置 active 为YES。如果这样会直接中断当前其他语音SDK。

    • setMode 这个方法如果设置,会影响其他SDK,categoryOptions 会随之更改。实测如果设置 mode = VoiceChat/Measurement,categoryOptions 会变成1 。

      解决这个问题就是结束自己语音的时候,把开始存储的对应session category和categoryOptions 重新设置为原始值以防止影响其他语音SDK。

  • 容易引起崩溃的点:

    • 快速点击/连续启动引起的崩溃:CMBAudioUnitRecorder *recorder = (__bridge CMBAudioUnitRecorder *)(inRefCon); 类似录音单元这里 inRefCon 有可能是空指针引起崩溃,特别是如果没有控制用户行为连续快速点击启动的时候

    解决这个问题的方法:在你开始设置callback时进行强引用,然后在结束的时间进行释放

    开始时 复制代码
        AURenderCallbackStruct inputCallBackStruce;
        inputCallBackStruce.inputProc = inputCallBackFun;
        self.inputProcRefCon = (__bridge_retained void *)self;
        inputCallBackStruce.inputProcRefCon = self.inputProcRefCon;
    结束时 复制代码
       // 释放 retained 的 self
       if (self.inputProcRefCon) {
           CFRelease(self.inputProcRefCon);
           self.inputProcRefCon = NULL;
       }
    • 无网飞行模式下引起的崩溃:这个主要原因和上面 inRefCon 空指针类似。AudioUnit(特别是 RemoteIO)的输入回调是系统底层音频线程(AURemoteIO::IOThread)触发的。即使你在主线程调用 [recorder stopRecord] 或释放对象,只要没有正确 停止 AudioUnit 并移除回调 ,系统仍然会在底层线程调用 inputCallBackFun(), 这时 inRefCon 就成了一个悬空指针(dangling pointer) ,转成 (__bridge CMBAudioUnitRecorder *) 时自然就是 nil 或无效内存。

    解决方案:结束记得回收资源

结束释放 复制代码
            CheckError(AudioOutputUnitStop(self->audioUnit),"AudioOutputUnitStop failed");
        AURenderCallbackStruct emptyCallback = {0};
        AudioUnitSetProperty(self->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &emptyCallback, sizeof(emptyCallback));
        // 释放 retained 的 self
        if (self.inputProcRefCon) {
            CFRelease(self.inputProcRefCon);
            self.inputProcRefCon = NULL;
        }
        
        self->_isRecording = NO;
        AudioUnitUninitialize(self->audioUnit);
        AudioComponentInstanceDispose(self->audioUnit);
        self->audioUnit = NULL;
  • 无限录制时造成内存泄露:inputCallBackFun设置回调方法时,会有持续数据流进入,这时要注意对内存进行管理

三、优化相关:

  • 加快整个SDK启动速度及效率:
    • 使用多线程,使用队列,单独维持一条线程进行语音SDK的整个录音启动流程
    • session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil\];AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation opt 可以告诉系统不用等前面的 session 状态马上启动自己的session

    • 监控设备之间的切换,根据不同状态进行SDK重启相关操作
注册通知 复制代码
        //注册通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
相关方法 复制代码
- (void)handleInterruption:(NSNotification *)notification {
    AVAudioSessionInterruptionType type = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
    if (type == AVAudioSessionInterruptionTypeBegan) {
        [self stop];
    }else if (type == AVAudioSessionInterruptionTypeEnded) {
        [self start];
    }
}
  • 注意整个工程的内存管理,特别是自己管理内存的相关地方(例如自己实现的 C/C++方法相关,及其他CF需要内存管理的地方)
相关推荐
00后程序员张38 分钟前
Swift 应用加密工具的全面方案,从源码混淆到 IPA 成品加固的多层安全实践
安全·ios·小程序·uni-app·ssh·iphone·swift
2501_916008894 小时前
提高 iOS 应用逆向难度的工程实践,多工具联动的全栈安全方案
android·安全·ios·小程序·uni-app·cocoa·iphone
2501_915909068 小时前
iOS App 测试工具全景指南,构建从开发、性能到系统级调试的多工具协同测试体系
android·测试工具·ios·小程序·uni-app·iphone·webview
AskHarries9 小时前
RevenueCat 接入 Apple App Store 订阅全流程详解(2025 最新)
flutter·ios·app
ajassi200011 小时前
开源 Objective-C IOS 应用开发(十三)通讯--Http访问
ios·开源·objective-c
游戏开发爱好者811 小时前
Fiddler抓包工具完整教程 HTTPHTTPS抓包、代理配置与API调试实战技巧(开发者进阶指南)
前端·测试工具·ios·小程序·fiddler·uni-app·webview
m0_4955627811 小时前
iOS的蓝牙
macos·ios·cocoa
songgeb11 小时前
[WWDC]Why is my app getting killed 学习笔记
ios
00后程序员张12 小时前
iOS 性能优化的体系化方法论 从启动速度到渲染链路的多工具协同优化
android·ios·性能优化·小程序·uni-app·iphone·webview
shanyanwt12 小时前
1分钟解决iOS App Store上架图片尺寸问题
前端·ios