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需要内存管理的地方)
相关推荐
2501_916007479 小时前
Fastlane 结合 开心上架 命令行版本实现跨平台上传发布 iOS App
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张10 小时前
iOS 26 内存占用监控 多工具协同下的性能稳定性分析实战
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_9159090611 小时前
iOS 26 性能监控工具有哪些?多工具协同打造全方位性能分析体系
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_9387739911 小时前
Objective-C 类的归档与解档:NSCoding 协议实现对象持久化存储
开发语言·ios·objective-c
美狐美颜SDK开放平台12 小时前
美颜SDK跨平台适配实战解析:让AI美颜功能在iOS与Android都丝滑运行
android·人工智能·ios·美颜sdk·直播美颜sdk·第三方美颜sdk·美颜api
2501_9159184113 小时前
uni-app 上架 iOS 应用全流程 从云打包到开心上架(Appuploader)免 Mac 上传发布指南
android·macos·ios·小程序·uni-app·iphone·webview
2501_9159214314 小时前
iOS 抓包工具有哪些,开发者的选型与实战指南
android·ios·小程序·https·uni-app·iphone·webview
2501_9159184114 小时前
iOS 26 应用管理实战 多工具协同构建开发与调试的高效体系
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
2501_9387802814 小时前
Kotlin Multiplatform Mobile(KMM):实现 iOS 与 Android 共享业务逻辑
android·ios·kotlin