読者です 読者をやめる 読者になる 読者になる

よいちろ日記

忘れないようにメモ。

iOSでBluetoothヘッドセット音声認識アプリを作りかけた。(録音、マイク音量取得 編)

に引き続き。 
 
・その他の機能について。
 ┗ 音声認識開始トリガーのためにマイク音量を取得する。
 ┗ 音声認識対象の音声データの保存する。
この2つについてメモ。
 

音声認識開始トリガーのためにマイク音量を取得する。

マイク音量を取るには、AudioToolboxを使うようだ。 

ここが一番詳しくわかりやすく書かれていた。この通りに実装したら動いたのでとりあえず良し。
 

Core Audio その2 AudioStreamBasicDescription | Objective-Audio
AudioStreamBasicDescription dataFormat;
の中で、kAudioFormatLinearPCMとかフォーマットっぽい定数が出てくるんだけどそれは構造体としてフレームワーク(AudioToolbox?)のなかで定義されているっぽい。詳しくは追いきれていない。

 
1つ問題があって、UIDictationControllerを使って音声認識する際には、マイク入力がそっちに取られてしまう。
UIDictationControllerでの音声認識中のマイク音量は[UIDictationController audioLevel]で取得して、切り替える必要がある。なので、普通に、
BOOL isUIDictContSpeechRecognition = YES;
って感じでフラグ管理する。
 
//UIDictationController マイク音量取得
_audioLevelTimer = [NSTimer
                    scheduledTimerWithTimeInterval:1
                    target:self
                    selector:@selector(audioLevel)
                    userInfo:nil repeats:YES];

//UIDictationControllerで音声認識中はこれでマイク音量が取れる。
//AudioQueueRefへのマイク入力はなくなってしまう。UIDictControllerに乗っ取られる。
- (float)audioLevel {
    float audioLevel;
    audioLevel = [_dictationController audioLevel];
    NSLog(@"audioLevel: %f", audioLevel);
    return audioLevel;
}
UIDictationControllerによる音量取得は取り続けていればOKだが、AudioToolboxによる音量取得はUIDictationControllerにマイクを乗っ取られて戻ってくるときにリセットしないとおかしくなる。
- (void)stopUpdatingVolume {
    AudioQueueFlush(queue);
    AudioQueueStop(queue, NO);
    AudioQueueDispose(queue, YES);
}
でリセットできるので、切替時にマイク音量数値をリセットする。
 

音声認識対象の音声データの保存する。

マイク入力で得た音声データを保存する。

ここのコードとチュートリアルが完璧すぎてもうほかに何も要らない。
 
備忘録に触りがわかるようなコメントをしたコードを記載。 
.hファイル
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface RecordAudio : UIView <AVAudioRecorderDelegate, AVAudioPlayerDelegate, NSURLConnectionDataDelegate>

@property (strong, nonatomic) AVAudioRecorder *recorder; //録音用インスタンス
@property (strong, nonatomic) AVAudioPlayer *player; //再生用インスタンス
@property (strong, nonatomic) AVAudioSession *session; //セッション管理インスタンス

//音声ファイルのパス
@property (nonatomic, strong) NSString *filePath;

- (void)initInstance; //インスタンシエイト。サンプリング周波数など録音設定をする。
- (void)recordStart; //[_recorder record]を実行。
- (void)playAudio; //[_player play]を実行。
- (void)recordStop; //[_recorder stop]を実行。
@end
 
.mファイル
#import "RecordAudio.h"

@implementation RecordAudio

- (void)initInstance {
    //録音先のファイルパスを設定する。
    self.filePath = [self makeFilePath];
    NSURL *outputFileURL = [NSURL URLWithString:self.filePath];
    
    //セッションを張る。
    _session = [AVAudioSession sharedInstance];
    [_session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; //お約束。
    
    //NSMutableDictionary形式で録音設定を定義。
    //kで始まる定数はAudioToolbox(?)で構造体として定義されているっぽい。
    NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
    [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
    [recordSetting setValue:[NSNumber numberWithFloat:16000.0] forKey:AVSampleRateKey];
    [recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
    [recordSetting setValue:[NSNumber numberWithUnsignedInt:16] forKey:AVLinearPCMBitDepthKey];
    
    //録音用インスタンスを容易。
    _recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
    _recorder.delegate = self;
    _recorder.meteringEnabled = YES;
    [_recorder prepareToRecord]; //お約束。
}

- (void)recordStart {
    //念のため。あと2回連続で録音開始しようとしたとき用。
    if (_player.playing) {
        [_player stop];
    }
    if (!_recorder.recording) {
        [_session setActive:YES error:nil];
        //録音開始。
        [_recorder record];
    } else {
        //ポーズもできる。
        [_recorder pause];
    }
}

//============================================================
//録音した音声データを再生。
- (void)playAudio {
    if (!_recorder.recording){
        _player = [[AVAudioPlayer alloc] initWithContentsOfURL:_recorder.url error:nil];
        [_player setDelegate:self];
        [_player play];
        
        NSString *recorderUrl = self.filePath;
        NSData *data = [NSData dataWithContentsOfFile:recorderUrl];
        NSLog(@"@%@", recorderUrl);
    }
}

//録音停止。
- (void)recordStop {
    [_recorder stop];
}

#pragma mark - AVAudioRecorderDelegate
//デリゲートメソッド。ログを出すのみ。
- (void) audioRecorderDidFinishRecording:(AVAudioRecorder *)avrecorder successfully:(BOOL)flag{
    NSLog(@"audioRecorderDidFinishRecording");
}

#pragma mark - AVAudioPlayerDelegate
//デリゲートメソッド。ログを出すのみ。
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"audioPlayerDidFinishPlaying");
}
//============================================================
//音声データを保存するファイルパスを設定するメソッド。
- (NSString *)makeFilePath
{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyyMMdd"];
    NSString *fileName = [NSString stringWithFormat:@"%@.wav", [formatter stringFromDate:[NSDate date]]];
    
    return [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
}

@end

 

その他Tipsを載せておく。

  • ダウンロードしたサンプルコードとかがiphone4サイズとかで、iphone6でビルドすると画面の上下に余白ができることがあったのだけど、それは「General」の「App Icons and Launch Images」を設定しないようにしたらなおった。

  • 今回特に学んだ、NSNotificationCenterについて。http:// http://yatemmma.hatenablog.com/entry/2014/04/12/002806
  • NSDictionaryControllerは非公開クラスとだけあって(?)、普通に[[hoge alloc] init]形式で記述しようとするとエラーになるので、[NSClassFromString(@"UIDictationController") performSelector:@selector(sharedInstance)];という書き方になる。
  • 初出のstatic voidについて。C言語のstatic指定子について
  • - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNilはコードからUIViewControllerを作るメソッド。