Trong bài này, TechMaster sẽ hướng dẫn bạn lập trình ios tạo một ứng dụng đơn giản để thu âm (record) và phát lại (playback) dùng thư viện AVFoundation. Nếu chức năng đứng riêng, thì app quá đơn giản, khó mà bán được trên AppStore. Tuy nhiên nếu biết kết hợp, nhúng vào các ứng dụng khác như: bóp méo tiếng, phát lại tốc độ nhanh chậm và hoạt hình, chúng ta có thể tạo ra ứng dụng như Talking Tom, hay ghi nhật ký bằng lời, upload file âm thanh lên SoundCloud rồi chia xẻ link trên FaceBook, đọc truyện đêm khuya cho trẻ con, khi cần phát lại…

 

Khóa học lập trình di động tại Techmaster:

Để cài đặt MacOSX lên phần cứng không phải Apple liên hệ chuyên gia cài Hackintosh:

  • Nguyễn Minh Sơn: 01287065634
  • Huỳnh Minh Sơn: 0936225565
  • Website: caidatmacos.com 

Bước 1:
Tạo mới một dự án sử dụng project template là Single View Application.
Chọn StoryBoard và ARC.

Bước 2:
Bổ xung thư viện AVFoundation bằng cách vào Build Phrases > Link Binary With Libraries > chọn dấu + để mở danh sách các thư viên Apple cũng cấp sẵn, gõ AVFoundation để tìm cho nhanh.

Học lập trình ios trực tuyến

Bổ xung thư viện AVFoundation của Apple để lập trình Audio – Video. Ấn để xem rõ hơn.

Trong thư viện AVFoundation, chúng ta sẽ sử dụng 2 class AVAudioRecorder để thu âm và AVAudioPlayer để phát lại
Bước 3:
Tạo hai UIButton: record để thu âm, còn playback để nghe lại.
01 UIProgressView khi thu âm sẽ ẩn (hidden), còn khi playback sẽ hiện ra tiến độ phát lại đoạn âm thanh vừa thu.
01 UILabel để hiển thời gian đã ghi âm hoặc phát lại tính bằng giây.
01 UILabel mô tả lỗi hệ thống khi thu / phát.
 

Học lập trình iPhone iPad

Kéo thả các control để tạo giao diện trên StoryBoard. Ấn để xem rõ hình.

Bước 4:
Nối những phần tử giao diện từ StoryBoard vào ViewController.h để cho việc lập trình tiếp theo.

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface ViewController : UIViewController <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
@property (strong, nonatomic) AVAudioRecorder *recorder;
@property (strong, nonatomic) AVAudioPlayer *audioPlayer;
@property (strong, nonatomic) IBOutlet UIButton *recordButton;
@property (strong, nonatomic) IBOutlet UIButton *playButton;
@property (strong, nonatomic) IBOutlet UILabel *errorMessage;
@property (strong, nonatomic) IBOutlet UIProgressView *prgIndicator;
@property (strong, nonatomic) IBOutlet UILabel *lblTime;
@property (strong, nonatomic) NSTimer *tmrCounter;
@end

– Phần đầu ViewController.h cần import file header AVFoundation.h
– Khai báo ViewController sẽ tuân thủ 2 protocol là AVAudioRecorderDelegate và AVAudioPlayerDelegate. Các sự kiện phát sinh trong lúc thu âm và phát lại sẽ được hàm viết trong ViewController xử lý thay vì phải viết vào lớp thừa kế của class AVAudioRecorder và AVAudioPlayer.
– AVAudioRecorder *recorder là đối tượng để thực hiện việc thu âm
– AVAudioPlayer *audioPlayer là đối tượng để phát lại đoạn âm thanh vừa thu
– Các phần tử giao diện được nối từ StoryBoard vào sẽ thêm mô tả IBOutlet
– NSTimer *tmrCounter sẽ bộ định thời sẽ đều đặn kiểm tra tiến độ thu âm và phát lại để cập nhật thông tin lên giao diện

Bước 5:
– Trước khi thu âm, cần chuẩn bị đường dẫn để ghi file âm thanh, đường dẫn này sẽ được đối tượng AVAudioPlayer *audioPlayer dùng khi phát lại.
– Cấu hình định dạng, chất lượng, tần số lấy mẫu âm thanh (tần số các cao, số lượng mẫu lớn, âm thanh sẽ chi tiết nhưng kích thước file sẽ lớn…)
– Do đây là chương trình thu âm, nên giao diện ban đầu là nút Record sẽ sẵn sàng còn nút PlayBack bị disabled. Nút Record sẽ có 2 hình ảnh: nút màu xám là chế độ nghỉ, nút màu đỏ là đang ghi âm.

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSArray *dirPaths;
    NSString *docsDir;
    
    dirPaths = NSSearchPathForDirectoriesInDomains(
                                                   NSDocumentDirectory, NSUserDomainMask, YES);
    docsDir = [dirPaths objectAtIndex:0];
    NSString *soundFilePath = [docsDir
                               stringByAppendingPathComponent:@"sound.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;
    
    self.recorder = [[AVAudioRecorder alloc]
                     initWithURL:soundFileURL
                     settings:recordSettings
                     error:&error];
    
    if (error)
    {
        //NSLog(@"error: %@", [error localizedDescription]);
        errorMessage.text = [NSString stringWithFormat:@"error: %@", [error localizedDescription]];
    } else {
        [self.recorder prepareToRecord];
    }
    
    [recordButton setImage:[UIImage imageNamed:@"record.png"] forState:UIControlStateSelected];
    [recordButton setImage:[UIImage imageNamed:@"stopRecord.png"]  forState:UIControlStateNormal];
}

Lưu ý đoạn lệnh dưới đây sẽ khởi tạo đối tượng thu âm. Con trỏ error ban đầu không được khởi tạo mà trỏ vào nil. Ta chuyền vào địa chỉ của con trỏ error (&error) chứ không phải địa chỉ đối tượng mà con trỏ trỏ vào. Nếu có lỗi, đối tượng lỗi NSError được khởi tạo và con trỏ sẽ trỏ vào đó, lúc này nó không còn là nil nữa. Đây là cách mà Objective-C bắt lỗi các hàm trong thư viện Foundation

NSError *error = nil;
self.recorder = [[AVAudioRecorder alloc]
                     initWithURL:soundFileURL
                     settings:recordSettings
                     error:&error];

Hứng sự kiện người dùng ấn nút ghi âm. Nếu không ghi âm, thì ghi âm và ngược lại. Các nút recordButton và playButton cũng được enable tương ứng.

- (IBAction)recordAudio:(id)sender {
    if (!self.recorder.recording)
    {   
        recordButton.selected = YES;
        playButton.enabled = NO;
        [self.recorder record];      
    } else {
        recordButton.selected = NO;
        playButton.enabled = YES;
        [self.recorder stop];
    }
}

Để giao diện trực quan, thân thiện hơn, chúng ta cần lập trình để cập nhật thời gian kể từ lúc bắt đầu thu âm.
tmrCounter là property được định nghĩa ở ViewController.h: @property (strong, nonatomic) NSTimer *tmrCounter;
tmrCounter sẽ định thời 0.5 giây chay hàm updateRecordingTime

if (!self.recorder.recording)
    {   
        recordButton.selected = YES;
        playButton.enabled = NO;
        [self.recorder record];
        if (self.tmrCounter) {
            [self.tmrCounter invalidate];
            self.tmrCounter = nil;
        }
        self.tmrCounter = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(updateRecordingTime) userInfo:nil repeats:YES];
        prgIndicator.hidden = YES;
    } else {
        recordButton.selected = NO;
        [self.recorder stop];
        [self.tmrCounter invalidate];
        playButton.enabled = YES;
    }
-(void)updateRecordingTime{
    if (self.recorder) {        
        [lblTime setText:[NSString stringWithFormat:@"%@", [self formatTime:[self.recorder currentTime]]]];
    }
}

Cuối cùng là hàm phát lại âm thanh vừa thu. Hàm sử dụng audioPlayer để phát lại và sử dụng đối tượng prgIndicator kiểu UIProgressView để hiển tiến độ phát lại. Đối tượng tmrCounter để cập nhật label hiển thị thời gian âm thanh đã phát lại trên tổng thời gian của đoạn âm thanh vừa thu âm.

- (IBAction)playBack:(id)sender {
    {
        recordButton.enabled = NO;        
        if (self.audioPlayer)
            self.audioPlayer = nil;
        NSError *error;
        self.audioPlayer = [[AVAudioPlayer alloc] 
                       initWithContentsOfURL:self.recorder.url                                    
                       error:&error];        
        audioPlayer.delegate = self;        
        if (error)
            errorMessage.text = [NSString stringWithFormat:@"error: %@", [error localizedDescription]];
        else 
        {
            [audioPlayer prepareToPlay];
            prgIndicator.hidden = NO;
            [audioPlayer play];
            if (self.tmrCounter) {
                self.tmrCounter = nil;
            }
            self.tmrCounter = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:YES];
            [prgIndicator setProgress:0.0];
        }
    }
}