2012-05-08 17 views
5

Necesito transmitir audio desde el micrófono a un servidor http.
Estos ajustes de grabación son lo que necesito:stream media FROM iphone

NSDictionary *audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
              [NSNumber numberWithInt: kAudioFormatULaw],AVFormatIDKey,   
              [NSNumber numberWithFloat:8000.0],AVSampleRateKey,//was 44100.0 
              [NSData dataWithBytes: &acl length: sizeof(AudioChannelLayout) ], AVChannelLayoutKey, 
              [NSNumber numberWithInt:1],AVNumberOfChannelsKey, 
              [NSNumber numberWithInt:64000],AVEncoderBitRateKey, 
              nil]; 

API im codificación de estados:

enviar un flujo continuo de audio a la cámara está viendo actualmente. El audio debe codificarse en mu-ley G711 a 64 kbit/s para transferirlo a la cámara Axis junto a la cama. enviar (esto debe ser una dirección URL POST en SSL al servidor conectado): Enviar/transmitaudio id = Content-type: audio/básica Content-Length: 99999 (longitud se ignora)

continuación se presentan una lista de enlaces con los que he intentado trabajar

LINK - (SO) explicación básica de que solo la unidad de audio y las colas de audio permitirán nsdata como salida cuando se graba a través del micrófono | no es un ejemplo sino una buena definición de lo que se necesita (colas de audio o unidades de audio)

LINK - (SO) ejemplo de devolución de audio | solo incluye la devolución de llamada

LINK - (SO) Ejemplo REMOTE IO | no tiene inicio/detención y es para guardar en un archivo

LINK - (SO) Ejemplo REMOTE IO | sin respuesta no funciona

LINK - (SO) Ejemplo de grabación de audio básico | buen ejemplo pero los registros para presentar

LINK - (SO) Pregunta que me guió a la clase InMemoryAudioFile (no podía conseguir trabajo) | seguido de enlaces a inMemoryFile (o algo así) pero no pudo hacerlo funcionar.

LINK - (SO) más unidad de audio y ejemplo/problemas remotos io | funcionó, pero una vez más no hay una función de parada, e incluso cuando traté de averiguar cuál es la llamada y la detuve, no pareció transmitir el audio al servidor.

LINK - Decente remoteIO y ejemplo de cola de audio pero | Otro buen ejemplo y casi funciona, pero tenía algunos problemas con el código (el compilador no es obj-C++) y una vez más, no sé cómo obtener "datos" de audio en lugar de un archivo.

LINK - Apple docs for audio queue | tuvo problemas con los marcos. lo resolvió (vea la pregunta a continuación) pero al final no pudo hacerlo funcionar, sin embargo, probablemente no le dio a este tanto el tiempo que los demás, y tal vez debería tener.

LINK - (SO) problemas que tuve al intentar implementar la cola/unidad de audio | no es un ejemplo

LINK - (SO) otro ejemplo de remoteIO | otro buen ejemplo pero no puedo encontrar la manera de obtenerlo en lugar de archivos.

LINK - también parece interesante, buffers circulares | no pudo encontrar la manera de incorporar esto con la devolución de llamada de audio

Aquí está mi clase actual intentando transmitir. Parece que funciona, aunque sale estática de los altavoces en el extremo del receptor (conectado al servidor). Lo cual parece indicar un problema con el formato de datos de audio.

IOS Version (métodos de delegado para la toma menos GCD):

@implementation MicCommunicator { 
AVAssetWriter * assetWriter; 
AVAssetWriterInput * assetWriterInput; 
} 

@synthesize captureSession = _captureSession; 
@synthesize output = _output; 
@synthesize restClient = _restClient; 
@synthesize uploadAudio = _uploadAudio; 
@synthesize outputPath = _outputPath; 
@synthesize sendStream = _sendStream; 
@synthesize receiveStream = _receiveStream; 

@synthesize socket = _socket; 
@synthesize isSocketConnected = _isSocketConnected; 

-(id)init { 
    if ((self = [super init])) { 

     _receiveStream = [[NSStream alloc]init]; 
     _sendStream = [[NSStream alloc]init]; 
     _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 
     _isSocketConnected = FALSE; 

     _restClient = [RestClient sharedManager]; 
     _uploadAudio = false; 

     NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
     _outputPath = [NSURL fileURLWithPath:[[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"micOutput.output"]]; 

     NSError * assetError; 

     AudioChannelLayout acl; 
     bzero(&acl, sizeof(acl)); 
     acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; //kAudioChannelLayoutTag_Stereo; 
     NSDictionary *audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
              [NSNumber numberWithInt: kAudioFormatULaw],AVFormatIDKey,   
              [NSNumber numberWithFloat:8000.0],AVSampleRateKey,//was 44100.0 
              [NSData dataWithBytes: &acl length: sizeof(AudioChannelLayout) ], AVChannelLayoutKey, 
              [NSNumber numberWithInt:1],AVNumberOfChannelsKey, 
              [NSNumber numberWithInt:64000],AVEncoderBitRateKey, 
              nil]; 

     assetWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioOutputSettings]retain]; 
     [assetWriterInput setExpectsMediaDataInRealTime:YES]; 

     assetWriter = [[AVAssetWriter assetWriterWithURL:_outputPath fileType:AVFileTypeWAVE error:&assetError]retain]; //AVFileTypeAppleM4A 

     if (assetError) { 
      NSLog (@"error initing mic: %@", assetError); 
      return nil; 
     } 
     if ([assetWriter canAddInput:assetWriterInput]) { 
      [assetWriter addInput:assetWriterInput]; 
     } else { 
      NSLog (@"can't add asset writer input...!"); 
      return nil; 
     } 

    } 
    return self; 
} 

-(void)dealloc { 
    [_output release]; 
    [_captureSession release]; 
    [_captureSession release]; 
    [assetWriter release]; 
    [assetWriterInput release]; 
    [super dealloc]; 
} 


-(void)beginStreaming { 

    NSLog(@"avassetwrter class is %@",NSStringFromClass([assetWriter class])); 

    self.captureSession = [[AVCaptureSession alloc] init]; 
    AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; 
    NSError *error = nil; 
    AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error]; 
    if (audioInput) 
     [self.captureSession addInput:audioInput]; 
    else { 
     NSLog(@"No audio input found."); 
     return; 
    } 

    self.output = [[AVCaptureAudioDataOutput alloc] init]; 

    dispatch_queue_t outputQueue = dispatch_queue_create("micOutputDispatchQueue", NULL); 
    [self.output setSampleBufferDelegate:self queue:outputQueue]; 
    dispatch_release(outputQueue); 

    self.uploadAudio = FALSE; 

    [self.captureSession addOutput:self.output]; 
    [assetWriter startWriting]; 
    [self.captureSession startRunning]; 
} 

-(void)pauseStreaming 
{ 
    self.uploadAudio = FALSE; 
} 

-(void)resumeStreaming 
{ 
    self.uploadAudio = TRUE; 
} 

-(void)finishAudioWork 
{ 
    [self dealloc]; 
} 

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 


    AudioBufferList audioBufferList; 
    NSMutableData *data= [[NSMutableData alloc] init]; 
    CMBlockBufferRef blockBuffer; 
    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer); 

    for (int y = 0; y < audioBufferList.mNumberBuffers; y++) { 
     AudioBuffer audioBuffer = audioBufferList.mBuffers[y]; 
     Float32 *frame = (Float32*)audioBuffer.mData; 

     [data appendBytes:frame length:audioBuffer.mDataByteSize]; 
    } 

    // append [data bytes] to your NSOutputStream 

    // These two lines write to disk, you may not need this, just providing an example 
    [assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)]; 
    [assetWriterInput appendSampleBuffer:sampleBuffer]; 

    //start upload audio data 
    if (self.uploadAudio) { 

     if (!self.isSocketConnected) { 
      [self connect]; 
     } 
      NSString *requestStr = [NSString stringWithFormat:@"POST /transmitaudio?id=%@ HTTP/1.0\r\n\r\n",self.restClient.sessionId]; 

      NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding];   
     [self.socket writeData:requestData withTimeout:5 tag:0];  
     [self.socket writeData:data withTimeout:5 tag:0]; 
    } 
    //stop upload audio data 

    CFRelease(blockBuffer); 
    blockBuffer=NULL; 
    [data release]; 
} 

Y la versión de JAVA:

import java.io.BufferedInputStream; 
import java.io.BufferedOutputStream; 
import java.io.BufferedReader; 
import java.io.DataInputStream; 
import java.io.DataOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.io.PrintWriter; 
import java.nio.ByteBuffer; 
import java.nio.ByteOrder; 
import java.util.Arrays; 

import javax.net.ssl.SSLContext; 
import javax.net.ssl.SSLSocket; 
import javax.net.ssl.SSLSocketFactory; 
import javax.net.ssl.TrustManager; 
import javax.net.ssl.X509TrustManager; 

import android.media.AudioFormat; 
import android.media.AudioManager; 
import android.media.AudioRecord; 
import android.media.AudioTrack; 
import android.media.MediaRecorder.AudioSource; 
import android.util.Log; 

public class AudioWorker extends Thread 
{ 
    private boolean stopped = false; 

    private String host; 
    private int port; 
    private long id=0; 
    boolean run=true; 
    AudioRecord recorder; 

    //ulaw encoder stuff 
    private final static String TAG = "UlawEncoderInputStream"; 

    private final static int MAX_ULAW = 8192; 
    private final static int SCALE_BITS = 16; 

    private InputStream mIn; 

    private int mMax = 0; 

    private final byte[] mBuf = new byte[1024]; 
    private int mBufCount = 0; // should be 0 or 1 

    private final byte[] mOneByte = new byte[1]; 
    //// 
    /** 
    * Give the thread high priority so that it's not canceled unexpectedly, and start it 
    */ 
    public AudioWorker(String host, int port, long id) 
    { 
     this.host = host; 
     this.port = port; 
     this.id = id; 
     android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 
//  start(); 
    } 

    @Override 
    public void run() 
    { 
     Log.i("AudioWorker", "Running AudioWorker Thread"); 
     recorder = null; 
     AudioTrack track = null; 
     short[][] buffers = new short[256][160]; 
     int ix = 0; 

     /* 
     * Initialize buffer to hold continuously recorded AudioWorker data, start recording, and start 
     * playback. 
     */ 
     try 
     { 
      int N = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT); 
      recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, N*10); 
      track = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, N*10, AudioTrack.MODE_STREAM); 
      recorder.startRecording(); 
//   track.play(); 
      /* 
      * Loops until something outside of this thread stops it. 
      * Reads the data from the recorder and writes it to the AudioWorker track for playback. 
      */ 


      SSLContext sc = SSLContext.getInstance("SSL"); 
      sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
      SSLSocketFactory sslFact = sc.getSocketFactory(); 
      SSLSocket socket = (SSLSocket)sslFact.createSocket(host, port); 

      socket.setSoTimeout(10000); 
      InputStream inputStream = socket.getInputStream(); 
      DataInputStream in = new DataInputStream(new BufferedInputStream(inputStream)); 
      OutputStream outputStream = socket.getOutputStream(); 
      DataOutputStream os = new DataOutputStream(new BufferedOutputStream(outputStream)); 
      PrintWriter socketPrinter = new PrintWriter(os); 
      BufferedReader br = new BufferedReader(new InputStreamReader(in)); 

//   socketPrinter.println("POST /transmitaudio?patient=1333369798370 HTTP/1.0"); 
      socketPrinter.println("POST /transmitaudio?id="+id+" HTTP/1.0"); 
      socketPrinter.println("Content-Type: audio/basic"); 
      socketPrinter.println("Content-Length: 99999"); 
      socketPrinter.println("Connection: Keep-Alive"); 
      socketPrinter.println("Cache-Control: no-cache"); 
      socketPrinter.println(); 
      socketPrinter.flush(); 


      while(!stopped) 
      { 
       Log.i("Map", "Writing new data to buffer"); 
       short[] buffer = buffers[ix++ % buffers.length]; 

       N = recorder.read(buffer,0,buffer.length); 
       track.write(buffer, 0, buffer.length); 

       byte[] bytes2 = new byte[buffer.length * 2]; 
       ByteBuffer.wrap(bytes2).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(buffer); 

       read(bytes2, 0, bytes2.length); 
       os.write(bytes2,0,bytes2.length); 

// 
//    ByteBuffer byteBuf = ByteBuffer.allocate(2*N); 
//    System.out.println("byteBuf length "+2*N); 
//    int i = 0; 
//    while (buffer.length > i) { 
//     byteBuf.putShort(buffer[i]); 
//     i++; 
//    }   
//    byte[] b = new byte[byteBuf.remaining()]; 
      } 
      os.close(); 
     } 
     catch(Throwable x) 
     { 
      Log.w("AudioWorker", "Error reading voice AudioWorker", x); 
     } 
     /* 
     * Frees the thread's resources after the loop completes so that it can be run again 
     */ 
     finally 
     { 
      recorder.stop(); 
      recorder.release(); 
      track.stop(); 
      track.release(); 
     } 
    } 

    /** 
    * Called from outside of the thread in order to stop the recording/playback loop 
    */ 
    public void close() 
    { 
     stopped = true; 
    } 
    public void resumeThread() 
    { 
     stopped = false; 
     run(); 
    } 

    TrustManager[] trustAllCerts = new TrustManager[]{ 
      new X509TrustManager() { 
       public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
        return null; 
       } 
       public void checkClientTrusted(
         java.security.cert.X509Certificate[] certs, String authType) { 
       } 
       public void checkServerTrusted(
         java.security.cert.X509Certificate[] chain, String authType) { 
        for (int j=0; j<chain.length; j++) 
        { 
         System.out.println("Client certificate information:"); 
         System.out.println(" Subject DN: " + chain[j].getSubjectDN()); 
         System.out.println(" Issuer DN: " + chain[j].getIssuerDN()); 
         System.out.println(" Serial number: " + chain[j].getSerialNumber()); 
         System.out.println(""); 
        } 
       } 
      } 
    }; 


    public static void encode(byte[] pcmBuf, int pcmOffset, 
      byte[] ulawBuf, int ulawOffset, int length, int max) { 

     // from 'ulaw' in wikipedia 
     // +8191 to +8159       0x80 
     // +8158 to +4063 in 16 intervals of 256 0x80 + interval number 
     // +4062 to +2015 in 16 intervals of 128 0x90 + interval number 
     // +2014 to +991 in 16 intervals of 64 0xA0 + interval number 
     // +990 to +479 in 16 intervals of 32 0xB0 + interval number 
     // +478 to +223 in 16 intervals of 16 0xC0 + interval number 
     // +222 to +95 in 16 intervals of 8 0xD0 + interval number 
     // +94 to +31 in 16 intervals of 4 0xE0 + interval number 
     // +30 to +1 in 15 intervals of 2 0xF0 + interval number 
     //  0         0xFF 

     // -1         0x7F 
     // -31 to -2 in 15 intervals of 2 0x70 + interval number 
     // -95 to -32 in 16 intervals of 4 0x60 + interval number 
     // -223 to -96 in 16 intervals of 8 0x50 + interval number 
     // -479 to -224 in 16 intervals of 16 0x40 + interval number 
     // -991 to -480 in 16 intervals of 32 0x30 + interval number 
     // -2015 to -992 in 16 intervals of 64 0x20 + interval number 
     // -4063 to -2016 in 16 intervals of 128 0x10 + interval number 
     // -8159 to -4064 in 16 intervals of 256 0x00 + interval number 
     // -8192 to -8160       0x00 

     // set scale factors 
     if (max <= 0) max = MAX_ULAW; 

     int coef = MAX_ULAW * (1 << SCALE_BITS)/max; 

     for (int i = 0; i < length; i++) { 
      int pcm = (0xff & pcmBuf[pcmOffset++]) + (pcmBuf[pcmOffset++] << 8); 
      pcm = (pcm * coef) >> SCALE_BITS; 

      int ulaw; 
      if (pcm >= 0) { 
       ulaw = pcm <= 0 ? 0xff : 
         pcm <= 30 ? 0xf0 + (( 30 - pcm) >> 1) : 
         pcm <= 94 ? 0xe0 + (( 94 - pcm) >> 2) : 
         pcm <= 222 ? 0xd0 + ((222 - pcm) >> 3) : 
         pcm <= 478 ? 0xc0 + ((478 - pcm) >> 4) : 
         pcm <= 990 ? 0xb0 + ((990 - pcm) >> 5) : 
         pcm <= 2014 ? 0xa0 + ((2014 - pcm) >> 6) : 
         pcm <= 4062 ? 0x90 + ((4062 - pcm) >> 7) : 
         pcm <= 8158 ? 0x80 + ((8158 - pcm) >> 8) : 
         0x80; 
      } else { 
       ulaw = -1 <= pcm ? 0x7f : 
          -31 <= pcm ? 0x70 + ((pcm - -31) >> 1) : 
          -95 <= pcm ? 0x60 + ((pcm - -95) >> 2) : 
         -223 <= pcm ? 0x50 + ((pcm - -223) >> 3) : 
         -479 <= pcm ? 0x40 + ((pcm - -479) >> 4) : 
         -991 <= pcm ? 0x30 + ((pcm - -991) >> 5) : 
         -2015 <= pcm ? 0x20 + ((pcm - -2015) >> 6) : 
         -4063 <= pcm ? 0x10 + ((pcm - -4063) >> 7) : 
         -8159 <= pcm ? 0x00 + ((pcm - -8159) >> 8) : 
         0x00; 
      } 
      ulawBuf[ulawOffset++] = (byte)ulaw; 
     } 
    } 
    public static int maxAbsPcm(byte[] pcmBuf, int offset, int length) { 
     int max = 0; 
     for (int i = 0; i < length; i++) { 
      int pcm = (0xff & pcmBuf[offset++]) + (pcmBuf[offset++] << 8); 
      if (pcm < 0) pcm = -pcm; 
      if (pcm > max) max = pcm; 
     } 
     return max; 
    } 

    public int read(byte[] buf, int offset, int length) throws IOException { 
     if (recorder == null) throw new IllegalStateException("not open"); 

     // return at least one byte, but try to fill 'length' 
     while (mBufCount < 2) { 
      int n = recorder.read(mBuf, mBufCount, Math.min(length * 2, mBuf.length - mBufCount)); 
      if (n == -1) return -1; 
      mBufCount += n; 
     } 

     // compand data 
     int n = Math.min(mBufCount/2, length); 
     encode(mBuf, 0, buf, offset, n, mMax); 

     // move data to bottom of mBuf 
     mBufCount -= n * 2; 
     for (int i = 0; i < mBufCount; i++) mBuf[i] = mBuf[i + n * 2]; 

     return n; 
    } 

} 

Respuesta

3

Mi trabajo sobre este tema ha sido asombroso y largo. Finalmente conseguí que funcionara, por más pirateado que fuera. Debido a que voy a enumerar algunas advertencias antes de la publicación de la respuesta:

  1. Todavía hay un ruido entre los tampones

  2. consigo advertencias debido a la forma en que uso mis clases obj-c en el obj-clase de C++, así que hay algo mal allí (sin embargo de mi investigación con una piscina hace lo mismo que la liberación, así que no creo que esto es importante para mucho):

    objeto 0x13cd20 de __NSCFString clase autoreleased sin piscina lugar - solo tiene fugas - rompe en objc _autoreleaseNoPool() para depurar

  3. Con el fin de conseguir este trabajo que tenía que comentar todas las referencias AQPlayer de SpeakHereController (véase más adelante) debido a errores no pude arreglar de otra manera. No nos importó para mí, sin embargo ya que sólo estoy grabando

Así que la principal respuesta a lo anterior es que hay un error en AVAssetWriter que lo detuvo añadiendo de los bytes y escritura de los datos de audio. Finalmente descubrí esto después de contactar al soporte de Apple y hacer que me notifiquen sobre esto. Hasta donde sé, el error es específico de ulaw y AVAssetWriter, aunque no he probado muchos otros formatos para verificar.
En respuesta a esto, la única otra opción es/era usar AudioQueues. Algo que había intentado antes, pero que me había traído un montón de problemas. El mayor problema es mi falta de conocimiento en obj-C++. La siguiente clase que hace que las cosas funcionen es del ejemplo speakHere con ligeros cambios para que el audio tenga un formato ulaw. Los otros problemas surgieron al intentar que todos los archivos funcionaran correctamente. Sin embargo, esto se solucionó fácilmente cambiando todos los nombres de archivo de la cadena a .mm. El siguiente problema fue tratar de usar las clases en armonía. Esto sigue siendo un WIP, y se relaciona con el número de advertencia 2.Pero mi solución básica para esto fue usar el SpeakHereController (también incluido en el ejemplo de speakhere) en lugar de acceder directamente a AQRecorder.

De todas formas aquí está el código:

Utilizando el SpeakHereController de una clase obj-c

.h

@property(nonatomic,strong) SpeakHereController * recorder; 

.mm

[init method] 
     //AQRecorder wrapper (SpeakHereController) allocation 
     _recorder = [[SpeakHereController alloc]init]; 
     //AQRecorder wrapper (SpeakHereController) initialization 
     //technically this class is a controller and thats why its init method is awakeFromNib 
     [_recorder awakeFromNib]; 

[recording] 
    bool buttonState = self.audioRecord.isSelected; 
[self.audioRecord setSelected:!buttonState]; 

if ([self.audioRecord isSelected]) { 

    [self.recorder startRecord]; 
}else { 
    [self.recorder stopRecord]; 
} 

SpeakHereController

#import "SpeakHereController.h" 

@implementation SpeakHereController 

@synthesize player; 
@synthesize recorder; 

@synthesize btn_record; 
@synthesize btn_play; 
@synthesize fileDescription; 
@synthesize lvlMeter_in; 
@synthesize playbackWasInterrupted; 

char *OSTypeToStr(char *buf, OSType t) 
{ 
    char *p = buf; 
    char str[4], *q = str; 
    *(UInt32 *)str = CFSwapInt32(t); 
    for (int i = 0; i < 4; ++i) { 
     if (isprint(*q) && *q != '\\') 
      *p++ = *q++; 
     else { 
      sprintf(p, "\\x%02x", *q++); 
      p += 4; 
     } 
    } 
    *p = '\0'; 
    return buf; 
} 

-(void)setFileDescriptionForFormat: (CAStreamBasicDescription)format withName:(NSString*)name 
{ 
    char buf[5]; 
    const char *dataFormat = OSTypeToStr(buf, format.mFormatID); 
    NSString* description = [[NSString alloc] initWithFormat:@"(%d ch. %s @ %g Hz)", format.NumberChannels(), dataFormat, format.mSampleRate, nil]; 
    fileDescription.text = description; 
    [description release]; 
} 

#pragma mark Playback routines 

-(void)stopPlayQueue 
{ 
// player->StopQueue(); 
    [lvlMeter_in setAq: nil]; 
    btn_record.enabled = YES; 
} 

-(void)pausePlayQueue 
{ 
// player->PauseQueue(); 
    playbackWasPaused = YES; 
} 


-(void)startRecord 
{ 
    // recorder = new AQRecorder(); 

    if (recorder->IsRunning()) // If we are currently recording, stop and save the file. 
    { 
     [self stopRecord]; 
    } 
    else // If we're not recording, start. 
    { 
     //  btn_play.enabled = NO; 

     // Set the button's state to "stop" 
     //  btn_record.title = @"Stop"; 

     // Start the recorder 
     recorder->StartRecord(CFSTR("recordedFile.caf")); 

     [self setFileDescriptionForFormat:recorder->DataFormat() withName:@"Recorded File"]; 

     // Hook the level meter up to the Audio Queue for the recorder 
     //  [lvlMeter_in setAq: recorder->Queue()]; 
    } 
} 

- (void)stopRecord 
{ 
    // Disconnect our level meter from the audio queue 
// [lvlMeter_in setAq: nil]; 

    recorder->StopRecord(); 

    // dispose the previous playback queue 
// player->DisposeQueue(true); 

    // now create a new queue for the recorded file 
    recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: @"recordedFile.caf"]; 
// player->CreateQueueForFile(recordFilePath); 

    // Set the button's state back to "record" 
// btn_record.title = @"Record"; 
// btn_play.enabled = YES; 
} 

- (IBAction)play:(id)sender 
{ 
    if (player->IsRunning()) 
    { 
     if (playbackWasPaused) { 
//   OSStatus result = player->StartQueue(true); 
//   if (result == noErr) 
//    [[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:self]; 
     } 
     else 
//   [self stopPlayQueue]; 
      nil; 
    } 
    else 
    {  
//  OSStatus result = player->StartQueue(false); 
//  if (result == noErr) 
//   [[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:self]; 
    } 
} 

- (IBAction)record:(id)sender 
{ 
    if (recorder->IsRunning()) // If we are currently recording, stop and save the file. 
    { 
     [self stopRecord]; 
    } 
    else // If we're not recording, start. 
    { 
//  btn_play.enabled = NO; 
//  
//  // Set the button's state to "stop" 
//  btn_record.title = @"Stop"; 

     // Start the recorder 
     recorder->StartRecord(CFSTR("recordedFile.caf")); 

     [self setFileDescriptionForFormat:recorder->DataFormat() withName:@"Recorded File"]; 

     // Hook the level meter up to the Audio Queue for the recorder 
     [lvlMeter_in setAq: recorder->Queue()]; 
    } 
} 
#pragma mark AudioSession listeners 
void interruptionListener( void * inClientData, 
          UInt32 inInterruptionState) 
{ 
    SpeakHereController *THIS = (SpeakHereController*)inClientData; 
    if (inInterruptionState == kAudioSessionBeginInterruption) 
    { 
     if (THIS->recorder->IsRunning()) { 
      [THIS stopRecord]; 
     } 
     else if (THIS->player->IsRunning()) { 
      //the queue will stop itself on an interruption, we just need to update the UI 
      [[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueStopped" object:THIS]; 
      THIS->playbackWasInterrupted = YES; 
     } 
    } 
    else if ((inInterruptionState == kAudioSessionEndInterruption) && THIS->playbackWasInterrupted) 
    { 
     // we were playing back when we were interrupted, so reset and resume now 
//  THIS->player->StartQueue(true); 
     [[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueResumed" object:THIS]; 
     THIS->playbackWasInterrupted = NO; 
    } 
} 

void propListener( void *     inClientData, 
        AudioSessionPropertyID inID, 
        UInt32     inDataSize, 
        const void *   inData) 
{ 
    SpeakHereController *THIS = (SpeakHereController*)inClientData; 
    if (inID == kAudioSessionProperty_AudioRouteChange) 
    { 
     CFDictionaryRef routeDictionary = (CFDictionaryRef)inData;   
     //CFShow(routeDictionary); 
     CFNumberRef reason = (CFNumberRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason)); 
     SInt32 reasonVal; 
     CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal); 
     if (reasonVal != kAudioSessionRouteChangeReason_CategoryChange) 
     { 
      /*CFStringRef oldRoute = (CFStringRef)CFDictionaryGetValue(routeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_OldRoute)); 
      if (oldRoute) 
      { 
       printf("old route:\n"); 
       CFShow(oldRoute); 
      } 
      else 
       printf("ERROR GETTING OLD AUDIO ROUTE!\n"); 

      CFStringRef newRoute; 
      UInt32 size; size = sizeof(CFStringRef); 
      OSStatus error = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &newRoute); 
      if (error) printf("ERROR GETTING NEW AUDIO ROUTE! %d\n", error); 
      else 
      { 
       printf("new route:\n"); 
       CFShow(newRoute); 
      }*/ 

      if (reasonVal == kAudioSessionRouteChangeReason_OldDeviceUnavailable) 
      {   
       if (THIS->player->IsRunning()) { 
        [THIS pausePlayQueue]; 
        [[NSNotificationCenter defaultCenter] postNotificationName:@"playbackQueueStopped" object:THIS]; 
       }  
      } 

      // stop the queue if we had a non-policy route change 
      if (THIS->recorder->IsRunning()) { 
       [THIS stopRecord]; 
      } 
     } 
    } 
    else if (inID == kAudioSessionProperty_AudioInputAvailable) 
    { 
     if (inDataSize == sizeof(UInt32)) { 
      UInt32 isAvailable = *(UInt32*)inData; 
      // disable recording if input is not available 
      THIS->btn_record.enabled = (isAvailable > 0) ? YES : NO; 
     } 
    } 
} 

#pragma mark Initialization routines 
- (void)awakeFromNib 
{  
    // Allocate our singleton instance for the recorder & player object 
    recorder = new AQRecorder(); 
    player = nil;//new AQPlayer(); 

    OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, self); 
    if (error) printf("ERROR INITIALIZING AUDIO SESSION! %d\n", error); 
    else 
    { 
     UInt32 category = kAudioSessionCategory_PlayAndRecord; 
     error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category); 
     if (error) printf("couldn't set audio category!"); 

     error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, self); 
     if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", error); 
     UInt32 inputAvailable = 0; 
     UInt32 size = sizeof(inputAvailable); 

     // we do not want to allow recording if input is not available 
     error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable); 
     if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", error); 
//  btn_record.enabled = (inputAvailable) ? YES : NO; 

     // we also need to listen to see if input availability changes 
     error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, self); 
     if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", error); 

     error = AudioSessionSetActive(true); 
     if (error) printf("AudioSessionSetActive (true) failed"); 
    } 

// [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackQueueStopped:) name:@"playbackQueueStopped" object:nil]; 
// [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackQueueResumed:) name:@"playbackQueueResumed" object:nil]; 

// UIColor *bgColor = [[UIColor alloc] initWithRed:.39 green:.44 blue:.57 alpha:.5]; 
// [lvlMeter_in setBackgroundColor:bgColor]; 
// [lvlMeter_in setBorderColor:bgColor]; 
// [bgColor release]; 

    // disable the play button since we have no recording to play yet 
// btn_play.enabled = NO; 
// playbackWasInterrupted = NO; 
// playbackWasPaused = NO; 
} 

# pragma mark Notification routines 
- (void)playbackQueueStopped:(NSNotification *)note 
{ 
    btn_play.title = @"Play"; 
    [lvlMeter_in setAq: nil]; 
    btn_record.enabled = YES; 
} 

- (void)playbackQueueResumed:(NSNotification *)note 
{ 
    btn_play.title = @"Stop"; 
    btn_record.enabled = NO; 
    [lvlMeter_in setAq: player->Queue()]; 
} 

#pragma mark Cleanup 
- (void)dealloc 
{ 
    [btn_record release]; 
    [btn_play release]; 
    [fileDescription release]; 
    [lvlMeter_in release]; 

// delete player; 
    delete recorder; 

    [super dealloc]; 
} 

@end 

AQRecorder (.h tiene 2 líneas de importancia

#define kNumberRecordBuffers 3 
#define kBufferDurationSeconds 5.0 

)

#include "AQRecorder.h" 
//#include "UploadAudioWrapperInterface.h" 
//#include "RestClient.h" 

RestClient * restClient; 
NSData* data; 

// ____________________________________________________________________________________ 
// Determine the size, in bytes, of a buffer necessary to represent the supplied number 
// of seconds of audio data. 
int AQRecorder::ComputeRecordBufferSize(const AudioStreamBasicDescription *format, float seconds) 
{ 
    int packets, frames, bytes = 0; 
    try { 
     frames = (int)ceil(seconds * format->mSampleRate); 

     if (format->mBytesPerFrame > 0) 
      bytes = frames * format->mBytesPerFrame; 
     else { 
      UInt32 maxPacketSize; 
      if (format->mBytesPerPacket > 0) 
       maxPacketSize = format->mBytesPerPacket; // constant packet size 
      else { 
       UInt32 propertySize = sizeof(maxPacketSize); 
       XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize, 
               &propertySize), "couldn't get queue's maximum output packet size"); 
      } 
      if (format->mFramesPerPacket > 0) 
       packets = frames/format->mFramesPerPacket; 
      else 
       packets = frames; // worst-case scenario: 1 frame in a packet 
      if (packets == 0)  // sanity check 
       packets = 1; 
      bytes = packets * maxPacketSize; 
     } 
    } catch (CAXException e) { 
     char buf[256]; 
     fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); 
     return 0; 
    } 
    return bytes; 
} 

// ____________________________________________________________________________________ 
// AudioQueue callback function, called when an input buffers has been filled. 
void AQRecorder::MyInputBufferHandler( void *        inUserData, 
             AudioQueueRef      inAQ, 
             AudioQueueBufferRef     inBuffer, 
             const AudioTimeStamp *    inStartTime, 
             UInt32        inNumPackets, 
             const AudioStreamPacketDescription* inPacketDesc) 
{ 
    AQRecorder *aqr = (AQRecorder *)inUserData; 


    try { 
     if (inNumPackets > 0) { 
      // write packets to file 
//   XThrowIfError(AudioFileWritePackets(aqr->mRecordFile, FALSE, inBuffer->mAudioDataByteSize, 
//           inPacketDesc, aqr->mRecordPacket, &inNumPackets, inBuffer->mAudioData), 
//      "AudioFileWritePackets failed"); 
      aqr->mRecordPacket += inNumPackets; 



//   int numBytes = inBuffer->mAudioDataByteSize;  
//   SInt8 *testBuffer = (SInt8*)inBuffer->mAudioData; 
//    
//   for (int i=0; i < numBytes; i++) 
//   { 
//    SInt8 currentData = testBuffer[i]; 
//    printf("Current data in testbuffer is %d", currentData); 
//     
//    NSData * temp = [NSData dataWithBytes:currentData length:sizeof(currentData)]; 
//   } 


      data=[[NSData dataWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize]retain]; 

      [restClient uploadAudioData:data url:nil]; 

     } 


     // if we're not stopping, re-enqueue the buffer so that it gets filled again 
     if (aqr->IsRunning()) 
      XThrowIfError(AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed"); 
    } catch (CAXException e) { 
     char buf[256]; 
     fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); 
    } 

} 

AQRecorder::AQRecorder() 
{ 
    mIsRunning = false; 
    mRecordPacket = 0; 

    data = [[NSData alloc]init]; 
    restClient = [[RestClient sharedManager]retain]; 
} 

AQRecorder::~AQRecorder() 
{ 
    AudioQueueDispose(mQueue, TRUE); 
    AudioFileClose(mRecordFile); 

    if (mFileName){ 
    CFRelease(mFileName); 
    } 

    [restClient release]; 
    [data release]; 
} 

// ____________________________________________________________________________________ 
// Copy a queue's encoder's magic cookie to an audio file. 
void AQRecorder::CopyEncoderCookieToFile() 
{ 
    UInt32 propertySize; 
    // get the magic cookie, if any, from the converter  
    OSStatus err = AudioQueueGetPropertySize(mQueue, kAudioQueueProperty_MagicCookie, &propertySize); 

    // we can get a noErr result and also a propertySize == 0 
    // -- if the file format does support magic cookies, but this file doesn't have one. 
    if (err == noErr && propertySize > 0) { 
     Byte *magicCookie = new Byte[propertySize]; 
     UInt32 magicCookieSize; 
     XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_MagicCookie, magicCookie, &propertySize), "get audio converter's magic cookie"); 
     magicCookieSize = propertySize; // the converter lies and tell us the wrong size 

     // now set the magic cookie on the output file 
     UInt32 willEatTheCookie = false; 
     // the converter wants to give us one; will the file take it? 
     err = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); 
     if (err == noErr && willEatTheCookie) { 
      err = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, magicCookieSize, magicCookie); 
      XThrowIfError(err, "set audio file's magic cookie"); 
     } 
     delete[] magicCookie; 
    } 
} 

void AQRecorder::SetupAudioFormat(UInt32 inFormatID) 
{ 
    memset(&mRecordFormat, 0, sizeof(mRecordFormat)); 

    UInt32 size = sizeof(mRecordFormat.mSampleRate); 
    XThrowIfError(AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareSampleRate, 
             &size, 
             &mRecordFormat.mSampleRate), "couldn't get hardware sample rate"); 

    //override samplearate to 8k from device sample rate 

    mRecordFormat.mSampleRate = 8000.0; 

    size = sizeof(mRecordFormat.mChannelsPerFrame); 
    XThrowIfError(AudioSessionGetProperty( kAudioSessionProperty_CurrentHardwareInputNumberChannels, 
             &size, 
             &mRecordFormat.mChannelsPerFrame), "couldn't get input channel count"); 


// mRecordFormat.mChannelsPerFrame = 1; 

    mRecordFormat.mFormatID = inFormatID; 
    if (inFormatID == kAudioFormatLinearPCM) 
    { 
     // if we want pcm, default to signed 16-bit little-endian 
     mRecordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; 
     mRecordFormat.mBitsPerChannel = 16; 
     mRecordFormat.mBytesPerPacket = mRecordFormat.mBytesPerFrame = (mRecordFormat.mBitsPerChannel/8) * mRecordFormat.mChannelsPerFrame; 
     mRecordFormat.mFramesPerPacket = 1; 
    } 

    if (inFormatID == kAudioFormatULaw) { 
//  NSLog(@"is ulaw"); 
     mRecordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; 
     mRecordFormat.mSampleRate = 8000.0; 
//  mRecordFormat.mFormatFlags = 0; 
     mRecordFormat.mFramesPerPacket = 1; 
     mRecordFormat.mChannelsPerFrame = 1; 
     mRecordFormat.mBitsPerChannel = 16;//was 8 
     mRecordFormat.mBytesPerPacket = 1; 
     mRecordFormat.mBytesPerFrame = 1; 
    } 
} 

NSString * GetDocumentDirectory(void) 
{  
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; 
    return basePath; 
} 


void AQRecorder::StartRecord(CFStringRef inRecordFile) 
{ 
    int i, bufferByteSize; 
    UInt32 size; 
    CFURLRef url; 

    try {  
     mFileName = CFStringCreateCopy(kCFAllocatorDefault, inRecordFile); 

     // specify the recording format 
     SetupAudioFormat(kAudioFormatULaw /*kAudioFormatLinearPCM*/); 

     // create the queue 
     XThrowIfError(AudioQueueNewInput(
             &mRecordFormat, 
             MyInputBufferHandler, 
             this /* userData */, 
             NULL /* run loop */, NULL /* run loop mode */, 
             0 /* flags */, &mQueue), "AudioQueueNewInput failed"); 

     // get the record format back from the queue's audio converter -- 
     // the file may require a more specific stream description than was necessary to create the encoder. 
     mRecordPacket = 0; 

     size = sizeof(mRecordFormat); 
     XThrowIfError(AudioQueueGetProperty(mQueue, kAudioQueueProperty_StreamDescription, 
             &mRecordFormat, &size), "couldn't get queue's format"); 

     NSString *basePath = GetDocumentDirectory(); 
     NSString *recordFile = [basePath /*NSTemporaryDirectory()*/ stringByAppendingPathComponent: (NSString*)inRecordFile]; 

     url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)recordFile, NULL); 

     // create the audio file 
     XThrowIfError(AudioFileCreateWithURL(url, kAudioFileCAFType, &mRecordFormat, kAudioFileFlags_EraseFile, 
              &mRecordFile), "AudioFileCreateWithURL failed"); 
     CFRelease(url); 

     // copy the cookie first to give the file object as much info as we can about the data going in 
     // not necessary for pcm, but required for some compressed audio 
     CopyEncoderCookieToFile(); 


     // allocate and enqueue buffers 
     bufferByteSize = ComputeRecordBufferSize(&mRecordFormat, kBufferDurationSeconds); // enough bytes for half a second 
     for (i = 0; i < kNumberRecordBuffers; ++i) { 
      XThrowIfError(AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers[i]), 
         "AudioQueueAllocateBuffer failed"); 
      XThrowIfError(AudioQueueEnqueueBuffer(mQueue, mBuffers[i], 0, NULL), 
         "AudioQueueEnqueueBuffer failed"); 
     } 
     // start the queue 
     mIsRunning = true; 
     XThrowIfError(AudioQueueStart(mQueue, NULL), "AudioQueueStart failed"); 
    } 
    catch (CAXException &e) { 
     char buf[256]; 
     fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); 
    } 
    catch (...) { 
     fprintf(stderr, "An unknown error occurred\n"); 
    } 

} 

void AQRecorder::StopRecord() 
{ 
    // end recording 
    mIsRunning = false; 
// XThrowIfError(AudioQueueReset(mQueue), "AudioQueueStop failed"); 
    XThrowIfError(AudioQueueStop(mQueue, true), "AudioQueueStop failed"); 
    // a codec may update its cookie at the end of an encoding session, so reapply it to the file now 
    CopyEncoderCookieToFile(); 
    if (mFileName) 
    { 
     CFRelease(mFileName); 
     mFileName = NULL; 
    } 
    AudioQueueDispose(mQueue, true); 
    AudioFileClose(mRecordFile); 
} 

No dude en comentar o refinar mi respuesta, lo aceptaré como la respuesta si es una mejor solución. Tenga en cuenta que este fue mi primer intento y estoy seguro de que no es la solución más elegante o adecuada.

+0

¿Alguna actualización de este trabajo? Estoy tratando de transmitir audio G.711 (A) desde el iPhone a un servidor RTP que se ejecuta en Linux y reproducir la transmisión en iOS. Puedo poner los encabezados RTP yo mismo siempre que pueda acceder a los buffers. No soy competente en Objective C, por lo que agradecería que me dieras un enlace a tu código anterior como proyecto. – TanB

+0

para ser honesto esto fue hace mucho tiempo para que realmente ayude mucho. y no creo que haya llegado mucho más allá que el anterior. Sin embargo, una cosa que me ayudó fue descubrir que con tu suscripción a Apple Dev realmente recibes soporte de Dev (los primeros 3 casos son gratis, creo) –

0

podría utilizar el marco GameKit? Luego envía el audio por bluetooth. Hay ejemplos en la biblioteca de desarrolladores ios

+0

Miro en el marco del juego de juegos pero los datos deben ir a través de Internet (a través de wifi/3g) no una conexión local como bluetooth –

+0

¿Tiene un servidor? Si lo hace, puede retransmitir los datos a través del servidor a través de wifi – Coder404

+0

@owengerig Tengo problemas para ejecutar su código. ¿Puedo tener un proyecto xcode de muestra? – zzzzz