月別アーカイブ: 2010年8月

Touch the Wave 2 v1.1.1公開されました

Touch the Wave 2のv1.1.1が公開されました。

今回のアップデートは、プレイヤー画面で波形をタッチしたまま「×」ボタンや「↓」ボタンを押すと、波形へのタッチが無効になってしまう問題を修正しました。

アップデートついでに、ファイル共有を有効にしてみました。ただ、ファイル共有で直接取り込んだ場合、ファイルフォーマットをTouch the Wave用に変換する処理を入れていないので、WAV・16ビット・44.1kHz・ステレオでないとアプリが受け付けません。その他のフォーマットのファイルを入れた場合は無視されるか削除されます。まぁ、iPadでも簡単にファイルが取り込めた方がいいかなぁと思っての気まぐれ追加機能なので、iPadがiOS4対応になったら無効にすると思います。

iPodライブラリからのファイル書き出し その2

※この記事の内容は、iOS4.2以降では正常に動作しない可能性があります。

iPodライブラリからの書き出し、第二回です。

前回、MP3からAACへの書き出しができないと書きましたが、おそらくAVAssetExportSessionがサポートしているファイルタイプにMP3がないからと思われます。supportedFileTypesメソッドで取得できるAVAssetExportSessionがサポートしているとおぼしきファイルタイプは以下のようなものです。

"com.apple.quicktime-movie",
"com.apple.m4a-audio",
"public.mpeg-4",
"com.apple.m4v-video",
"public.3gpp",
"org.3gpp.adaptive-multi-rate-audio",
"com.microsoft.waveform-audio",
"public.aiff-audio",
"public.aifc-audio"

オーディオで対応しているのは、「m4a」「3gpp」「wave」「aiff」「aifc」といったところのようです。

MP3の書き出しの他にも、WAVやAIFFの非圧縮ファイルをそのまま使いたい場合にも、ちょっとめんどくさい方法を使わないといけないようです。僕があれこれ試して成功した方法をとりあえず記録しておきます。もし他にスマートな方法がありましたらご指摘いただけるとうれしいです。

とりあえずコードです。前回のmediaPicker:didPickMediaItems:メソッドを以下のようにマルッと差し替えてください。あと、CoreMedia.Frameworkもインポートしてください。

- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    MPMediaItem *item = [mediaItemCollection.items lastObject];
    NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
    AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:nil];
    
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
                                           initWithAsset:urlAsset
                                           presetName:AVAssetExportPresetPassthrough];
    
    
    NSArray *tracks = [urlAsset tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *track = [tracks objectAtIndex:0];
    id desc = [track.formatDescriptions objectAtIndex:0];
    const AudioStreamBasicDescription *audioDesc = CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)desc);
    FourCharCode formatID = audioDesc->mFormatID;
    
    NSString *fileType = nil;
    NSString *ex = nil;
    
    switch (formatID) {
            
        case kAudioFormatLinearPCM:
        {
            UInt32 flags = audioDesc->mFormatFlags;
            if (flags & kAudioFormatFlagIsBigEndian) {
                fileType = @"public.aiff-audio";
                ex = @"aif";
            } else {
                fileType = @"com.microsoft.waveform-audio";
                ex = @"wav";
            }
        }
            break;
            
        case kAudioFormatMPEGLayer3:
            fileType = @"com.apple.quicktime-movie";
            ex = @"mp3";
            break;
            
        case kAudioFormatMPEG4AAC:
            fileType = @"com.apple.m4a-audio";
            ex = @"m4a";
            break;
            
        case kAudioFormatAppleLossless:
            fileType = @"com.apple.m4a-audio";
            ex = @"m4a";
            break;
            
        default:
            break;
    }
    
    exportSession.outputFileType = fileType;
    
    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *filePath = [[docDir stringByAppendingPathComponent:[item valueForProperty:MPMediaItemPropertyTitle]] stringByAppendingPathExtension:ex];
    exportSession.outputURL = [NSURL fileURLWithPath:filePath];
    
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"export session completed");
        } else {
            NSLog(@"export session error");
        }
        
        [exportSession release];
    }];
}

では、ちょっと解説していきます。

AVAssetExportSessionのpresetNameは前回のM4AではなくAVAssetExportPresetPassthroughにしています。これはiPodライブラリから変換をせずにコピーをするpresetのようです。ただし、この場合、outputFileTypeに指定すべきファイルタイプをM4Aの時のように絞ってくれませんので、自分で調べる必要があります。

まず、AVURLAssetが持っているオーディオのAVAssetTrackを取得。さらにAVAssetTrackがもっているformatDescriptionsを取得。formatDescriptionsはNSArrayなのですが、その中に入ってるのはObjective-Cのオブジェクトではなくて、CMAudioFormatDescriptionRefというCarbon?なオブジェクトが入っています。このCMAudioFormatDescriptionRefからCMAudioFormatDescriptionGetStreamBasicDescription()という関数を使って、みなさんおなじみのAudioStreamBasicDescriptionが取得できますので、これを見て判断しています。

wavやaiffのLinearPCMなファイルはどちらもFileIDがkAudioFormatLinearPCMですので、エンディアンで違いを判別しています。

MP3はあれこれ試した結果、FileTypeを@”com.apple.quicktime-movie”にして、ファイル名に拡張子「.mp3」をつけておくと、ExtAudioFileで開くことができました。この挙動は試してみたらできちゃった的な感じなので、なにか正当な方法があるといいなと思っているのですが…。ちなみに、もしかしたらMP3以外もQuickTimeで共通でいけるんじゃないかと思ったのですが、MP3以外でもデータはコピーされるものの、ExtAudioFileでそのままOpenというわけにはいかないようです。

あと、何かが取得できなかった時とかのエラー処理とかはいっさいやってませんのであしからずご了承ください。

Touch the Wave 2 v1.1リリース

Touch the Wave 2のv1.1がリリースされました。ダウンロードはこちら

変更点は、念願のiPodライブラリからの読み込みに対応。あと、波形やタブの画像などを高解像度に対応させました。iPodライブラリから読み込めるようになったことで、だれでも使えるアプリになったんじゃないかなと思います。

ちなみに、波形表示はいままでテクスチャに画像を描いて拡大縮小していたのですが、テクスチャはやめました。おかげで画像作成時間のロスが無くなったり、拡大して表示が荒くなったのが無くなったりしてます。パフォーマンスに関してはiPhone 3Gじゃないとわかんないくらいの違いですけど、なぜか前はテクスチャの方が速いと思い込んでたんですよねぇ、なんでだろう。

iPodライブラリからのファイル書き出し その1

iOS 4.0になってようやくiPodライブラリにある音楽を生のデータとして扱うことができるようになりましたので、その方法をまとめておきたいと思います。ムービーも同じような方法でできるようですが、とりあえずここではオーディオに限定して説明していきます。ちなみにシミュレータにはiPodライブラリがないので試せません。コードは実機で実行してください。

大まかな手順としては以下のような感じです。

① MPMediaItemを取得
② AVURLAssetを生成
③ AVAssetExportSessionを生成
④ Exportを実行

では、やっていきましょう。

① MPMediaItemを取得

MPMediaItemは、iOS3.xのときからありましたのでご存知の方も多いと思いますが、iPodライブラリに中にあるひとつひとつの曲(音楽の場合)の情報をもっているクラスです。これの取得の仕方は、いろいろiPhoneアプリ開発本にもくわしく載っていますので、細かいことはそれらを参考にしていただいた方が良いと思います。

取得の仕方としては、MPMediaPickerControllerを使う方法とMPMediaQueryを使う方法とありますが、今回は簡単に実装できるMPMediaPickerControllerを使ってみます。

では、実際にコードを書いていきたいと思います。Xcodeの新規プロジェクトでWindow Based Applicationを選択し、プロジェクトを作成します。MPMediaPickerControllerを使うのに必要なフレームワークはMediaPlayer.Frameworkですのでインポートします。またあとで必要になるので、AVFoundation.Frameworkもインポートしてください。AppDelegateの中に以下のコードを追加します。

#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>

それから、起動するときにMPMediaPickerControllerを表示したいので、以下のコードをapplication:didFinishLaunchingWithOptions:メソッドに追加してください。MPMediaPickerControllerはどうやらUIViewControllerが表示されていないと出せないみたいなので、ダミーでUIViewControllerを作ってからMPMediaPickerControllerを出しています。このへんは適当ですので、あまり参考にしない方が良いかと思います。同期すると消えてしまいますし。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    UIViewController *viewController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
    [window addSubview:viewController.view];
    
    MPMediaPickerController *pickerController = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
    pickerController.delegate = self;
    [viewController presentModalViewController:pickerController animated:NO];
    [pickerController release];
    [window makeKeyAndVisible];
return YES;
}

これでアプリを実行して起動するとMPMediaPickerControllerが表示される状態になっています。

② AVURLAssetを生成

ここからはMPMediaPickerControllerで曲が選択されたあとの処理です。MPMediaPickerControllerのデリゲートに処理を書き込んでいきましょう。

MPMediaItemを取得したら、valueForPropertyにMPMediaItemPropertyAssetURLを渡してNSURLを取得します。ちなみにこのURLはあくまでiPodライブラリの中の場所でしかないので、ここに直接アクセスしようとしても無駄です。AVPlayerなどを使えば再生できたりしますが、AudioFileService系では開くことはできません。まずは、取得したNSURLからAVURLAssetというメディアファイルを表すオブジェクトを生成します。

- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    MPMediaItem *item = [mediaItemCollection.items lastObject];
    NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
    AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:nil];

③ AVAssetExportSessionを生成

次に、AVURLAssetを渡してAVAssetExportSessionを生成します。これが、普通のオーディオファイルに書き出してくれるものです。生成するのはこんな感じです。

AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
                                           initWithAsset:urlAsset
                                           presetName:AVAssetExportPresetAppleM4A];

この2つ目の引数のpresetNameというのは書き出すときのフォーマットで、オーディオで書き出すpresetNameはこのAVAssetExportPresetAppleM4Aしかないようです。AVAssetExportSessionのexportPresetsCompatibleWithAsset:で使えるものが取得できます。元のファイルのフォーマットをいろいろ変えて試しましたが、AVAssetExportPresetAppleM4Aにした状態では、常にAACの44.1kHz・Low Complexityで書き出されるようです。

AVAssetExportSessionが生成できたら、さらに情報を与えていきます。最低限必要なものは、outputFileTypeとoutputURLです。

exportSession.outputFileType = [[exportSession supportedFileTypes] objectAtIndex:0];
    
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [[docDir stringByAppendingPathComponent:[item valueForProperty:MPMediaItemPropertyTitle]] stringByAppendingPathExtension:@"m4a"];
exportSession.outputURL = [NSURL fileURLWithPath:filePath];

outputURLは書き出す先なので説明は省きます。お好きなところを指定してください。今回はDocumentディレクトリの中にTitleをファイルネームにして書き出しています。

exportSessionに設定できるFileTypeはsupportedFileTypesで取得できます。presetNameをAVAssetExportPresetAppleM4Aにしている場合、supportedFileTypesの中身は「@”com.apple.m4a-audio”」ひとつだけのようでしたので、そのままそれを設定しています。

④ Exportの実行

ここまで準備ができたらあとは書き出しを実行するだけです。書き出しはexportAsynchronouslyWithCompletionHandler:というメソッドで行います。とりあえずコードは以下のような感じ。メソッド名にAsynchronouslyとあるように非同期で実行されます。

    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"export session completed");
        } else {
            NSLog(@"export session error");
        }
        [exportSession release];
    }];
}

書き出しが終了したら、このメソッドで渡したブロックが呼ばれます。exportSessionのstatusプロパティには書き出しが成功したか失敗したかキャンセルされたか等の状態が入ってますので、そのstatusに応じて処理を記述するという感じです。

ブロックの最後には、もうexportSessionが必要なくなったということでreleaseしています。キャンセル処理や進捗を実装する場合等はインスタンス変数とかにexportSessionを保持しておかないといけないと思いますので、releaseのタイミングはうまいことやってください。

以上のコードを実行して曲を選択すると、アプリ内のDocumentディレクトリにファイルがコピーされると思います。File SharingをONにしておけばiTunesからみたりしてコピーされたかどうかが確認できると思います。ただしコピーできないものがあって、DRM付きのファイルはもちろんなのですが、なぜかMP3もコピーしてくれませんでした。また、今回の方法だと自動的にAACに変換されてしまうので、WAVやAIFFなどの非圧縮ファイルをそのままコピーしたい場合などの対応についてはまた次回やります。