una telefónica telefonica puedo puede programa para llamadas llamada gratis grabar entrante cómo conversaciones conversacion con como aplicacion ios iphone audio audio-recording

telefonica - ¿Cómo puedo grabar una conversación/llamada telefónica en iOS?



programa para grabar llamadas en iphone gratis (5)

Apple no lo permite y no proporciona ninguna API para ello.

Sin embargo, en un dispositivo con jailbreak estoy seguro de que es posible. De hecho, creo que ya está hecho. Recuerdo haber visto una aplicación cuando mi teléfono estaba jailbroken que cambió tu voz y grabó la llamada. Recuerdo que era una compañía estadounidense que lo ofrecía solo en Estados Unidos. Lamentablemente, no recuerdo el nombre ...

¿Es teóricamente posible grabar una llamada telefónica en iPhone?

Estoy aceptando respuestas que:

  • puede o no requerir que el teléfono sea jailbroken
  • puede o no aprobar las directrices de apple debido al uso de API privadas (no me importa, no es para la App Store)
  • puede o no usar SDK privados

No quiero respuestas simplemente diciendo "Apple no permite eso". Sé que no habría una forma oficial de hacerlo, y ciertamente no para una aplicación de App Store, y sé que hay aplicaciones de grabación de llamadas que colocan llamadas salientes a través de sus propios servidores.


La única solución que se me ocurre es usar el marco de Core Telephony , y más específicamente la propiedad callEventHandler , para interceptar cuando entra una llamada, y luego usar un AVAudioRecorder para grabar la voz de la persona con el teléfono (y tal vez un poco de la persona en la voz de la otra línea). Obviamente, esto no es perfecto, y solo funcionaría si su aplicación está en primer plano en el momento de la llamada, pero puede ser lo mejor que pueda obtener. Vea más sobre cómo averiguar si hay una llamada entrante aquí: ¿Podemos iniciar un evento cuando haya llamadas entrantes y salientes en el iPhone? .

EDITAR:

.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; }];

Esta es la primera vez que utilizo muchas de estas funciones, por lo que no estoy seguro de si esto es exactamente lo correcto, pero creo que se entiende la idea. No probado, ya que no tengo acceso a las herramientas adecuadas en este momento. Compilado usando estas fuentes:


Sí. Audio Recorder por un desarrollador llamado Limneos hace eso (y bastante bien). Puedes encontrarlo en Cydia. Puede grabar cualquier tipo de llamada en iPhone 5 y posteriores sin usar ningún servidor, etc. '' La llamada se colocará en el dispositivo en un archivo de audio. También es compatible con iPhone 4S pero solo para altavoces.

Se sabe que este ajuste es el primer ajuste que logró grabar ambas transmisiones de audio sin utilizar servidores de terceros, VOIP o algo similar.

El desarrollador colocó señales sonoras al otro lado de la llamada para alertar a la persona que está grabando, pero también fueron eliminadas por hackers a través de la red. Para responder a su pregunta, sí, es muy posible, y no solo teóricamente.

Otras lecturas


Supongo que algún hardware podría resolver esto. Conectado al puerto minijack; tener audífonos y un micrófono que pasa a través de una pequeña grabadora. Esta grabadora puede ser muy simple. Mientras no esté conversando, la grabadora podría alimentar el teléfono con datos / la grabación (a través del cable jack). Y con un simple botón de inicio (al igual que los controles volum en los auriculares) podría ser suficiente para sincronizar la grabación.

Algunas configuraciones


Aqui tienes. Ejemplo de trabajo completo. Tweak debe cargarse en mediaserverd daemon. /var/mobile/Media/DCIM/result.m4a cada llamada telefónica en /var/mobile/Media/DCIM/result.m4a . El archivo de audio tiene dos canales. A la izquierda está el micrófono, a la derecha está el altavoz. En iPhone 4S, la llamada se graba solo cuando el altavoz está encendido. En iPhone 5, las llamadas 5C y 5S se graban de cualquier manera. Puede haber pequeños contratiempos al cambiar a / del altavoz, pero la grabación continuará.

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

Algunas palabras sobre lo que está pasando. AudioUnitProcess función AudioUnitProcess se usa para procesar flujos de audio con el fin de aplicar algunos efectos, mezclar, convertir, etc. Estamos conectando AudioUnitProcess para acceder a las transmisiones de audio de las llamadas telefónicas. Mientras la llamada telefónica está activa, estas transmisiones se están procesando de varias maneras.

Estamos escuchando las notificaciones de CoreTelephony para obtener cambios en el estado de las llamadas telefónicas. Cuando recibimos muestras de audio, necesitamos determinar de dónde provienen: micrófono o altavoz. Esto se hace usando el campo componentSubType en la estructura AudioComponentDescription . Ahora, podría pensar, ¿por qué no almacenamos objetos AudioUnit para que no tengamos que verificar componentSubType todo el tiempo? Lo hice pero romperá todo cuando enciendes / apagas el altavoz en el iPhone 5 porque los objetos AudioUnit cambiarán, se recrearán. Entonces, ahora abrimos archivos de audio (uno para micrófono y uno para altavoz) y escribimos muestras en ellos, así de simple. Cuando finalice la llamada telefónica, recibiremos la notificación CoreTelephony correspondiente y cerraremos los archivos. Tenemos dos archivos separados con audio de micrófono y altavoz que necesitamos fusionar. Esto es para lo que es el void Convert() . Es bastante simple si conoces la API. No creo que deba explicarlo, los comentarios son suficientes.

Sobre bloqueos. Hay muchos hilos en mediaserverd . El procesamiento de audio y las notificaciones de CoreTelephony están en diferentes hilos, por lo que necesitamos alguna sincronización amable. Elegí cerraduras giratorias porque son rápidas y porque la probabilidad de que se produzca un bloqueo es pequeña en nuestro caso. En iPhone 4S e incluso iPhone 5, todo el trabajo en AudioUnitProcess debe hacerse lo más rápido posible, de lo contrario, escuchará hipo del altavoz del dispositivo, lo que obviamente no es bueno.