OpenAL のヘルパーライブラリである ALURE に実装されているカスタムデコーダの機能を使ってデフォルトでは対応していない mp3 や aac を AVFoundation を利用して OpenAL 上でも再生出来るようにするコードを書いてみる。
カスタムデコーダの実装
エラー処理がないので、実際に使う際はエラー処理を書いておくこと。
# import <AVFoundation/AVFoundation.h>
# import <OpenAL/al.h>
# include "OpenAL/alure.h"
/* ARC 前提 */
struct AlureDecoder {
AlureDecoder(const char *path)
: asset(nil),
reader(nil),
output(nil)
{
NSString *filePath = [[NSString alloc] initWithUTF8String:path];
NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:filePath];
asset = [AVURLAsset assetWithURL:fileUrl];
NSError *error = nil;
reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];
if (error == nil) {
/* オーディオトラックのみを取り出す */
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeAudio];
/* 44.1KHz 2ch 16bits PCM で取り出す設定を行う */
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithUnsignedLong:kAudioFormatLinearPCM], AVFormatIDKey, [NSNumber numberWithFloat:44100.0f], AVSampleRateKey, [NSNumber numberWithInt:2], AVNumberOfChannelsKey, [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleavedKey, [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, nil];
output = [[AVAssetReaderAudioMixOutput alloc] initWithAudioTracks:tracks audioSettings:outputSettings];
if ([reader canAddOutput:output]) {
[reader addOutput:output];
[reader startReading];
}
else {
/* エラー処理(普通ここに到達しないはず) */
}
}
else {
/* エラー処理 */
}
}
~AlureDecoder() {
}
static void install() {
alureInstallDecodeCallbacks(0, createFromFile, createFromMemory, getFormat, decode, rewind, destroy);
}
static void *createFromFile(const ALchar *path) {
AlureDecoder *decoder = new AlureDecoder(path);
return decoder;
}
static void *createFromMemory(const ALubyte * /* bytes */, ALuint /* buffer */) {
/* 未対応 */
return 0;
}
static ALboolean getFormat(void *opaque, ALenum *format, ALuint *samples, ALuint *bps) {
/* 出力設定に従って設定 */
*format = AL_FORMAT_STEREO16;
*samples = 44100;
/* channels * (bits / 8) */
*bps = 4;
return AL_TRUE;
}
static ALuint decode(void *opaque, ALubyte *bytes, ALuint size) {
ALuint output = 0;
AlureDecoder *decoder = static_cast<AlureDecoder *>(opaque);
if (decoder->reader.status == AVAssetReaderStatusReading) {
CMSampleBufferRef sampleBuffer = [decoder->output copyNextSampleBuffer];
if (sampleBuffer) {
AudioBufferList audioBufferList;
CMBlockBufferRef blockBuffer;
/* 生の PCM データを取り出し、bytes にコピーする */
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
UInt64 sampleSize = CMSampleBufferGetTotalSampleSize(sampleBuffer);
memcpy(bytes, audioBufferList.mBuffers[0].mData, sampleSize);
CFRelease(blockBuffer);
CFRelease(sampleBuffer);
output = ALuint(sampleSize);
}
}
/* (エラー含む)読み込み完了した場合 0 を返す */
return output;
}
static ALboolean rewind(void * /* opaque */) {
/* 未対応 */
return AL_FALSE;
}
static void destroy(void *opaque) {
AlureDecoder *decoder = static_cast<AlureDecoder *>(opaque);
delete decoder;
}
AVAsset *asset;
AVAssetReader *reader;
AVAssetReaderAudioMixOutput *output;
};
動作確認のための実装
以下は動作確認のためのコード。コンパイルする際は先ほどのコードと AVFoundation.framework と OpenAL.framework と ALURE をリンクする必要がある。
static bool g_playing = true;
static void callback(void *opaque, ALuint source)
{
g_playing = false;
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
alureInitDevice(NULL, NULL);
AlureDecoder::install();
ALuint buffer, source;
alGenBuffers(1, &buffer);
alGenSources(1, &source);
alureBufferDataFromFile("/path/to/audio.mp3", buffer);
alSourcei(source, AL_BUFFER, buffer);
if (alurePlaySource(source, callback, NULL)) {
while (g_playing) {
alureSleep(0.125f);
alureUpdate();
}
}
alDeleteBuffers(1, &buffer);
alDeleteSources(1, &source);
alureShutdownDevice();
}
return 0;
}
ALURE は外部向けの API は C ではあるが内部実装は C++ のため静的リンクする場合は C++ としてコンパイルする必要がある。
おまけ
バッファの長さを求める際はサイズから計算する
int size, channels, bits, frequency, bytesPerSecond, durationSeconds;
alGetBufferi(buffer, AL_SIZE, &size);
alGetBufferi(buffer, AL_CHANNELS, &channels);
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_FREQUENCY, &frequency);
bytesPerSecond = (frequency * channels * bits) / 8;
durationSeconds = size / bytesPerSecond;