В прошлой статье показал, как можно работать с WAV и Ogg форматами в ndk с помощью OpenaAL. Вот только в той реализации файлы целиком в память грузились. Кое-что дописал для работы с WAV, теперь файл можно грузить кусками по мере необходимости.
Вообще, изначально пытался создать несколько буферов, а потом, когда заканчивает играть один, переключался на другой. При таком способе в момент переключения между буферами слышен щелчок, так что этот вариант пришлось сразу отбросить. Как оказалось, в OpenAL можно сразу очередь буферов настроить (надо приучить себя сначала документацию читать, перед тем, как свои костыли городить).
Небольшое отступление
Этот код по работе с OpenAL уже вышел за рамки туториала, поэтому решил как отдельный проект сделать. Может позже по фану и работу с другими форматами сделаю (с тем же mp3, к примеру). Сам проект можно посмотреть на github’е.
Для стриминга начальную инициализацию необходимо слегка поменять:
//создаём сорс и буферы
alGenBuffers(BUFF_COUNT, buffers);
alGenSources(1, &source);
if(alGetError() != AL_NO_ERROR){
LOGI("Error generating :(\n");
return;
}
//создаём буферы
for(int i=0;i<BUFF_COUNT;++i)
creatBuffer(i);
if(alGetError() != AL_NO_ERROR) {
LOGI("Error loading :(\n");
return ;
}
//ставим в очередь к источнику source буферы
alSourceQueueBuffers(source, BUFF_COUNT, buffers);
if(alGetError() != AL_NO_ERROR) {
LOGI("Error alSourceQueueBuffers :(\n");
return;
}
Создаём столько буферов, сколько нам необходимо.
void OALWav ::creatBuffer(int index){
//определям размер данных для чтения
int size = Min(BUFFER_SIZE, header.dataSize -curPos);
//читаем чанки
unsigned char * data = readRiffs(size);
if(!data)
return;
//создаём буферы
createBufferFromWave(data,size, index);
}
Ну и ещё метод по чтению куска файла:
Чтение куска WAV файла
unsigned char* OALWav::readRiffs(int size){
if(curPos>=header.dataSize) {
f.close();
return 0;
}
if (!(
// Заголовки должны быть валидны.
// Проблема в том, что не всегда так.
// Многие конвертеры недобросовестные пихают в эти заголовки свои логотипы =/
memcmp("RIFF",header.riff,4) ||
memcmp("WAVE",header.wave,4) ||
memcmp("fmt ",header.fmt,4) ||
memcmp("data",header.data,4)
))
{
if (buf){
int r = f.read(buf,size,1);
if(r){
curPos +=r;
return buf;
}
free(buf);
}
}
f.close();
return 0;
}
Теперь остаётся только проиграть аудио файл:
//начинаем играть
alSourcePlay(source);
//если ещё не весь файл прочитали
while(curPos<header.dataSize) {
ALint val;
ALuint buffer;
//проверяем, какие буферы уже отыграли
alGetSourcei(source, AL_BUFFERS_PROCESSED, &val);
if(val <= 0)
continue;
//для каждого проигранного буфера
while(val--) {
//размер данных для чтения
int size = Min(BUFFER_SIZE, header.dataSize -curPos);
//читаем новые чанки
unsigned char * data = readRiffs(size);
//дропаем из очереди отыгравший буфер
alSourceUnqueueBuffers(source, 1, &buffer);
//создаём его по новой с новыми чанками
alBufferData(buffer, format, data, size, header.samplesPerSec);
//ставим в очередь к источнику source буферы
alSourceQueueBuffers(source, 1, &buffer);
if(alGetError() != AL_NO_ERROR) {
LOGI("Error buffering :(\n");
}
}
}
Надо бы тоже самое и для Ogg сделать, как время будет. Сам проект, как уже выше писал, можно посмотреть на github’е.