diff --git a/HaishinKit.xcodeproj/project.pbxproj b/HaishinKit.xcodeproj/project.pbxproj index 5c276dcf9..85a8fc827 100644 --- a/HaishinKit.xcodeproj/project.pbxproj +++ b/HaishinKit.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 2923A1F81D6300650019FBCD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543641D62FEB700734698 /* AppDelegate.swift */; }; 2930D0411E12D35400DA2DC5 /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2930D03F1E12D17C00DA2DC5 /* SampleHandler.swift */; }; 293B42E92340B4840086F973 /* RTMPObjectEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293B42E82340B4840086F973 /* RTMPObjectEncoding.swift */; }; - 2941746B22D069B300A2944F /* AudioEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2941746A22D069B300A2944F /* AudioEffect.swift */; }; 2942424D1CF4C01300D65DCB /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942424C1CF4C01300D65DCB /* MD5.swift */; }; 2942A4F821A9418A004E1BEE /* Running.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942A4F721A9418A004E1BEE /* Running.swift */; }; 2942EF841DFF4D06008E620C /* HaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; }; @@ -46,7 +45,7 @@ 294637A81EC89BC9008EEC71 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294637A71EC89BC9008EEC71 /* Config.swift */; }; 294637AA1EC8A79F008EEC71 /* SampleVideo_360x240_5mb.flv in Resources */ = {isa = PBXBuildFile; fileRef = 294637A91EC8A79F008EEC71 /* SampleVideo_360x240_5mb.flv */; }; 295018201FFA1BD700358E10 /* AudioCodecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2950181F1FFA1BD700358E10 /* AudioCodecTests.swift */; }; - 295018221FFA1C9D00358E10 /* CMAudioSampleBufferTestUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295018211FFA1C9D00358E10 /* CMAudioSampleBufferTestUtil.swift */; }; + 295018221FFA1C9D00358E10 /* CMAudioSampleBufferFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295018211FFA1C9D00358E10 /* CMAudioSampleBufferFactory.swift */; }; 295074301E4620FF007F15A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29205CBD1E461F4E009D3FFF /* Main.storyboard */; }; 295074311E462105007F15A4 /* PreferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2950742E1E4620B7007F15A4 /* PreferenceViewController.swift */; }; 2955F51F1D09EBAD004CC995 /* VisualEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296897461CDB01D20074D5F0 /* VisualEffect.swift */; }; @@ -155,6 +154,8 @@ BC1DC5122A04E46E00E928ED /* HEVCDecoderConfigurationRecordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1DC5112A04E46E00E928ED /* HEVCDecoderConfigurationRecordTests.swift */; }; BC1DC5142A05428800E928ED /* HEVCNALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1DC5132A05428800E928ED /* HEVCNALUnit.swift */; }; BC20DF38250377A3007BC608 /* IOUIScreenCaptureUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B131C1D35272D00A1E8F5 /* IOUIScreenCaptureUnit.swift */; }; + BC22EEEE2AAF50F200E3406D /* Codec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC22EEED2AAF50F200E3406D /* Codec.swift */; }; + BC22EEF22AAF5D6300E3406D /* AVAudioPCMBuffer+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC22EEF12AAF5D6300E3406D /* AVAudioPCMBuffer+Extension.swift */; }; BC2828AD2AA3225100741013 /* AVCaptureDevice.Format+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2828AC2AA3225100741013 /* AVCaptureDevice.Format+Extension.swift */; }; BC2828AF2AA322E400741013 /* AVFrameRateRange+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2828AE2AA322E400741013 /* AVFrameRateRange+Extension.swift */; }; BC2902352AA0E66A004821D2 /* Screencast.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2915EC521D85BDF100621092 /* Screencast.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -172,8 +173,8 @@ BC34FA0B286CB90A00EFAF27 /* PiPHKView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC34FA0A286CB90A00EFAF27 /* PiPHKView.swift */; }; BC3802122AB5E770001AE399 /* IOVideoCaptureUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3802112AB5E770001AE399 /* IOVideoCaptureUnit.swift */; }; BC3802142AB5E7CC001AE399 /* IOAudioCaptureUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3802132AB5E7CC001AE399 /* IOAudioCaptureUnit.swift */; }; + BC3802192AB6AD79001AE399 /* IOAudioResamplerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3802182AB6AD79001AE399 /* IOAudioResamplerTests.swift */; }; BC3E384429C216BB007CD972 /* ADTSReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3E384329C216BB007CD972 /* ADTSReaderTests.swift */; }; - BC44A1A923D31E92002D4297 /* AudioCodecRingBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC44A1A823D31E92002D4297 /* AudioCodecRingBuffer.swift */; }; BC4914A228DDD33D009E2DF6 /* VTSessionConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A128DDD33D009E2DF6 /* VTSessionConvertible.swift */; }; BC4914A628DDD367009E2DF6 /* VTSessionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */; }; BC4914AE28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */; }; @@ -181,7 +182,7 @@ BC4914B628DEC2FE009E2DF6 /* VTSessionMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */; }; BC4C9EAC23F00F3A004A14F2 /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 291468161E581C7D00E619BA /* Preference.swift */; }; BC4C9EAF23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4C9EAE23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift */; }; - BC5019C12A6D266B0046E02F /* IOAudioMonitorRingBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5019C02A6D266B0046E02F /* IOAudioMonitorRingBuffer.swift */; }; + BC5019C12A6D266B0046E02F /* IOAudioRingBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5019C02A6D266B0046E02F /* IOAudioRingBuffer.swift */; }; BC558268240BB40E00011AC0 /* RTMPStreamInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC558267240BB40E00011AC0 /* RTMPStreamInfo.swift */; }; BC562DC7295767860048D89A /* AVCaptureDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC562DC6295767860048D89A /* AVCaptureDevice+Extension.swift */; }; BC562DCB29576D220048D89A /* AVCaptureSession.Preset+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC562DCA29576D220048D89A /* AVCaptureSession.Preset+Extension.swift */; }; @@ -189,6 +190,7 @@ BC570B4828E9ACC10098A12C /* IOUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC570B4728E9ACC10098A12C /* IOUnit.swift */; }; BC6FC91E29609A6800A746EE /* ShapeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6FC91D29609A6800A746EE /* ShapeFactory.swift */; }; BC6FC9222961B3D800A746EE /* vImage_CGImageFormat+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6FC9212961B3D800A746EE /* vImage_CGImageFormat+Extension.swift */; }; + BC701F322AAC676C00C4BEFE /* AVAudioFormatFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC701F312AAC676C00C4BEFE /* AVAudioFormatFactory.swift */; }; BC701F332AAD808F00C4BEFE /* SRTHaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */; }; BC701F342AAD808F00C4BEFE /* SRTHaishinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BC701F372AAD809D00C4BEFE /* SRTHaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */; }; @@ -206,8 +208,6 @@ BC7C56CD29A786AE00C41A9B /* ADTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7C56CC29A786AE00C41A9B /* ADTS.swift */; }; BC7C56D129A78D4F00C41A9B /* ADTSHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7C56D029A78D4F00C41A9B /* ADTSHeaderTests.swift */; }; BC83A4732403D83B006BDE06 /* VTCompressionSession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC83A4722403D83B006BDE06 /* VTCompressionSession+Extension.swift */; }; - BC8446052A30BE1600609FFD /* CMAudioSampleBufferFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8446042A30BE1600609FFD /* CMAudioSampleBufferFactory.swift */; }; - BC8446092A30BFC800609FFD /* CMAudioSampleBufferFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8446082A30BFC800609FFD /* CMAudioSampleBufferFactoryTests.swift */; }; BC8A29902AA63D9E00F6D27F /* HaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; }; BC8A29912AA63D9E00F6D27F /* HaishinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BC8A29942AA63DA300F6D27F /* HaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; }; @@ -230,7 +230,6 @@ BCCBCE9529A7C9C90095B51C /* AVCFormatStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCBCE9429A7C9C90095B51C /* AVCFormatStreamTests.swift */; }; BCCBCE9729A90D880095B51C /* AVCNALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCBCE9629A90D880095B51C /* AVCNALUnit.swift */; }; BCCBCE9B29A9D96A0095B51C /* NALUnitReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCBCE9A29A9D96A0095B51C /* NALUnitReaderTests.swift */; }; - BCCBCEA029ADF55A0095B51C /* AudioCodecBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCBCE9F29ADF55A0095B51C /* AudioCodecBufferTests.swift */; }; BCCC45992AA289FA0016EFE8 /* SRTHaishinKit.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCC45982AA289FA0016EFE8 /* SRTHaishinKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCCC459C2AA289FA0016EFE8 /* SRTHaishinKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */; }; BCCC459D2AA289FA0016EFE8 /* SRTHaishinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -256,9 +255,11 @@ BCD63ADD26FDF34C0084842D /* HaishinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BCD63AE126FDF3500084842D /* Logboard.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC34DFD125EBB12C005F975A /* Logboard.xcframework */; }; BCD63AE226FDF3500084842D /* Logboard.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BC34DFD125EBB12C005F975A /* Logboard.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - BCD91C0D2A700FF50033F9E1 /* IOAudioMonitorRingBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD91C0C2A700FF50033F9E1 /* IOAudioMonitorRingBufferTests.swift */; }; + BCD91C0D2A700FF50033F9E1 /* IOAudioRingBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */; }; BCFB355524FA27EA00DC5108 /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355324FA275600DC5108 /* PlaybackViewController.swift */; }; BCFB355A24FA40DD00DC5108 /* PlaybackContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355924FA40DD00DC5108 /* PlaybackContainerViewController.swift */; }; + BCFC51FE2AAB420700014428 /* IOAudioResampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFC51FD2AAB420700014428 /* IOAudioResampler.swift */; }; + BCFC9BE02AB43A3A00378E56 /* AudioCodecSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFC9BDF2AB43A3A00378E56 /* AudioCodecSettingsTests.swift */; }; BCFF640B29C0C44B004EFF2F /* SampleVideo_360x240_5mb_2ch.ts in Resources */ = {isa = PBXBuildFile; fileRef = BCFF640A29C0C44B004EFF2F /* SampleVideo_360x240_5mb_2ch.ts */; }; /* End PBXBuildFile section */ @@ -438,7 +439,6 @@ 2927A2991E7ED2D70044AF91 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 2930D03F1E12D17C00DA2DC5 /* SampleHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleHandler.swift; sourceTree = ""; }; 293B42E82340B4840086F973 /* RTMPObjectEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTMPObjectEncoding.swift; sourceTree = ""; }; - 2941746A22D069B300A2944F /* AudioEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEffect.swift; sourceTree = ""; }; 2942424C1CF4C01300D65DCB /* MD5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = ""; }; 2942A4F721A9418A004E1BEE /* Running.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Running.swift; sourceTree = ""; }; 2945CBBD1B4BE66000104112 /* HaishinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HaishinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -446,7 +446,7 @@ 294637A91EC8A79F008EEC71 /* SampleVideo_360x240_5mb.flv */ = {isa = PBXFileReference; lastKnownFileType = file; path = SampleVideo_360x240_5mb.flv; sourceTree = ""; }; 294852551D84BFAD002DE492 /* RTMPTSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTMPTSocket.swift; sourceTree = ""; }; 2950181F1FFA1BD700358E10 /* AudioCodecTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecTests.swift; sourceTree = ""; }; - 295018211FFA1C9D00358E10 /* CMAudioSampleBufferTestUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMAudioSampleBufferTestUtil.swift; sourceTree = ""; }; + 295018211FFA1C9D00358E10 /* CMAudioSampleBufferFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMAudioSampleBufferFactory.swift; sourceTree = ""; }; 2950742E1E4620B7007F15A4 /* PreferenceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceViewController.swift; sourceTree = ""; }; 2958910D1EEB8D3C00CE51E1 /* FLVVideoCodec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FLVVideoCodec.swift; sourceTree = ""; }; 295891111EEB8D7200CE51E1 /* FLVFrameType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FLVFrameType.swift; sourceTree = ""; }; @@ -565,6 +565,8 @@ BC1DC50D2A039E1900E928ED /* FLVVideoPacketType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLVVideoPacketType.swift; sourceTree = ""; }; BC1DC5112A04E46E00E928ED /* HEVCDecoderConfigurationRecordTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HEVCDecoderConfigurationRecordTests.swift; sourceTree = ""; }; BC1DC5132A05428800E928ED /* HEVCNALUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HEVCNALUnit.swift; sourceTree = ""; }; + BC22EEED2AAF50F200E3406D /* Codec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Codec.swift; sourceTree = ""; }; + BC22EEF12AAF5D6300E3406D /* AVAudioPCMBuffer+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioPCMBuffer+Extension.swift"; sourceTree = ""; }; BC2828AC2AA3225100741013 /* AVCaptureDevice.Format+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice.Format+Extension.swift"; sourceTree = ""; }; BC2828AE2AA322E400741013 /* AVFrameRateRange+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVFrameRateRange+Extension.swift"; sourceTree = ""; }; BC3004CD296B0A1700119932 /* Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shape.swift; sourceTree = ""; }; @@ -580,15 +582,15 @@ BC34FA0A286CB90A00EFAF27 /* PiPHKView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPHKView.swift; sourceTree = ""; }; BC3802112AB5E770001AE399 /* IOVideoCaptureUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOVideoCaptureUnit.swift; sourceTree = ""; }; BC3802132AB5E7CC001AE399 /* IOAudioCaptureUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioCaptureUnit.swift; sourceTree = ""; }; + BC3802182AB6AD79001AE399 /* IOAudioResamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioResamplerTests.swift; sourceTree = ""; }; BC3E384329C216BB007CD972 /* ADTSReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADTSReaderTests.swift; sourceTree = ""; }; - BC44A1A823D31E92002D4297 /* AudioCodecRingBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecRingBuffer.swift; sourceTree = ""; wrapsLines = 1; }; BC4914A128DDD33D009E2DF6 /* VTSessionConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionConvertible.swift; sourceTree = ""; }; BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionOption.swift; sourceTree = ""; }; BC4914AD28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VTDecompressionSession+Extension.swift"; sourceTree = ""; }; BC4914B128DDFE31009E2DF6 /* VTSessionOptionKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionOptionKey.swift; sourceTree = ""; }; BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VTSessionMode.swift; sourceTree = ""; }; BC4C9EAE23F2E736004A14F2 /* AudioStreamBasicDescription+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioStreamBasicDescription+Extension.swift"; sourceTree = ""; }; - BC5019C02A6D266B0046E02F /* IOAudioMonitorRingBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioMonitorRingBuffer.swift; sourceTree = ""; }; + BC5019C02A6D266B0046E02F /* IOAudioRingBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioRingBuffer.swift; sourceTree = ""; }; BC558267240BB40E00011AC0 /* RTMPStreamInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTMPStreamInfo.swift; sourceTree = ""; }; BC562DC6295767860048D89A /* AVCaptureDevice+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+Extension.swift"; sourceTree = ""; }; BC562DCA29576D220048D89A /* AVCaptureSession.Preset+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureSession.Preset+Extension.swift"; sourceTree = ""; }; @@ -596,6 +598,7 @@ BC570B4728E9ACC10098A12C /* IOUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOUnit.swift; sourceTree = ""; }; BC6FC91D29609A6800A746EE /* ShapeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeFactory.swift; sourceTree = ""; }; BC6FC9212961B3D800A746EE /* vImage_CGImageFormat+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "vImage_CGImageFormat+Extension.swift"; sourceTree = ""; }; + BC701F312AAC676C00C4BEFE /* AVAudioFormatFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVAudioFormatFactory.swift; sourceTree = ""; }; BC7C56B6299E579F00C41A9B /* AudioCodecSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecSettings.swift; sourceTree = ""; }; BC7C56BA299E595000C41A9B /* VideoCodecSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCodecSettings.swift; sourceTree = ""; }; BC7C56C229A1F28700C41A9B /* TSReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSReaderTests.swift; sourceTree = ""; }; @@ -603,8 +606,6 @@ BC7C56CC29A786AE00C41A9B /* ADTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADTS.swift; sourceTree = ""; }; BC7C56D029A78D4F00C41A9B /* ADTSHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADTSHeaderTests.swift; sourceTree = ""; }; BC83A4722403D83B006BDE06 /* VTCompressionSession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VTCompressionSession+Extension.swift"; sourceTree = ""; }; - BC8446042A30BE1600609FFD /* CMAudioSampleBufferFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMAudioSampleBufferFactory.swift; sourceTree = ""; }; - BC8446082A30BFC800609FFD /* CMAudioSampleBufferFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMAudioSampleBufferFactoryTests.swift; sourceTree = ""; }; BC959EEE296EE4190067BA97 /* ImageTransform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransform.swift; sourceTree = ""; }; BC959F0D29705B1B0067BA97 /* SCStreamPublishViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCStreamPublishViewController.swift; sourceTree = ""; }; BC959F1129717EDB0067BA97 /* PreferenceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceViewController.swift; sourceTree = ""; }; @@ -623,7 +624,6 @@ BCCBCE9429A7C9C90095B51C /* AVCFormatStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCFormatStreamTests.swift; sourceTree = ""; }; BCCBCE9629A90D880095B51C /* AVCNALUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCNALUnit.swift; sourceTree = ""; }; BCCBCE9A29A9D96A0095B51C /* NALUnitReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NALUnitReaderTests.swift; sourceTree = ""; }; - BCCBCE9F29ADF55A0095B51C /* AudioCodecBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecBufferTests.swift; sourceTree = ""; }; BCCC45962AA289FA0016EFE8 /* SRTHaishinKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SRTHaishinKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BCCC45982AA289FA0016EFE8 /* SRTHaishinKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTHaishinKit.h; sourceTree = ""; }; BCCC45A12AA28A6E0016EFE8 /* Data+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extension.swift"; sourceTree = ""; }; @@ -643,9 +643,11 @@ BCD63AB626FDF1250084842D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; BCD63AB826FDF12A0084842D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BCD63ABB26FDF12A0084842D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - BCD91C0C2A700FF50033F9E1 /* IOAudioMonitorRingBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioMonitorRingBufferTests.swift; sourceTree = ""; }; + BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioRingBufferTests.swift; sourceTree = ""; }; BCFB355324FA275600DC5108 /* PlaybackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackViewController.swift; sourceTree = ""; }; BCFB355924FA40DD00DC5108 /* PlaybackContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackContainerViewController.swift; sourceTree = ""; }; + BCFC51FD2AAB420700014428 /* IOAudioResampler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioResampler.swift; sourceTree = ""; }; + BCFC9BDF2AB43A3A00378E56 /* AudioCodecSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCodecSettingsTests.swift; sourceTree = ""; }; BCFF640A29C0C44B004EFF2F /* SampleVideo_360x240_5mb_2ch.ts */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.typescript; path = SampleVideo_360x240_5mb_2ch.ts; sourceTree = ""; }; /* End PBXFileReference section */ @@ -745,8 +747,8 @@ isa = PBXGroup; children = ( 29B876571CD70A7900FC07DA /* AudioCodec.swift */, - BC44A1A823D31E92002D4297 /* AudioCodecRingBuffer.swift */, BC7C56B6299E579F00C41A9B /* AudioCodecSettings.swift */, + BC22EEED2AAF50F200E3406D /* Codec.swift */, BC4914A128DDD33D009E2DF6 /* VTSessionConvertible.swift */, BC4914B528DEC2FE009E2DF6 /* VTSessionMode.swift */, BC4914A528DDD367009E2DF6 /* VTSessionOption.swift */, @@ -760,10 +762,10 @@ 290907CE1C3961BC00F2E80C /* Util */ = { isa = PBXGroup; children = ( + BC701F312AAC676C00C4BEFE /* AVAudioFormatFactory.swift */, 298BCF321DD4C44A007FF86A /* AnyUtil.swift */, 29DC17B221D0CC0600E26CED /* Atomic.swift */, 29B876B81CD70B3900FC07DA /* ByteArray.swift */, - BC8446042A30BE1600609FFD /* CMAudioSampleBufferFactory.swift */, 29B876631CD70AB300FC07DA /* Constants.swift */, BC0D236C26331BAB001DDA0C /* DataBuffer.swift */, 29B876671CD70AB300FC07DA /* DataConvertible.swift */, @@ -810,7 +812,6 @@ isa = PBXGroup; children = ( 290EA8A41DFB61E700053022 /* ByteArrayTests.swift */, - BC8446082A30BFC800609FFD /* CMAudioSampleBufferFactoryTests.swift */, 290EA8A51DFB61E700053022 /* CRC32Tests.swift */, BCC9E9082636FF7400948774 /* DataBufferTests.swift */, 290EA8A61DFB61E700053022 /* EventDispatcherTests.swift */, @@ -896,8 +897,8 @@ 295018191FFA196800358E10 /* Codec */ = { isa = PBXGroup; children = ( - BCCBCE9F29ADF55A0095B51C /* AudioCodecBufferTests.swift */, 2950181F1FFA1BD700358E10 /* AudioCodecTests.swift */, + BCFC9BDF2AB43A3A00378E56 /* AudioCodecSettingsTests.swift */, ); path = Codec; sourceTree = ""; @@ -996,7 +997,7 @@ BC0BF4F329866FB700D72CB4 /* Media */, 291C2ACE1CE9FF25006F042B /* RTMP */, 291C2AD01CE9FF33006F042B /* Util */, - 295018211FFA1C9D00358E10 /* CMAudioSampleBufferTestUtil.swift */, + 295018211FFA1C9D00358E10 /* CMAudioSampleBufferFactory.swift */, 294637A71EC89BC9008EEC71 /* Config.swift */, 29798E5D1CE60E5300F5CBD0 /* Info.plist */, ); @@ -1045,12 +1046,12 @@ 29BDE0BD1C65BC2400D6A768 /* Media */ = { isa = PBXGroup; children = ( - 2941746A22D069B300A2944F /* AudioEffect.swift */, BC9F9C7726F8C16600B01ED0 /* Choreographer.swift */, 299B13261D3B751400A1E8F5 /* HKView.swift */, BC3802132AB5E7CC001AE399 /* IOAudioCaptureUnit.swift */, BC31DBD12A653D1600C4DEA3 /* IOAudioMonitor.swift */, - BC5019C02A6D266B0046E02F /* IOAudioMonitorRingBuffer.swift */, + BCFC51FD2AAB420700014428 /* IOAudioResampler.swift */, + BC5019C02A6D266B0046E02F /* IOAudioRingBuffer.swift */, 29B876891CD70AFE00FC07DA /* IOAudioUnit.swift */, BC1102492925147300D48035 /* IOCaptureUnit.swift */, 29B8768B1CD70AFE00FC07DA /* IOMixer.swift */, @@ -1121,6 +1122,7 @@ isa = PBXGroup; children = ( 1A2166D3A449D813866FE9D9 /* AVAudioFormat+Extension.swift */, + BC22EEF12AAF5D6300E3406D /* AVAudioPCMBuffer+Extension.swift */, BC562DC6295767860048D89A /* AVCaptureDevice+Extension.swift */, BC2828AC2AA3225100741013 /* AVCaptureDevice.Format+Extension.swift */, BC1DC4A329F4F74F00E928ED /* AVCaptureSession+Extension.swift */, @@ -1175,8 +1177,9 @@ BC0BF4F329866FB700D72CB4 /* Media */ = { isa = PBXGroup; children = ( - BCD91C0C2A700FF50033F9E1 /* IOAudioMonitorRingBufferTests.swift */, BCA7C2512A9254C500882D85 /* IOAudioMonitorTests.swift */, + BC3802182AB6AD79001AE399 /* IOAudioResamplerTests.swift */, + BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */, BC0BF4F429866FDE00D72CB4 /* IOMixerTests.swift */, BCA7C24E2A91AA0500882D85 /* IORecorderTests.swift */, ); @@ -1685,7 +1688,6 @@ files = ( BC4914AE28DDF445009E2DF6 /* VTDecompressionSession+Extension.swift in Sources */, 29B876B11CD70B2800FC07DA /* RTMPMessage.swift in Sources */, - 2941746B22D069B300A2944F /* AudioEffect.swift in Sources */, BCB9773F2621812800C9A649 /* AVCFormatStream.swift in Sources */, BC83A4732403D83B006BDE06 /* VTCompressionSession+Extension.swift in Sources */, BC4914A228DDD33D009E2DF6 /* VTSessionConvertible.swift in Sources */, @@ -1693,6 +1695,7 @@ BC11023E2917C35B00D48035 /* CVPixelBufferPool+Extension.swift in Sources */, 29C2631C1D0083B50098D4EF /* IOVideoUnit.swift in Sources */, 29B876B41CD70B2800FC07DA /* RTMPSharedObject.swift in Sources */, + BC701F322AAC676C00C4BEFE /* AVAudioFormatFactory.swift in Sources */, 2901A4EE1D437170002BBD23 /* MediaLink.swift in Sources */, 2958911E1EEB8E9600CE51E1 /* FLVSoundRate.swift in Sources */, 29B876941CD70AFE00FC07DA /* SoundTransform.swift in Sources */, @@ -1708,10 +1711,8 @@ BC1DC5142A05428800E928ED /* HEVCNALUnit.swift in Sources */, BC6FC9222961B3D800A746EE /* vImage_CGImageFormat+Extension.swift in Sources */, 299B13271D3B751400A1E8F5 /* HKView.swift in Sources */, - BC44A1A923D31E92002D4297 /* AudioCodecRingBuffer.swift in Sources */, BC20DF38250377A3007BC608 /* IOUIScreenCaptureUnit.swift in Sources */, 29B876AF1CD70B2800FC07DA /* RTMPChunk.swift in Sources */, - BC8446052A30BE1600609FFD /* CMAudioSampleBufferFactory.swift in Sources */, 29B876841CD70AE800FC07DA /* AVCDecoderConfigurationRecord.swift in Sources */, 296242621D8DB86500C451A3 /* TSWriter.swift in Sources */, BC9CFA9323BDE8B700917EEF /* NetStreamDrawable.swift in Sources */, @@ -1727,7 +1728,7 @@ BCC1A72B264FAC1800661156 /* ESSpecificData.swift in Sources */, 295891221EEB8EC500CE51E1 /* FLVAVCPacketType.swift in Sources */, 29B876B61CD70B2800FC07DA /* RTMPStream.swift in Sources */, - BC5019C12A6D266B0046E02F /* IOAudioMonitorRingBuffer.swift in Sources */, + BC5019C12A6D266B0046E02F /* IOAudioRingBuffer.swift in Sources */, BC566F6E25D2ECC500573C4C /* HLSService.swift in Sources */, 29EA87EA1E79A3B70043A5F8 /* CMBlockBuffer+Extension.swift in Sources */, BC3004CE296B0A1700119932 /* Shape.swift in Sources */, @@ -1744,6 +1745,7 @@ 2958912A1EEB8F1D00CE51E1 /* FLVSoundSize.swift in Sources */, 29EA87DC1E79A0460043A5F8 /* Data+Extension.swift in Sources */, 29DF20622312A3DD004057C3 /* RTMPNWSocket.swift in Sources */, + BC22EEF22AAF5D6300E3406D /* AVAudioPCMBuffer+Extension.swift in Sources */, BCCBCE9729A90D880095B51C /* AVCNALUnit.swift in Sources */, 29B876BD1CD70B3900FC07DA /* CRC32.swift in Sources */, BCA2252C293CC5B600DD7CB2 /* IOScreenCaptureUnit.swift in Sources */, @@ -1769,6 +1771,7 @@ 29B876831CD70AE800FC07DA /* AudioSpecificConfig.swift in Sources */, 295891121EEB8D7200CE51E1 /* FLVFrameType.swift in Sources */, 29B876961CD70AFE00FC07DA /* VideoEffect.swift in Sources */, + BCFC51FE2AAB420700014428 /* IOAudioResampler.swift in Sources */, BC1DC5062A02963600E928ED /* FLVTagType.swift in Sources */, 29B876691CD70AB300FC07DA /* Constants.swift in Sources */, 29B8766D1CD70AB300FC07DA /* DataConvertible.swift in Sources */, @@ -1777,6 +1780,7 @@ 2976A4861D4903C300B53EF2 /* DeviceUtil.swift in Sources */, BC7C56BB299E595000C41A9B /* VideoCodecSettings.swift in Sources */, 29B876881CD70AE800FC07DA /* TSPacket.swift in Sources */, + BC22EEEE2AAF50F200E3406D /* Codec.swift in Sources */, 29B876BE1CD70B3900FC07DA /* EventDispatcher.swift in Sources */, BC2828AF2AA322E400741013 /* AVFrameRateRange+Extension.swift in Sources */, 29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */, @@ -1814,7 +1818,7 @@ 290EA89B1DFB619600053022 /* TSPacketTests.swift in Sources */, BCCBCE9529A7C9C90095B51C /* AVCFormatStreamTests.swift in Sources */, 290EA8A91DFB61E700053022 /* ByteArrayTests.swift in Sources */, - 295018221FFA1C9D00358E10 /* CMAudioSampleBufferTestUtil.swift in Sources */, + 295018221FFA1C9D00358E10 /* CMAudioSampleBufferFactory.swift in Sources */, BCA7C2522A9254C500882D85 /* IOAudioMonitorTests.swift in Sources */, BC7C56C329A1F28700C41A9B /* TSReaderTests.swift in Sources */, BC7C56D129A78D4F00C41A9B /* ADTSHeaderTests.swift in Sources */, @@ -1823,7 +1827,6 @@ 295018201FFA1BD700358E10 /* AudioCodecTests.swift in Sources */, 290EA8AC1DFB61E700053022 /* MD5Tests.swift in Sources */, 290EA8A01DFB61B100053022 /* AMFFoundationTests.swift in Sources */, - BC8446092A30BFC800609FFD /* CMAudioSampleBufferFactoryTests.swift in Sources */, 2917CB662104CA2800F6823A /* AudioSpecificConfigTests.swift in Sources */, 290EA8AB1DFB61E700053022 /* EventDispatcherTests.swift in Sources */, 290EA8901DFB616000053022 /* Foundation+ExtensionTests.swift in Sources */, @@ -1831,11 +1834,11 @@ 290EA8911DFB616000053022 /* SwiftCore+ExtensionTests.swift in Sources */, BC0BF4F529866FDE00D72CB4 /* IOMixerTests.swift in Sources */, 290EA89A1DFB619600053022 /* TSProgramTests.swift in Sources */, - BCCBCEA029ADF55A0095B51C /* AudioCodecBufferTests.swift in Sources */, + BC3802192AB6AD79001AE399 /* IOAudioResamplerTests.swift in Sources */, BC1DC5042A02894D00E928ED /* FLVVideoFourCCTests.swift in Sources */, BC1DC5122A04E46E00E928ED /* HEVCDecoderConfigurationRecordTests.swift in Sources */, BCA7C24F2A91AA0500882D85 /* IORecorderTests.swift in Sources */, - BCD91C0D2A700FF50033F9E1 /* IOAudioMonitorRingBufferTests.swift in Sources */, + BCD91C0D2A700FF50033F9E1 /* IOAudioRingBufferTests.swift in Sources */, 2976077F20A89FBB00DCF24F /* RTMPMessageTests.swift in Sources */, BC7C56C729A7701F00C41A9B /* ESSpecificDataTests.swift in Sources */, BCCBCE9B29A9D96A0095B51C /* NALUnitReaderTests.swift in Sources */, @@ -1845,6 +1848,7 @@ 290EA8AA1DFB61E700053022 /* CRC32Tests.swift in Sources */, 035AFA042263868E009DD0BB /* RTMPStreamTests.swift in Sources */, 290686031DFDB7A7008EB7ED /* RTMPConnectionTests.swift in Sources */, + BCFC9BE02AB43A3A00378E56 /* AudioCodecSettingsTests.swift in Sources */, BCC9E9092636FF7400948774 /* DataBufferTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Codec/AudioCodec.swift b/Sources/Codec/AudioCodec.swift index 6cab33f79..26ae4ccac 100644 --- a/Sources/Codec/AudioCodec.swift +++ b/Sources/Codec/AudioCodec.swift @@ -18,50 +18,14 @@ public protocol AudioCodecDelegate: AnyObject { * - seealso: https://developer.apple.com/library/ios/technotes/tn2236/_index.html */ public class AudioCodec { + private static let frameCapacity: UInt32 = 1024 + /// The AudioCodec error domain codes. public enum Error: Swift.Error { - case failedToCreate(from: AVAudioFormat, to: AVAudioFormat) + case failedToCreate(from: AVAudioFormat?, to: AVAudioFormat?) case failedToConvert(error: NSError) } - static func makeAudioFormat(_ inSourceFormat: inout AudioStreamBasicDescription) -> AVAudioFormat? { - if inSourceFormat.mFormatID == kAudioFormatLinearPCM && kLinearPCMFormatFlagIsBigEndian == (inSourceFormat.mFormatFlags & kLinearPCMFormatFlagIsBigEndian) { - // ReplayKit audioApp. - guard inSourceFormat.mBitsPerChannel == 16 else { - return nil - } - if let layout = Self.makeChannelLayout(inSourceFormat.mChannelsPerFrame) { - return .init(commonFormat: .pcmFormatInt16, sampleRate: inSourceFormat.mSampleRate, interleaved: true, channelLayout: layout) - } - return AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: inSourceFormat.mSampleRate, channels: inSourceFormat.mChannelsPerFrame, interleaved: true) - } - if let layout = Self.makeChannelLayout(inSourceFormat.mChannelsPerFrame) { - return .init(streamDescription: &inSourceFormat, channelLayout: layout) - } - return .init(streamDescription: &inSourceFormat) - } - - static func makeChannelLayout(_ numberOfChannels: UInt32) -> AVAudioChannelLayout? { - guard numberOfChannels > 2 else { - return nil - } - return AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_DiscreteInOrder | numberOfChannels) - } - - /// Creates a channel map for specific input and output format - static func makeChannelMap(inChannels: Int, outChannels: Int, outputChannelsMap: [Int: Int]) -> [NSNumber] { - var result = Array(repeating: -1, count: outChannels) - for inputIndex in 0.. = [] var lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.AudioCodec.lock") var inSourceFormat: AudioStreamBasicDescription? { didSet { guard var inSourceFormat, inSourceFormat != oldValue else { return } + inputBuffers.removeAll() outputBuffers.removeAll() - ringBuffer = .init(&inSourceFormat) audioConverter = makeAudioConverter(&inSourceFormat) + for _ in 0.. AVAudioBuffer? { + private func makeInputBuffer() -> AVAudioBuffer? { guard let inputFormat = audioConverter?.inputFormat else { return nil } switch inSourceFormat?.mFormatID { case kAudioFormatLinearPCM: - return AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: 1024) + let buffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: Self.frameCapacity) + buffer?.frameLength = Self.frameCapacity + return buffer default: return AVAudioCompressedBuffer(format: inputFormat, packetCapacity: 1, maximumPacketSize: 1024) } } - func releaseOutputBuffer(_ buffer: AVAudioBuffer) { - outputBuffers.append(buffer) - } - - private func getOutputBuffer() -> AVAudioBuffer? { - guard let outputFormat = audioConverter?.outputFormat else { - return nil - } - if outputBuffers.isEmpty { - return settings.format.makeAudioBuffer(outputFormat) - } - return outputBuffers.removeFirst() - } - private func makeAudioConverter(_ inSourceFormat: inout AudioStreamBasicDescription) -> AVAudioConverter? { guard - let inputFormat = Self.makeAudioFormat(&inSourceFormat), + let inputFormat = AVAudioFormatFactory.makeAudioFormat(&inSourceFormat), let outputFormat = settings.format.makeAudioFormat(inSourceFormat) else { return nil } - logger.debug("inputFormat: \(inputFormat)") - logger.debug("outputFormat: \(outputFormat)") + if logger.isEnabledFor(level: .info) { + logger.info("inputFormat:", inputFormat, ",outputFormat:", outputFormat) + } let converter = AVAudioConverter(from: inputFormat, to: outputFormat) - let channelMap = Self.makeChannelMap(inChannels: Int(inputFormat.channelCount), outChannels: Int(outputFormat.channelCount), outputChannelsMap: settings.outputChannelsMap) - logger.debug("channelMap: \(channelMap)") - converter?.channelMap = channelMap settings.apply(converter, oldValue: nil) if converter == nil { delegate?.audioCodec(self, errorOccurred: .failedToCreate(from: inputFormat, to: outputFormat)) @@ -207,6 +155,29 @@ public class AudioCodec { } } +extension AudioCodec: Codec { + // MARK: Codec + typealias Buffer = AVAudioBuffer + + var inputBuffer: AVAudioBuffer { + return inputBuffers[cursor] + } + + var outputBuffer: AVAudioBuffer { + guard let outputFormat = audioConverter?.outputFormat else { + return .init() + } + if outputBuffers.isEmpty { + return settings.format.makeAudioBuffer(outputFormat) ?? .init() + } + return outputBuffers.removeFirst() + } + + func releaseOutputBuffer(_ buffer: AVAudioBuffer) { + outputBuffers.append(buffer) + } +} + extension AudioCodec: Running { // MARK: Running public func startRunning() { @@ -228,7 +199,6 @@ extension AudioCodec: Running { } self.inSourceFormat = nil self.audioConverter = nil - self.ringBuffer = nil self.isRunning.mutate { $0 = false } } } diff --git a/Sources/Codec/AudioCodecRingBuffer.swift b/Sources/Codec/AudioCodecRingBuffer.swift deleted file mode 100644 index d09234d34..000000000 --- a/Sources/Codec/AudioCodecRingBuffer.swift +++ /dev/null @@ -1,131 +0,0 @@ -import Accelerate -import AVFoundation -import Foundation - -final class AudioCodecRingBuffer { - enum Error: Swift.Error { - case isReady - case noBlockBuffer - } - - static let numSamples: UInt32 = 1024 - static let maxBuffers: Int = 6 - - var isReady: Bool { - numSamples == index - } - - var current: AVAudioPCMBuffer { - return buffers[cursor] - } - - private(set) var presentationTimeStamp: CMTime = .invalid - private var index: Int = 0 - private var numSamples: Int - private var format: AVAudioFormat - private var buffers: [AVAudioPCMBuffer] = [] - private var cursor: Int = 0 - private var workingBuffer: AVAudioPCMBuffer - private var maxBuffers: Int = AudioCodecRingBuffer.maxBuffers - - init?(_ inSourceFormat: inout AudioStreamBasicDescription, numSamples: UInt32 = AudioCodecRingBuffer.numSamples) { - guard - inSourceFormat.mFormatID == kAudioFormatLinearPCM, - let format = AudioCodec.makeAudioFormat(&inSourceFormat), - let workingBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: numSamples) else { - return nil - } - for _ in 0.. Int { - if isReady { - return -1 - } - if presentationTimeStamp == .invalid { - let offsetTimeStamp: CMTime = offset == 0 ? .zero : CMTime(value: CMTimeValue(offset), timescale: sampleBuffer.presentationTimeStamp.timescale) - presentationTimeStamp = CMTimeAdd(sampleBuffer.presentationTimeStamp, offsetTimeStamp) - } - if offset == 0 { - if workingBuffer.frameLength < sampleBuffer.numSamples { - if let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(sampleBuffer.numSamples)) { - self.workingBuffer = buffer - } - } - workingBuffer.frameLength = AVAudioFrameCount(sampleBuffer.numSamples) - CMSampleBufferCopyPCMDataIntoAudioBufferList( - sampleBuffer, - at: 0, - frameCount: Int32(sampleBuffer.numSamples), - into: workingBuffer.mutableAudioBufferList - ) - if kLinearPCMFormatFlagIsBigEndian == ((sampleBuffer.formatDescription?.audioStreamBasicDescription?.mFormatFlags ?? 0) & kLinearPCMFormatFlagIsBigEndian) { - if format.isInterleaved { - switch format.commonFormat { - case .pcmFormatInt16: - let length = sampleBuffer.dataBuffer?.dataLength ?? 0 - var image = vImage_Buffer(data: workingBuffer.mutableAudioBufferList[0].mBuffers.mData, height: 1, width: vImagePixelCount(length / 2), rowBytes: length) - vImageByteSwap_Planar16U(&image, &image, vImage_Flags(kvImageNoFlags)) - default: - break - } - } - } - } - let numSamples = min(self.numSamples - index, Int(sampleBuffer.numSamples) - offset) - if format.isInterleaved { - let channelCount = Int(format.channelCount) - switch format.commonFormat { - case .pcmFormatInt16: - memcpy(current.int16ChannelData?[0].advanced(by: index * channelCount), workingBuffer.int16ChannelData?[0].advanced(by: offset * channelCount), numSamples * 2 * channelCount) - case .pcmFormatInt32: - memcpy(current.int32ChannelData?[0].advanced(by: index * channelCount), workingBuffer.int32ChannelData?[0].advanced(by: offset * channelCount), numSamples * 4 * channelCount) - case .pcmFormatFloat32: - memcpy(current.floatChannelData?[0].advanced(by: index * channelCount), workingBuffer.floatChannelData?[0].advanced(by: offset * channelCount), numSamples * 4 * channelCount) - default: - break - } - } else { - for i in 0.. AVAudioBuffer? { switch self { case .aac: @@ -119,20 +130,34 @@ public struct AudioCodecSettings: Codable { /// Specifies the bitRate of audio output. public var bitRate: Int - - /// Map of the output to input channels - public var outputChannelsMap: [Int: Int] - + /// Specifies the sampleRate of audio output. + public var sampleRate: Float64 + /// Specifies the channels of audio output. + public var channels: Int + /// Map of the output to input channels. + public var channelMap: [Int: Int] /// Specifies the output format. var format: AudioCodecSettings.Format = .aac /// Create an new AudioCodecSettings instance. public init( bitRate: Int = 64 * 1000, - outputChannelsMap: [Int: Int] = [0: 0, 1: 1] + sampleRate: Float64 = 0, + channels: Int = 0, + channelMap: [Int: Int] = [0: 0, 1: 1] ) { self.bitRate = bitRate - self.outputChannelsMap = outputChannelsMap + self.sampleRate = sampleRate + self.channels = channels + self.channelMap = channelMap + } + + func invalidateConverter(_ oldValue: AudioCodecSettings) -> Bool { + return !( + sampleRate == oldValue.sampleRate && + channels == oldValue.channels && + channelMap == oldValue.channelMap + ) } func apply(_ converter: AVAudioConverter?, oldValue: AudioCodecSettings?) { @@ -149,4 +174,38 @@ public struct AudioCodecSettings: Codable { converter.bitRate = min(maxAvailableBitRate, max(minAvailableBitRate, bitRate)) } } + + func makeOutputChannels(_ inChannels: Int) -> Int { + return min(channels == 0 ? inChannels : channels, Int(Self.maximumNumberOfChannels)) + } + + func makeChannelMap(_ inChannels: Int) -> [NSNumber] { + let outChannels = makeOutputChannels(inChannels) + var result = Array(repeating: -1, count: outChannels) + for inputIndex in 0.. AVAudioFormat? { + guard let inputFormat else { + return nil + } + let numberOfChannels = makeOutputChannels(Int(inputFormat.channelCount)) + guard let channelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_DiscreteInOrder | UInt32(numberOfChannels)) else { + return nil + } + return .init( + commonFormat: inputFormat.commonFormat, + sampleRate: min(sampleRate == 0 ? inputFormat.sampleRate : sampleRate, Self.mamimumSampleRate), + interleaved: inputFormat.isInterleaved, + channelLayout: channelLayout + ) + } } diff --git a/Sources/Codec/Codec.swift b/Sources/Codec/Codec.swift new file mode 100644 index 000000000..4a627cbd3 --- /dev/null +++ b/Sources/Codec/Codec.swift @@ -0,0 +1,10 @@ +import Foundation + +protocol Codec { + associatedtype Buffer + + var inputBuffer: Buffer { get } + var outputBuffer: Buffer { get } + + func releaseOutputBuffer(_ buffer: Buffer) +} diff --git a/Sources/Extension/AVAudioFormat+Extension.swift b/Sources/Extension/AVAudioFormat+Extension.swift index 6081ce81e..8b2104db0 100644 --- a/Sources/Extension/AVAudioFormat+Extension.swift +++ b/Sources/Extension/AVAudioFormat+Extension.swift @@ -1,173 +1,107 @@ -/* - It's convenient during framework development, but there might be inconveniences when releasing it as an external library, - so I've temporarily commented it out. +import AVFoundation - // - // Created by Lev Sokolov on 2023-08-24. - // Copyright (c) 2023 Shogo Endo. All rights reserved. - // +extension AVAudioCommonFormat: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .pcmFormatFloat32: + return "float32" + case .pcmFormatFloat64: + return "float64" + case .pcmFormatInt16: + return "int16" + case .pcmFormatInt32: + return "int32" + case .otherFormat: + return "other" + @unknown default: + return "unknown" + } + } +} - import AVFoundation - - extension AVAudioFormat { - override public var description: String { - var descriptionParts: [String] = [] - - descriptionParts.append("Sample Rate: \(sampleRate) Hz") - descriptionParts.append("Channels: \(channelCount)") - - if let channelLayout = channelLayout { - descriptionParts.append("Channel Layout: \(channelLayout.layout.readableDescription)") - } - - descriptionParts.append("Format: \(commonFormat.readableDescription)") - descriptionParts.append(isInterleaved ? "Interleaved" : "Non-interleaved") - descriptionParts.append(isStandard ? "Standard" : "Non-standard") - - if let audioFormatID = audioFormatID { - descriptionParts.append("AudioFormatID: \(audioFormatID.audioFormatIDDescription) (\(audioFormatID))") - } - - descriptionParts.append("Settings: \(settings)") - - return descriptionParts.joined(separator: ", ") - } - - var audioFormatID: AudioFormatID? { - guard let formatIDValue = settings[AVFormatIDKey] as? NSNumber else { - return nil - } - return AudioFormatID(formatIDValue.uint32Value) - } - } - - extension UnsafePointer { - var readableDescription: String { - let layout = pointee - let channelTag = layout.mChannelLayoutTag - let bitmap = layout.mChannelBitmap - let numberChannelDescriptions = layout.mNumberChannelDescriptions - let channelDescriptions = channelDescriptions - let channelLabels = channelDescriptions.map { $0.mChannelLabel } - return "tag: \(channelTag), bitmap: \(bitmap), channels: \(numberChannelDescriptions), channelLabels: \(channelLabels)" - } - - var channelDescriptions: [AudioChannelDescription] { - var mutablePointee = UnsafeMutablePointer(mutating: self).pointee - let numberOfDescriptions = Int(mutablePointee.mNumberChannelDescriptions) - return withUnsafePointer(to: &mutablePointee.mChannelDescriptions) { start in - let descriptionsPointer = UnsafeBufferPointer(start: start, count: numberOfDescriptions) - return (0.. CMSampleBuffer? { + var status: OSStatus = noErr + var sampleBuffer: CMSampleBuffer? + status = CMAudioSampleBufferCreateWithPacketDescriptions( + allocator: nil, + dataBuffer: nil, + dataReady: false, + makeDataReadyCallback: nil, + refcon: nil, + formatDescription: format.formatDescription, + sampleCount: Int(frameLength), + presentationTimeStamp: presentationTimeStamp, + packetDescriptions: nil, + sampleBufferOut: &sampleBuffer + ) + guard let sampleBuffer else { + logger.warn("CMAudioSampleBufferCreateWithPacketDescriptions returned errorr: ", status) + return nil + } + status = CMSampleBufferSetDataBufferFromAudioBufferList( + sampleBuffer, + blockBufferAllocator: kCFAllocatorDefault, + blockBufferMemoryAllocator: kCFAllocatorDefault, + flags: 0, + bufferList: audioBufferList + ) + if status != noErr { + logger.warn("CMSampleBufferSetDataBufferFromAudioBufferList returned errorr: ", status) + } + return sampleBuffer + } + + final func copy(_ audioPCMBuffer: AVAudioBuffer) -> Bool { + guard let audioPCMBuffer = audioPCMBuffer as? AVAudioPCMBuffer, frameLength == audioPCMBuffer.frameLength else { + return false + } + let numSamples = Int(frameLength) + if format.isInterleaved { + let channelCount = Int(format.channelCount) + switch format.commonFormat { + case .pcmFormatInt16: + memcpy(int16ChannelData?[0], audioPCMBuffer.int16ChannelData?[0], numSamples * channelCount * 2) + case .pcmFormatInt32: + memcpy(int32ChannelData?[0], audioPCMBuffer.int32ChannelData?[0], numSamples * channelCount * 4) + case .pcmFormatFloat32: + memcpy(floatChannelData?[0], audioPCMBuffer.floatChannelData?[0], numSamples * channelCount * 4) + default: + break + } + } else { + for i in 0.. CMSampleBuffer? { + func muted(_ muted: Bool) -> CMSampleBuffer { guard muted else { return self } guard let dataBuffer = dataBuffer else { - return nil + return self } let status = CMBlockBufferFillDataBytes( with: 0, @@ -91,7 +91,7 @@ extension CMSampleBuffer { dataLength: dataBuffer.dataLength ) guard status == noErr else { - return nil + return self } return self } diff --git a/Sources/Media/AudioEffect.swift b/Sources/Media/AudioEffect.swift deleted file mode 100644 index 381fd4ce7..000000000 --- a/Sources/Media/AudioEffect.swift +++ /dev/null @@ -1,9 +0,0 @@ -import AVFoundation -import Foundation - -/// An object that apply an audio effect. -open class AudioEffect: NSObject { - /// Executes to apply an audio effect. - open func execute(_ buffer: AVAudioBuffer, presentationTimeStamp: CMTime) { - } -} diff --git a/Sources/Media/IOAudioMonitor.swift b/Sources/Media/IOAudioMonitor.swift index de1b7fcb3..0861218c5 100644 --- a/Sources/Media/IOAudioMonitor.swift +++ b/Sources/Media/IOAudioMonitor.swift @@ -1,4 +1,5 @@ import AudioUnit +import AVFoundation import CoreMedia import Foundation @@ -28,7 +29,7 @@ final class IOAudioMonitor { } } } - private var ringBuffer: IOAudioMonitorRingBuffer? + private var ringBuffer: IOAudioRingBuffer? private let callback: AURenderCallback = { (inRefCon: UnsafeMutableRawPointer, _: UnsafeMutablePointer, _: UnsafePointer, _: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer?) in let monitor = Unmanaged.fromOpaque(inRefCon).takeUnretainedValue() @@ -39,15 +40,27 @@ final class IOAudioMonitor { stopRunning() } - func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer, offset: Int = 0) { + func appendAudioPCMBuffer(_ audioPCMBuffer: AVAudioPCMBuffer) { guard isRunning.value else { return } - ringBuffer?.appendSampleBuffer(sampleBuffer) + ringBuffer?.appendAudioPCMBuffer(audioPCMBuffer) } private func render(_ inNumberFrames: UInt32, ioData: UnsafeMutablePointer?) -> OSStatus { - return ringBuffer?.render(inNumberFrames, ioData: ioData) ?? noErr + guard let ringBuffer else { + return noErr + } + if ringBuffer.counts == 0 { + guard let bufferList = UnsafeMutableAudioBufferListPointer(ioData) else { + return noErr + } + for i in 0.. AudioUnit? { diff --git a/Sources/Media/IOAudioMonitorRingBuffer.swift b/Sources/Media/IOAudioMonitorRingBuffer.swift deleted file mode 100644 index 98bb953f1..000000000 --- a/Sources/Media/IOAudioMonitorRingBuffer.swift +++ /dev/null @@ -1,120 +0,0 @@ -import AVFoundation -import CoreMedia -import Foundation - -class IOAudioMonitorRingBuffer { - private static let bufferCounts: UInt32 = 16 - private static let numSamples: UInt32 = 1024 - - private var head = 0 - private var tail = 0 - private var format: AVAudioFormat - private var buffer: AVAudioPCMBuffer - private var workingBuffer: AVAudioPCMBuffer - - init?(_ inSourceFormat: inout AudioStreamBasicDescription, bufferCounts: UInt32 = IOAudioMonitorRingBuffer.bufferCounts) { - guard - inSourceFormat.mFormatID == kAudioFormatLinearPCM, - let format = AudioCodec.makeAudioFormat(&inSourceFormat), - let workingBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: Self.numSamples) else { - return nil - } - guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: Self.numSamples * bufferCounts) else { - return nil - } - self.format = format - self.buffer = buffer - self.buffer.frameLength = self.buffer.frameCapacity - self.workingBuffer = workingBuffer - } - - func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer, offset: Int = 0) { - let numSamples = min(sampleBuffer.numSamples, Int(buffer.frameLength) - head) - if offset == 0 { - if workingBuffer.frameLength < sampleBuffer.numSamples { - if let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(sampleBuffer.numSamples)) { - self.workingBuffer = buffer - } - } - workingBuffer.frameLength = AVAudioFrameCount(sampleBuffer.numSamples) - CMSampleBufferCopyPCMDataIntoAudioBufferList( - sampleBuffer, - at: 0, - frameCount: Int32(sampleBuffer.numSamples), - into: workingBuffer.mutableAudioBufferList - ) - } - if format.isInterleaved { - let channelCount = Int(format.channelCount) - switch format.commonFormat { - case .pcmFormatInt16: - memcpy(buffer.int16ChannelData?[0].advanced(by: head * channelCount), workingBuffer.int16ChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 2) - case .pcmFormatInt32: - memcpy(buffer.int32ChannelData?[0].advanced(by: head * channelCount), workingBuffer.int32ChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 4) - case .pcmFormatFloat32: - memcpy(buffer.floatChannelData?[0].advanced(by: head * channelCount), workingBuffer.floatChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 4) - default: - break - } - } else { - for i in 0..?, offset: Int = 0) -> OSStatus { - let numSamples = min(Int(inNumberFrames), Int(buffer.frameLength) - tail) - guard let bufferList = UnsafeMutableAudioBufferListPointer(ioData), head != tail else { - return noErr - } - if format.isInterleaved { - let channelCount = Int(format.channelCount) - switch format.commonFormat { - case .pcmFormatInt16: - memcpy(bufferList[0].mData?.advanced(by: offset * channelCount * 2), buffer.int16ChannelData?[0].advanced(by: tail * channelCount), numSamples * channelCount * 2) - case .pcmFormatInt32: - memcpy(bufferList[0].mData?.advanced(by: offset * channelCount * 4), buffer.int32ChannelData?[0].advanced(by: tail * channelCount), numSamples * channelCount * 4) - case .pcmFormatFloat32: - memcpy(bufferList[0].mData?.advanced(by: offset * channelCount * 4), buffer.floatChannelData?[0].advanced(by: tail * channelCount), numSamples * channelCount * 4) - default: - break - } - } else { - for i in 0.., didOutput audioFormat: AVAudioFormat) + func resampler(_ resampler: IOAudioResampler, didOutput audioPCMBuffer: AVAudioPCMBuffer, presentationTimeStamp: CMTime) + func resampler(_ resampler: IOAudioResampler, errorOccurred error: AudioCodec.Error) +} + +final class IOAudioResampler { + var settings: AudioCodecSettings = .default { + didSet { + guard var inSourceFormat, settings.invalidateConverter(oldValue) else { + return + } + setUp(&inSourceFormat) + } + } + weak var delegate: T? + + var outputFormat: AVAudioFormat? { + return audioConverter?.outputFormat + } + + private var inSourceFormat: AudioStreamBasicDescription? { + didSet { + guard var inSourceFormat, inSourceFormat != oldValue else { + return + } + setUp(&inSourceFormat) + } + } + private var sampleRate: Int32 = 0 + private var ringBuffer: IOAudioRingBuffer? + private var inputBuffer: AVAudioPCMBuffer? + private var outputBuffer: AVAudioPCMBuffer? + private var audioConverter: AVAudioConverter? { + didSet { + guard let audioConverter else { + return + } + audioConverter.channelMap = settings.makeChannelMap(Int(audioConverter.inputFormat.channelCount)) + audioConverter.primeMethod = .normal + delegate?.resampler(self, didOutput: audioConverter.outputFormat) + } + } + private var presentationTimeStamp: CMTime = kIOAudioResampler_presentationTimeStamp + + func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer) { + inSourceFormat = sampleBuffer.formatDescription?.audioStreamBasicDescription + guard let inputBuffer, let outputBuffer, let ringBuffer else { + return + } + ringBuffer.appendSampleBuffer(sampleBuffer) + var status: AVAudioConverterOutputStatus? = .endOfStream + repeat { + var error: NSError? + status = audioConverter?.convert(to: outputBuffer, error: &error) { inNumberFrames, status in + if inNumberFrames <= ringBuffer.counts { + _ = ringBuffer.render(inNumberFrames, ioData: inputBuffer.mutableAudioBufferList) + inputBuffer.frameLength = inNumberFrames + status.pointee = .haveData + return inputBuffer + } else { + status.pointee = .noDataNow + return nil + } + } + switch status { + case .haveData: + if presentationTimeStamp == .zero { + presentationTimeStamp = CMTime(seconds: sampleBuffer.presentationTimeStamp.seconds, preferredTimescale: sampleRate) + } + delegate?.resampler(self, didOutput: outputBuffer, presentationTimeStamp: presentationTimeStamp) + self.presentationTimeStamp = CMTimeAdd(presentationTimeStamp, .init(value: 1024, timescale: sampleRate)) + case .error: + if let error { + delegate?.resampler(self, errorOccurred: .failedToConvert(error: error)) + } + default: + break + } + } while(status == .haveData) + } + + private func setUp(_ inSourceFormat: inout AudioStreamBasicDescription) { + let inputFormat = AVAudioFormatFactory.makeAudioFormat(&inSourceFormat) + let outputFormat = settings.makeOutputFormat(inputFormat) ?? inputFormat + ringBuffer = .init(&inSourceFormat) + if let inputFormat { + inputBuffer = .init(pcmFormat: inputFormat, frameCapacity: 1024 * 4) + } + if let outputFormat { + outputBuffer = .init(pcmFormat: outputFormat, frameCapacity: kIOAudioResampler_frameCapacity) + } + if let inputFormat, let outputFormat { + if logger.isEnabledFor(level: .info) { + logger.info("inputFormat:", inputFormat, ",outputFormat:", outputFormat) + } + sampleRate = Int32(outputFormat.sampleRate) + presentationTimeStamp = .zero + audioConverter = .init(from: inputFormat, to: outputFormat) + } else { + delegate?.resampler(self, errorOccurred: .failedToCreate(from: inputFormat, to: outputFormat)) + } + } +} diff --git a/Sources/Media/IOAudioRingBuffer.swift b/Sources/Media/IOAudioRingBuffer.swift new file mode 100644 index 000000000..04ca50819 --- /dev/null +++ b/Sources/Media/IOAudioRingBuffer.swift @@ -0,0 +1,194 @@ +import Accelerate +import AVFoundation +import CoreMedia +import Foundation + +final class IOAudioRingBuffer { + private static let bufferCounts: UInt32 = 16 + private static let numSamples: UInt32 = 1024 + + var counts: Int { + if tail <= head { + return head - tail + skip + } + return Int(buffer.frameLength) - tail + head + skip + } + + private(set) var presentationTimeStamp: CMTime = .zero + private var head = 0 + private var tail = 0 + private var skip = 0 + private var format: AVAudioFormat + private var buffer: AVAudioPCMBuffer + private var workingBuffer: AVAudioPCMBuffer + + init?(_ inSourceFormat: inout AudioStreamBasicDescription, bufferCounts: UInt32 = IOAudioRingBuffer.bufferCounts) { + guard + inSourceFormat.mFormatID == kAudioFormatLinearPCM, + let format = AVAudioFormatFactory.makeAudioFormat(&inSourceFormat), + let workingBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: Self.numSamples) else { + return nil + } + guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: Self.numSamples * bufferCounts) else { + return nil + } + self.format = format + self.buffer = buffer + self.buffer.frameLength = self.buffer.frameCapacity + self.workingBuffer = workingBuffer + } + + func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer) { + if presentationTimeStamp == .zero { + presentationTimeStamp = sampleBuffer.presentationTimeStamp + } + if workingBuffer.frameLength < sampleBuffer.numSamples { + if let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(sampleBuffer.numSamples)) { + self.workingBuffer = buffer + } + } + workingBuffer.frameLength = AVAudioFrameCount(sampleBuffer.numSamples) + let status = CMSampleBufferCopyPCMDataIntoAudioBufferList( + sampleBuffer, + at: 0, + frameCount: Int32(sampleBuffer.numSamples), + into: workingBuffer.mutableAudioBufferList + ) + if status != noErr && kLinearPCMFormatFlagIsBigEndian == ((sampleBuffer.formatDescription?.audioStreamBasicDescription?.mFormatFlags ?? 0) & kLinearPCMFormatFlagIsBigEndian) { + if format.isInterleaved { + switch format.commonFormat { + case .pcmFormatInt16: + let length = sampleBuffer.dataBuffer?.dataLength ?? 0 + var image = vImage_Buffer(data: workingBuffer.mutableAudioBufferList[0].mBuffers.mData, height: 1, width: vImagePixelCount(length / 2), rowBytes: length) + vImageByteSwap_Planar16U(&image, &image, vImage_Flags(kvImageNoFlags)) + default: + break + } + } + } + skip = numSamples(sampleBuffer) + appendAudioPCMBuffer(workingBuffer) + } + + func appendAudioPCMBuffer(_ audioPCMBuffer: AVAudioPCMBuffer, offset: Int = 0) { + let numSamples = min(Int(audioPCMBuffer.frameLength) - offset, Int(buffer.frameLength) - head) + if format.isInterleaved { + let channelCount = Int(format.channelCount) + switch format.commonFormat { + case .pcmFormatInt16: + memcpy(buffer.int16ChannelData?[0].advanced(by: head * channelCount), audioPCMBuffer.int16ChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 2) + case .pcmFormatInt32: + memcpy(buffer.int32ChannelData?[0].advanced(by: head * channelCount), audioPCMBuffer.int32ChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 4) + case .pcmFormatFloat32: + memcpy(buffer.floatChannelData?[0].advanced(by: head * channelCount), audioPCMBuffer.floatChannelData?[0].advanced(by: offset * channelCount), numSamples * channelCount * 4) + default: + break + } + } else { + for i in 0..?, offset: Int = 0) -> OSStatus { + if 0 < skip { + let numSamples = min(Int(inNumberFrames), skip) + guard let bufferList = UnsafeMutableAudioBufferListPointer(ioData) else { + return noErr + } + if format.isInterleaved { + let channelCount = Int(format.channelCount) + switch format.commonFormat { + case .pcmFormatInt16: + bufferList[0].mData?.assumingMemoryBound(to: Int16.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) + case .pcmFormatInt32: + bufferList[0].mData?.assumingMemoryBound(to: Int32.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) + case .pcmFormatFloat32: + bufferList[0].mData?.assumingMemoryBound(to: Float32.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) + default: + break + } + } else { + for i in 0.. Int { + let presentationTimeStamp = CMTimeAdd(presentationTimeStamp, CMTime(value: CMTimeValue(counts), timescale: presentationTimeStamp.timescale)) + return max(Int(sampleBuffer.presentationTimeStamp.value - presentationTimeStamp.value), 0) + } +} diff --git a/Sources/Media/IOAudioUnit.swift b/Sources/Media/IOAudioUnit.swift index f00a0657b..64fbe6c2a 100644 --- a/Sources/Media/IOAudioUnit.swift +++ b/Sources/Media/IOAudioUnit.swift @@ -5,9 +5,6 @@ import SwiftPMSupport #endif final class IOAudioUnit: NSObject, IOUnit { - private static let defaultPresentationTimeStamp: CMTime = .invalid - private static let sampleBuffersThreshold: Int = 1 - lazy var codec: AudioCodec = { var codec = AudioCodec() codec.lockQueue = lockQueue @@ -30,21 +27,23 @@ final class IOAudioUnit: NSObject, IOUnit { } } } + + var settings: AudioCodecSettings = .default { + didSet { + codec.settings = settings + resampler.settings = settings + } + } + + private lazy var resampler: IOAudioResampler = { + var resampler = IOAudioResampler() + resampler.delegate = self + return resampler + }() private var monitor: IOAudioMonitor = .init() #if os(iOS) || os(macOS) private(set) var capture: IOAudioCaptureUnit = .init() #endif - private var inSourceFormat: AudioStreamBasicDescription? { - didSet { - guard inSourceFormat != oldValue else { - return - } - presentationTimeStamp = Self.defaultPresentationTimeStamp - codec.inSourceFormat = inSourceFormat - monitor.inSourceFormat = inSourceFormat - } - } - private var presentationTimeStamp = IOAudioUnit.defaultPresentationTimeStamp #if os(iOS) || os(macOS) func attachAudio(_ device: AVCaptureDevice?, automaticallyConfiguresApplicationAudioSession: Bool) throws { @@ -67,51 +66,7 @@ final class IOAudioUnit: NSObject, IOUnit { #endif func appendSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - guard CMSampleBufferDataIsReady(sampleBuffer), let sampleBuffer = sampleBuffer.muted(muted) else { - return - } - inSourceFormat = sampleBuffer.formatDescription?.streamBasicDescription?.pointee - // Synchronization between video and audio, need to synchronize the gaps. - let numGapSamples = numGapSamples(sampleBuffer) - let numSampleBuffers = Int(numGapSamples / sampleBuffer.numSamples) - if Self.sampleBuffersThreshold <= numSampleBuffers { - var gapPresentationTimeStamp = presentationTimeStamp - for i in 0 ... numSampleBuffers { - let numSamples = numSampleBuffers == i ? numGapSamples % sampleBuffer.numSamples : sampleBuffer.numSamples - guard let gapSampleBuffer = CMAudioSampleBufferFactory.makeSampleBuffer(sampleBuffer, numSamples: numSamples, presentationTimeStamp: gapPresentationTimeStamp) else { - continue - } - mixer?.recorder.appendSampleBuffer(gapSampleBuffer) - codec.appendSampleBuffer(gapSampleBuffer) - gapPresentationTimeStamp = CMTimeAdd(gapPresentationTimeStamp, gapSampleBuffer.duration) - } - } - monitor.appendSampleBuffer(sampleBuffer) - mixer?.recorder.appendSampleBuffer(sampleBuffer) - codec.appendSampleBuffer(sampleBuffer) - presentationTimeStamp = sampleBuffer.presentationTimeStamp - } - - func registerEffect(_ effect: AudioEffect) -> Bool { - codec.effects.insert(effect).inserted - } - - func unregisterEffect(_ effect: AudioEffect) -> Bool { - codec.effects.remove(effect) != nil - } - - private func numGapSamples(_ sampleBuffer: CMSampleBuffer) -> Int { - guard let mSampleRate = inSourceFormat?.mSampleRate, presentationTimeStamp != Self.defaultPresentationTimeStamp else { - return 0 - } - let sampleRate = Int32(mSampleRate) - // Device audioMic or ReplayKit audioMic. - if presentationTimeStamp.timescale == sampleRate { - return Int(sampleBuffer.presentationTimeStamp.value - presentationTimeStamp.value) - sampleBuffer.numSamples - } - // ReplayKit audioApp. PTS = {69426976806125/1000000000 = 69426.977} - let diff = CMTime(seconds: sampleBuffer.presentationTimeStamp.seconds, preferredTimescale: sampleRate) - CMTime(seconds: presentationTimeStamp.seconds, preferredTimescale: sampleRate) - return Int(diff.value) - sampleBuffer.numSamples + resampler.appendSampleBuffer(sampleBuffer.muted(muted)) } } @@ -125,7 +80,6 @@ extension IOAudioUnit: IOUnitEncoding { func stopEncoding() { codec.stopRunning() codec.delegate = nil - inSourceFormat = nil } } @@ -145,7 +99,6 @@ extension IOAudioUnit: IOUnitDecoding { } codec.stopRunning() codec.delegate = nil - inSourceFormat = nil } } @@ -181,9 +134,31 @@ extension IOAudioUnit: AudioCodecDelegate { guard let audioBuffer = audioBuffer as? AVAudioPCMBuffer else { return } - if let mixer = mixer { + if let mixer { mixer.delegate?.mixer(mixer, didOutput: audioBuffer, presentationTimeStamp: presentationTimeStamp) } mixer?.mediaLink.enqueueAudio(audioBuffer) } } + +extension IOAudioUnit: IOAudioResamplerDelegate { + // MARK: IOAudioResamplerDelegate + func resampler(_ resampler: IOAudioResampler, errorOccurred error: AudioCodec.Error) { + } + + func resampler(_ resampler: IOAudioResampler, didOutput audioFormat: AVAudioFormat) { + codec.inSourceFormat = audioFormat.formatDescription.audioStreamBasicDescription + monitor.inSourceFormat = audioFormat.formatDescription.audioStreamBasicDescription + } + + func resampler(_ resampler: IOAudioResampler, didOutput audioBuffer: AVAudioPCMBuffer, presentationTimeStamp: CMTime) { + if let mixer { + mixer.delegate?.mixer(mixer, didOutput: audioBuffer, presentationTimeStamp: presentationTimeStamp) + if mixer.recorder.isRunning.value, let sampleBuffer = audioBuffer.makeSampleBuffer(presentationTimeStamp) { + mixer.recorder.appendSampleBuffer(sampleBuffer) + } + } + monitor.appendAudioPCMBuffer(audioBuffer) + codec.appendAudioBuffer(audioBuffer, presentationTimeStamp: presentationTimeStamp) + } +} diff --git a/Sources/Net/NetStream.swift b/Sources/Net/NetStream.swift index 13b36db6b..646ba1fe5 100644 --- a/Sources/Net/NetStream.swift +++ b/Sources/Net/NetStream.swift @@ -158,10 +158,10 @@ open class NetStream: NSObject { /// Specifies the audio compression properties. public var audioSettings: AudioCodecSettings { get { - mixer.audioIO.codec.settings + mixer.audioIO.settings } set { - mixer.audioIO.codec.settings = newValue + mixer.audioIO.settings = newValue } } @@ -276,20 +276,6 @@ open class NetStream: NSObject { } } - /// Register a audio effect. - public func registerAudioEffect(_ effect: AudioEffect) -> Bool { - mixer.audioIO.lockQueue.sync { - self.mixer.audioIO.registerEffect(effect) - } - } - - /// Unregister a audio effect. - public func unregisterAudioEffect(_ effect: AudioEffect) -> Bool { - mixer.audioIO.lockQueue.sync { - self.mixer.audioIO.unregisterEffect(effect) - } - } - /// Starts recording. public func startRecording(_ settings: [AVMediaType: [String: Any]] = IORecorder.defaultOutputSettings) { mixer.recorder.outputSettings = settings diff --git a/Sources/RTMP/RTMPMessage.swift b/Sources/RTMP/RTMPMessage.swift index 094e97ba3..84df0b064 100644 --- a/Sources/RTMP/RTMPMessage.swift +++ b/Sources/RTMP/RTMPMessage.swift @@ -603,7 +603,7 @@ final class RTMPAudioMessage: RTMPMessage { private func makeAudioBuffer(_ stream: RTMPStream) -> AVAudioBuffer? { return payload.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> AVAudioBuffer? in - guard let baseAddress = buffer.baseAddress, let buffer = stream.mixer.audioIO.codec.makeInputBuffer() as? AVAudioCompressedBuffer else { + guard let baseAddress = buffer.baseAddress, let buffer = stream.mixer.audioIO.codec.inputBuffer as? AVAudioCompressedBuffer else { return nil } let byteCount = payload.count - codec.headerSize diff --git a/Sources/Util/AVAudioFormatFactory.swift b/Sources/Util/AVAudioFormatFactory.swift new file mode 100644 index 000000000..18041f833 --- /dev/null +++ b/Sources/Util/AVAudioFormatFactory.swift @@ -0,0 +1,28 @@ +import AVFoundation +import Foundation + +enum AVAudioFormatFactory { + static func makeAudioFormat(_ inSourceFormat: inout AudioStreamBasicDescription) -> AVAudioFormat? { + if inSourceFormat.mFormatID == kAudioFormatLinearPCM && kLinearPCMFormatFlagIsBigEndian == (inSourceFormat.mFormatFlags & kLinearPCMFormatFlagIsBigEndian) { + // ReplayKit audioApp. + guard inSourceFormat.mBitsPerChannel == 16 else { + return nil + } + if let layout = Self.makeChannelLayout(inSourceFormat.mChannelsPerFrame) { + return .init(commonFormat: .pcmFormatInt16, sampleRate: inSourceFormat.mSampleRate, interleaved: true, channelLayout: layout) + } + return AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: inSourceFormat.mSampleRate, channels: inSourceFormat.mChannelsPerFrame, interleaved: true) + } + if let layout = Self.makeChannelLayout(inSourceFormat.mChannelsPerFrame) { + return .init(streamDescription: &inSourceFormat, channelLayout: layout) + } + return .init(streamDescription: &inSourceFormat) + } + + private static func makeChannelLayout(_ numberOfChannels: UInt32) -> AVAudioChannelLayout? { + guard 2 < numberOfChannels else { + return nil + } + return AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_DiscreteInOrder | numberOfChannels) + } +} diff --git a/Sources/Util/CMAudioSampleBufferFactory.swift b/Sources/Util/CMAudioSampleBufferFactory.swift deleted file mode 100644 index 7eeb48c0e..000000000 --- a/Sources/Util/CMAudioSampleBufferFactory.swift +++ /dev/null @@ -1,53 +0,0 @@ -import CoreMedia -import Foundation - -enum CMAudioSampleBufferFactory { - static func makeSampleBuffer(_ buffer: CMSampleBuffer, numSamples: Int, presentationTimeStamp: CMTime) -> CMSampleBuffer? { - guard 0 < numSamples, let formatDescription = buffer.formatDescription, let streamBasicDescription = formatDescription.streamBasicDescription else { - return nil - } - var status: OSStatus = noErr - var blockBuffer: CMBlockBuffer? - let blockSize = numSamples * Int(streamBasicDescription.pointee.mBytesPerPacket) - status = CMBlockBufferCreateWithMemoryBlock( - allocator: nil, - memoryBlock: nil, - blockLength: blockSize, - blockAllocator: nil, - customBlockSource: nil, - offsetToData: 0, - dataLength: blockSize, - flags: 0, - blockBufferOut: &blockBuffer - ) - guard let blockBuffer, status == noErr else { - return nil - } - status = CMBlockBufferFillDataBytes( - with: 0, - blockBuffer: blockBuffer, - offsetIntoDestination: 0, - dataLength: blockSize - ) - guard status == noErr else { - return nil - } - var sampleBuffer: CMSampleBuffer? - status = CMAudioSampleBufferCreateWithPacketDescriptions( - allocator: nil, - dataBuffer: blockBuffer, - dataReady: true, - makeDataReadyCallback: nil, - refcon: nil, - formatDescription: formatDescription, - sampleCount: numSamples, - presentationTimeStamp: presentationTimeStamp, - packetDescriptions: nil, - sampleBufferOut: &sampleBuffer - ) - guard let sampleBuffer, status == noErr else { - return nil - } - return sampleBuffer - } -} diff --git a/Tests/CMAudioSampleBufferTestUtil.swift b/Tests/CMAudioSampleBufferFactory.swift similarity index 99% rename from Tests/CMAudioSampleBufferTestUtil.swift rename to Tests/CMAudioSampleBufferFactory.swift index f51e71b90..578531294 100644 --- a/Tests/CMAudioSampleBufferTestUtil.swift +++ b/Tests/CMAudioSampleBufferFactory.swift @@ -1,6 +1,6 @@ import AVFoundation -enum CMAudioSampleBufferTestUtil { +enum CMAudioSampleBufferFactory { static func makeSilence(_ sampleRate: Double = 44100, numSamples: Int = 1024, channels: UInt32 = 1, presentaionTimeStamp: CMTime = .zero) -> CMSampleBuffer? { var asbd = AudioStreamBasicDescription( mSampleRate: sampleRate, diff --git a/Tests/Codec/AudioCodecBufferTests.swift b/Tests/Codec/AudioCodecBufferTests.swift deleted file mode 100644 index dc7cca7bf..000000000 --- a/Tests/Codec/AudioCodecBufferTests.swift +++ /dev/null @@ -1,198 +0,0 @@ -import Foundation -import XCTest -import AVFoundation - -@testable import HaishinKit - -final class AudioCodecBufferTests: XCTestCase { - func testMonoSamples256_16bit() { - guard - let sampleBuffer = CMAudioSampleBufferTestUtil.makeSinWave(44100, numSamples: 256), - var asbd = sampleBuffer.formatDescription?.audioStreamBasicDescription else { - XCTFail() - return - } - let buffer = AudioCodecRingBuffer(&asbd, numSamples: 1024) - for _ in 0..<1024/256 { - _ = buffer?.appendSampleBuffer(sampleBuffer, offset: 0) - } - XCTAssertTrue(buffer?.isReady == true) - let sampleBufferData = (try? sampleBuffer.dataBuffer?.dataBytes()) ?? Data() - var expectedData = Data() - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - if let pointer = buffer?.current.int16ChannelData?[0] { - let data = Data(bytes: pointer, count: 1024 * 2) - XCTAssertEqual(expectedData, data) - } - } - - func testStereoSamples256_16bit() { - guard - let sampleBuffer = CMAudioSampleBufferTestUtil.makeSinWave(44100, numSamples: 256, channels: 2), - var asbd = sampleBuffer.formatDescription?.audioStreamBasicDescription else { - XCTFail() - return - } - let buffer = AudioCodecRingBuffer(&asbd, numSamples: 1024) - for _ in 0..<1024/256 { - _ = buffer?.appendSampleBuffer(sampleBuffer, offset: 0) - } - XCTAssertTrue(buffer?.isReady == true) - let sampleBufferData = (try? sampleBuffer.dataBuffer?.dataBytes()) ?? Data() - var expectedData = Data() - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - expectedData.append(sampleBufferData) - if let pointer = buffer?.current.int16ChannelData?[0] { - let data = Data(bytes: pointer, count: 1024 * 2 * 2) - XCTAssertEqual(expectedData, data) - } - } - - func testMonoSamples920_921_16bit() { - guard - let sampleBuffer_1 = CMAudioSampleBufferTestUtil.makeSinWave(44100, numSamples: 920), - let sampleBuffer_2 = CMAudioSampleBufferTestUtil.makeSinWave(44100, numSamples: 921), - var asbd = sampleBuffer_1.formatDescription?.audioStreamBasicDescription, - let buffer = AudioCodecRingBuffer(&asbd, numSamples: 1024) else { - XCTFail() - return - } - - let sampleBuffer_1Data = (try? sampleBuffer_1.dataBuffer?.dataBytes()) ?? Data() - let sampleBuffer_2Data = (try? sampleBuffer_2.dataBuffer?.dataBytes()) ?? Data() - - var numBuffer = buffer.appendSampleBuffer(sampleBuffer_1, offset: 0) - numBuffer = buffer.appendSampleBuffer(sampleBuffer_2, offset: 0) - XCTAssertTrue(buffer.isReady) - if let pointer = buffer.current.int16ChannelData?[0] { - let data = Data(bytes: pointer, count: 1024 * 2) - var expectedData = Data() - expectedData.append(sampleBuffer_1Data) - expectedData.append(sampleBuffer_2Data.subdata(in: 0.., didOutput audioFormat: AVAudioFormat) { + } + + func resampler(_ resampler: HaishinKit.IOAudioResampler, didOutput audioPCMBuffer: AVAudioPCMBuffer, presentationTimeStamp: CMTime) { + } + + func resampler(_ resampler: HaishinKit.IOAudioResampler, errorOccurred error: HaishinKit.AudioCodec.Error) { + } +} + +final class IOAudioResamplerTests: XCTestCase { + private lazy var nullIOAudioResamplerDelegate = NullIOAudioResamplerDelegate() + + func testpKeep16000() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 16000, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(48000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 16000) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 16000) + } + + func testpKeep44100() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 44100, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(48000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(16000, numSamples: 1024 * 20, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + } + + func testpKeep48000() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 48000, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(48000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 48000) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024 * 2, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 48000) + } + + func testpPassthrough48000_44100() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 0, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44000) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(48000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 48000) + } + + func testpPassthrough44100_48000() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 0, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(48000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 48000) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + } + + func testpPassthrough16000_48000() { + let resampler = IOAudioResampler() + resampler.settings = .init(bitRate: 0, sampleRate: 0, channels: 1) + resampler.delegate = nullIOAudioResamplerDelegate + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(16000, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 16000) + resampler.appendSampleBuffer(CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: 1024, channels: 1)!) + XCTAssertEqual(resampler.outputFormat?.sampleRate, 44100) + } +} diff --git a/Tests/Media/IOAudioMonitorRingBufferTests.swift b/Tests/Media/IOAudioRingBufferTests.swift similarity index 87% rename from Tests/Media/IOAudioMonitorRingBufferTests.swift rename to Tests/Media/IOAudioRingBufferTests.swift index 0fab920e3..89e2201fd 100644 --- a/Tests/Media/IOAudioMonitorRingBufferTests.swift +++ b/Tests/Media/IOAudioRingBufferTests.swift @@ -4,7 +4,7 @@ import AVFoundation @testable import HaishinKit -final class IOAudioMonitorRingBufferTests: XCTestCase { +final class IOAudioRingBufferTests: XCTestCase { func testMonoAppendSampleBuffer_920() { appendSampleBuffer(920, channels: 1) } @@ -33,10 +33,10 @@ final class IOAudioMonitorRingBufferTests: XCTestCase { mBitsPerChannel: 16, mReserved: 0 ) - let buffer = IOAudioMonitorRingBuffer(&asbd, bufferCounts: 3) + let buffer = IOAudioRingBuffer(&asbd, bufferCounts: 3) guard let readBuffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(streamDescription: &asbd)!, frameCapacity: AVAudioFrameCount(numSamples)), - let sinWave = CMAudioSampleBufferTestUtil.makeSinWave(44100, numSamples: numSamples, channels: channels) else { + let sinWave = CMAudioSampleBufferFactory.makeSinWave(44100, numSamples: numSamples, channels: channels) else { return } let bufferList = UnsafeMutableAudioBufferListPointer(readBuffer.mutableAudioBufferList) diff --git a/Tests/Media/IORecorderTests.swift b/Tests/Media/IORecorderTests.swift index c1aa0a10c..8a7722192 100644 --- a/Tests/Media/IORecorderTests.swift +++ b/Tests/Media/IORecorderTests.swift @@ -18,7 +18,7 @@ final class IORecorderTests: XCTestCase, IORecorderDelegate { sleep(1) var presentationTimeStamp: CMTime = .zero for _ in 0...100 { - guard let sampleBuffer = CMAudioSampleBufferTestUtil.makeSilence(44100, numSamples: 1024, channels: 2, presentaionTimeStamp: presentationTimeStamp) else { + guard let sampleBuffer = CMAudioSampleBufferFactory.makeSilence(44100, numSamples: 1024, channels: 2, presentaionTimeStamp: presentationTimeStamp) else { return } presentationTimeStamp = CMTimeAdd(presentationTimeStamp, sampleBuffer.duration) @@ -40,7 +40,7 @@ final class IORecorderTests: XCTestCase, IORecorderDelegate { sleep(1) var presentationTimeStamp: CMTime = .zero for _ in 0...100 { - guard let sampleBuffer = CMAudioSampleBufferTestUtil.makeSilence(44100, numSamples: 1024, channels: 4, presentaionTimeStamp: presentationTimeStamp) else { + guard let sampleBuffer = CMAudioSampleBufferFactory.makeSilence(44100, numSamples: 1024, channels: 4, presentaionTimeStamp: presentationTimeStamp) else { return } presentationTimeStamp = CMTimeAdd(presentationTimeStamp, sampleBuffer.duration) diff --git a/Tests/Util/CMAudioSampleBufferFactoryTests.swift b/Tests/Util/CMAudioSampleBufferFactoryTests.swift deleted file mode 100644 index 7da194773..000000000 --- a/Tests/Util/CMAudioSampleBufferFactoryTests.swift +++ /dev/null @@ -1,127 +0,0 @@ -import Foundation -import XCTest -import CoreMedia - -@testable import HaishinKit - -final class CMAudioSampleBufferFactoryTests: XCTestCase { - func test48000_2chTest() { - let streamBasicDescription = AudioStreamBasicDescription( - mSampleRate: 48000.0, - mFormatID: kAudioFormatLinearPCM, - mFormatFlags: 0xc, - mBytesPerPacket: 4, - mFramesPerPacket: 1, - mBytesPerFrame: 4, - mChannelsPerFrame: 2, - mBitsPerChannel: 16, - mReserved: 0 - ) - if let buffer = makeSampleBuffer(streamBasicDescription) { - XCTAssertNotNil(CMAudioSampleBufferFactory.makeSampleBuffer(buffer, numSamples: 1024, presentationTimeStamp: .zero)) - } else { - XCTFail() - } - } - - func test48000_4chTest() { - let streamBasicDescription = AudioStreamBasicDescription( - mSampleRate: 48000.0, - mFormatID: kAudioFormatLinearPCM, - mFormatFlags: 0xc, - mBytesPerPacket: 8, - mFramesPerPacket: 1, - mBytesPerFrame: 8, - mChannelsPerFrame: 4, - mBitsPerChannel: 16, - mReserved: 0 - ) - if let buffer = makeSampleBuffer(streamBasicDescription) { - XCTAssertNotNil(CMAudioSampleBufferFactory.makeSampleBuffer(buffer, numSamples: 1024, presentationTimeStamp: .zero)) - } else { - XCTFail() - } - } - - func test48000_3chTest() { - let streamBasicDescription = AudioStreamBasicDescription( - mSampleRate: 48000.0, - mFormatID: kAudioFormatLinearPCM, - mFormatFlags: 0xc, - mBytesPerPacket: 6, - mFramesPerPacket: 1, - mBytesPerFrame: 6, - mChannelsPerFrame: 3, - mBitsPerChannel: 16, - mReserved: 0 - ) - if let buffer = makeSampleBuffer(streamBasicDescription) { - XCTAssertNotNil(CMAudioSampleBufferFactory.makeSampleBuffer(buffer, numSamples: 1024, presentationTimeStamp: .zero)) - } else { - XCTFail() - } - } - - func test48000_2chTest_mac() { - let streamBasicDescription = AudioStreamBasicDescription( - mSampleRate: 48000.0, - mFormatID: kAudioFormatLinearPCM, - mFormatFlags: 0x29, - mBytesPerPacket: 4, - mFramesPerPacket: 1, - mBytesPerFrame: 4, - mChannelsPerFrame: 1, - mBitsPerChannel: 32, - mReserved: 0 - ) - if let buffer = makeSampleBuffer(streamBasicDescription) { - XCTAssertNotNil(CMAudioSampleBufferFactory.makeSampleBuffer(buffer, numSamples: 1024, presentationTimeStamp: .zero)) - } else { - XCTFail() - } - } - - private func makeSampleBuffer(_ streamBasicDescription: AudioStreamBasicDescription) -> CMSampleBuffer? { - guard let formatDescription = try? CMAudioFormatDescription(audioStreamBasicDescription: streamBasicDescription) else { - return nil - } - var status: OSStatus = noErr - var blockBuffer: CMBlockBuffer? - let blockSize = 1024 * Int(streamBasicDescription.mBytesPerPacket) - status = CMBlockBufferCreateWithMemoryBlock( - allocator: nil, - memoryBlock: nil, - blockLength: blockSize, - blockAllocator: nil, - customBlockSource: nil, - offsetToData: 0, - dataLength: blockSize, - flags: 0, - blockBufferOut: &blockBuffer - ) - guard let blockBuffer, status == noErr else { - return nil - } - status = CMBlockBufferFillDataBytes(with: 0, blockBuffer: blockBuffer, offsetIntoDestination: 0, dataLength: blockSize) - guard status == noErr else { - return nil - } - var sampleBuffer: CMSampleBuffer? - status = CMAudioSampleBufferCreateWithPacketDescriptions( - allocator: nil, - dataBuffer: blockBuffer, - dataReady: true, - makeDataReadyCallback: nil, - refcon: nil, - formatDescription: formatDescription, - sampleCount: 1024, - presentationTimeStamp: CMTimeMake(value: 1024, timescale: Int32(streamBasicDescription.mSampleRate)), - packetDescriptions: nil, - sampleBufferOut: &sampleBuffer - ) - guard let sampleBuffer, status == noErr else { - return nil - } - return sampleBuffer - } -}