使用音频队列框架录制的数据格式

Posted

技术标签:

【中文标题】使用音频队列框架录制的数据格式【英文标题】:Data format from recording using Audio Queue framework 【发布时间】:2010-10-18 22:13:54 【问题描述】:

我正在编写一个 iPhone 应用程序,它应该记录用户的声音,并将音频数据输入到库中以进行修改,例如改变速度和音高。我从 Apple 的 SpeakHere 示例代码开始:

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

该项目为录制和播放用户的声音奠定了基础。效果很好。

现在我正在深入研究代码,我需要弄清楚如何将音频数据输入 SoundTouch 库 (http://www.surina.net/soundtouch/) 以更改音高。在看代码的过程中熟悉了Audio Queue框架,找到了我从录音中接收音频数据的地方。

本质上,您调用AudioQueueNewInput 来创建一个新的输入队列。您传递一个回调函数,每次有大量音频数据可用时调用该函数。正是在这个回调中,我需要将数据块传递到 SoundTouch。

我已经完成了所有设置,但是我从 SoundTouch 库中播放的噪音非常不稳定(它几乎不像原来的那样)。如果我不通过 SoundTouch 传递它并播放原始音频,它就可以正常工作。

基本上,我遗漏了我所获得的实际数据所代表的内容。我假设我得到一个shorts 流,它们是样本,每个通道一个样本。这就是 SoundTouch 的预期,所以它一定是不正确的。

这是设置音频队列的代码,您可以看到它是如何配置的。

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;


下面是实际设置它的部分代码:

    SetupAudioFormat(kAudioFormatLinearPCM);

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

最后,这是处理新音频数据的回调:

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


您可以看到我正在将inBuffer->mAudioData 中的数据传递给SoundTouch。在我的回调中,字节究竟代表什么,即如何从mAudioData 中提取样本?

【问题讨论】:

您打算如何将 SoundTouch 库的 LGPL 许可与拒绝为 iOS 应用程序动态链接库的 Apple 政策相匹配? 能不能在ios中合法使用soundTouch库? 【参考方案1】:

音频队列的默认字节序可能与您的预期相反。您可能需要在录制后和播放前交换每个 16 位音频样本的高字节和低字节。

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

【讨论】:

我也是这么想的,我找到了一个标志,我可以在格式选项中传递它以使其成为大端而不是小端,这样我就不必手动进行了。这不是问题,但谢谢!【参考方案2】:

您必须检查您所获得的字节序、符号等是否与库所期望的相匹配。使用AudioStreamBasicDescriptionmFormatFlags 来确定源格式。然后你可能需要转换样本(例如newSample = sample + 0x8000

【讨论】:

我终于发现 SoundTouch 期望样本是浮点数。现在可以使用 int 样本类型重新编译它。谢谢!

以上是关于使用音频队列框架录制的数据格式的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iPhone 上录制 AMR 音频格式?

音频队列服务录制到 .mp4 文件,无法播放。和魔术饼干问题

iOS利用FFmpeg解码音频数据并播放

基于AudioTrack、AudioRecord获取分贝值、录制时长、PCM解码与编码

录制音频数据并将其发送到 c++ 函数

如何在gstreamer中录制音频和视频