我正在编写一个应该记录用户语音的iPhone应用程序,并将音频数据提供给库以进行修改,例如改变速度和音高。我开始使用Apple的SpeakHere示例代码:
http://developer.apple.com/library/ios/#samplecode/SpeakHere/Introduction/Intro.html
该项目为记录用户的声音并播放它奠定了基础。效果很好。
现在我正在深入研究代码,我需要弄清楚如何将音频数据输入SoundTouch库(http://www.surina.net/soundtouch/)来改变音高。我在浏览代码时熟悉了音频队列框架,并找到了从录音中接收音频数据的地方。
基本上,您调用AudioQueueNewInput
来创建新的输入队列。您传递一个回调函数,每当有一大块音频数据可用时调用该函数。我需要将这些数据块传递给SoundTouch。
我有所有设置,但是我从SoundTouch库播放的噪音非常严重(它几乎与原版相似)。如果我没有通过SoundTouch传递它并播放原始音频,它可以正常工作。
基本上,我错过了一些关于我所获得的实际数据的信息。我假设我得到了一个short
s流,这是一个样本,每个通道有一个样本。这就是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
中提取样本?
答案 0 :(得分:1)
音频队列的默认字节顺序可能与您的预期相反。在录制之后和播放之前,您可能必须交换每个16位音频样本的高字节和低字节。
sample_le = (0xff00 & (sample_be << 8)) | (0x00ff & (sample_be >> 8)) ;
答案 1 :(得分:0)
您必须检查所获得的内容,签名等是否与库所期望的相符。使用mFormatFlags
的{{1}}来确定源格式。然后,您可能必须转换样本(例如AudioStreamBasicDescription
)