Программирование 3D-звука при помощи библиотеки OpenAL

Библиотека OpenAL (Open Audio Library), разработанная и поддерживаемая компаниями Creative Labs и Loki Entertainment, представляет собой бесплатную, кроссплатформенную библиотеку для работы с 3D-звуком с открытым кодом. Существуют версии OpenAL для платформ Mac OS 8/9, Mac OS X, Linux, BSD, Solaris, IRIX и M$ Windows.

Эта библиотека была использована в ряде игр, таких как Unreal II, Unreal Tournament 2003/2004, версии Doom III для Linux.

Большими плюсами этой библиотеки (кроме поддержки такой компании как Creative Labs) является как ее простота, так и то, что по стилю она очень сильно напоминает библиотеку OpenGL. Поэтому для программистов, уже знакомых с OpenGL, освоение OpenAL становится очень простым и быстрым.

Для начала работы с OpenAL скачайте OpenAL SDK для используемой Вами платформы. Все примеры к данной статье компилировались как для Windows, так и для Linux.

Основными понятиями OpenAL являются аудиоустройство (audio device), аудиоконтекст (audio context), слушатель (listener), источники звука (sources) и буфера (buffers).

Понятие контекста в OpenAL очень близко к своему аналогу в OpenGL. Все производимые изменения параметров происходят в пределах определенного контекста, связанного с аудиоустройством. Возможно наличие нескольких аудиоконтекстов для одного аудиоустройства, между которыми можно переключаться.

Для простейших примеров всю работу с аудио устройствами и контекстами можно переложить на входящую в состав OpenAL SDK библиотеку alut.

Библиотека OpenAL позволяет задавать в трехмерном пространстве различные источники звука и слушателя. И для каждого источника звука и для слушателя можно задать ряд параметров (таких как, положение, скорость и т.п.), влияющих на получающийся в результате звук.

Библиотека OpenAL на основе этой информации осуществляет обработку звука (учитывая при этом как взаимное расположение слушателя и источников звука, так и их скорости). После обработки происходит смешение звука от различных источников для получение выходного сигнала для каждого из выходных каналов.

Кроме интуитивно понятных понятий слушателя и источников, в OpenAL важную роль играет понятие буфера. Именно посредством буферов осуществляется задание непосредственных звуковых данных (Raw PCM), которые должны выводится для каждого из источников.

Можно для источника задать сразу несколько буферов, организовав их в очередь. Тогда после того как все данные из одного буфера будут выведены, происходит автоматическое переключение на следующий буфер.

Рассмотрим сначала несколько простых примеров работы с OpenAL, основанных на использовании библиотеки alut, в конце статьи будут приведены несколько классов на С++, заметно облегчающих использование OpenAL.

Простейший случай - один неподвижный источник звука

В этом и ряде следующих примеров мы будем активно использовать библиотеку alut для упрощения работы с OpenAL.

Первым шагом при работе с OpenAL является инициализация - получение устройства и создание контекста. При использовании alut для этого можно использовать всего одну функцию - alutInit.

void alutInit ( int * argc, char * argv [] );

Входными параметрами этой функции являются указатель на аргумент argc и аргумент argv функции main (полностью аналогично функции glutInit).

Эта функция осуществляет создание устройства (под M$ Windows обычно используется DirectSound3D) и контекста для него.

Для проверки успешности вызова alutInit (как и практически любой функции OpenAL) служит функция alGetError. При отсутствии ошибок она возвращает значение AL_NO_ERROR.

int main ( int argc, char * argv [] )
{
    alutInit ( &argc, argv );
    
    if ( alGetError () != AL_NO_ERROR )
    {
        printf ( "Ошибка инициализации\n" );
        exit   ( 1 );
    }
    
    . . . . . . .
    
}

Для завершения работы с OpenAL можно использовать функция alutExit:

void alutExit ();

После инициализации OpenAL необходимо создать источник звука и буфер для него, а также задать их свойства.

Каждый источник звука (и буфер) задается беззнаковым целым числом (типа ALuint). Для создания источников и буферов служат следующие функции.

ALvoid alGenSources ( ALsizei n, ALuint * sources );
ALvoid alGenBuffers ( ALsizei n, ALuint * buffers );

Каждая из этих функций принимает два параметра - сколько соответствующих объектов (источников или буферов) надо создать (n) и указатель на массив, в который надо поместить идентификаторы созданных объектов (sourcesили buffers).

Как и ранее, для проверки успешности создания объектов следует использовать функцию alGetError.

ALuint buffer;
ALuint source;

alGenBuffers ( 1, &buffer );

if ( alGetError () != AL_NO_ERROR )
	exit ( 1 );

alGenSources ( 1, &source );

if ( alGetError () != AL_NO_ERROR )
	exit ( 1 );

Следующие функции служат для уничтожения созданных источников и буферов.

ALvoid alDeleteBuffers ( ALsizei n, ALuint * buffers );
ALvoid alDeleteSources ( ALsizei n, ALuint * sources );

Сама библиотека OpenAL не содержит в себе функций для работы со звуковыми файлами различных форматов (таких как wav, mp3, ogg и т.п.). Задачей пользователя является декодирование таких файлов (к счастью для этого хватает библиотек) и передача декодированных PCM-данных в OpenAL.

Однако для удобства в библиотеку alut входят функции, позволяющие загружать звуковые данные из популярного формата wav.

ALboolean alutLoadWAVFile ( const char * fileName, ALsizei * format, ALsizei * size, 
                            ALsizei * bits, ALsizei * freq, ALboolean *loop );
ALvoid    alutUnloadWAV   ( ALenum format, ALvoid * data, ALsizei size, ALsizei freq );

Входной параметр fileName задает имя файла, из которого следует загрузить звуковые данные. Параметр format является указателем на переменную, в которую будет записан формат звуковых данных (см. далее описание функции alBufferData). Параметр size является указателем на переменную, в которую будет записан объем декодированных данных в байтах.

Параметр bits является указателем на переменную, в которую будет записано количество бит, служащее для представления единицы данных (sample) - 8 или 16 бит. Параметр freq задает частоту воспроизведения данных. Последний параметр loop определяет надо ли зациклить данный звук (т.е. постоянно повторять).

Функция alutUnloadWAVFile служит для освобождения памяти, выделенной при декодировании wav-файла.

Используя эти функции, легко осуществлять загрузку звуковых данных в буфер. Для этого в OpenAL служит функция alBufferData.

ALvoid alBufferData ( ALuint buffer, ALenum format, ALvoid * data, ALsizei size, ALsizei freq );

Параметр buffer является идентификатором буфера, данные для которого задаются этой командой. Параметр format определяет формат звуковых данных и может принимать одно из следующих значений - AL_FORMAT_MONO8, AL_FORMAT_MONO16, AL_FORMAT_STEREO8 и AL_FORMAT_STEREO16.

Параметр data является указателем на буфер со звуковыми данными. Обратите внимание, что данная команда копирует данные, поэтому после данной команды память, на которую указывает данный параметр можно изменять или освободить.

Остальные параметры имеют тот же смысл, что и в команде alutLoadWAVFile.

Ниже приводится фрагмент кода, осуществляющий загрузку wav-файла в созданный буфер.

ALenum      format;
ALsizei     size;
ALvoid    * data;
ALsizei     freq;
ALboolean   loop;

alutLoadWAVFile ( fileName, &format, &data, &size, &freq, &loop );
alBufferData    ( buffer, format, data, size, freq );
alutUnloadWAV   ( format, data, size, freq );

После создания буфера и заполнения его звуковыми данными, следует задать параметры источника звука. Для этого служат следующие функции.

ALvoid alSourcef  ( ALuint source, ALenum pname, ALfloat value );
ALvoid alSourcefv ( ALuint source, ALenum pname, ALfloat * values );
ALvoid alSource3f ( ALuint source, ALenum pname, ALfloat v1, ALfloat v2, ALfloat v3 );
ALvoid alSourcei  ( ALuint source, ALenum pname, ALint value );

Параметр source задает для какого именно источника звука задается значение.

Параметр pname задает имя изменяемого свойства. В следующей таблице приводятся допустимые значения для данного параметра.

Таблица 1. Параметры источника.

КонстантаИспользуется в функцияхЗначение
AL_PITCHalSourcefПараметр pitch
AL_GAINalSourcefПараметр, используемый в модели учета расстояния
AL_MAX_DISTANCEalSourcefМаксимальное расстояние, далее него уже не будет происходить дальнейшего ослабления звука
AL_ROLLOFF_FACTORalSourcefПараметр rolloff, используемый в модели учета расстояния
AL_REFERENCE_DISTANCEalSourcefРасстояние, на котором сила звука уменьшается вдвое (до воздействия параметра rolloff
AL_MIN_GAINalSourcefМинимальное значение параметра gain
AL_MAX_GAINalSourcefМаксимальное значение параметра gain
AL_CONE_OUTER_GAINalSourcefЗначение параметра gain вне конуса звука
AL_CONE_INNER_ANGLEalSourceiЗначение параметра gain внутри конуса звука
AL_CONE_OUTER_ANGLEalSourceiУгол конуса для источника звука, по умолчанию 360
AL_POSITIONalSourcefv
alSource3f
Положение источника звука
AL_VELOCITYalSourcefv
alSource3f
Скорость источника звука
AL_DIRECTIONalSourcefv
alSource3f
Направление источника
AL_SOURCE_RELATIVEalSourceiОпределяет, задаются ли координаты источников относительно слушателя или нет
AL_LOOPINGalSourceiВключить ли режим повторения (looping)для источника
AL_BUFFERalSourceiИдентификатор используемого источника звука
AL_SOURCE_STATEalSourceiТекущее состояние источника (AL_PLAYING, AL_STOPPED,...)

Следующий пример - задание основных параметров источника звука.

Vector3D sourcePos ( 0, 0, 2 );           // source position
Vector3D sourceVel ( 0, 0, 0 );           // source velocity (used for doppler effect)

alSourcei ( source, AL_BUFFER,   buffer    );
alSourcef ( source, AL_PITCH,    1.0f      );
alSourcef ( source, AL_GAIN,     1.0f      );
alSourcefv( source, AL_POSITION, sourcePos );
alSourcefv( source, AL_VELOCITY, sourceVel );
alSourcei ( source, AL_LOOPING,  AL_TRUE   );

В отличии от источников звука и буферов, слушатель существует всегда (и он всегда один). Он также обладает рядом свойств, которые можно устанавливать при помощи следующих команд:

ALvoid alListenerf  ( ALenum pname, ALfloat value );
ALvoid alListener3f ( ALenum pname, ALfloat v1, ALfloat v2, ALfloat v3 );
ALvoid alListenerfv ( ALenum pname, ALfloat * values );
ALvoid alListeneri  ( ALenum pname, ALint value );

Параметр pname задает имя изменяемого свойства. В следующей таблице приводятся допустимые значения для данного параметра.

Таблица 2. Параметры слушателя.

КонстантаИспользуется в функцияхЗначение
AL_POSITIONalListener3f
alListenerfv
Координаты слушателя
AL_VELOCITYalListener3f
alListenerfv
скорость слушателя
AL_ORIENTATIONalListenerfvОриентация наблюдателя. Задается 6 числами
AL_GAINalListenerfПараметр master gain, используемый для учета расстояния

OpenAL может использовать несколько моделей учета расстояния от слушателя до источника звука. Выбор модели осуществляется при помощи следующей функции:

ALvoid alDistanceModel ( ALenum value );

Параметр value задает используемую модель и может принимать одно из следующий значений - AL_NONE, AL_INVERSE_DISTANCE и AL_INVERSE_DISTANCE_CLAMPED.

Изменение громкости звука в зависимости от расстояния в модели AL_INVERSE_DISTANCE задается следующие формулы:

f = AL_GAIN – 20*lg (1 + AL_ROLLOFF_FACTOR*(distance–AL_REFERENCE_DISTANCE)/
                                            AL_REFERENCE_DISTANCE))
f = min(f, AL_MAX_GAIN);
f = max(f, AL_MIN_GAIN);

При использовании модели AL_INVERSE_DISTANCE_CLAMPED используются следующие формулы:

distance = max(distance, AL_REFERENCE_DISTANCE);
distance = min(distance, AL_MAX_DISTANCE);
f        = AL_GAIN – 20*log(1 + AL_ROLLOFF_FACTOR*(distance-AL_REFERENCE_DISTANCE)/
                                                   AL_REFERENCE_DISTANCE));
f        = min(G_dB, AL_MAX_GAIN);
f        = max(G_dB, AL_MIN_GAIN);

При использовании модели AL_NONE применяется следующая формула:

f = AL_GAIN;

Ориентация наблюдателя в OpenAL задается при помощи 6 вещественных чисел, определяющих два трехмерных вектора - at и up (см рис. 1).

setting listener orientation

Рис 1. Задание ориентации слушателя.

Следующие функции служат для управления воспроизведением звука.

ALvoid alSourcePlay    ( ALuint source );
ALvoid alSourcePlayv   ( ALsizei n, ALuint * sources );
ALvoid alSourcePause   ( ALuint source );
ALvoid alSourcePausev  ( ALsizei n, ALuint * sources );
ALvoid alSourceStop    ( ALuint source );
ALvoid alSourceStopv   ( ALsizei n, ALuint * sources );
ALvoid alSourceRewind  ( ALuint source );
ALvoid alSourceRewindv ( ALsizei n, ALuint * sources );

Действия данных функций понятны из названия. Обратите внимание, на наличие функций, позволяющих применять выбранную операцию сразу к целому набору источников - параметр n задает количество источников звука в массиве sources.

Ниже приводится полный исходный код простейшего примера использования OpenAL.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include <al.h>
#include <alc.h>
#include <alut.h>

#include "Vector3D.h"

#ifndef	_WIN32
    #include "kbhit.h"
#else
    #include <conio.h>
    
    #define  initKeyboard()
    #define  closeKeyboard()
#endif

ALuint buffer;                            // buffer for sound data
ALuint source;                            // source of sound

Vector3D  sourcePos ( 0, 0, 2 );          // source position
Vector3D  sourceVel ( 0, 0, 0 );          // source velocity (used for Doppler effect)

Vector3D  listenerPos ( 0, 0, 0 );        // listeners position
Vector3D  listenerVel ( 0, 0, 0 );        // listern's velocity

                                          // listeners orientation (forward, up)
float 		listenerOri [] = { 0, 0, -1,  0, 1, 0 };

bool	setupSource ( char * fileName )
{
    ALenum      format;
    ALsizei     size;
    ALvoid    * data;
    ALsizei     freq;
    ALboolean   loop;

    alGenBuffers ( 1, &buffer );          // create buffer

    if ( alGetError () != AL_NO_ERROR )
        return false;
                                          // load .wav data using alut
    alutLoadWAVFile ( (ALbyte *)fileName, &format, &data, &size, &freq, &loop );
    alBufferData    ( buffer, format, data, size, freq );
    alutUnloadWAV   ( format, data, size, freq );

    alGenSources ( 1, &source );          // create source

    if ( alGetError () != AL_NO_ERROR )
        return false;
                                          // setup source params
    alSourcei ( source, AL_BUFFER,   buffer    );
    alSourcef ( source, AL_PITCH,    1.0f      );
    alSourcef ( source, AL_GAIN,     1.0f      );
    alSourcefv( source, AL_POSITION, sourcePos );
    alSourcefv( source, AL_VELOCITY, sourceVel );
    alSourcei ( source, AL_LOOPING,  loop      );

    return true;
}

int main ( int argc, char  * argv [] )
{
    initKeyboard ();                      // for Linux setup keyboard raw mode
    alutInit     ( NULL, 0 );             // initialize OpenAL and clear the error bit.
    alGetError   ();

    char * fileName = argv [1];

    if ( argc < 2 )
        fileName = "wavdata/Footsteps.wav";

    if ( !setupSource ( fileName ) )      // load the wav data.
    {
        printf ( "Error loading data \"%s\"\n.", fileName );

        return 0;
    }

    alListenerfv ( AL_POSITION,    listenerPos );
    alListenerfv ( AL_VELOCITY,    listenerVel );
    alListenerfv ( AL_ORIENTATION, listenerOri );

    printf ( "Use p to play sound, s to stop sound, h to pause sound and q to quit\n" );

    for ( char ch = '\0'; ch != 'q'; )
    {
        ch = getche ();

        switch ( ch )
       {
            case 'p':
                    alSourcePlay ( source );
                    break;

            case 's':
                    alSourceStop ( source );
                    break;

            case 'h':
                    alSourcePause ( source );
                    break;
        }
    }

    alDeleteBuffers ( 1, &buffer );
    alDeleteSources ( 1, &source );
    alutExit        ();
    closeKeyboard   ();                   // for Linux setup keyboard raw mode
	
	return 0;
}

Функции initKeyboard и closeKeyboard служат для приведения клавиатуры под Linux в небуферизованный режим, используемый реализациями функций kbhit и getche.

Двигающиеся источники звука

Библиотека OpenAL осуществляет учет как расстояния до источника звука, так и его скорости (а также и скорости самого слушателя).

Расстояние до источника звука влияет на громкость звука, а для учета скоростей используется эффект Допплера (т.е. изменение воспринимаемой слушателем частоты звука).

Также учитывается ориентация слушателя относительно источника звука.

Обратите внимание, что все эти действия относятся только к моно-источникам звука. В случае стерео-источников происходит просто их проигрывания без учета расстояний, скоростей и ориентации.

Для организации движения источника звука достаточно просто периодически изменять его положение в пространстве. Обратите внимание, что для источника звука (как и для слушателя) положение и скорость никак между собой не связаны - скорость используется только для эффекта Допплера, перемещать источники (и наблюдателя) должны Вы сами.

На следующем листинге приводится пример, в котором источник звука двигается по круговой орбите вокруг наблюдателя.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include <al.h>
#include <alc.h>
#include <alut.h>

#include "Vector3D.h"

#ifndef	_WIN32
    #include "kbhit.h"
#else
    #include <conio.h>
    
    #define initKeyboard()
    #define closeKeyboard()
#endif

ALuint buffer;                            // buffer for sound data
ALuint source;                            // source of sound

Vector3D  sourcePos   ( 0, 0, 2 );        // source position
Vector3D  sourceVel   ( 0, 0, 0 );        // source velocity (used for doppler effect)
Vector3D  listenerPos ( 0, 0, 0 );        // listeners position
Vector3D  listenerVel ( 0, 0, 0 );        // listern's velocity

                                          // listeners orientation (forward, up)
float     listenerOri [] = { 0, 0, -1,  0, 1, 0 };

bool  setupSource ( char * fileName )
{
    ALenum      format;
    ALsizei     size;
    ALvoid    * data;
    ALsizei     freq;
    ALboolean   loop;

    alGenBuffers ( 1, &buffer );          // create buffer

    if ( alGetError () != AL_NO_ERROR )
        return false;
                                          // load .wav data using alut
    alutLoadWAVFile ( (ALbyte *)fileName, &format, &data, &size, &freq, &loop );
    alBufferData    ( buffer, format, data, size, freq );
    alutUnloadWAV   ( format, data, size, freq );

    alGenSources ( 1, &source );          // create source

    if ( alGetError () != AL_NO_ERROR )
        return false;
                                          // setup source params
    alSourcei ( source, AL_BUFFER,   buffer    );
    alSourcef ( source, AL_PITCH,    1.0f      );
    alSourcef ( source, AL_GAIN,     1.0f      );
    alSourcefv( source, AL_POSITION, sourcePos );
    alSourcefv( source, AL_VELOCITY, sourceVel );
    alSourcei ( source, AL_LOOPING,  AL_TRUE    );

    return true;
}

int main ( int argc, char  * argv [] )
{
    initKeyboard ();
    alutInit     ( NULL, 0 );             // initialize OpenAL and clear the error bit.
    alGetError   ();

    char * fileName = argv [1];

    if ( argc < 2 )
        fileName = "wavdata/Footsteps.wav";

    if ( !setupSource ( fileName ) )      // load the wav data.
    {
        printf ( "Error loading data \"%s\"\n.", fileName );

        return 0;
    }

    alListenerfv ( AL_POSITION,    listenerPos );
    alListenerfv ( AL_VELOCITY,    listenerVel );
    alListenerfv ( AL_ORIENTATION, listenerOri );

    printf ( "Press q to quit\n" );
	
    alSourcePlay ( source );

    while ( !kbhit () )
    {
        float  secs = clock () / CLOCKS_PER_SEC;

        sourcePos.x = 5*sin(secs*0.3);
        sourcePos.z = 5*sin(secs*0.3);

        alSourcefv ( source, AL_POSITION, sourcePos );
    }

    getche ();

    alDeleteBuffers ( 1, &buffer );
    alDeleteSources ( 1, &source );
    alutExit        ();
    closeKeyboard   ();
	
    return 0;
}

С помощью OpenAL легко можно осуществлять смешение звуков от нескольких источников - для этого достаточно просто создать соответствующие источники и начать воспроизведение каждого из них.

Использование нескольких буферов для одного источника звука

Если мы хотим воспроизводить звуковые данные из mp3 или ogg файлов, то возникает ситуация, когда полностью распакованные данные из такого файла будут занимать десятки мегабайт при сравнительно небольшом объеме исходного файла.

Поэтому в таком случае желательно иметь возможность не полностью распаковывать весь сжатый файл, а делать это по частям, по мере проигрывания данных.

Именно такую возможность и предоставляет OpenAL через механизм выстраивания нескольких буферов для одного источника звука при помощи функций:

ALvoid alSourceQueueBuffers   ( ALuint source, ALsizei n, ALuint * buffers );
ALvoid alSourceUnqueueBuffers ( ALuint source, ALsizei n, ALuint * buffers );

Первая из этих функций ставит в очередь к источнику source сразу n буферов, идентификаторы которых передаются в массиве buffers. По мере проигрывания данных будет происходить автоматическое переключение к следующему буферу из очереди.

Буфера, звуковые данные из которых уже были воспроизведены и больше не нужны, удаляются из очереди при помощи функции alSourceUnqueueBuffers.

Узнать сколько буферов для данного источника уже были обработаны (т.е. больше не нужны и могут быть удалены из очереди) можно при помощи следующего фрагмента кода.

ALuint	processed;

alGetSourceiv ( source, AL_BUFFERS_PROCESSED, &processed );

После этого вызова в переменной processed будет содержаться количество буферов, которые можно удалить из очереди и переиспользовать (т.е. загрузить новыми звуковыми данными и опять поставить в очередь).

Рекомендуется при использовании очереди для задания буферов всегда использовать команду alSourceQueueBuffers и использовать буфера с одинаковым форматом звуковых данных.

Непосредственная работа с устройствами и контекстами (без использования alut)

Для работы с OpenAL без использования alut необходимо открыть подходящее аудиоустройство и создать для него аудиоконтекст.

Для работы с аудиоустройствами в OpenAL используются следующие функции:

ALCdevice * alcOpenDevice  ( const ALubyte * tokstr );
void        alcCloseDevice ( ALCdevice     * dev );

В качестве параметра tokstr выступает строка, описывающая устройство, например "DirectSound3D". Вместо строки можно передать значение NULL. В этом случае OpenAL сам подберет подходящее устройство (под M$ Windows это обычно DirectSound3D).

Следующим шагом после открытия аудиоустройства является создание для него аудиоконтекста и выбор его в качестве текущего.

Для работы с контекстами в OpenAL служат следующие функции:

void  * alcCreateContext      ( ALCdevice * dev, ALint * attrlist );
ALCenum alcDestroyContext     ( ALvoid    * alcHandle );
ALCenum alcMakeContextCurrent ( ALvoid    * alcHandle );
void    alcSuspendContext     ( ALvoid    * alcHandle );
void    alcProcessContext     ( ALvoid    * alcHandle );

Функция alcCreateContext используется для создание нового аудиоконтекста. Ее первым параметром служит указатель на аудиоустройства, для которого создается аудиоконтекст. Второй параметр является указателем на список атрибутов. Обычно он используется, когда есть несколько аудиоконтекстов. В случае одиночного контекста проще всего в качестве второго параметра использовать значения NULL.

Функция alcDestroyContext служит для уничтожения созданного контекста, она возвращает код ошибки.

Функция alcMakeContextCurrent позволяет выбрать заданный аудиоконтекст в качестве текущего.

Функции alcSuspendContext и alcProcessContext позволяют приостановить обработку заданного аудиоконтекста и снова ее возобновить. В частности приостановление аудиоконтекста приводит к приостановке воспроизведения источников звука, связанных с данным контекстом.

Таким образом, для инициализации OpenAL можно использовать следующий фрагмент кода

ALCdevice  * device  = NULL;
ALCcontext * context = NULL;

device  = alcOpenDevice    ( NULL );
context = alcCreateContext ( device, NULL );

alcMakeCurrent ( context );

Для завершения работы с OpenAL служит следующий фрагмент кода:

alcMakeCurrent    ( NULL    );
alcDestroyContext ( context );
alcCloseDevice    ( device  );

Подобно библиотеке OpenGL в OpenAL также есть поддержка расширений. Для получения списка всех поддерживаемых расширением следует использовать функцию alcGetString:

const char * exts = (const char *) alcGetString ( device, ALC_EXTENSIONS );

Для проверки поддерживается ли данное расширение можно использовать следующую функцию.

ALboolean alcIsExtensionPresent ( ALCdevice * device, ALubyte * extName );

В случае поддержки расширения можно использовать функцию alcGetProcAddress для получения адресов вводимых расширением функций по их именам:

ALvoid * alcGetProcAddress ( ALCdevice * device, ALubyte * funcName );

Заворачиваем все в классы

Для облегчения работы будет очень удобно завернуть все основные используемые понятия в объекты. Явными кандидатами на такие объекты является аудиоконтекст (и аудиустройство, связанное с ним), источники данных и файлы со звуковыми данными, слушатель.

Отдельно заворачивать буфера в объекты не имеет смысла, так как они используются только в связи с источниками звука и файлами со звуковыми данными.

Используемые классы и их взаимоотношения изображены на следующем рисунке.

OpenAL object wrapper classes

Рис 2. Диаграмма используемых классов.

Ниже приводится описание класса OpenAlContext, обеспечивающего как инициализацию/деинициализацию OpenAL, так и поддержку основных свойств слушателя.

class	OpenAlContext
{
protected:
    ALCdevice  * device;                  // device & context for working with OpenAL
    ALCcontext * context;
    Vector3D     pos;                     // listener's position
    Vector3D     velocity;                // listeners velocity (for Doppler effect)
    Vector3D     viewDir;                 // listener's orientation - view and up vectors
    Vector3D     upDir;

public:
    OpenAlContext  ();
    ~OpenAlContext ();

    bool  isOk () const
    {
        return device != NULL && context != NULL;
    }

    const Vector3D& getPos () const
    {
        return pos;
    }

    const Vector3D& getVelocity () const
    {
        return velocity;
    }

    const Vector3D& getViewDir () const
    {
         return viewDir;
    }

    const Vector3D& getUpDir () const
    {
        return upDir;
    }

    void    moveTo         ( const Vector3D& newPos );
    void    moveBy         ( const Vector3D& delta  );
    void    setVelocity    ( const Vector3D& newVel );
    void    setOrientation ( const Vector3D& view, const Vector3D& up );

    static  SoundData * open            ( const char * fileName );
    static  bool        registerSound   ( Sound * sound );
    static  bool        unregisterSound ( Sound * sound );
    static  void        updateSounds    ();
};

Для работы со источниками звука используется класс Sound.

class	Sound
{
protected:
    SoundData * dataSource;
    bool        looping;
    bool        stream;
    ALuint      format;
    ALuint      source;
    ALuint      buffer [2];               // id's of OpenAL buffers
    int         currentBuffer;
    byte      * buf;                      // buffer for sound data
    Vector3D    pos;                      // sound source position
    Vector3D    velocity;                 // sound source velocity
    float       pitch;
    float       gain;

public:
    Sound  ( SoundData * data, bool theLoop = false );
    ~Sound ();

    const Vector3D& getPos () const
    {
        return pos;
    }

    const Vector3D& getVelocity () const
    {
        return velocity;
    }

    float  getGain () const
    {
        return gain;
    }

    float  getPitch () const
    {
        return pitch;
    }

    bool  getLooping () const
    {
        return looping;
    }

    bool    isPlaying   () const;
    bool    atEnd       () const;

    void    setPos      ( const Vector3D& newPos );
    void    setVelocity ( const Vector3D& newVel );
    void    setPitch    ( float newPitch );
    void    setGain     ( float newGain  );
    void    setLooping  ( bool flag      );

    void    play       ();
    void    pause      ();
    void    stop       ();
    void    rewind     ();
    void    update     ();

    enum
    {
        BUFFER_SIZE = 65536               // buffer size for unpacked PCM data
    };
};

За счет использования данных классов работа с OpenAL предельно упрощается - ниже приводится листинг программы, использующей введенные классы для вывода звука (как из wav, так и из mp3 и ogg файлов).

#include  <stdio.h>
#include  <stdlib.h>

#ifndef	_WIN32
    #include  "kbhit.h"
#else
    #include  <windows.h>
    #include  <conio.h>
    #include  <process.h>

    #define	initKeyboard()
    #define	closeKeyboard()
#endif

#include "OpenAlContext.h"
#include "Sound.h"
#include "SoundData.h"

int main ()
{
    OpenAlContext   context;
    SoundData     * data = OpenAlContext :: open ( "wavdata/Chi Mai.mp3" );

    if ( data == NULL )
        return 1;
		
    initKeyboard ();
	
    Sound * sound = new Sound ( data, true );

    sound -> play ();

    for ( ; ; )
    {
        sound -> update ();

        if ( kbhit () )
            break;

        int i, j;
                                          // small delay
        for ( i = j = 0; i < 1000; i++ )
            j += i*j + 1;	    
    }
	
    closeKeyboard ();

    return 0;
}

Полный исходный код к данной статье (включая реализации всех вводимых классов) можно скачать здесь.

Для компилирования и работы этих примеров кроме OpenAL SKD, Вам также понадобятся библиотека libmad (используемая для декодирования mp3-файлов) и библиотеки Ogg Vorbis и Ogg Bitstream, используемые для декодирования ogg файлов.

Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.

Valid HTML 4.01 Transitional

Напиши мне
Используются технологии uCoz