LoginSignup
8
9

More than 5 years have passed since last update.

ALURE + AVFoundation で aac や mp3 でも OpenAL で再生出来るようにする

Last updated at Posted at 2015-02-15

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;

from http://stackoverflow.com/questions/7978912/how-to-get-length-duration-of-a-source-with-single-buffer-in-openal

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9