2010-10-18 12 views
5

Escribo una aplicación de iPhone que debe registrar la voz de los usuarios y alimentar los datos de audio en una biblioteca para modificaciones tales como cambiar el tempo y el tono. Me comenzó con el ejemplo de código SPEAKHERE de Apple:Formato de datos de la grabación utilizando el marco de Audio Queue

http://developer.apple.com/library/ios/#samplecode/SpeakHere/Introduction/Intro.html

Ese proyecto sienta las bases para la grabación de la voz del usuario y reproducción del mismo. Funciona bien.

Ahora estoy buceando en el código y tengo que averiguar cómo alimentar los datos de audio en la biblioteca SoundTouch (http://www.surina.net/soundtouch/) para cambiar el tono. Me familiaricé con el marco de Audio Queue mientras revisaba el código, y encontré el lugar donde recibo los datos de audio de la grabación.

Esencialmente, llama al AudioQueueNewInput para crear una nueva cola de entrada. Pasa una función de devolución de llamada que se invoca cada vez que hay un fragmento de datos de audio disponible. Es dentro de esta devolución de llamada que necesito pasar los fragmentos de datos a SoundTouch.

Lo tengo todo configurado, pero el ruido que reproduzco de la biblioteca SoundTouch es muy estático (apenas se parece al original). Si no lo paso a través de SoundTouch y reproduzco el audio original, funciona bien.

Básicamente, me falta algo acerca de lo que representan los datos reales que obtengo. Asumí que obtendría una secuencia de short s, que son muestras, 1 muestra para cada canal. Así es como SoundTouch lo espera, por lo que no debe ser correcto de alguna manera.

Aquí está el código que configura la cola de audio para que pueda ver cómo está configurada.

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"); 

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

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

Y aquí es parte del código que en realidad se crea dicha:

SetupAudioFormat(kAudioFormatLinearPCM); 

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

Y, por último, aquí es la devolución de llamada que se ocupa de los nuevos datos de audio:

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) { 
      CAStreamBasicDescription queueFormat = aqr->DataFormat(); 
      SoundTouch *soundTouch = aqr->getSoundTouch(); 

      soundTouch->putSamples((const SAMPLETYPE *)inBuffer->mAudioData, 
            inBuffer->mAudioDataByteSize/2/queueFormat.NumberChannels()); 

      SAMPLETYPE *samples = (SAMPLETYPE *)malloc(sizeof(SAMPLETYPE) * 10000 * queueFormat.NumberChannels()); 
      UInt32 numSamples; 
      while((numSamples = soundTouch->receiveSamples((SAMPLETYPE *)samples, 10000))) { 
       // write packets to file 
       XThrowIfError(AudioFileWritePackets(aqr->mRecordFile, 
                FALSE, 
                numSamples * 2 * queueFormat.NumberChannels(), 
                NULL, 
                aqr->mRecordPacket, 
                &numSamples, 
                samples), 
           "AudioFileWritePackets failed"); 
       aqr->mRecordPacket += numSamples; 
      } 
      free(samples); 
     } 

     // if we're not stopping, re-enqueue the buffe 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)); 
} 
} 

Se puede ver que Estoy pasando los datos en inBuffer->mAudioData a SoundTouch. En mi devolución de llamada, ¿qué representan exactamente los bytes, es decir, cómo extraigo muestras del mAudioData?

+0

¿cómo piensa hacer coincidir la licencia LGPL de la biblioteca SoundTouch con la política de Apple que niega la vinculación dinámica de bibliotecas para aplicaciones iOS? – IlDan

+0

¿podemos usar la biblioteca soundTouch en ios legalmente o no? – Tornado

Respuesta

0

Debes comprobar que la endianess, la firma, etc. de lo que obtienes coinciden con lo que la biblioteca espera. Use mFormatFlags de AudioStreamBasicDescription para determinar el formato fuente. Entonces es posible que tenga que convertir las muestras (por ejemplo, newSample = sample + 0x8000)

+3

Finalmente descubrí que SoundTouch esperaba que las muestras fuesen flotantes. Volver a compilarlo con los tipos de muestra int funciona ahora. ¡Gracias! –

1

La endiabilidad predeterminada de la cola de audio puede ser la contraria a la esperada. Es posible que deba intercambiar bytes superiores e inferiores de cada muestra de audio de 16 bits después de la grabación y antes de la reproducción.

sample_le = (0xff00 & (sample_be << 8)) | (0x00ff & (sample_be >> 8)) ; 
+0

Pensé en eso también, y encontré una bandera que puedo pasar en las opciones de formato para convertirla en big endian en lugar de small endian para no tener que hacerlo manualmente. Ese no era el problema, ¡pero gracias! –

Cuestiones relacionadas