【问题标题】:Granular Synthesis in iOS 6 using AudioFileServicesiOS 6 中使用 AudioFileServices 进行粒度合成
【发布时间】:2012-12-24 01:57:42
【问题描述】:

我有一个关于我正在开发的声音合成应用程序的问题。我正在尝试读取音频文件,使用granular synthesis techniques 创建随机“颗粒”,将它们放入输出缓冲区,然后能够使用 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 中打开它时,看起来有很多别名。如果我试图向它发送浮点数据,这是有道理的,但发生了某种我不知道的转换。

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

【问题讨论】:

  • 我注意到了你的推文,所以我认为你已经解决了这个问题,但如果你需要效果,我建议将NovocaineNVDSP 结合使用。这比尝试学习 CoreAudio/AudioUnits 容易得多,只需跳过它并开始创建! ;)
  • 感谢@bartolsthoorn 的回复。做得很好!

标签: c++ iphone objective-c audio core-audio


【解决方案1】:

您对第 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 == %f\n", data[0], pfData[0]);

这个输出将是-27158 == 23.198200,说明您如何获得大致的23.2f,而不是预期的-27158.0f。发生了两件有问题的事情。首先,sizeof(float) 不是sizeof(short)。其次,浮点数的“一和零”的存储方式与整数非常不同。见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 中进行所有处理,尽管从你写的内容来看,我想你不会喜欢后一种方法。

【讨论】:

  • 感谢您的回复。我最初定义了以下内容:#define kSInt16ToFloatScalar (1.0 / 32768.0) 在 for 循环中,我将每个样本值乘以这个宏。然而,不管怎样,我似乎得到了相同的值,所以我把它拿出来了。你说的是有道理的,但是有没有一种将 mData (void * 短裤数组)转换为浮点数据的常用方法?此外,使用 Float32 会比简单的浮点数据更有效/更容易/等吗?提前致谢!
  • 一个简单的循环就可以了。它非常基本,因此很有效。 floatFloat32 在这里是一样的。你的问题就是一个很好的例子。稍后将添加更多解释。
猜你喜欢
  • 2012-04-25
  • 1970-01-01
  • 1970-01-01
  • 2020-07-27
  • 2019-05-20
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多