Android NDK: работа с OpenAL и постепенная подгрузка WAV

В прошлой статье показал, как можно работать с 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’е.