How can I record a conversation / phone call in iOS?

Is it theoretically possible to record a phone call on an iPhone?

I accept answers that:

  • may or may not require the phone to be hacked.
  • may or may not transmit Apple recommendations due to the use of a private API (I don’t care, this is not for the App Store)
  • may or may not use private SDKs

I don’t want the answers directly saying: "Apple does not allow this." I know that there would be no official way to do this, and, of course, not for an application on the App Store, and I know that there are call recording applications that place outgoing calls through their own servers.

+51
ios iphone audio audio-recording
Nov 27 '09 at 15:25
source share
5 answers

Here you go. Full working example. Tweak must be loaded into the mediaserverd daemon. He will record every phone call in /var/mobile/Media/DCIM/result.m4a . The audio file has two channels. On the left is a microphone, on the right is a speaker. On iPhone 4S, a call is recorded only when the speaker is on. On iPhone 5, 5C and 5S, the call is recorded anyway. There may be slight hiccups when switching to / from the speaker, but recording will continue.

 #import <AudioToolbox/AudioToolbox.h> #import <libkern/OSAtomic.h> //CoreTelephony.framework extern "C" CFStringRef const kCTCallStatusChangeNotification; extern "C" CFStringRef const kCTCallStatus; extern "C" id CTTelephonyCenterGetDefault(); extern "C" void CTTelephonyCenterAddObserver(id ct, void* observer, CFNotificationCallback callBack, CFStringRef name, void *object, CFNotificationSuspensionBehavior sb); extern "C" int CTGetCurrentCallCount(); enum { kCTCallStatusActive = 1, kCTCallStatusHeld = 2, kCTCallStatusOutgoing = 3, kCTCallStatusIncoming = 4, kCTCallStatusHanged = 5 }; NSString* kMicFilePath = @"/var/mobile/Media/DCIM/mic.caf"; NSString* kSpeakerFilePath = @"/var/mobile/Media/DCIM/speaker.caf"; NSString* kResultFilePath = @"/var/mobile/Media/DCIM/result.m4a"; OSSpinLock phoneCallIsActiveLock = 0; OSSpinLock speakerLock = 0; OSSpinLock micLock = 0; ExtAudioFileRef micFile = NULL; ExtAudioFileRef speakerFile = NULL; BOOL phoneCallIsActive = NO; void Convert() { //File URLs CFURLRef micUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kMicFilePath, kCFURLPOSIXPathStyle, false); CFURLRef speakerUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kSpeakerFilePath, kCFURLPOSIXPathStyle, false); CFURLRef mixUrl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)kResultFilePath, kCFURLPOSIXPathStyle, false); ExtAudioFileRef micFile = NULL; ExtAudioFileRef speakerFile = NULL; ExtAudioFileRef mixFile = NULL; //Opening input files (speaker and mic) ExtAudioFileOpenURL(micUrl, &micFile); ExtAudioFileOpenURL(speakerUrl, &speakerFile); //Reading input file audio format (mono LPCM) AudioStreamBasicDescription inputFormat, outputFormat; UInt32 descSize = sizeof(inputFormat); ExtAudioFileGetProperty(micFile, kExtAudioFileProperty_FileDataFormat, &descSize, &inputFormat); int sampleSize = inputFormat.mBytesPerFrame; //Filling input stream format for output file (stereo LPCM) FillOutASBDForLPCM(inputFormat, inputFormat.mSampleRate, 2, inputFormat.mBitsPerChannel, inputFormat.mBitsPerChannel, true, false, false); //Filling output file audio format (AAC) memset(&outputFormat, 0, sizeof(outputFormat)); outputFormat.mFormatID = kAudioFormatMPEG4AAC; outputFormat.mSampleRate = 8000; outputFormat.mFormatFlags = kMPEG4Object_AAC_Main; outputFormat.mChannelsPerFrame = 2; //Opening output file ExtAudioFileCreateWithURL(mixUrl, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &mixFile); ExtAudioFileSetProperty(mixFile, kExtAudioFileProperty_ClientDataFormat, sizeof(inputFormat), &inputFormat); //Freeing URLs CFRelease(micUrl); CFRelease(speakerUrl); CFRelease(mixUrl); //Setting up audio buffers int bufferSizeInSamples = 64 * 1024; AudioBufferList micBuffer; micBuffer.mNumberBuffers = 1; micBuffer.mBuffers[0].mNumberChannels = 1; micBuffer.mBuffers[0].mDataByteSize = sampleSize * bufferSizeInSamples; micBuffer.mBuffers[0].mData = malloc(micBuffer.mBuffers[0].mDataByteSize); AudioBufferList speakerBuffer; speakerBuffer.mNumberBuffers = 1; speakerBuffer.mBuffers[0].mNumberChannels = 1; speakerBuffer.mBuffers[0].mDataByteSize = sampleSize * bufferSizeInSamples; speakerBuffer.mBuffers[0].mData = malloc(speakerBuffer.mBuffers[0].mDataByteSize); AudioBufferList mixBuffer; mixBuffer.mNumberBuffers = 1; mixBuffer.mBuffers[0].mNumberChannels = 2; mixBuffer.mBuffers[0].mDataByteSize = sampleSize * bufferSizeInSamples * 2; mixBuffer.mBuffers[0].mData = malloc(mixBuffer.mBuffers[0].mDataByteSize); //Converting while (true) { //Reading data from input files UInt32 framesToRead = bufferSizeInSamples; ExtAudioFileRead(micFile, &framesToRead, &micBuffer); ExtAudioFileRead(speakerFile, &framesToRead, &speakerBuffer); if (framesToRead == 0) { break; } //Building interleaved stereo buffer - left channel is mic, right - speaker for (int i = 0; i < framesToRead; i++) { memcpy((char*)mixBuffer.mBuffers[0].mData + i * sampleSize * 2, (char*)micBuffer.mBuffers[0].mData + i * sampleSize, sampleSize); memcpy((char*)mixBuffer.mBuffers[0].mData + i * sampleSize * 2 + sampleSize, (char*)speakerBuffer.mBuffers[0].mData + i * sampleSize, sampleSize); } //Writing to output file - LPCM will be converted to AAC ExtAudioFileWrite(mixFile, framesToRead, &mixBuffer); } //Closing files ExtAudioFileDispose(micFile); ExtAudioFileDispose(speakerFile); ExtAudioFileDispose(mixFile); //Freeing audio buffers free(micBuffer.mBuffers[0].mData); free(speakerBuffer.mBuffers[0].mData); free(mixBuffer.mBuffers[0].mData); } void Cleanup() { [[NSFileManager defaultManager] removeItemAtPath:kMicFilePath error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:kSpeakerFilePath error:NULL]; } void CoreTelephonyNotificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { NSDictionary* data = (NSDictionary*)userInfo; if ([(NSString*)name isEqualToString:(NSString*)kCTCallStatusChangeNotification]) { int currentCallStatus = [data[(NSString*)kCTCallStatus] integerValue]; if (currentCallStatus == kCTCallStatusActive) { OSSpinLockLock(&phoneCallIsActiveLock); phoneCallIsActive = YES; OSSpinLockUnlock(&phoneCallIsActiveLock); } else if (currentCallStatus == kCTCallStatusHanged) { if (CTGetCurrentCallCount() > 0) { return; } OSSpinLockLock(&phoneCallIsActiveLock); phoneCallIsActive = NO; OSSpinLockUnlock(&phoneCallIsActiveLock); //Closing mic file OSSpinLockLock(&micLock); if (micFile != NULL) { ExtAudioFileDispose(micFile); } micFile = NULL; OSSpinLockUnlock(&micLock); //Closing speaker file OSSpinLockLock(&speakerLock); if (speakerFile != NULL) { ExtAudioFileDispose(speakerFile); } speakerFile = NULL; OSSpinLockUnlock(&speakerLock); Convert(); Cleanup(); } } } OSStatus(*AudioUnitProcess_orig)(AudioUnit unit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData); OSStatus AudioUnitProcess_hook(AudioUnit unit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inNumberFrames, AudioBufferList *ioData) { OSSpinLockLock(&phoneCallIsActiveLock); if (phoneCallIsActive == NO) { OSSpinLockUnlock(&phoneCallIsActiveLock); return AudioUnitProcess_orig(unit, ioActionFlags, inTimeStamp, inNumberFrames, ioData); } OSSpinLockUnlock(&phoneCallIsActiveLock); ExtAudioFileRef* currentFile = NULL; OSSpinLock* currentLock = NULL; AudioComponentDescription unitDescription = {0}; AudioComponentGetDescription(AudioComponentInstanceGetComponent(unit), &unitDescription); //'agcc', 'mbdp' - iPhone 4S, iPhone 5 //'agc2', 'vrq2' - iPhone 5C, iPhone 5S if (unitDescription.componentSubType == 'agcc' || unitDescription.componentSubType == 'agc2') { currentFile = &micFile; currentLock = &micLock; } else if (unitDescription.componentSubType == 'mbdp' || unitDescription.componentSubType == 'vrq2') { currentFile = &speakerFile; currentLock = &speakerLock; } if (currentFile != NULL) { OSSpinLockLock(currentLock); //Opening file if (*currentFile == NULL) { //Obtaining input audio format AudioStreamBasicDescription desc; UInt32 descSize = sizeof(desc); AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desc, &descSize); //Opening audio file CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)((currentFile == &micFile) ? kMicFilePath : kSpeakerFilePath), kCFURLPOSIXPathStyle, false); ExtAudioFileRef audioFile = NULL; OSStatus result = ExtAudioFileCreateWithURL(url, kAudioFileCAFType, &desc, NULL, kAudioFileFlags_EraseFile, &audioFile); if (result != 0) { *currentFile = NULL; } else { *currentFile = audioFile; //Writing audio format ExtAudioFileSetProperty(*currentFile, kExtAudioFileProperty_ClientDataFormat, sizeof(desc), &desc); } CFRelease(url); } else { //Writing audio buffer ExtAudioFileWrite(*currentFile, inNumberFrames, ioData); } OSSpinLockUnlock(currentLock); } return AudioUnitProcess_orig(unit, ioActionFlags, inTimeStamp, inNumberFrames, ioData); } __attribute__((constructor)) static void initialize() { CTTelephonyCenterAddObserver(CTTelephonyCenterGetDefault(), NULL, CoreTelephonyNotificationCallback, NULL, NULL, CFNotificationSuspensionBehaviorHold); MSHookFunction(AudioUnitProcess, AudioUnitProcess_hook, &AudioUnitProcess_orig); } 

A few words about what is happening. The AudioUnitProcess function AudioUnitProcess used to process audio streams, to apply some effects, mix, convert, etc. We connect AudioUnitProcess to access the audio streams of phone calls. While a phone call is active, these flows are handled in various ways.

We listen to CoreTelephony notifications to get phone status changes. When we get sound samples, we need to determine where they came from - a microphone or speaker. This is done using the componentSubType field in the AudioComponentDescription structure. Now you might think why we do not store AudioUnit objects, so we do not need to check componentSubType every time. I did this, but it will break everything when you turn the speaker on / off on the iPhone 5, because the AudioUnit objects change, they are recreated. So, now we open the audio files (one for the microphone and one for the speakers) and record the samples in them, just like that. Upon completion of the telephone call, we will receive a corresponding notification to CoreTelephony and close the files. We have two separate audio files from the microphone and speaker, which we need to combine. To do this, use void Convert() . This is pretty simple if you know the API. I don’t think I need to explain this, there are enough comments.

About castles. There are many threads in mediaserverd . The sound processing and notifications of CoreTelephony belong to different streams, so we need some synchronization. I chose spin-locks because they are fast, and because the probability of blocking in our case is small. On iPhone 4S and even iPhone 5, all work in AudioUnitProcess should be done as quickly as possible, otherwise you will hear hiccups from the device’s speaker, which is clearly not good.

+61
Feb 05 '14 at 7:34
source share

Yes. Developer Audio Recorder by Limneos does this (and pretty well). You can find it on Cydia. It can record any type of call on iPhone 5 without using any servers, etc. ". The call will be placed on the device in an audio file. It also supports iPhone 4S, but only for speakers.

This setting is known as the first setting, which allows you to record both streams of sound without using any third-party dividers, VOIP, or anything like that.

The developer beeped on the other side of the call to alert the person you are recording, but also removed by hackers over the network. To answer your question, yes, it is very possible, and not just theoretically.

enter image description here

Further reading

+10
Feb 02 '14 at 21:37
source share

The only solution I can think of is to use Core Telephony , or rather callEventHandler , to intercept when a call arrives, and then use AVAudioRecorder to record the voice of the person with the phone (and maybe a little person to another voice). This is obviously not perfect, and will only work if your application is in the foreground during a call, but it may be the best you can get. Find out more about whether there is an incoming phone call here: Can we trigger an event when there is an incoming and outgoing call on the iphone? .

EDIT:

.h:

 #import <AVFoundation/AVFoundation.h> #import<CoreTelephony/CTCallCenter.h> #import<CoreTelephony/CTCall.h> @property (strong, nonatomic) AVAudioRecorder *audioRecorder; 

ViewDidLoad:

 NSArray *dirPaths; NSString *docsDir; dirPaths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES); docsDir = dirPaths[0]; NSString *soundFilePath = [docsDir stringByAppendingPathComponent:@"sound.caf"]; NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath]; NSDictionary *recordSettings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:AVAudioQualityMin], AVEncoderAudioQualityKey, [NSNumber numberWithInt:16], AVEncoderBitRateKey, [NSNumber numberWithInt: 2], AVNumberOfChannelsKey, [NSNumber numberWithFloat:44100.0], AVSampleRateKey, nil]; NSError *error = nil; _audioRecorder = [[AVAudioRecorder alloc] initWithURL:soundFileURL settings:recordSettings error:&error]; if (error) { NSLog(@"error: %@", [error localizedDescription]); } else { [_audioRecorder prepareToRecord]; } CTCallCenter *callCenter = [[CTCallCenter alloc] init]; [callCenter setCallEventHandler:^(CTCall *call) { if ([[call callState] isEqual:CTCallStateConnected]) { [_audioRecorder record]; } else if ([[call callState] isEqual:CTCallStateDisconnected]) { [_audioRecorder stop]; } }]; 

AppDelegate.m:

 - (void)applicationDidEnterBackground:(UIApplication *)application//Makes sure that the recording keeps happening even when app is in the background, though only can go for 10 minutes. { __block UIBackgroundTaskIdentifier task = 0; task=[application beginBackgroundTaskWithExpirationHandler:^{ NSLog(@"Expiration handler called %f",[application backgroundTimeRemaining]); [application endBackgroundTask:task]; task=UIBackgroundTaskInvalid; }]; 

This is the first time you use many of these features, so I'm not sure if this is accurate, but I think you understand this idea. Untested, since I do not have access to the right tools at the moment. Compiled using these sources:

+7
Jan 28 '14 at 18:32
source share

I guess some hardware might solve this problem. Connected to the mini-port; with headphones and a microphone passing through a small recorder. This recorder can be very simple. At a time when he was not talking, the recorder could give the phone data / recording (via a cable connector). And with a simple start button (just like controlling the volume on the headphones) may be enough to synchronize recording.

Some settings

+2
Jan 28 '14 at 19:27
source share

Apple does not allow this and does not provide any API for it.

However, on a jailbreak device, I am sure that this is possible. In fact, I think this has already been done. I remember seeing the application when my phone was hacked, which changed your voice and recorded the call. I remember that it was an American company offering it only in the states. Unfortunately, I do not remember the name ...

+1
Nov 27 '09 at 15:33
source share



All Articles