Как стать автором
Обновить

Использование OpenAL на iOS. Или самый чистый звук

Доброго времени суток, %username%.

В моём последнем проекте для iPhone была задача воспроизведения максимально качественного звука.
После попыток использовать AVAudioPlayer и AudioServices и долгой переписки с заказчиком был сделан выбор в пользу Open AL.

Принцип работы с AVAudoPlayer можно легко понять из документации, а работа с AudioServices прекрасно описана в статье «Создаем приложение — «Ударная установка»».


Заголовочный файл


Для того чтобы все заработало нужно подключить следующие заголовочные файлы:
OpenAL/al.h
OpenAL/alc.h
AudioToolbox/AudioFile.h

Также необходимо объявить глобальные переменные для хранения источника аудио и буфера данных. В нашем случае, это будут два массива:
bufferArray
soundsArray

Кроме того, для работы со звуком используя библиотеку OpenAL нам придется открыть текущее устройство и создать контекст для воспроизведения. Нужно объявить соответствующие переменные:
mContext
mDevice

Также объявим метод для добавления источника аудио:
- (void) addSoundWithFileName: (NSString *) filename;

В итоге, заголовочный файл будет выглядеть так:

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioFile.h>

@interface NotePlayer : NSObject
{
ALCcontext* mContext;
ALCdevice* mDevice;

NSMutableArray *bufferArray;
NSMutableArray *soundsArray;
}

- (void) addSoundWithFileName: (NSString *) filename;

@end


Реализация


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

-(id)init
{
// Открываем девайс
mDevice = alcOpenDevice(NULL);
if (mDevice)
{
// Если девайс открыт успешно, создаем контекст
mContext=alcCreateContext(mDevice,NULL);
alcMakeContextCurrent(mContext);
}

// И инициализируем массивы
bufferStorageArray = [[NSMutableArray alloc] init];
soundsArray = [[NSMutableArray alloc] init];

[super init];
return self;
}


Далее реализуем метод для добавления аудио файла:

- (void) addSoundWithFileName: (NSString *) filename
{
NSString* fileName = [[NSBundle mainBundle] pathForResource: filename ofType:@"caf"];

// Открываем файл для чтения. Дальнейшая работа будет происходить с fileID.
AudioFileID fileID;
NSURL * afUrl = [NSURL fileURLWithPath:fileName];
OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, kAudioFileReadPermission, 0, &fileID);
if (result != 0) NSLog(@"cannot openf file: %@",fileName);

// Вычисляем размер аудио файла.
UInt64 outDataSize = 0;
UInt32 thePropSize = sizeof(UInt64);
result = noErr;
result = AudioFileGetProperty(fileID, kAudioFilePropertyAudioDataByteCount, &thePropSize, &outDataSize);
if(result != 0) NSLog(@"cannot find file size");
UInt32 fileSize = (UInt32)outDataSize;
// Выделяем память
unsigned char * data = malloc(fileSize);

// Считываем файл побайтово в data
result = noErr;
result = AudioFileReadBytes(fileID, false, 0, &fileSize, data);
AudioFileClose(fileID);
if (result != 0) NSLog(@"cannot load effect: %@",fileName);

// Получаем идентификатор буфера у OpenAL
NSUInteger bufferID;
alGenBuffers(1, &bufferID);

// Записываем аудио данные в буфер. Параметры аудио- 16 бит, моно, 22500 Гц
alBufferData(bufferID,AL_FORMAT_MONO16,data,fileSize,22500);

// Добавляем идентификатор буфера в массив для того чтобы очистить память после воспроизвидения.
[bufferArray addObject:[NSNumber numberWithUnsignedInteger:bufferID]];

NSUInteger sourceID;

// Получаем идентификатор нового аудио источника у OpenAL
alGenSources(1, &sourceID);

// Сопоставляем созданный буфер и источник
alSourcei(sourceID, AL_BUFFER, bufferID);

// Настраиваем воспроизведение
alSourcef(sourceID, AL_PITCH, 1.0f);
alSourcef(sourceID, AL_GAIN, 0.6f);

// Ну и, наконец, добавляем источник в массив.
[soundsArray addObject: [NSNumber numberWithUnsignedInteger: sourceID]];
}


В этом коде используются жестко заданные параметры для аудио файла -16 бит, моно, 22500 Гц. Возможно написать код для определения параметров, но, в большинстве случаев, это не нужно. Параметры аудио можно посмотреть в QuickTime.

Для разных параметров используйте соответствующие константы:
AL_FORMAT_MONO8
AL_FORMAT_MONO16
AL_FORMAT_STEREO8
AL_FORMAT_STEREO16

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

Воспроизведение



Осталось только вызвать функцию alSourcePlay() и передать ей указатель на нужный источник. Например:
alSourcePlay( [[soundsArray objectAtIndex: 0] unsignedIntegerValue]);

Вызовите этот метод там где Вам удобно.

Очистка памяти



После воспроизведения нужно вызвать alDeleteSources() и alDeleteBuffers() для каждого источника и буфера соответственно. Например:

for (NSNumber *sourceNumber in soundsArray)
{
NSUInteger sourceID = [sourceNumber unsignedIntegerValue];
alDeleteSources(1, &sourceID);
}
[soundsArray removeAllObjects];

for (NSNumber * bufferNumber in bufferStorageArray)
{
NSUInteger bufferID = [bufferNumber unsignedIntegerValue];
alDeleteBuffers(1, &bufferID);
}
[bufferStorageArray removeAllObjects];


Этот код можно свернуть в один цикл for, оставляю так для наглядности.

И последнее что нужно сделать: уничтожить контекст, закрыть девайс и удалить массивы из память. Например так:
- (void)dealloc
{

// Уничтожаем контекст
alcDestroyContext(mContext);
// Закрываем девайс
alcCloseDevice(mDevice);

[soundsArray release];
[bufferStorageArray release];
[super dealloc];
}


Статья написана по мотивом замечательного туториала «openAL sound on the iPhone».
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.