2012-05-08 2 views
5

Мне нужно передать аудио с микрофона на http-сервер.
Эти записи настройки, что мне нужно: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-им кодирование для состояний:

Отправить непрерывный поток звука в просматриваемой в данный момент камеры. Аудио должно быть закодировано по закону мужа G711 со скоростью 64 кбит/с для передачи Камера Axis у постели больного. отправить (это должно быть URL POST в SSL для подключенного сервера): POST/transmitaudio ID = Content-Type: аудио/Основной Content-Length: 99999 (длина игнорируется)

Ниже приведены список ссылок, с которыми я пытался работать.

LINK - (SO) базовое объяснение, что только звуковые устройства и звуковые очереди позволят выводить nsdata при записи через микрофон | не пример, но хорошее определение того, что необходимо (звуковые очереди или аудиоустройства)

LINK - (SO) audio callback example | включает только обратный вызов

LINK - (SO) REMOTE IO example | не имеет пуска/остановки и предназначен для сохранения в файл

LINK - (SO) REMOTE IO example | неотвечаемый не работает

LINK - (SO) Основной пример записи звука | хороший пример, но записи в файл

LINK - (SO) Вопрос, который направлял меня в класс InMemoryAudioFile (не мог работать) | следил за ссылками на inMemoryFile (или что-то в этом роде), но не смог заставить его работать.

LINK - (SO) более аудиоустройство и удаленный io пример/проблемы | получил эту работу, но еще раз не существует функции остановки, и даже когда я пытался выяснить, что это за звонок, и он остановился, он все равно не передавал звук на сервер.

LINK - Приличный remoteIO и аудио очередь пример, но | еще один хороший пример и почти заставил его работать, но некоторые проблемы с кодом (компилятор думает о нем не obj-C++) и еще раз не знают, как получить от него аудиоданные, а не в файл.

LINK - Apple docs для аудио очереди | имел проблемы с фреймворками. (см. вопрос ниже), но, в конце концов, не смог заставить его работать, но, вероятно, не дал ему столько времени, сколько другим, и, возможно, должен был быть.

LINK - (SO) проблемы, которые у меня были при попытке реализовать аудио очередь/блок | не пример

LINK - (SO) другой remoteIO пример | еще один хороший пример, но не могу понять, как получить его вместо данных.

LINK - также выглядит интересным, круговые буферы | не мог понять, как включить это с обратным вызовом звука

Вот мой текущий класс, пытающийся перелистать. Кажется, что это работает, хотя на выходе ресивера (подключенном к серверу) есть статический выход. Который, кажется, указывает на проблему с форматом аудиоданных.

IOS версии (делегат методы минус для НОД розетки):

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

И версия 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; 
    } 

} 

ответ

3

Мои работы по этой теме были ошеломляющими и длинными. Я, наконец, получил это, чтобы работать, но взломать это может быть. Из-за этого я перечислю некоторые предупреждения до размещения ответа:

  1. Там есть еще щелчки между буферами

  2. Я получаю предупреждение в связи с тем, как я использую мой Obj-C классы в Obj-C++ класс, так что есть что-то неправильно там (однако от моего исследования с использованием пула делает то же самое по высвобождению поэтому я не считаю, что это важно для много):

    объект 0x13cd20 класса __NSCFString autoreleased, без бассейна в место - просто утечка - перерыв на objc _autoreleaseNoPool() для отладки

  3. Для того, чтобы получить эту работу, я должен был комментировать все ссылки AQPlayer от SpeakHereController (см ниже) из-за ошибки я не смог исправить любой другой путь. Оно не имеет значения для меня, однако, так как я только записи

Так что главный ответ на вышесказанное, что есть ошибка в AVAssetWriter, что остановили его от добавления байтов и записи аудиоданных. Я, наконец, узнал об этом после того, как связался с поддержкой Apple, и сообщите мне об этом. Насколько я знаю, ошибка специфична для ulaw и AVAssetWriter, хотя я havnt пытался проверить многие другие форматы.
В ответ на это единственным другим вариантом является использование AudioQueues. Что-то, что я пробовал раньше, но вызвало множество проблем. Самая большая проблема - отсутствие знаний в obj-C++. Класс ниже, который получил работу, - это пример sayHere с небольшими изменениями, чтобы звук был отформатирован. Другие проблемы возникли, пытаясь заставить все файлы играть хорошо. Однако это легко исправить, изменив все имена файлов в цепочке на .mm. Следующая проблема заключалась в том, чтобы использовать классы в гармонии. Это по-прежнему WIP, и связано с предупреждением номер 2.Но основным решением для этого было использование SpeakHereController (также включенного в примерный пример) вместо прямого доступа к AQRecorder.

В любом случае вот код:

Использование SpeakHereController из 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 имеет 2 строки значение

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

Пожалуйста, не стесняйтесь комментировать или уточнить свой ответ, я принимаю это как ответ, если его лучше решение. Обратите внимание, что это была моя первая попытка, и я уверен, что это не самое элегантное или правильное решение.

+0

Любые обновления по этой работе? Я пытаюсь передать G.711 (A) аудио с iPhone на RTP-сервер, работающий в Linux, и воспроизвести поток на iOS. Я могу разместить заголовки RTP самостоятельно, пока я могу получить доступ к буферам. Я не разбираюсь в Objective C, поэтому было бы признательно, если бы вы могли дать мне ссылку на ваш выше код в качестве проекта! – TanB

+0

, честно говоря, это было давно для меня, чтобы действительно помочь. и я не думаю, что сделал это намного дальше, чем выше. однако одна вещь, которая помогла мне, заключалась в том, чтобы узнать, что с подпиской Apple Dev вы фактически получаете поддержку Dev (первые 3 случая бесплатны, я считаю) –

0

Вы можете использовать GameKit Framework? Затем отправьте аудио через Bluetooth. В библиотеке разработчиков ios есть примеры

+0

плохо смотреть в рамки gamekit, но данные должны проходить через Интернет (через Wi-Fi/3g), а не локальное соединение, например bluetooth. –

+0

У вас есть сервер? Если вы это сделаете, вы можете передать данные через сервер через wifi – Coder404

+0

@owengerig У меня возникают проблемы с запуском вашего кода. Может, у меня есть образец проекта xcode? – zzzzz