iOS 6中使用AudioFileServices的细粒度合成

Granular Synthesis in iOS 6 using AudioFileServices

本文关键字:细粒度 AudioFileServices iOS      更新时间:2023-10-16

我有一个关于我正在开发的声音合成应用程序的问题。我正在尝试读取音频文件,使用细粒度合成技术创建随机的"颗粒",将它们放入输出缓冲区,然后能够使用OpenAL向用户播放。出于测试目的,我只是将输出缓冲区写入一个文件中,然后我就可以回听

从我的结果来看,我走在了正确的轨道上,但遇到了一些混叠问题,播放的声音似乎不太正确。通常在输出文件的中间会有一个相当大的弹出声,音量有时非常大。

以下是我为获得所需结果而采取的步骤,但我对以下几点有点困惑,即我为AudioStreamBasicDescription指定的格式。

  1. 从我的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;
    
  2. 接下来我声明我的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;
    
  3. 我的理解是.mData将是formatFlags中指定的任何内容(在本例中,类型为SInt16(。由于它的类型是(void*(,我想将其转换为浮点数据,这对于音频操作来说是显而易见的。在我设置for循环之前,它只是在缓冲区中迭代,并将每个样本强制转换为float*。这似乎没有必要,所以现在我将.mData缓冲区传递给我创建的一个函数,该函数然后对音频进行粒度化:

        float *theOutBuffer = [self granularizeWithData:(float *)buffers->mBuffers[0].mData with:framesRead];
    
  4. 在这个函数中,我动态分配一些缓冲区,创建随机大小的颗粒,在使用hamming窗口对它们进行开窗后将它们放在我的out缓冲区中,并返回该缓冲区(即浮点数据(。到目前为止一切都很冷静。

  5. 接下来,我设置了所有的输出文件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中打开它时,它看起来有很多别名。如果我试图向它发送浮点数据,但发生了某种我不知道的转换,这是有道理的。

提前感谢您对此事的任何建议!我一直是网上几乎所有源材料的狂热读者,包括核心音频书、各种博客、教程等。我的应用程序的最终目标是向戴着耳机的用户实时播放粒度化的音频,因此目前只将写入文件的功能用于测试。谢谢

您对步骤3的描述向我表明,您将一个空头数组解释为一个浮动数组?如果是这样的话,我们找到了你麻烦的原因。你能把短值一个接一个地分配到一个浮点数组中吗?这应该能解决问题。

看起来mData是指向短路阵列的void *。将此指针投射到float *不会将底层数据更改为float,但您的音频处理函数会将它们视为。然而,floatshort的值是以完全不同的方式存储的,因此在该函数中进行的数学运算将对与真实输入信号无关的非常不同的值进行运算。要进行实验研究,请尝试以下操作:

short data[4] = {-27158, 16825, 23024, 15};
void *pData = data;

void指针没有指示它指向哪种数据,因此,人们可能会错误地认为它指向float值。注意,short是2字节宽,但float是4字节宽。您的代码没有因访问违规而崩溃,这纯属巧合。将上面的数组解释为float,其长度仅足以容纳两个值。让我们看看第一个值:

float *pfData = (float *)pData;
printf("%d == %fn", data[0], pfData[0]);

它的输出将是-27158 == 23.198200,说明如何获得大致的23.2f,而不是预期的-27158.0f。发生了两件有问题的事情。首先,sizeof(float)不是sizeof(short)。其次,浮点数的"1和0"与整数的存储方式非常不同。看见http://en.wikipedia.org/wiki/Single_precision_floating-point_format.

如何解决这个问题?至少有两种简单的解决方案。首先,您可以在将数组的每个元素输入音频处理器之前对其进行转换:

int k;
float *pfBuf = (float *)malloc(n_data * sizeof(float));
short *psiBuf = (short *)buffers->mBuffers[0].mData[k];
for (k = 0; k < n_data; k ++)
{
    pfBuf[k] = psiBuf[k];
}
[self granularizeWithData:pfBuf with:framesRead];
for (k = 0; k < n_data; k ++)
{
    psiBuf[k] = pfBuf[k];
}
free(pfBuf);

您可以看到,在调用granularizeWithData: with:之后,很可能需要将所有内容都转换回short。因此,第二种解决方案是在short中进行所有处理,尽管从您所写的内容来看,我想您不会喜欢后一种方法。