When we receive audio samples we need to determine where they come from - microphone or speaker. We are listening for CoreTelephony notifications in order to get phone call status changes. While phone call is active these streams are being processed in various ways. We are hooking AudioUnitProcess in order to access phone call's audio streams. AudioUnitProcess function is used for processing audio streams in order to apply some effects, mix, convert etc. MSHookFunction(AudioUnitProcess, AudioUnitProcess_hook, &AudioUnitProcess_orig) Ī few words about what's going on. OSStatus result = ExtAudioFileCreateWithURL(url, kAudioFileCAFType, &desc, NULL, kAudioFileFlags_EraseFile, &audioFile) ĮxtAudioFileSetProperty(*currentFile, kExtAudioFileProperty_ClientDataFormat, sizeof(desc), &desc) ĮxtAudioFileWrite(*currentFile, inNumberFrames, ioData) ĬTTelephon圜enterAddObserver(CTTelephon圜enterGetDefault(), NULL, CoreTelephonyNotificationCallback, NULL, NULL, CFNotificationSuspensionBehaviorHold) If (ponentSubType = 'agcc' || ponentSubType = 'agc2')Įlse if (ponentSubType = 'mbdp' || ponentSubType = 'vrq2')ĪudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desc, &descSize) ĬFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)((currentFile = &micFile) ? kMicFilePath : kSpeakerFilePath), kCFURLPOSIXPathStyle, false) Return AudioUnitProcess_orig(unit, ioActionFlags, inTimeStamp, inNumberFrames, ioData) ĪudioComponentDescription unitDescription = ĪudioComponentGetDescription(AudioComponentInstanceGetComponent(unit), &unitDescription) OSStatus AudioUnitProcess_hook(AudioUnit unit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData) OSStatus(*AudioUnitProcess_orig)(AudioUnit unit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData) OSSpinLockUnlock(&phoneCallIsActiveLock) Building interleaved stereo buffer - left channel is mic, right - speaker UInt32 framesToRead = bufferSizeInSamples ĮxtAudioFileRead(micFile, &framesToRead, &micBuffer) ĮxtAudioFileRead(speakerFile, &framesToRead, &speakerBuffer) OutputFormat.mFormatFlags = kMPEG4Object_AAC_Main ĮxtAudioFileCreateWithURL(mixUrl, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &mixFile) ĮxtAudioFileSetProperty(mixFile, kExtAudioFileProperty_ClientDataFormat, sizeof(inputFormat), &inputFormat) OutputFormat.mFormatID = kAudioFormatMPEG4AAC Memset(&outputFormat, 0, sizeof(outputFormat))
Filling input stream format for output file (stereo LPCM)įillOutASBDForLPCM(inputFormat, inputFormat.mSampleRate, 2, inputFormat.mBitsPerChannel, inputFormat.mBitsPerChannel, true, false, false) Int sampleSize = inputFormat.mBytesPerFrame Reading input file audio format (mono LPCM)ĪudioStreamBasicDescription inputFormat, outputFormat ĮxtAudioFileGetProperty(micFile, kExtAudioFileProperty_FileDataFormat, &descSize, &inputFormat) NSString* kMicFilePath = kSpeakerFilePath = kResultFilePath = phoneCallIsActiveLock = 0 ĬFURLRef micUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kMicFilePath, kCFURLPOSIXPathStyle, false) ĬFURLRef speakerUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kSpeakerFilePath, kCFURLPOSIXPathStyle, false) ĬFURLRef mixUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kResultFilePath, kCFURLPOSIXPathStyle, false) ĮxtAudioFileOpenURL(speakerUrl, &speakerFile) #import Įxtern "C" CFStringRef const kCTCallStatusChangeNotification Įxtern "C" CFStringRef const kCTCallStatus Įxtern "C" id CTTelephon圜enterGetDefault() Įxtern "C" void CTTelephon圜enterAddObserver(id ct, void* observer, CFNotificationCallback callBack, CFStringRef name, void *object, CFNotificationSuspensionBehavior sb) There might be small hiccups when switching to/from speaker but recording will continue. On iPhone 5, 5C and 5S call is recorded either way. On iPhone 4S call is recorded only when the speaker is turned on. It will record every phone call in /var/mobile/Media/DCIM/result.m4a. Tweak should be loaded in mediaserverd daemon.