21

マイク付きのボタン (他の機能と共に) を持つ iOS アプリを開発しています。ユーザーがマイクを押すと強調表示され、アプリはデバイスのマイクから音声の録音を開始し、サーバー (アプリ専用のサーバーで、私が知っている人によって開発されたので、その設計に影響を与えることができます) に送信する必要があります。 )。

これを行うための最も単純でありながら最も堅牢なアプローチを探しています。つまり、複雑なストリーミング ソリューションや VoIP 機能を開発する必要はありません。

主な問題は、ユーザーがサウンドを録音している時間が分からないことですが、サウンドがサーバーに継続的に送信されることを確認したいので、ユーザーが録音を終了するまで待ちたくありません。データがチャンクでサーバーに到着しても問題ありませんが、ユーザーが記録している可能性のある情報を見逃すことは望ましくないため、1 つのチャンクは前のチャンクが終了した場所から継続する必要があります。

最初に考えたのは、たとえば 10 秒のサウンド クリップの「チャンク」を作成し、それらをサーバーに継続的に送信することでした。私が見逃している、より優れた/よりシンプルなストリーミングソリューションはありますか?

私の質問は、iOS でこのタスクを解決するための最もシンプルでありながら信頼できるアプローチは何でしょうか?

実際に録音を停止せずに、AVAudioRecorder によって実行中の録音からサウンドのチャンクを抽出する方法はありますか?

4

2 に答える 2

20

look at this
in this tutorial, the sound recorded will be saved at soundFileURL, then you will just have to create an nsdata with that content, and then send it to your server.
hope this helped.

EDIT :
I just created a version that contain 3 buttons, REC, SEND and Stop :
REC : will start recording into a file.
SEND : will save what was recorded on that file in a NSData, and send it to a server, then will restart recording.
and STOP : will stop recording.
here is the code : in your .h file :

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

@interface ViewController : UIViewController <AVAudioRecorderDelegate>

@property (nonatomic, retain) AVAudioRecorder *audioRecorder;
@property (nonatomic, retain) IBOutlet UIButton *recordButton;
@property (nonatomic, retain) IBOutlet UIButton *stopButton;
@property (nonatomic, retain) IBOutlet UIButton *sendButton;

@property BOOL stoped;

- (IBAction)startRec:(id)sender;
- (IBAction)sendToServer:(id)sender;
- (IBAction)stop:(id)sender;
@end

and in the .m file :

#import "ViewController.h"

@implementation ViewController
@synthesize audioRecorder;
@synthesize recordButton,sendButton,stopButton;
@synthesize stoped;

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
sendButton.enabled = NO;
stopButton.enabled = NO;
stoped = YES;

NSArray *dirPaths;
NSString *docsDir;

dirPaths = NSSearchPathForDirectoriesInDomains(
                                               NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
NSString *soundFilePath = [docsDir
                           stringByAppendingPathComponent:@"tempsound.caf"];

NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];

NSDictionary *recordSettings = [NSDictionary 
                                dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithInt:AVAudioQualityMin],
                                AVEncoderAudioQualityKey,
                                [NSNumber numberWithInt:16], 
                                AVEncoderBitRateKey,
                                [NSNumber numberWithInt: 2], 
                                AVNumberOfChannelsKey,
                                [NSNumber numberWithFloat:44100.0], 
                                AVSampleRateKey,
                                nil];

NSError *error = nil;

audioRecorder = [[AVAudioRecorder alloc]
                 initWithURL:soundFileURL
                 settings:recordSettings
                 error:&error];
audioRecorder.delegate = self;
if (error)
{
    NSLog(@"error: %@", [error localizedDescription]);

} else {
    [audioRecorder prepareToRecord];
}
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

- (BOOL) sendAudioToServer :(NSData *)data {
    NSData *d = [NSData dataWithData:data];
    //now you'll just have to send that NSData to your server

    return YES;
}
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
{
NSLog(@"stoped");
    if (!stoped) {
        NSData *data = [NSData dataWithContentsOfURL:recorder.url];
        [self sendAudioToServer:data];
        [recorder record];
NSLog(@"stoped sent and restarted");
    }
}
- (IBAction)startRec:(id)sender {
if (!audioRecorder.recording)
{
    sendButton.enabled = YES;
    stopButton.enabled = YES;
    [audioRecorder record];
}
}

- (IBAction)sendToServer:(id)sender {
stoped = NO;
[audioRecorder stop];
}

- (IBAction)stop:(id)sender {
stopButton.enabled = NO;
sendButton.enabled = NO;
recordButton.enabled = YES;

stoped = YES;
if (audioRecorder.recording)
{
    [audioRecorder stop];
}
}
@end

Good Luck.

于 2012-08-07T12:04:55.630 に答える
20

固定時間ではなく、固定サイズのチャンクを使用する方が実際には簡単かもしれません。次に、現在サンプル データで満たされている 1 つのバッファーを 2 つ持つことができます。アクティブなバッファーがいっぱいになると、他のバッファーを埋めるように切り替え、最初のバッファーを取得してサーバーに送信する送信側スレッドにシグナルを送信します。

もちろん代わりに固定時間を使用することもできますが、すべてのサンプルを保持するのに十分な大きさのバッファーを確保するか、必要に応じてサイズを大きくできる動的バッファーを使用する必要があります。ただし、ダブルバッファリングと送信スレッドは同じままにすることができます。

于 2012-07-27T11:41:05.443 に答える