Reputation: 143
i have an app which uses an AudioUnit
with subtype kAudioUnitSubType_VoiceProcessingIO
. The input comes from a network stream. That works fine, but now i want to play a simple sound from file (alert sound after receiving a push notification). That does not work while the AudioUnit
is in action, only when it's not started yet or already disposed. I can even stop the AudioUnit
while the sound file stills plays to hear the final samples, so the playback seems to work, but it's silent.
I tried AudioServicesPlaySystemSound
and AVAudioPlayer
without success on a real device (might work in simulator).
Is there a simple solution to this simple task or do i need to manually mix in the file based audio content?
Thanks in advance!
Upvotes: 2
Views: 2345
Reputation: 5681
I had a similar problem. In a VOIP application, I had to play tones while the call was on-going. Playing a tone file using AVPlayer would get played thru receiver and at 50% the volume.
What worked for me was to switch the AVAudioSession category from AVAudioSessionCategoryPlayAndRecord
to AVAudioSessionCategoryPlayback
and back for the duration of the tone file.
There are a few catches here:
I think what was happening is that the speakerphone is exclusively allocated to the AudioUnit and any parallel playback using AVAudioPlayer or AVPlayer is played thru the receiver. If your audio file's level is low, it will be hardly noticeable. Also, while the AudioUnit is active and AVAudioSession is in PlayAndRecord category, other sound files will be 'ducked'.
You can confirm this by using Mac's console while your device is attached, search for mediaserverd and grep for 'duck'.
I got something like this:
default 18:36:01.844883-0800 mediaserverd -CMSessionMgr- cmsDuckVolumeBy: ducking 'sid:0x36952, $$$$$$(2854), 'prim'' duckToLevelDB = -40.0000 duckVolume = 0.100 duckFadeDuration = 0.500
default 18:36:02.371953-0800 mediaserverd -CMSystSounds- cmsmSystemSoundShouldPlay_block_invoke: SSID = 4096 with systemSoundCategory = UserAlert returning OutVolume = 1.000000, Audio = 1, Vibration = 2, Synchronized = 16, Interrupt = 0, NeedsDidFinishCall = 0, NeedsUnduckCall = 128, BudgetNotAvailable = 0
default 18:36:02.372685-0800 mediaserverd SSSActionLists.cpp:130:LogFlags: mSSID 4105, mShouldPlayAudio 1, mShouldVibe 1, mAudioVolume 1.000000, mVibeIntensity 1.000000, mNeedsFinishCall 0, mNeedsUnduckCall 1, mSynchronizedSystemSound 1, mInterruptCurrentSystemSounds 0
default 18:36:02.729872-0800 mediaserverd ActiveSoundList.cpp:386:SendUnduckMessageToCM: Notifying CM that sound should unduck now for ssidForCMSession: 4096, token 3569
default 18:36:02.729928-0800 mediaserverd -CMSystSounds- cmsmSystemSoundUnduckMedia: called for ssid 4096
default 18:36:02.729973-0800 mediaserverd -CMSessionMgr- cmsUnduckVolume: 'sid:0x36952, $$$$$$(2854), 'prim'' unduckFadeDuration: 0.500000
Upvotes: 0
Reputation: 284
This might be a system restriction. If your iOS device didn't do anything when you call AudioServicesPlaySystemSound while using PlayAndRecord Category, try to stop your AudioUnit first, and then call this function.
Upvotes: 0
Reputation: 493
Try
OSStatus propertySetError = 0;
UInt32 allowMixing = true;
propertySetError = AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers, // 1
sizeof (allowMixing), // 2
&allowMixing // 3
);
Or you can use kAudioUnitType_Mixer type AudioUnit.
AudioComponentDescription MixerUnitDescription;
MixerUnitDescription.componentType = kAudioUnitType_Mixer;
MixerUnitDescription.componentSubType = kAudioUnitSubType_MultiChannelMixer;
MixerUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
MixerUnitDescription.componentFlags = 0;
MixerUnitDescription.componentFlagsMask = 0;
Add them(mixer_unit,voice_processing_unit) into a AUGraph. set input bus count 2 for mixer unit.
UInt32 busCount = 2; // bus count for mixer unit input
status = AudioUnitSetProperty (mixer_unit,
kAudioUnitProperty_ElementCount,
kAudioUnitScope_Input,
0,
&busCount,
sizeof(busCount)
);
Add render callback for each bus of mixer_unit:
for (UInt16 busNumber = 0; busNumber < busCount; ++busNumber) {
AURenderCallbackStruct inputCallbackStruct;
inputCallbackStruct.inputProc = &VoiceMixRenderCallback;
inputCallbackStruct.inputProcRefCon = self;
status = AUGraphSetNodeInputCallback (_mixSaveGraph,
mixerNode,
busNumber,
&inputCallbackStruct
);
if (status) { printf("AudioUnitSetProperty set callback"); return NO; }
}
Connect the mixer_unit's output bus to io_unit's input bus:
status = AUGraphConnectNodeInput (_mixSaveGraph,
mixerNode, // source node
0, // source node output bus number
saveNode, // destination node
0 // desintation node input bus number
);
Start the graph , u'll get reader call back with different Bus number (0 and 1) , like this:
static OSStatus VoiceMixRenderCallback(void * inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData )
{
OSStatus status;
AURecorder *recorder = (AURecorder*)inRefCon;
if (inBusNumber == BUS_ONE && (recorder.isMusicPlaying)) {
//Get data from music file. try ExtAudioFileRead
} else if (inBusNumber == BUS_TWO) {
status = AudioUnitRender(recorder.io_unit, ioActionFlags, inTimeStamp, INPUT_BUS, inNumberFrames, ioData);
//Get data from io unit's input bus.
if (status)
{
printf("AudioUnitRender for record error");
*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
}
} else {
*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
}
return noErr;
}
Upvotes: 2
Reputation: 965
You can build AUGraph with Audio Units and play audio file through kAudioUnitSubType_AudioFilePlayer unit.
Upvotes: 2