zoukankan      html  css  js  c++  java
  • Mac catalyst 使用iOS-AudioUnit的音频采集、播放

    (禁止转载--因为可能有错误的地方-有指导意见麻烦评论)

    将iOS程序用于Mac上;编译MacCatalyst(让能够在iPad上使用的iOS程序也能在Mac上);

    1 使用audiounit声音采集和播放;采集和播放函数,尤其是format之类的最好一起设置;否则会出如下问题,如果采集端和播放端使用的是同一个unit,只配置了该unit的采集部分的属性

     [auvp] AUVPAggregate.cpp:1538:Initialize: client-side input and output formats do not match (err=-10875)
    
    [auvp] AUVPAggregate.cpp:1572:Initialize: err=-10875
    
     [auvp] AUVPAggregate.cpp:1603:Start: start called on uninitialized unit (err=-10867)
    
    [avas] AVAudioSession_MacOS.mm:258:-[AVAudioSession getChannelsFromAU:PortName:PortID:]: ERROR in getting channel layout for auScope 1768845428 element 1
    
    [avas] AVAudioSession_MacOS.mm:258:-[AVAudioSession getChannelsFromAU:PortName:PortID:]: ERROR in getting channel layout for auScope 1869968496 element 0
    
    [avas]AVAudioSession_MacOS.mm:258:-[AVAudioSession getChannelsFromAU:PortName:PortID:]: ERROR in getting channel layout for auScope 1768845428 element 1
    
    ERROR in getting channel layout for auScope 1768845428 element 1
    
    [avas] AVAudioSession_MacOS.mm:258:-[AVAudioSession getChannelsFromAU:PortName:PortID:]: ERROR in getting channel layout for auScope 1869968496 element 0

    2 iOS中使用了audiosession,如果用这个代码编译catalyst,可以用session设置如下属性

      NSError *error = nil;
            AVAudioSession *se_Instance = [AVAudioSession sharedInstance];
           
    
            AVAudioSessionCategory category = AVAudioSessionCategoryPlayAndRecord;
            AVAudioSessionMode mode = AVAudioSessionModeDefault;
            AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionAllowBluetooth |AVAudioSessionCategoryOptionDefaultToSpeaker;
    
            [se_Instance setCategory:category mode:mode options:options error:&error];
           
    
    
            [se_Instance setActive:YES error:&error];
            if (error)
            {
                NSLog(@" setActive, error = %@", error);
            }
        
        }

    但是AudioSessionGetProperty这个函数的使用可能会报错,说当前系统不存在或者不支持该函数;那就不要用;

    3 如果使用了augraph,并且使用的通知函数进行回调(AudioUnitRenderNotify;)这个函数可能大概率是不会触发的,即便你设置都没有任何错误;所以建议使用property设置的简单采集采集播放回调kAudioOutputUnitProperty_SetInputCallback,kAudioUnitProperty_SetRenderCallback;

    4 相关博文;见结尾的错误总结

    Using the kAudioOutputUnitProperty_CurrentDevice property with the Apple Voice Processing I/O audio unit ( kAudioUnitSubType_VoiceProcessingIO ) on macOS can trip you up if you don't follow these guidelines:
    
    The kAudioOutputUnitProperty_CurrentDevice property allows you to select the audio device being used by the I/O unit.
    
    @constant kAudioOutputUnitProperty_CurrentDevice
    
    Global Scope
    Value Type: AudioObjectID
    Access: read/write
    
    When using the this property to select an audio device used by the Voice Processing I/O unit on macOS, the following guidelines should be observed:
    
    A) Both input and output busses must be enabled.
    B) The property must be set before initializing the audio unit.
    C) The property must be set on global scope ( kAudioUnitScope_Global ).
    D) You must set the desired input device by setting this property on the input element/bus.
    
    AudioUnitSetProperty(theVoiceIO, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 1, &deviceID, sizeof(AudioDeviceID));
    
    You must set the desired output device by setting the property on the output element/bus.
    
    AudioUnitSetProperty(theVoiceIO, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceID, sizeof(AudioDeviceID));
    
    E) Both input and output devices must be aggregatable, i.e. the device cannot be an aggregated device. A check is also performed for ( safety offset + presentation latency > maximum buffer frame size ) of the other device both ways.
    F) Setting both the input and output device simultaneously is recommended. Setting the property for one bus and not the other may fail if either the input or output device is not aggregatable. Explicitly setting both makes you aware if the devices can be aggregated.
    
    Error -10849 kAudioUnitErr_Initialized is returned if condition B has not been met.
    Error -10851 kAudioUnitErr_InvalidPropertyValue is returned if the device is an aggregate device, condition E.
    Error -10875 kAudioUnitErr_FailedInitialization may be returned if condition F has occurred.

    5 Mac catalyst程序运行时,main函数崩溃;可能是签名认证的地方选错了,选development就好

     6 无采集数据(经常遇到Mac程序也会出现采集数据全0)

     (6.1)给APP麦克风权限;APP-target

     (6.2) info.plist给一下权限 Privacy - Microphone Usage Description

     (6.3) 偏好设置->安全隐私->隐私:麦克风,会看到你的APP是被允许的;

     7播放模块适配

    //转载,下方见出处
    要支持MAC版的音频采集与播放,把IOS上现有的AudioUnit代码进行移植。 发现的差别有如下几点:
    1.kAudioOutputUnitProperty_EnableIO 属性在 MAC上不能设置。在Mac上,input和output都是默认打开的,并且不允许修改。 2.SampleRate在input和output上,必须一样。不然会报错。ios似乎没有这样的限制。 设置bufferSize的属性不一样。在Mac上使用kAudioDevicePropertyBufferFrameSize,而在ios上使用 kAudioSessionProperty_PreferredHardwareIOBufferDuration. 4.最坑爹的是,测试中发现, kAudioFormatFlagIsSignedInteger 可以用于采集,但是用于播放,则会出现没有声音(此处存争议)。需要使用kAudioFormatFlagIsFloat。 另外还有个非AudioUnit的问题: Capabilities下的App Sandbox,要么关闭,如果打开,记得开里面的各种权限。不然会没有网络。 AudioUnitMAC的DEMO地址:https://github.com/pinkydodo/AudioUnitMac 里面实现了MAC采集,并把采集数据延迟播放的功能。 ------------------------------------------------------------- 作者:妖精不语 链接:https://www.jianshu.com/p/00b7e1092685 来源:简书

    播放模块注意点:(以下我们是基于采集和播放使用同一个AudioUnit对象的)

    (7.1)采集端和播放端必须同时开启,这里意思是至少你在初始化audio unit的输入端属性的时候,也要把输出端的属性也设置掉,否则可能会报-10875的错误:-10875 kAudioUnitErr_FailedInitialization

    (7.2)enableIO属性,同时都保持开启状态

     // iOS AudioUnit输入端默认是关闭,需要将他打开;Mac端也需要;INBUSELEMENT=1
        UInt32 flag = 1;
        status      = AudioUnitSetProperty(G_TEMPAudiounit,
                                           kAudioOutputUnitProperty_EnableIO,
                                           kAudioUnitScope_Input,
                                           INBUSELEMENT,
                                           &flag,
                                           sizeof(flag));
    
     flag = 1;//开通输出端(输出端默认开通) OUTBUSELEMENT = 0;
        status = AudioUnitSetProperty(G_TEMPAudioUnit,
                                      kAudioOutputUnitProperty_EnableIO,
                                      kAudioUnitScope_Output,
                                      OUTBUSELEMENT,
                                      &flag,
                                      sizeof(flag));

    (7.3)我们设置Mac catalyst的播放端16位深,componentSubType  = kAudioUnitSubType_VoiceProcessingIO;线性PCM的格式是:kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;并设置采样率16K;单声道;播放一个音频试一下,发现声音是次次次滋滋滋的杂音,完全听不清任何内容;然后我们尝试修改了音频MIDI设置->扬声器的一些参数选择,发现并没有卵用;

          此时,(同一个unit)我采集端:16K 单声道 ,integer类型kAudioFormatFlagIsSignedInteger,16位宽;播放也是同样的参数,我直接将componentSubType  = kAudioUnitSubType_RemoteIO,原来是VoiceProcessingIO,发现播放端可以正常播放声音,但是RemoteIO不支持AEC,而且官方文档上也没说VoiceProcessingIO不支持catalyst;所以这里还是先不要下结论,不要去修改,还继续在voiceprocessingIO 上继续尝试;

           然后我们就借鉴上边的那个简书上的博客内容,说播放端只能支持float32浮点量化精度,emmm...;做出如下修改:(简书-妖精不语的那个Git上摘来的)

          (7.3.1)修改参数格式:       

           AudioStreamBasicDescription audioFormatPlay = {0};
           audioFormatPlay.mSampleRate =sampleRate;
           audioFormatPlay.mFormatID = kAudioFormatLinearPCM;
           audioFormatPlay.mFormatFlags =kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked |kAudioFormatFlagIsNonInterleaved ;
           audioFormatPlay.mChannelsPerFrame = 1;
           audioFormatPlay.mFramesPerPacket = 1;
           audioFormatPlay.mBitsPerChannel = 32;
           audioFormatPlay.mBytesPerPacket = 4;
           audioFormatPlay.mBytesPerFrame = 4;
           audioFormatPlay.mReserved = 0;

         (7.3.2)量化位数转换(量化位数  ≠  精度转换)

    void Convert_int16_to_float32(void *pInput, void* pOutput, uint8_t pu1SampleSz, uint32_t pu4TotalSz, bool bInt16)
    {//输入buffer,输出buffer,倍数关系,输入字节数,是否是int16位的数据输入
    
        int16_t i2SampleNumTotal = pu4TotalSz / pu1SampleSz;//int16=short 的个数
        Float32 *pf4Buf = (Float32 *)pOutput;
        if (!pInput || !pOutput) {
            return;
        }
        
        if ( bInt16 && pu1SampleSz == 2) {
            int16_t* pi2Buf = (int16_t*)pInput;
            for (int i = 0; i < i2SampleNumTotal; i++) {
                pf4Buf[i] =    (Float32)pi2Buf[i] / 32767 ;  // int16 -> float(有一点争议,不过使用的时候不影响,因为你基本不会碰到超大破音音量值)
            }
           
        }
        return;
    }

         这里介绍下量化位数:16位深转32位深:是﹢32767~-32768到  ﹢1~-1之间的归一化转换;16位深用一个short表示,最高位是符号位,那就是﹢32767~-32768;映射到浮点数﹢1~-1之间;那int16转float32就是除以32768; float32转int16,就是乘以32767,(乘以32768或造成+1*32768超过int16有符号数的表示范围)

            (7.3.3)经过上边两个步骤,我们试图将一个16K单声道,16位深的PCM数据,通过将其在播放前转化成32位浮点量化,来播放,发现能听到内容了,只不过声音有些变声变速?那这个肯定就是和采样率有关系了。也就是说 audioFormatPlay.mSampleRate =16000;并没有给Mac device真正起到作用;我换了一个大采样率的PCM单声道,16位深播放试试,发现能听清楚内容了;也就是说Mac有自己的默认支持采样率,你现在通过设置kAudioUnitProperty_StreamFormat,并没有真的让它起作用

     status = AudioUnitSetProperty(AudioUnit,
                             kAudioUnitProperty_StreamFormat,
                             kAudioUnitScope_Input,
                             OUTBUS,
                             &audioFormatPlay,
                             sizeof(audioFormatPlay));

    (7.4)formate格式;注意在Mac-catalyst上这个默认的采样率返回的是44.1K

    我们经过7.3,尝试怎么才能修改一下Mac底层支持的采样率呢,如果改不了,那输入的待播放音频就要做重采样,来适配默认采样率了。先找方法获取下输出端支持的采样率;

        Float64 sampleRate ;

        UInt32 iodatasize = sizeof(sampleRate);

        AudioUnitGetProperty(AudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, OUTBUS, &sampleRate, &iodatasize );

      发现是44.1K;查阅一些别人的代码,Mac audiounit播放时可以支持16k 采样率的呀,那我们能不能改一下呢,让Mac支持16K直接播放;而且采集端是支持设置16K等其他常见采样率的,那我们是不是可以给播放端修改支持的采样率呢?

     尝试设置一下?AudioUnitSetProperty(AudioUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, OUTBUS, &sampleRate = 16000, sizeof(sampleRate) );测试发现那个16K的音频可以直接并且声音正常的播放了;

    这里有一个注意点是播放回调的参数解析:

    PlayAudioCallback(void *inRefCon,
                                 AudioUnitRenderActionFlags *ioActionFlags,
                                 const AudioTimeStamp *inTimeStamp,
                                 UInt32 inBusNumber,
                                 UInt32 inNumberFrames,
                                 AudioBufferList *ioData)

    ioData->mBuffers[0].mDataByteSize这个参数的意义是字节数:假设float32 形式播放。每次播放回调触发的时候需要1800个字节;那你需要保证每次有900字节的16位原始待播放音频,900字节是450个short(int16);每个short转化成Float32 ,就是由两字节变四字节,450个short变成450个float32,450个float32 就是1800字节;正好是本次需要的播放字节数;fwrite(ioData->mBuffers[0].data,sizeof(Float32),ioData->mBuffers[0].mDataByteSize/4, ptrFile);

    (7.5)关于采集端和播放端的量化精度问题

    在我们完成上边的步骤的时候,有这样一个问题;输入端,也叫采集端,明明支持16K,16位深的音频,难道就因为播放端需要float32,采集端也要是这个量化位数?那我如果需要采集16位深的音频进行后边的操作,我就需要在采集后的float32 进行一个Float32转int16的转换;有点麻烦了;那我就尝试输入端和输出端用不同的精度,但是采样率保持一样,行不行呢?答案是可以的:

    输入参数
    dataFormat.mFormatID = kAudioFormatLinearPCM;
    dataFormat.mSampleRate =nSampleRate;
    
    dataFormat.mChannelsPerFrame = 1;
    
    dataFormat.mFormatFlags     = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    dataFormat.mBitsPerChannel  = 16;
    dataFormat.mBytesPerPacket  = dataFormat.mBytesPerFrame = (dataFormat.mBitsPerChannel / 8)*dataFormat.mChannelsPerFrame;//2
    
      dataFormat.mFramesPerPacket = 1;
    //---------------------------------------------------------
    输出参数
    dataFormat.mFormatID = kAudioFormatLinearPCM;
    dataFormat.mSampleRate =nSampleRate;
    dataFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked |kAudioFormatFlagIsNonInterleaved ;
    dataFormat.mChannelsPerFrame = 1;
    dataFormat.mFramesPerPacket = 1;
    dataFormat.mBitsPerChannel = 32;
    dataFormat.mBytesPerPacket = 4;
    dataFormat.mBytesPerFrame = 4;

    8 iOS代码编译Mac-catalyst

     需要xcode 12.2及以上;来打开静态库或者动态库程序;然后scheme->any mac(apple silicion ,intel);

    project->build setting->architectures->记住编译的是iOS的而不是mac 的;编译好,是支持arm64和x86_64的(控制台:lipo -info XXX.a);

      

     

  • 相关阅读:
    2018/1/1 XML和DOM、SAX解析
    2018/1/1 Html+CSS+JavaScript
    2017/12/30 GUI和动态代理
    开发运维实施一系列问题归类
    如何理解并发,并行,串行
    java内存溢出与内存泄漏
    IDEA 导入的聚合工程父工程报错Cannot resolve symbol 'modelVersion'
    jvm垃圾回收算法
    String创建对象的难题一
    String详解
  • 原文地址:https://www.cnblogs.com/8335IT/p/15460244.html
Copyright © 2011-2022 走看看