2013年2月22日金曜日

[iOS]バックグラウンドで音楽を再生させつづける方法

音楽系アプリを開発していてバックグラウンドでも音楽を鳴らし続ける方法につまづいたのでメモ。

やり方は簡単でソースコードを2カ所修正するだけ。

1. info.plistファイルを修正する
[Required background modes]を追加し、Item 0のValueを[App plays audio]にする。



2. アプリ起動時の初期化関数内に次のコードを挿入する。

        AVAudioSession *session = [AVAudioSession sharedInstance];
        [session setCategory:AVAudioSessionCategoryPlayback error:nil];

たったこれだけでバックグラウンドでも音楽を再生することが可能です。

2013年2月18日月曜日

iPhoneのミュージックライブラリ内の曲をopenALで使用する方法

最近はopenFrameworksを用いてiPhoneアプリを創作しています。

音楽とインタラクションをテーマにしたアプリを作成していたところ、iPhoneのミュージックライブラリ内の曲を編集するようにする方法につまづいたため記録しておきます。

AVAudioPlayerでは複数曲の再生や音楽データの加工に対応していません。このため、openALを用いて音楽データを扱います。
ところが、openALではm4aやmp3のファイルに対応していないためcafファイルに変換して書き出す必要があります。

やりかたはこのサイトを参考にして実装しました。

基本的にはMPMediaPickerControllerで選択した曲に対して、AVAssetReaderとAVAssetWriterを用いてcaf形式に変換してます。

書き出したcaf形式の音楽データをopenALのインスタンスから読み込めば音楽データの加工できます。

ちなみに書き出す際のチャンネルは1つにしておかなければ距離に応じた音の減衰ができないので気をつけてください。
ここに数時間とられました。。。

// iPodの曲目のピッカーを表示
-(IBAction)showMediaPicker:(id)sender
{
    MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeMusic];
    [picker setDelegate: self];
    [picker setAllowsPickingMultipleItems: NO];
    picker.prompt = NSLocalizedString (@"Add songs to play", "Prompt in media item picker");

    // pickerをModalViewに表示
    [self presentModalViewController: picker animated: YES];
    [picker release];
}



//選択した曲に対して処理を行う
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    MPMediaItem *item = [mediaItemCollection.items lastObject];
    
    NSLog(@"export succeeded?:%@", [self exportItem:item]? @"YES": @"NO");
    
    [self dismissModalViewControllerAnimated:YES];
}

//cafファイルに変換してファイルの書き出し
- (BOOL)exportItem:(MPMediaItem *)item
{   
    NSError *error = nil;
    
    NSDictionary *audioSetting = [NSDictionary dictionaryWithObjectsAndKeys:
                                  [NSNumber numberWithFloat:44100.0],AVSampleRateKey,
                                  [NSNumber numberWithInt:1],AVNumberOfChannelsKey, //チャネルを1にしておかないと音の減衰ができない
                                  [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                                  [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
                                  [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey,
                                  [NSNumber numberWithBool:0], AVLinearPCMIsBigEndianKey,
                                  [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
                                  [NSData data], AVChannelLayoutKey, nil];
    
    //読み込み側のセットアップ
    NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
    AVURLAsset *URLAsset = [AVURLAsset URLAssetWithURL:url options:nil];
    if (!URLAsset) return NO;
    
    AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:URLAsset error:&error];
    if (error) return NO;
    
    NSArray *tracks = [URLAsset tracksWithMediaType:AVMediaTypeAudio];
    if (![tracks count]) return NO;
    
    AVAssetReaderAudioMixOutput *audioMixOutput = [AVAssetReaderAudioMixOutput
                                                   assetReaderAudioMixOutputWithAudioTracks:tracks
                                                   audioSettings:audioSetting];
    
    if (![assetReader canAddOutput:audioMixOutput]) return NO;
    
    [assetReader addOutput:audioMixOutput];
    
    if (![assetReader startReading]) return NO;
    
    
    //書き込み側のセットアップ
    NSString *title = [item valueForProperty:MPMediaItemPropertyTitle];
    NSArray *docDirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docDir = [docDirs objectAtIndex:0];
    NSString *outPath = [[docDir stringByAppendingPathComponent:@"music1"]
                         stringByAppendingPathExtension:@"caf"];
    
    NSURL *outURL = [NSURL fileURLWithPath:outPath];
    AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:outURL
                                                        fileType:AVFileTypeCoreAudioFormat
                                                             error:&error];
    //ファイルが存在している場合は削除する
    NSFileManager *manager = [NSFileManager defaultManager];
    if([manager fileExistsAtPath:outPath]){
        [manager removeItemAtPath:outPath error:&error];
    }
    
    if (error) return NO;
    
    AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
                                                                              outputSettings:audioSetting];
    assetWriterInput.expectsMediaDataInRealTime = NO;
    
    if (![assetWriter canAddInput:assetWriterInput]) return NO;
    
    [assetWriter addInput:assetWriterInput];
    
    if (![assetWriter startWriting]) return NO;
    
    
    
    //コピー処理
    [assetReader retain];
    [assetWriter retain];
    
    [assetWriter startSessionAtSourceTime:kCMTimeZero];
    
    dispatch_queue_t queue = dispatch_queue_create("assetWriterQueue", NULL);
        
    //書き込み処理
    [assetWriterInput requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
        
        NSLog(@"start");
        
        while (1){
            if ([assetWriterInput isReadyForMoreMediaData]) {
            
                CMSampleBufferRef sampleBuffer = [audioMixOutput copyNextSampleBuffer];
                
                if (sampleBuffer) {
                    [assetWriterInput appendSampleBuffer:sampleBuffer];
                    CFRelease(sampleBuffer);
                }
                else {
                    [assetWriterInput markAsFinished];
                    break;
                }
            }
        }
        
        [assetWriter finishWriting];
        [assetReader release];
        [assetWriter release];
        
        NSLog(@"finish");
                
    }];
    
    dispatch_release(queue);
    
    return YES;
}