-
-
Notifications
You must be signed in to change notification settings - Fork 620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multichannel audio mixer #1386
Add multichannel audio mixer #1386
Conversation
ef87fd7
to
a475e05
Compare
The PR is large, so I'll need some time for the review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it with ReplayKit@Control Center at hand. Only the audioApp could be heard. What device was it tested on? Just for reference, I'll leave the FormatDescription here.
iPhone 15 Pro max + iOS17.3 a475e05
audioMic
CMSampleBuffer 0x102506830 retainCount: 4 allocator: 0x1e8f806a8
invalid = NO
dataReady = YES
makeDataReadyCallback = 0x0
makeDataReadyRefcon = 0x0
formatDescription = <CMAudioFormatDescription 0x283311900 [0x1e8f806a8]> {
mediaType:'soun'
mediaSubType:'lpcm'
mediaSpecific: {
ASBD: {
mSampleRate: 48000.000000
mFormatID: 'lpcm'
mFormatFlags: 0xc
mBytesPerPacket: 2
mFramesPerPacket: 1
mBytesPerFrame: 2
mChannelsPerFrame: 1
mBitsPerChannel: 16 }
cookie: {(null)}
ACL: {(null)}
FormatList Array: {
Index: 0
ChannelLayoutTag: 0x640001
ASBD: {
mSampleRate: 48000.000000
mFormatID: 'lpcm'
mFormatFlags: 0xc
mBytesPerPacket: 2
mFramesPerPacket: 1
mBytesPerFrame: 2
mChannelsPerFrame: 1
mBitsPerChannel: 16 }}
}
extensions: {(null)}
}
audioApp
CMSampleBuffer 0x1047130f0 retainCount: 4 allocator: 0x1e8f806a8
invalid = NO
dataReady = YES
makeDataReadyCallback = 0x0
makeDataReadyRefcon = 0x0
formatDescription = <CMAudioFormatDescription 0x283ab08c0 [0x1e8f806a8]> {
mediaType:'soun'
mediaSubType:'lpcm'
mediaSpecific: {
ASBD: {
mSampleRate: 44100.000000
mFormatID: 'lpcm'
mFormatFlags: 0xe
mBytesPerPacket: 4
mFramesPerPacket: 1
mBytesPerFrame: 4
mChannelsPerFrame: 2
mBitsPerChannel: 16 }
cookie: {(null)}
ACL: {(null)}
FormatList Array: {
Index: 0
ChannelLayoutTag: 0x650002
ASBD: {
mSampleRate: 44100.000000
mFormatID: 'lpcm'
mFormatFlags: 0xe
mBytesPerPacket: 4
mFramesPerPacket: 1
mBytesPerFrame: 4
mChannelsPerFrame: 2
mBitsPerChannel: 16 }}
}
extensions: {(null)}
}
@@ -0,0 +1,154 @@ | |||
import AudioUnit | |||
|
|||
extension AudioNode: CustomStringConvertible { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Q]
It's convenient for debugging, but I prefer to avoid having it in the library to prevent changing the default behavior. Will moving it to an example still not work as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AudioNode
is a new class so I implemented a custom description. I can rename it to something else. Is it more about reimplementing a custom description for AudioStreamBasicDescription
? It's very handy to print mFormatFlags
as a readable flags. For example,
mFormatFlags: 12 AudioFormatFlags(audio_IsPacked | audio_IsSignedInteger | ll_32BitSourceData | pcm_IsPacked | pcm_IsSignedInteger)
instead of just mFormatFlags: 12
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding custom classes in HK, that's fine. However, please keep the Extension folder for Cocoa frame-only extensions. Please move it to the bottom of the definition file.
As for AudioStreamBasicDescription, I haven't adopted it because the default behavior changes. I acknowledge that it's convenient for debugging, but we often avoid it in application-level development due to the numerous problems it can cause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll move it tomorrow. Can I keep AudioStreamBasicDescription
extension while removing description
override and rename it to something like verboseDescription
for debug purpose?
Sources/IO/IOAudioMixer.swift
Outdated
track(channel: Int(channel))?.resampler.append(audioBuffer, when: when) | ||
} | ||
|
||
private func createTrack(channel: Int, settings: IOAudioResamplerSettings) -> Track { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Must]
- createTrack -> makeTrack
Following Apple's Cocoa naming conventions, factory methods adopt the 'make' convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to makeTrack
.
For the original issue of mic audio being silent, I've pushed |
I tried it with 44e506f using ReplayKit@Control Center, but this time the microphone audio was coming in, while the app audio was not. Please check if the same issue occurs on your end. The testing device is the same as mentioned above. |
Please try the new version. I've tested Screencast example with and without mic enabled from the start. |
Thank you very much. It's a great feature! The built-in microphone on the iPhone and external USB headphones seem to be working fine without any noticeable audio delay. However, during the next round of testing, the following issues were discovered:
|
Awesome. Thank you for testing!
|
The standard equipment adopted is the AirPods Pro. The ASBD (AudioStreamBasicDescription) is as follows: |
I've tested 3 headphones with my iPhone 11 Pro Max, iOS 17.4.1 and wasn't able to reproduce the issue. Here are my tests: SummaryTest 1: starting with Apple AirPods Pro Test 2: starting with built-in mic Test 3: start with Beats Fit Pro (Apple) Test 4: starting with built-in mic Test 5: start with non-Apple headphones (Bose QC 35 II) Test 6: starting with built-in mic I noticed that iOS keeps the initial audio format and performs the audio conversion before providing it to the ReplayKit sample handler when user changes the audio source. For example, using if users starts the broadcast with 16KHz headphones connected, disconnecting them doesn't switch back to 48KHz. Could you please verify couple of things when using AirPods Pro?
This covers the default mixing troubleshooting. Also please let me know if |
This can be switched with some actions. Here are the ones I know: Triggering an alarm using the clock function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it just now and it worked as expected. Thank you.
Since the audio part is fundamental, merging it as it is and releasing it may lead to many criticisms.
Therefore, I plan to proceed with the following steps:
- In version 1.7.x, make changes to method signatures and other related changes to accommodate this external API change.
- In version 1.8.0,
- Switch between this mixing feature and the existing audio processing, with the default being the existing audio.
- Make adjustments to allow switching the processing via rtmpConnection.requireNetworkFramework as an example.
- In version 1.9.0,
- After confirming stability, make this mixing process the default.
Please let me work on the branch shogo4405:feature/audio-mixer for now.
This plan looks great! Thank you. |
Description & motivation
This is a PR based on #1380. It replaces audio resampler with an audio mixer. Everything works as before when there is just one input source. Once there is more, audio mixer creates an audio graph; adds a resampler for each input source; starts the mixing process. It is using plain AudioUnit components instead of
AVAudioNode
to avoid interacting with audio session and instead ofAUAudioUnit
to avoid deprecatedAUGraph
.Users can provide dedicated audio settings for every input source with
sourceSettings
. This allows to setchannelMap
and other parameters independently for every resampler. However, outputsampleRate
andchannels
are enforced by the IOMixer to be the same as for the default resampler. This ensures that mixer node works with the same audio format from every source. Changes ofsettings
and audio format of the default resampler are monitored for this purpose.All interfaces and default behavior is kept the same.
Type of change
Please delete options that are not relevant.
Screenshots: