У меня есть вопрос относительно приложения для синтеза звука, над которым я работаю. Я пытаюсь прочитать аудиофайл, создать рандомизированные «зерна», используя методы гранулярного синтеза, поместите их в выходной буфер и затем сможете воспроизвести это для пользователя, используя 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). Так как это типа *), Я хочу преобразовать это в плавающие данные, что очевидно для манипуляций со звуком. Прежде чем я установил цикл for, который просто перебирал буфер и приводил каждый образец к плавающей точке *. Это казалось ненужным, так что теперь я передаю свой буфер .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 содержит несколько щелчков, и когда я открываю его в логике, похоже, что существует много псевдонимов. Это имеет смысл, если я пытаюсь отправить данные с плавающей запятой, но происходит какое-то преобразование, о котором я не знаю.
Заранее спасибо за любые советы по этому вопросу! Я был заядлым читателем практически всех исходных материалов в Интернете, включая Core Audio Book, различные блоги, учебные пособия и т. Д. Конечная цель моего приложения — воспроизводить детализированный звук в реальном времени для пользователя с наушниками, чтобы запись в файл сейчас используется только для тестирования. Спасибо!
То, что вы говорите о шаге 3, подсказывает мне, что вы интерпретируете массив шорт как массив чисел с плавающей точкой? Если это так, мы нашли причину вашей проблемы. Можете ли вы присваивать короткие значения одно за другим в массиве с плавающей точкой? Это должно исправить это.
Это выглядит как mData
это void *
указывая на массив шорт. Приведение этого указателя к float *
не меняет базовые данные в float
но ваша функция обработки звука будет обрабатывать их так, как если бы они были. Тем не мение, float
а также short
значения хранятся совершенно по-разному, поэтому математика, которую вы выполняете в этой функции, будет работать с очень разными значениями, которые не имеют ничего общего с вашим истинным входным сигналом. Чтобы исследовать это экспериментально, попробуйте следующее:
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
иллюстрирующий, как вместо ожидаемого -27158.0f
вы получаете примерно 23.2f
, Произошли две проблемные вещи. Первый, 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);
Вы видите, что, скорее всего, вам придется конвертировать все обратно в short
после вашего звонка granularizeWithData: with:
, Таким образом, второе решение было бы сделать всю обработку в short
хотя из того, что ты пишешь, я полагаю, тебе не понравится этот последний подход.
Других решений пока нет …