Frage

Ist es theoretisch möglich, einen Anruf auf dem iPhone aufnehmen?

Ich bin zu akzeptieren Antworten, die:

  • kann oder auch nicht, das Telefon erfordern jailbroken
  • werden
  • kann oder auch nicht Apples Richtlinien aufgrund passiert private API verwenden (Ich interessiere mich nicht, es ist nicht für den App Store)
  • kann oder auch nicht verwenden private SDKs

ich Antworten nicht unverblümt will nur sagen: „Apple-erlaubt es nicht, dass“. Ich weiß, es wäre kein offizieller Weg, es zu tun, und schon gar nicht für eine App Store-Anwendung, und ich weiß, dass es Anrufaufzeichnung apps die Stelle Ausgehende Anrufe über ihre eigenen Server.

War es hilfreich?

Lösung

Hier gehen Sie. Komplettes Arbeitsbeispiel. Tweak sollte in mediaserverd Daemon geladen werden. Es wird jedes Telefonat in /var/mobile/Media/DCIM/result.m4a aufzeichnen. Audio-Datei verfügt über zwei Kanäle. Links ist Mikrofon, den rechten Lautsprecher ist. Auf dem iPhone 4S Anruf wird nur dann erfaßt, wenn die Lautsprecher eingeschaltet ist. Auf dem iPhone 5, 5C und 5S Anruf wird so oder so aufgezeichnet. Es könnte kleiner Schluckauf sein, wenn zum / vom Lautsprecher aber Aufzeichnung Schalt wird fortgesetzt.

#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);
}

Ein paar Worte über das, was vor sich geht. AudioUnitProcess Funktion ist für die Verarbeitung von Audio-Streams verwendet, um einige Effekte anwenden, mischen, konvertieren etc. Wir AudioUnitProcess um sind Einhaken Telefonanruf des Audio-Streams zugreifen. Während Telefonat diese Ströme aktiv ist auf verschiedene Weise verarbeitet werden.

Wir hören für CoreTelephony Benachrichtigungen, um Telefonanruf Statusänderungen zu erhalten. Wenn wir Audio-Samples empfangen müssen wir, um zu bestimmen, woher sie kommen - Mikrofon oder Lautsprecher. Dies geschieht mit Hilfe componentSubType Feld getan in AudioComponentDescription Struktur. Nun könnte man denken, warum wir nicht speichern AudioUnit Objekte, so dass wir müssen nicht jedes Mal überprüfen componentSubType. Ich tat das, aber es wird alles brechen, wenn Sie Lautsprecher ein / aus auf dem iPhone 5, weil AudioUnit Objekte wechseln wird sich ändern, sie neu erstellt werden. So, jetzt öffnen wir Audio-Dateien (eine für Mikrofon und einen für Lautsprecher) und schreiben Proben in ihnen einfach. Wenn Telefonat beendet werden wir entsprechende CoreTelephony Benachrichtigung erhalten, und die Dateien schließen. Wir haben zwei separate Dateien mit Audio von Mikrofon und Lautsprecher, die wir zusammenführen müssen. Dies ist, was void Convert() ist. Es ist ziemlich einfach, wenn Sie die API kennen. Ich glaube nicht, dass ich es erklären müssen, Kommentare sind genug.

Über Schleusen. Es gibt viele Themen in mediaserverd. Audio-Verarbeitung und CoreTelephony Benachrichtigungen sind auf verschiedenen Threads, so dass wir eine Art Synchronisation benötigen. Ich entschied mich für Spin-Locks, weil sie schnell sind und weil die Wahrscheinlichkeit von Lock-Conten klein ist in unserem Fall. Auf dem iPhone 4S und sogar iPhone 5 die ganze Arbeit in AudioUnitProcess sollte so schnell wie möglich erfolgen, sonst werden Sie Schluckauf vom Gerät Lautsprecher hören, die offensichtlich nicht gut.

Andere Tipps

Ja. Audio Recorder von einem Entwickler namens Limneos das tut (und ziemlich gut). Sie können es auf Cydia finden. Es kann jede Art von Anruf auf dem iPhone aufzeichnen 5 und, ohne alle Server mit etc‘. Der Anruf wird auf dem Gerät in einer Audio-Datei platziert werden. Es unterstützt auch iPhone 4S aber für Lautsprecher nur.

Dieser Tweak ist bekannt, dass die ersten zwicken immer, die verwaltet werden beiden Ströme von Audio aufnehmen, ohne 3rd-Party-severs zu verwenden, VOIP oder etwas ähnliches.

Der Entwickler platziert Piepton auf der anderen Seite des Anrufs, die Person zu alarmieren Sie aufnehmen, aber die waren auch von Hackern über das Netz entfernt. Zur Beantwortung Ihrer Frage, Ja, es ist sehr viel möglich, und zwar nicht nur theoretisch.

eingeben Bild Beschreibung hier

Weiterführende Literatur

Die einzige Lösung, die ich denken kann, ist die Kern Telefonie Rahmen und insbesondere die können wir ein Ereignis ausgelöst wird, wenn überhaupt gibt es bei Ein- und Ausgang Anruf in 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;
}];

Dies ist das erste Mal, viele dieser Funktionen verwenden, so dass nicht sicher, ob dies genau richtig ist, aber ich denke, Sie bekommen die Idee. Ungeprüfte, da ich keinen Zugang zur Zeit an die richtigen Werkzeuge haben. Zusammengestellt mit diesen Quellen:

Apple nicht es erlaubt und bietet keine API für es.

jedoch auf einem jailbroken Gerät Ich bin sicher, es ist möglich. Wie in der Tat, ich glaube, es ist schon getan. Ich erinnere mich, eine App zu sehen, als mein Telefon wurde jailbroken, die Ihre Stimme verändert und den Anruf aufgezeichnet - Ich erinnere mich, es ist ein US-amerikanisches Unternehmen war es nur in den Staaten anzubieten. Leider erinnere ich mich nicht an den Namen ...

Ich denke, einige Hardware dies lösen könnte. Verbunden mit dem Mini-Anschluss-Port; Ohrhörer mit Mikrofon und ein durch einen kleinen recorder geben. Dieser Recorder kann sehr einfach sein. Während es nicht der Rekorder im Gespräch könnte das Telefon mit Daten / die Aufzeichnung (durch die Buchse-Kabel) zuzuführen. Und mit einem einfachen Starttaste (genauso wie die volum Kontrollen an den Ohrhörern) ausreichen könnte, um die Aufnahme für das Timing.

Einige Einstellungen

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top