4

我有一个关于我正在开发的声音合成应用程序的问题。我正在尝试读取音频文件,使用粒度合成技术创建随机“颗粒” ,将它们放入输出缓冲区,然后能够使用 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. 在这个函数中,我动态分配一些缓冲区,创建随机大小的颗粒,在使用汉明窗口将它们开窗后将它们放入我的输出缓冲区并返回该缓冲区(这是浮点数据)。到目前为止,一切都很酷。

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

提前感谢您对此事的任何建议!我一直是在线几乎所有源材料的狂热阅读者,包括核心音频书、各种博客、教程等。我的应用程序的最终目标是通过耳机向用户实时播放精细化的音频,因此写入文件的东西目前仅用于测试。谢谢!

4

1 回答 1

2

您对第 3 步所说的话向我表明您正在将一组短裤解释为一组浮点数?如果是这样,我们找到了您的麻烦的原因。您可以将短值一个一个地分配到一个浮点数组中吗?那应该解决它。

它看起来像是指向一组短裤mDatavoid *将此指针转换为 afloat *不会将基础数据更改为,float但您的音频处理函数会将它们视为它们。但是,floatshort以完全不同的方式存储,因此您在该函数中执行的数学运算将在与您的真实输入信号无关的非常不同的值上运行。要通过实验对此进行调查,请尝试以下操作:

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

指针不指示它void指向的数据类型,因此错误地认为它指向float值。请注意,ashort是 2 字节宽,但 afloat是 4 字节宽。巧合的是,您的代码没有因访问冲突而崩溃。解释为float上面的数组只够长两个值。让我们看看第一个值:

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

其输出将说明您如何获得粗略-27158 == 23.198200而不是预期。发生了两件有问题的事情。首先,不是。其次,浮点数的“一和零”的存储方式与整数非常不同。请参阅http://en.wikipedia.org/wiki/Single_precision_floating-point_format-27158.0f23.2fsizeof(float)sizeof(short)

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

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

您会看到,您很可能必须short在调用granularizeWithData: with:. 所以第二种解决方案是进行所有处理,short尽管从你写的内容来看,我想你不会喜欢后一种方法。

于 2013-01-08T04:48:54.840 回答