Mac catalyst 使用iOS-AudioUnit的音頻采集、播放


(禁止轉載--因為可能有錯誤的地方-有指導意見麻煩評論)

將iOS程序用於Mac上;編譯MacCatalyst(讓能夠在iPad上使用的iOS程序也能在Mac上);

1 使用audiounit聲音采集和播放;采集和播放函數,尤其是format之類的最好一起設置;否則會出如下問題,如果采集端和播放端使用的是同一個unit,只配置了該unit的采集部分的屬性;

1.1RemoteIO屬性支持只設置播放端相關屬性就能播放;VoiceProcessingIO屬性時候,聲音要想能播放,必須要把采集的屬性也一並設置;

 [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套着unit單元,並且使用的通知函數進行回調(AudioUnit可以設置通知回調函數)這個函數,我猜測它是需要你給augraph設置好輸出,並且格式還要匹配,才有可能觸發,邏輯較復雜沒有研究過;所以建議使用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);

  

 

 

 9 注意,在音頻播放回調拿數據的時候,他要多少你就給多少,如果他要200字節,你給100字節,在某些機器上會出現很大噪聲,以至於后邊再送正常數據進去仍然是噪聲或者無聲音;(目前只是部分機器蘋果電腦)

10 IOS程序直接在M1型號電腦上跑(不是catalyst,就是IOS程序),也有類似問題;播放聲音的量化位數,等問題;

11 RemoteIO音頻類型 是支持IOS ,catalyst的;但是純Mac程序使用audiounit,RemoteIO就不能用了。編譯不過去的;

 12 mac上還不能使用session

    AVAudioSession   API_UNAVAILABLE(macos)

    AudioSessionInitialize     API_UNAVAILABLE(macos)

 

13 通過命令行編譯Maccatalyst:

xcodebuild -project “***.xcodeproj” -scheme “***組件名字***” -destination “generic/platform=macOS,variant=Mac Catalyst,name=Any Mac” ARCHS=”x86_64 arm64”-configuration “Release”

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM