Attention! Translated article might be found on my English blog.
ラベル CoreAudio の投稿を表示しています。 すべての投稿を表示
ラベル CoreAudio の投稿を表示しています。 すべての投稿を表示

2017年10月6日金曜日

自作のV3 AudioUnitが10.13で動かなかった

NOTE: English article is available.

開発環境はXcode9.0, macOS 10.13です。

AVAudioUnitインスタンスが作れない

+[AVAudioUnit instantiateWithComponentDescription:options:completionHandler:]でAVAudioUnitインスタンス作成に失敗していました。
completionHandler内のerrorを出力してみると、
Error Domain=NSOSStatusErrorDomain Code=-3000 "invalidComponentID"
というエラーが出ていました。

試行錯誤の結果、ホスト側のSandbox化をoffにすることでインスタンスを作成できるようになりました。
なお、Appleのサンプルコード「AudioUnitV3Example」でもホスト側はSandboxがoffになっているようでした。

ちなみに、Sandboxがonでも+[AUAudioUnit instantiateWithComponentDescription:options:completionHandler]ではインスタンスを作れていたので、こちらに置き換えるのも良いかもしれません。
ついでにAudioComponentInstanceNew()も試しましたがダメでした。

AUViewControllerが作れない

数時間かけてようやくインスタンスが作れるようになったものの、
今度はビューが表示されないという問題が発生しました。

-[AUAudioUnit requestViewControllerWithCompletionHandler:]をコールしてもcompletionHandler内のviewControllerがnilになってしまいます。

ビューコントローラのメソッドにトレースを仕込んでみましたが、
そもそもオブジェクトが生成すらされてないようです。

AudioUnitV3Exampleは-[ViewController embedPlugInView]でバンドルから直接ビューを作っているようなので、
こちらに実装を変更するのが良さそうです。(未着手)

雑感

10.13だと.appexを内包するアプリを起動してもauval -aで表示されるリストの中に自作AUが表示されないようですね。
ということは他のホストアプリから使えないということになるはずなので、
こちらも解決しないといけませんね。

めんどくさい!

つづく…

2016年7月8日金曜日

ExtAudioFileを使ってMP3ファイルをPCMデータとして取得する

-- 2016/07/10追記 --

私が下の方で書いたコードだと、ファイルによってはいつまで経っても
whileループから抜け出さないケースに遭遇しました。

どうやらkExtAudioFileProperty_FileLengthFramesが返す総フレーム数と
実際の総フレーム数が異なるためwhileループの条件がfalseにならず
延々と読み込み処理を続けてしまうようでした。

ExtAudioFileRead()はファイルがEOFに達するとフレーム数には0が入るようです。
参考: Extended Audio File Services Reference

この動作を利用してExtAudioFileRead()の後に
        // 0ならend-of-fileらしい
        if (numFramesToRead == 0) {
            break;
        }
という処理を入れ、ループを抜ける仕組みを入れた方が良いでしょう。

-- 2016/07/10追記終了 --

前回の続き、というかExtAudioFileを使えば簡単にnon-LPCMを変換できることに気づきました…。
というわけで変更後のソースは以下の通り。
#include <CoreServices/CoreServices.h>
#include <AudioToolbox/AudioToolbox.h>

#define _ERR_RETURN(err)  {if(noErr != err){printf("%d - err:%d\n", __LINE__, err); return err;}}

typedef AudioStreamBasicDescription ASBD;
typedef AudioBufferList ABL;

OSStatus DecodeExtFileAtPath(const char *inputFilePath, const char *outputFilePath);
void SetStandardDescription(AudioStreamBasicDescription *descPtr);
static inline ABL MakeABL(UInt32 ch, UInt32 bytes, void *buf);

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: ./Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            OSStatus err = DecodeExtFileAtPath(argv[1], argv[2]);
            printf("done. err:%d\n", err);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    return 0;
}

/*
 inputFilePathで指定したオーディオファイルを読み込み
 CDフォーマット(ただしbig-endian)のバイナリデータとして
 outputFilePathにファイルとして書き出す。
 主にMP3, AACなどnon-LPCMデータのLPCMデータへの変換動作確認用。(一応.wav, .aiffも読める)
 処理に失敗した場合は失敗箇所でのOSStatusを返す。
 */
OSStatus DecodeExtFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    CFURLRef url = CFURLCreateWithBytes(NULL,
                                        (const UInt8 *)inputFilePath,
                                        strlen(inputFilePath),
                                        kCFStringEncodingUTF8,
                                        NULL);
    
    ExtAudioFileRef file;
    OSStatus err = ExtAudioFileOpenURL(url,
                                       &file);
    _ERR_RETURN(err);
    
    CFRelease(url);
    
    ASBD clientDesc;
    SetStandardDescription(&clientDesc);
    UInt32 size = sizeof(clientDesc);
    
    err = ExtAudioFileSetProperty(file,
                                  kExtAudioFileProperty_ClientDataFormat,
                                  size,
                                  &clientDesc);
    _ERR_RETURN(err);
    
    SInt64 fileFrameLength;
    size = sizeof(fileFrameLength);
    err = ExtAudioFileGetProperty(file,
                                  kExtAudioFileProperty_FileLengthFrames,
                                  &size,
                                  &fileFrameLength);
    _ERR_RETURN(err);
    
    const UInt32 numFramesToReadInACycle = 1024*1024;
    const UInt32 bufferSize = clientDesc.mBytesPerFrame * numFramesToReadInACycle;
    void *buffer = malloc(bufferSize);
    FILE *fp = fopen(outputFilePath, "w");
    SInt64 frameOffset = 0;
    while (frameOffset != fileFrameLength) {
        UInt32 numFramesToRead = numFramesToReadInACycle;
        AudioBufferList list = MakeABL(clientDesc.mChannelsPerFrame,
                                       bufferSize,
                                       buffer);
        err = ExtAudioFileRead(file,
                               &numFramesToRead,
                               &list);
        _ERR_RETURN(err); 
        
        fwrite(list.mBuffers[0].mData,
               list.mBuffers[0].mDataByteSize,
               1,
               fp);
        
        frameOffset += numFramesToRead;
    }
    fclose(fp);
    free(buffer);
    
    err = ExtAudioFileDispose(file);
    return err;
}

/*
 descPtrCD音質のASBDをセットする。
 ただし、big-endianなのでlittle-endianなプロセッサで扱う場合は注意
 */
void SetStandardDescription(AudioStreamBasicDescription *descPtr) {
    descPtr->mSampleRate = 44100.0;
    descPtr->mFormatID = kAudioFormatLinearPCM;
    descPtr->mFormatFlags = kAudioFormatFlagIsBigEndian |
                            kAudioFormatFlagIsSignedInteger |
                            kAudioFormatFlagIsPacked;
    descPtr->mBytesPerPacket = 4;
    descPtr->mBytesPerFrame = 4;
    descPtr->mFramesPerPacket = 1;
    descPtr->mChannelsPerFrame = 2;
    descPtr->mBitsPerChannel = 16;
}

static inline ABL MakeABL(UInt32 ch, UInt32 bytes, void *buf) {
    ABL list;
    list.mNumberBuffers = 1;
    list.mBuffers[0].mNumberChannels = ch;
    list.mBuffers[0].mDataByteSize = bytes;
    list.mBuffers[0].mData = buf;
    return list;
}
AudioConverterが不要なのは良いですね。(実際はExtAudioFileが内包しているようです)
kExtAudioFileProperty_FileLengthFramesで総フレーム数が取得できるのもありがたい…。
まだVBRなファイルは試してませんが、問題なく行けそうな気がします。

2016年7月7日木曜日

AudioFile, AudioConverter関連の関数をメインスレッド以外で実行させてみた。

前回の記事AudioToolboxを使ってMP3やAACなどのnon-LPCMをLPCMデータに変換するの続きです。
メインスレッド以外で実行できるか試してみました。
main()の中だけ修正し、他はいじりませんでした。
main()のコードは以下の通りです。

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: ./Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            OSStatus err = DecodeFileAtPath(argv[1], argv[2]);
            printf("done. err:%d\n", err);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    return 0;
}
参考: apply.c - Dispatch_Samples - Apple Developer
参考: エキスパートObjective-Cプログラミング -iOS/OS Xのメモリ管理とマルチスレッド- - Amazon

実行した結果、サブスレッドでも正常動作することを確認できました。
(ただし、Cocoaアプリ内のサブスレッドでも正常動作することを裏付けるものではないと思います)

Objc-CやSwiftを使っているならNSOperationを使うほうが楽そうですね。

あー、あとGrand Central Dispatch(GCD)関連のドキュメントを漁っていて、
Apple公式の"Introducing Blocks and Grand Central Dispatch"が入手できなかったのは
非常に残念&腹立たしかったです。

2016年7月6日水曜日

AudioToolboxを使ってMP3やAACなどのnon-LPCMをLPCMデータに変換する

MP3の読み込みがどうしても上手くいかないので
検証用コードを書いてみました。
なんとか意図した通り動くようになりました。

とりあえずノンリニアな場合はASPD(AudioStreamPacketDescription)とmagic cookieを
意識する必要がありますね。
昔(OS X 10.3辺り)はもっと雑なコードでも読めていたはずなんですが…。

ファイルの読み込み処理そのものはAudioConverterの入力コールバックで行っていて、
その際得られたASPDがあれば一緒に返してあげる感じですね。

検証用コードは以下の通りで、次はこれをworker-threadで行った時にどうなるかを
調べたいと思います。
#include <CoreServices/CoreServices.h>
#include <AudioToolbox/AudioToolbox.h>

#define _ERR_RETURN(err)  {if(noErr != err){printf("%d - err:%d\n", __LINE__, err); return err;}}

OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath);
OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData);
void SetStandardDescription(AudioStreamBasicDescription *descPtr);

typedef struct MyConverterData {
    AudioFileID fileID;
    UInt64 totalPacketCount;
    UInt64 packetOffset;
} MyConverterData;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    OSStatus err = DecodeFileAtPath((const char *)(argv[1]), (const char *)argv[2]);
    printf("done. err:%d\n", err);
    
    return 0;
}

/*
    inputFilePathで指定したオーディオファイルを読み込み
    CDフォーマット(ただしbig-endian)のバイナリデータとして
    outputFilePathにファイルとして書き出す。
    主にMP3, AACなどnon-LPCMデータのLPCMデータへの変換動作確認用。(一応.wav, .aiffも読める)
    処理に失敗した場合は失敗箇所でのOSStatusを返す。
 */
OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    OSStatus err = noErr;
    AudioFileID fileID;
    MyConverterData myConverterData = {0, 0, 0};
    
    // ファイルを開いてファイルのASBD, パケット数を取得する
    AudioStreamBasicDescription fileDesc;
    {
        CFURLRef url;
        url = CFURLCreateWithBytes(NULL,
                                   (const UInt8 *)inputFilePath,
                                   strlen(inputFilePath),
                                   kCFStringEncodingUTF8,
                                   NULL);
        
        err = AudioFileOpenURL(url,
                               fsRdPerm,
                               0,
                               &fileID);
        _ERR_RETURN(err);
        
        myConverterData.fileID = fileID;
        UInt32 size = sizeof(AudioStreamBasicDescription);
        memset(&fileDesc, 0, size);
        
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyDataFormat,
                                   &size,
                                   &fileDesc);
        _ERR_RETURN(err);
        size = sizeof(myConverterData.totalPacketCount);
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyAudioDataPacketCount,
                                   &size,
                                   &myConverterData.totalPacketCount);
        _ERR_RETURN(err);
    }
    
    // 出力フォーマット(ASBD)を設定する
    AudioStreamBasicDescription outDesc;
    SetStandardDescription(&outDesc);
    
    // ファイルのASBDと出力のASBDを使ってコンバータを作成する
    AudioConverterRef converter;
    {
        err = AudioConverterNew(&fileDesc, &outDesc, &converter);
        _ERR_RETURN(err);
    }

    // magic cookieがあればコンバータにセットする
    // (これをしないとAudioConverterFillComplexBuffer()!datが返る
    {
        UInt32 size = 0;
        err = AudioFileGetPropertyInfo(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       NULL);
        if(noErr == err){
            void *magic = calloc(1, size);
            err = AudioFileGetProperty(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       magic);
            if(noErr == err){
                printf("magic cookie info found.\n");
                err = AudioConverterSetProperty(converter,
                                                kAudioConverterDecompressionMagicCookie,
                                                size,
                                                magic);
                _ERR_RETURN(err);
            }
            err = noErr;
            if(NULL != magic){
                free(magic);
            }
            _ERR_RETURN(err);
        }else{
            printf("magic cookie info not found.\n");
            
        }
    }
    
    const UInt32 maxNumPacketsInACycle = 100; // 1ループあたりに読み込むパケット数
    FILE *fp = fopen(outputFilePath, "w");
    if (fp == NULL) {
        printf( "fopen failed.");
        return writErr;
    }
    
    err = noErr;
    
    // ファイル読み込み、デコード、書き込みが終わるまで繰り返す
    while(err == noErr){
        // 最後のパケットまで処理済みならループを抜ける
        if (myConverterData.packetOffset == myConverterData.totalPacketCount) break;
        
        UInt32 numPackets = maxNumPacketsInACycle; // 読み込みたい最大パケット数
        UInt32 numFrames = fileDesc.mFramesPerPacket * numPackets; // 読み込みたい最大フレーム数
        
        AudioBufferList list;
        // listの準備は必要(なければinputProc呼ばれず即-50)
        list.mNumberBuffers = 1;
        list.mBuffers[0].mNumberChannels = 2;
        list.mBuffers[0].mDataByteSize = numFrames * outDesc.mBytesPerFrame; // デコード後の最大バイトサイズ
        list.mBuffers[0].mData = malloc(list.mBuffers[0].mDataByteSize);
        err = AudioConverterFillComplexBuffer(converter,
                                              MyACComplexInputProc,
                                              &myConverterData,
                                              &numPackets,
                                              &list,
                                              NULL);
        
        if (err == noErr) {
            // デコードデータを書き込む
            fwrite(list.mBuffers[0].mData, list.mBuffers[0].mDataByteSize, 1, fp);
        }
        free(list.mBuffers[0].mData);
    }
    
    // 後始末
    fclose(fp);
    err = AudioConverterDispose(converter);
    _ERR_RETURN(err);
    
    err = AudioFileClose(fileID);
    return err;
}

/*
 コンバータ用コールバック関数
 必要なパケットだけファイルから読み込みioDataにセットして渡す。
 パケットのASBDが得られる場合はoutDataPacketDescriptionを通じて渡す。
 inUserDataMyConverterData変数へのポインタでなければならない
 */

OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData) {
    // fileIDを取得する
    MyConverterData *myConverterDataPtr = (MyConverterData *)inUserData;
    AudioFileID fileID = myConverterDataPtr->fileID;
    
    // ファイルから読み込むデータサイズを決定する。
    // 最大パケットサイズを取得して、それに要求されているパケット数をかけて算出する。
    // FIXME: コールバックを呼ぶたびに取得しなくても良いはず
    UInt32 maxPacketSize;
    UInt32 dataSize = sizeof(maxPacketSize);
    OSStatus err = AudioFileGetProperty(fileID,
                                        kAudioFilePropertyMaximumPacketSize,
                                        &dataSize,
                                        &maxPacketSize);
    _ERR_RETURN(err);
    
    UInt32 numBytes = maxPacketSize * *ioNumberDataPackets; // 有り得る最大のデータサイズ
    
    // バッファ確保
    static void *buffer = NULL;
    if (buffer != NULL)
        free(buffer);
    buffer = malloc(numBytes);
    
    // ASPD用メモリ確保
    static AudioStreamPacketDescription *descs = NULL;
    if (descs != NULL)
        free(descs);
    descs = malloc(sizeof(AudioStreamPacketDescription) * *ioNumberDataPackets);
    
    // ファイル読み込み
    err = AudioFileReadPacketData(fileID,
                                  false,
                                  &numBytes,
                                  descs,
                                  myConverterDataPtr->packetOffset,
                                  ioNumberDataPackets,
                                  buffer);
    _ERR_RETURN(err);
    
    // 読み込んだデータをioDataにセットする
    ioData->mBuffers[0].mDataByteSize = numBytes;
    ioData->mBuffers[0].mData = buffer;
    
    // 得られたASPDをセットする。.wav.aiffNULLが入っているのでNULLならセットしない
    if (outDataPacketDescription != NULL) {
        *outDataPacketDescription = descs;
    }
    
    // 読み込み済みパケット数を更新する
    myConverterDataPtr->packetOffset += *ioNumberDataPackets;
    
    // デバッグ用出力
    if (myConverterDataPtr->packetOffset % 100 == 0) {
        printf("%llu/%llu\n", myConverterDataPtr->packetOffset, myConverterDataPtr->totalPacketCount);
    }
    
    return err;
}

/*
 descPtrCD音質のASBDをセットする。
 ただし、big-endianなのでlittle-endianなプロセッサで扱う場合は注意
 */
void SetStandardDescription(AudioStreamBasicDescription *descPtr) {
    descPtr->mSampleRate = 44100.0;
    descPtr->mFormatID = kAudioFormatLinearPCM;
    descPtr->mFormatFlags = kAudioFormatFlagIsBigEndian |
                            kAudioFormatFlagIsSignedInteger |
                            kAudioFormatFlagIsPacked;
    descPtr->mBytesPerPacket = 4;
    descPtr->mBytesPerFrame = 4;
    descPtr->mFramesPerPacket = 1;
    descPtr->mChannelsPerFrame = 2;
    descPtr->mBitsPerChannel = 16;
}

ちなみにプログラムに興味ない人はafconvertコマンドを使うと良いです。