我有一个关于我正在开发的声音合成应用程序的问题。我正在尝试读取音频文件,使用粒度合成技术创建随机“颗粒” ,将它们放入输出缓冲区,然后能够使用 OpenAL 将其回放给用户。出于测试目的,我只是将输出缓冲区写入一个文件,然后我可以回听。
从我的结果来看,我在正确的轨道上,但遇到了一些混叠问题和播放声音似乎不太正确。输出文件的中间通常会有相当响亮的爆裂声,有时音量非常大。
以下是我为获得所需结果而采取的步骤,但我对一些事情有点困惑,即我为我的 AudioStreamBasicDescription 指定的格式。
从我的 mainBundle 中读取一个音频文件,这是一个 .aiff 格式的单声道文件:
ExtAudioFileRef extAudioFile; CheckError(ExtAudioFileOpenURL(loopFileURL, &extAudioFile), "couldn't open extaudiofile for reading"); memset(&player->dataFormat, 0, sizeof(player->dataFormat)); player->dataFormat.mFormatID = kAudioFormatLinearPCM; player->dataFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; player->dataFormat.mSampleRate = S_RATE; player->dataFormat.mChannelsPerFrame = 1; player->dataFormat.mFramesPerPacket = 1; player->dataFormat.mBitsPerChannel = 16; player->dataFormat.mBytesPerFrame = 2; player->dataFormat.mBytesPerPacket = 2; // tell extaudiofile about our format CheckError(ExtAudioFileSetProperty(extAudioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &player->dataFormat), "couldnt set client format on extaudiofile"); SInt64 fileLengthFrames; UInt32 propSize = sizeof(fileLengthFrames); ExtAudioFileGetProperty(extAudioFile, kExtAudioFileProperty_FileLengthFrames, &propSize, &fileLengthFrames); player->bufferSizeBytes = fileLengthFrames * player->dataFormat.mBytesPerFrame;
接下来我声明我的 AudioBufferList 并设置更多属性
AudioBufferList *buffers; UInt32 ablSize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * 1); buffers = (AudioBufferList *)malloc(ablSize); player->sampleBuffer = (SInt16 *)malloc(sizeof(SInt16) * player->bufferSizeBytes); buffers->mNumberBuffers = 1; buffers->mBuffers[0].mNumberChannels = 1; buffers->mBuffers[0].mDataByteSize = player->bufferSizeBytes; buffers->mBuffers[0].mData = player->sampleBuffer;
我的理解是 .mData 将是 formatFlags 中指定的任何内容(在这种情况下,键入 SInt16)。由于它是 (void * ) 类型,我想将其转换为浮点数据,这对于音频操作是显而易见的。在我设置一个 for 循环之前,它只是遍历缓冲区并将每个样本转换为 float*。这似乎没有必要,所以现在我将 .mData 缓冲区传递给我创建的函数,然后将音频细化:
float *theOutBuffer = [self granularizeWithData:(float *)buffers->mBuffers[0].mData with:framesRead];
在这个函数中,我动态分配一些缓冲区,创建随机大小的颗粒,在使用汉明窗口将它们开窗后将它们放入我的输出缓冲区并返回该缓冲区(这是浮点数据)。到目前为止,一切都很酷。
接下来,我设置了所有输出文件 ASBD 等:
AudioStreamBasicDescription outputFileFormat; bzero(audioFormatPtr, sizeof(AudioStreamBasicDescription)); outputFileFormat->mFormatID = kAudioFormatLinearPCM; outputFileFormat->mSampleRate = 44100.0; outputFileFormat->mChannelsPerFrame = numChannels; outputFileFormat->mBytesPerPacket = 2 * numChannels; outputFileFormat->mFramesPerPacket = 1; outputFileFormat->mBytesPerFrame = 2 * numChannels; outputFileFormat->mBitsPerChannel = 16; outputFileFormat->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; UInt32 flags = kAudioFileFlags_EraseFile; ExtAudioFileRef outputAudioFileRef = NULL; NSString *tmpDir = NSTemporaryDirectory(); NSString *outFilename = @"Decomp.caf"; NSString *outPath = [tmpDir stringByAppendingPathComponent:outFilename]; NSURL *outURL = [NSURL fileURLWithPath:outPath]; AudioBufferList *outBuff; UInt32 abSize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * 1); outBuff = (AudioBufferList *)malloc(abSize); outBuff->mNumberBuffers = 1; outBuff->mBuffers[0].mNumberChannels = 1; outBuff->mBuffers[0].mDataByteSize = abSize; outBuff->mBuffers[0].mData = theOutBuffer; CheckError(ExtAudioFileCreateWithURL((__bridge CFURLRef)outURL, kAudioFileCAFType, &outputFileFormat, NULL, flags, &outputAudioFileRef), "ErrorCreatingURL_For_EXTAUDIOFILE"); CheckError(ExtAudioFileSetProperty(outputAudioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(outputFileFormat), &outputFileFormat), "ErrorSettingProperty_For_EXTAUDIOFILE"); CheckError(ExtAudioFileWrite(outputAudioFileRef, framesRead, outBuff), "ErrorWritingFile");
该文件以 CAF 格式正确写入。我的问题是:我是否正确处理了 .mData 缓冲区,因为我将样本转换为浮动数据,操纵(颗粒化)各种窗口大小,然后使用 ExtAudioFileWrite(CAF 格式)将其写入文件?有没有更优雅的方法来做到这一点,例如将我的 ASBD formatFlag 声明为 kAudioFlagIsFloat?我的输出 CAF 文件中有一些点击,当我在 Logic 中打开它时,看起来有很多别名。如果我试图向它发送浮点数据,这是有道理的,但发生了某种我不知道的转换。
提前感谢您对此事的任何建议!我一直是在线几乎所有源材料的狂热阅读者,包括核心音频书、各种博客、教程等。我的应用程序的最终目标是通过耳机向用户实时播放精细化的音频,因此写入文件的东西目前仅用于测试。谢谢!