2013年2月3日日曜日

iOS 音声再生(AVAudioPlayer)

iOS, Mac OS でのオーディオ再生のためのObjective-Cインタフェースを提供するAVAudioPlayerの使い方
(Mac OS Xでも同じです)

AVAudioPlayer を利用すると, このようなことができます

- 任意の所要時間のサウンドの再生
- ファイルまたはメモリバッファからのサウンドの再生
- サウンドのループ再生
- 複数のサウンドの同時再生
- 再生中の各サウンドの相対的な再生レベルの制御
-  サウンドファイル内での特定の位置へのシーク(早送りや巻き戻しなどのアプリケーション機能
をサポートします)
-  オーディオレベル測定機能に使用できるデータの取得

ちなみにサポートしているフォーマットはこんな感じです。
AIFF                           .aif, .aiff
CAF                           .caf
MPEG-1, Layer3       .mp3
MPEG-2                    .aac
MPEG4                     .m4a, mp4
WAV                         .wav

手順
1. AVFoundation.framework を Link Binary With Libraries に追加する
#import <AVFoundation/AVFoundation.h> を忘れずに
2. UI などを用意する
3. コード

こんなサンプルを作ってみました
- サンプル1 音声を再生する

※ちなみに音声の時間を取得するには duration プロパティを利用します
NSTimeInterval ti = self.player.duration;                      // self.player は, AVAudioPlayerのインスタンス

時間に変換するには
int minutes = floor(ti / 60);
int second = round(ti - minutes * 60);   

といったところでしょうか
NSLog(@"%02d:%02d", minutes, second);     // これで 03:00 みたいな表記もできる

サンプル1: 音声を再生する
このプログラムは, View のスタート時に, AVAudioPlayer を初期化して,
再生終了時には, ログを出します。(AVAudioPlayerDelegate を実装)

AVAudioPlayerDelegate は,  audioPlayerDidFinishPlaying を提供します。これを使って, 終了時の動作を定義します。

ちなみに, ここで, AVAudioPlayerをもう1つ用意して, delegate を同じクラスに指定して, audioPlayerDidFinishPlaying  でもう1つをスタートさせれば, 2つの音声を連続再生できたりします




•ViewController.h

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

@interface ViewController : UIViewController<AVAudioPlayerDelegate>

@property(nonatomic) AVAudioPlayer *player;


@property (weak, nonatomic) IBOutlet UIButton *playButton;

- (IBAction)playClick:(id)sender;
@end


•ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize player;

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    
    [self prepareAudio];
}

-(void)prepareAudio
{
    NSError *error = nil;
    NSString *path = [[NSBundle mainBundle] pathForResource:@"ng" ofType:@"wav"];
    NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    
    if ( error != nil )
    {
        NSLog(@"Error %@", [error localizedDescription]);
    }
    [self.player prepareToPlay];
    [self.player setDelegate:self];
    NSTimeInterval ti = self.player.duration;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)playClick:(id)sender
{
    if ( self.player.playing )
    {
        [self.player pause];
        [self.playButton setTitle:@"Pause" forState:UIControlStateNormal];
    }
    else
    {
        [self.player play];
        [self.playButton setTitle:@"Play" forState:UIControlStateSelected];
    }
}

#pragma mark - AVAudioPlayerDelegate
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    if ( flag )
    {
        NSLog(@"Done");
        [self.playButton setTitle:@"Start" forState:UIControlStateNormal];
        
        // Can start next audio?
    }
}
@end




9 件のコメント:

  1. 初めまして。

    iOSアプリケーションを作ろうと思い、ネットを検索していて
    こちらにたどり着きました。

    ソースコードの掲示ありがとうございます。
    新規にプロジェクトを作り、こちらのコードを打ち込んでみました。
    ビルドは問題なく通ったのですが、シミュレータで実行し、
    ボタンを押したときにmain.mのmain関数にて
    「Thread1:signal SIGABRT」
    というエラーが発生し、プログラムが止まってしまいます。

    このエラーを解消する方法がありましたら教えてもらえないでしょうか?

    よろしくお願いします。

    返信削除
  2. はじめまして, このコードはStoryboardやXCodeで必要な操作は除外しています。 ボタンをクリックしたときとおっしゃいますが, playClick は実行されていますでしょうか? ボタンが押されたらここに行くはずなのですがどうでしょうか? breakポイントで止める前に, いきなり SIGABORTで止まってしまうのはちょっと変ですね。

    返信削除
  3. 返信ありがとうございます。

    説明が抜けていたのですが、Storyboardを使用せずにボタンを実装しました。

    playClickにブレークを入れてみましたが、この関数にくる前にmain関数にて
    止まってしまいます。

    返信削除
  4. viewDidLoadを最初に実行し, そこで再生したいwavファイルの準備及び, AVAudioPlayerの準備をします。ボタンを押すと, 再生状況を見て再生かストップを決めます。ボタンでということは, 上の部分は通っているということで間違いないですか? iOS5からの加発者でして, Storyboardでないやり方, xib や nib でUIを呼ぶやり方は, Mac OS Xプログラミングの方しかわかりませんが, ボタンをクリックしたときにplayClick の self.player.playing の部分にポイントしていないとなると, ボタンのアクション(playClick)とUIの部品とが, マッチしていない気がします。

    返信削除
  5. 素早いコメントありがとうございます。

    viewDidLoadにはこちらのソースと同様に
    [self prepareAudio];
    と記述しています。

    そして、prepareAudioに以下のようにボタンの初期化を記述しています。

    // ボタン初期化
    UIButton *playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    playButton.frame = CGRectMake(40.0, 80.0, 240.0, 40.0);
    [playButton addTarget:self action:@selector(playClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:playButton];

    名前のないボタンが表示され、これをクリックすると、
    「Thread1:signal SIGABRT」
    で止まってしまうという状況です。

    ご指摘のようにボタンのアクションとUI部品がマッチしていないように
    感じます。

    ちなみに、新たにプロジェクトを作り「Use Storyboard」のチェックを
    外してみましたが、同じ現象でした。。。

    返信削除
  6. なるほど, コードで直接UIをつくっているんですね。では, storyboard,xibの話は違いますね。 addTarget の引数のactionのところ playClick: ではなかったでしたっけ? "UIButton event" で検索すれば結構出てきますよ。あと, ARCを使用していた場合, playButtonそのものが, releaseされてしまうので, event情報が維持できなかった気がいます。ちょっとその辺覚えていないですが。まずAudioではなく, ボタンのクリックのまわりを調べてみてはどうでしょうか。

    返信削除
  7. アドバイスありがとうございます。
    UIButtonや、ARCとイベント情報について調べてみます。
    あと、Storyboardを使ったプロジェクトも試してみます。

    突然のコメントにも関わらず、ありがとうございます。

    返信削除
  8. こんばんわ。

    「addTarget の引数のactionのところ playClick: ではなかったでしたっけ? 」
    ご指摘の通り「:」が無いためにエラーになっていました・・・。
    ビルドは通ってしまうのに、、、何ともわかりにくいですね。
    (私の経験不足でもあるのですが。)

    とにかく、ありがとうございました。
    助かりました。

    返信削除
  9. 検索でたどり着きました。

    最近始めたiAppの開発で参考になりました。
    ありがとうございます

    返信削除