Вопрос или проблема
Я пишу пользовательский аудиосмешиватель на C++, который использует 16-битный PCM с плавающей запятой, частоту дискретизации 44100 Гц и 2 канала.
Проблема, с которой я столкнулся, заключается в том, что каждый раз, когда мне нужно зациклить звуки (то есть, когда я достиг конца буфера PCM), я сбрасываю индексы выборки на 0, но я заметил, что при воспроизведении стереозвука с активным только одним каналом звуковые каналы по какой-то причине меняются местами при зацикливании. Левые образцы на самом деле воспроизводятся справа, а правые — слева. Они постоянно меняются местами каждый раз при зацикливании!
Код:
// это вызывается постоянно в аудиопотоке:
if (BuffersPlaying() < MAX_SOUND_BUFFERS /* 2 */)
for (size_t i = 0; i < NUM_SAMPLES_PER_BUFFER /* 1024 */ * NUM_CHANNELS; ++i)
{
int mixedSamples = 0;
for (size_t k = 0; k < sounds.size(); ++k)
{
Sound* sound = sounds[k];
if (!sound->playing)
continue;
uint pcmSize = waveBuffers[sound->waveIndex].size / sizeof(int16);
int16* pcmData = waveBuffers[sound->waveIndex].data;
if (sound->sampleIndex < (int)pcmSize)
{
mixedSamples += (int)(pcmData[sound->sampleIndex] * soundsVolume);
sound->sampleIndex++;
}
else if (sound->loop)
{
sound->sampleIndex = 0;
mixedSamples += (int)(pcmData[sound->sampleIndex] * soundsVolume);
}
else
{
sound->playing = false;
sound->sampleIndex = 0;
}
}
soundBuffers[currentBuffer][i] = (int16)std::clamp((int)(mixedSamples * audioVolume), -32768, 32767);
}
QueueCurrentBuffer(); // загружает текущие аудиобуферы для воспроизведения аудио API
currentBuffer++;
currentBuffer %= MAX_SOUND_BUFFERS;
Однако я заметил, что в else if (sound->loop)
, если я установлю sound->sampleIndex
в NUM_CHANNELS - 1
вместо 0
, это действительно исправляет проблему, но я не понимаю, почему. Почему это так?
Я использую XAudio2 для воспроизведения звуковых буферов.
Ответ или решение
Понимание проблемы смены каналов звука при зацикливании
В процессе разработки пользовательского аудиосмешивателя на C++, вы столкнулись с проблемой обмена звуковыми каналами при зацикливании звуковых файлов. Чтобы разобраться в этом феномене, давайте проанализируем ваш код и основные принципы работы с аудио в контексте PCM.
Анализ проблемы
Ваш код обрабатывает PCM данные формата 16-битный знак, с частотой дискретизации 44100 Гц и двухканальным стереозвуком. Проблема начинается в этом фрагменте:
else if (sound->loop)
{
sound->sampleIndex = 0;
mixedSamples += (int)(pcmData[sound->sampleIndex] * soundsVolume);
}
Когда вы обнуляете sampleIndex
в начале буфера, вы фактически всегда берете первый элемент PCM, который соответствует левому каналу звука. Так как вы работаете с стереозвуком (два канала), первый элемент будет представлять левый канал, второй – правый и так далее. Это приводит к тому, что при зацикливании звука вы постоянно берете «левый» элемент, в то время как записи на самом деле содержат данные для обоих каналов, что вызывает ненадлежащее воспроизведение.
Решение с изменением индекса
Вы упомянули, что установка sound->sampleIndex
в NUM_CHANNELS - 1
устраняет проблему. Давайте рассмотрим, почему это работает.
При использовании двухканальной записи PCM, индексы образцов в массиве pcmData
распределены следующим образом:
pcmData[0]
— левый канал первого образцаpcmData[1]
— правый канал первого образцаpcmData[2]
— левый канал второго образцаpcmData[3]
— правый канал второго образца
и так далее.
Когда вы устанавливаете sampleIndex
в NUM_CHANNELS - 1
(то есть 1 для двухканального звука), вы начинаете с правого канала последнего образца, что может временно решить проблему канального смещения. Однако это не является корректным долгосрочным решением.
Рекомендации по исправлению
Лучший подход заключается в том, чтобы следить за текущим состоянием канала и корректно обновлять sampleIndex
в зависимости от того, какой канал воспроизводится в данный момент. Вам следует корректировать индекс, чтобы он с учетом количества каналов не всегда начинался с 0, а возвращался к первому образцу соответствующего канала при зацикливании. Вот концептуальная модификация вашего кода:
else if (sound->loop)
{
sound->sampleIndex = (sound->sampleIndex + NUM_CHANNELS) % (pcmSize + NUM_CHANNELS);
}
Заключение
Правильная обработка стереозвука требует внимательного управления индексами образцов при циклическом воспроизведении. Убедитесь, что всегда правильно рассчитываете индекс для доступа к PCM данным с учетом количества каналов. Это предотвращает проблемы воспроизведения и обеспечивает качественный вывод звука, создавая более профессиональный и эффективный аудиомикшер.