1. 程式人生 > >04.iOS 使用lame將wav轉換為mp3

04.iOS 使用lame將wav轉換為mp3

在iOS裝置中進行錄音,錄音檔案的格式為wav。但這種格式檔案會很大,上傳到伺服器會消耗大量流量。為了適應終端的播放功能以及檔案大小的要求,特將wav轉換為mp3格式檔案來使用。

注意:
在錄製wav檔案時,需要使用雙通道,否則在轉換為MP3格式時,聲音不對。


我們開啟物流唐山app,點選首頁地圖下的釋出按鈕,在彈出的錄音介面中,當我們點選"按住說話"按鈕 ,將會開始錄音。


錄音說明:

1.當錄音時間小於2.5s的時候,將會彈出說話時間太短對話方塊,錄音將會取消。

2.當點選按鈕,但是在按鈕外部鬆開的時候,錄音也將會取消。

3.只有當手指在按鈕內部鬆開,並且錄音時間大於2.5s的時候,錄音有效,儲存為wav格式檔案。

所以我們需要新增三個Target,新增按鈕的程式碼如下:

// 說話按鈕
UIImage *sayButtonImage = [UIImage imageNamed:@"完成_07"];
self.sayButton = [[UIButton alloc] initWithFrame:CGRectMake((ScreenW - sayButtonImage.size.width) / 2, ScreenH - 60 - sayButtonImage.size.height, sayButtonImage.size.width,sayButtonImage.size.height)];
[self.sayButton setImage:sayButtonImage forState:UIControlStateNormal];
[self.sayButton addTarget:self action:@selector(touchDown) forControlEvents:UIControlEventTouchDown];
[self.sayButton addTarget:self action:@selector(touchUpInside) forControlEvents:UIControlEventTouchUpInside];
[self.sayButton addTarget:self action:@selector(touchOutside) forControlEvents:UIControlEventTouchUpOutside];
[self addSubview:self.sayButton];
接下來就是使用AVAudioRecorder進行錄音:
- (void)startRecorder
{
    //根據當前時間生成檔名
    NSString *recordFileName = [VoiceConverter getCurrentTimeString];
    //獲取路徑
    self.recordFilePath = [VoiceConverter getPathFromFileName:recordFileName ofType:@"wav"];
    //初始化錄音
    self.recorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:self.recordFilePath] settings:[VoiceConverter GetAudioRecorderSettingDict] error:nil];
    self.recorder.delegate = self;
    //準備錄音
    if ([self.recorder prepareToRecord]){
        
        [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryRecord error:nil];
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        [self.recorder record];
    }
}
錄音完成會呼叫- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag;我們可以在這個函式裡邊將錄音檔案轉換為mp3檔案
#pragma mark - 錄音結束返回的資訊
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
{
    NSFileManager *manager = [[NSFileManager alloc] init];
    if (![manager fileExistsAtPath:self.recordFilePath]) {
        return;// 如果檔案不存在自己返回
    }
    // 獲取轉換後的mp3檔案路徑
    NSString *mp3FilePath = [VoiceConverter getPathFromFileName:@"lvRecord1" ofType:@"mp3"];
    if ([VoiceConverter ConvertWavToMp3:self.recordFilePath mp3SavePath:mp3FilePath]) {
        [manager removeItemAtPath:self.recordFilePath error:NULL];// 刪除錄音檔案
    }
    TSLPutAudioView *putView = [[TSLPutAudioView alloc] init];
    putView.filePath = mp3FilePath;
    [putView show];
}
在上面兩個函式中,都出現了VoiceConverter這個類,這是我自己定義的一個工具類,用於配置錄音以及將wav轉換為mp3。以下是其程式碼:

VoiceConverter.h

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

@interface VoiceConverter : NSObject

/**
 *  轉換wav到mp3
 *
 *  @param aWavPath  wav檔案路徑
 *  @param aSavePath mp3儲存路徑
 *
 *  @return 0失敗 1成功
 */
+ (int)ConvertWavToMp3:(NSString *)aWavPath mp3SavePath:(NSString *)aSavePath;
/**
	獲取錄音設定.
    建議使用此設定,如有修改,則轉換amr時也要對應修改引數,比較麻煩
	@returns 錄音設定
 */
+ (NSDictionary*)GetAudioRecorderSettingDict;

/**
 *  獲取快取路徑
 *
 *  @return 快取路徑
 */
+ (NSString *)getCacheDirectory;

/**
 *  生成當前時間字串
 */
+ (NSString *)getCurrentTimeString;
/**
 *  通過名字及型別獲得檔案路徑
 *
 *  @param fileName 檔名
 *  @param type     檔案型別
 *
 *  @return 檔案路徑
 */
+ (NSString *)getPathFromFileName:(NSString *)fileName ofType:(NSString *)type;
@end

VoiceConverter.mm
#import "VoiceConverter.h"
#import "lame.h"


@implementation VoiceConverter

//獲取錄音設定
+ (NSDictionary*)GetAudioRecorderSettingDict{
    NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
                                   [NSNumber numberWithFloat: 8000.0],AVSampleRateKey, //取樣率
                                   [NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
                                   [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,//取樣位數 預設 16
                                   [NSNumber numberWithInt: 2], AVNumberOfChannelsKey,//通道的數目
                                   //                                   [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,//大端還是小端 是記憶體的組織方式
                                   //                                   [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,//取樣訊號是整數還是浮點數
                                   //                                   [NSNumber numberWithInt: AVAudioQualityMedium],AVEncoderAudioQualityKey,//音訊編碼質量
                                   nil];
    return recordSetting;
}

+ (int)ConvertWavToMp3:(NSString *)aWavPath mp3SavePath:(NSString *)aSavePath
{
    int state = 0;
    @try {
        int read, write;
        
        FILE *pcm = fopen([aWavPath cStringUsingEncoding:NSASCIIStringEncoding], "rb");  //source
        fseek(pcm, 4*1024, SEEK_CUR);                                   //skip file header
        FILE *mp3 = fopen([aSavePath cStringUsingEncoding:NSASCIIStringEncoding], "wb");  //output
        
        const int PCM_SIZE = 8192;
        const int MP3_SIZE = 8192;
        short int pcm_buffer[PCM_SIZE*2];
        unsigned char mp3_buffer[MP3_SIZE];
        
        lame_t lame = lame_init(); // 初始化
        lame_set_num_channels(lame, 2); // 雙聲道
        lame_set_in_samplerate(lame, 8000); // 8k取樣率
        lame_set_brate(lame, 16);  // 壓縮的位元率為16
        lame_set_quality(lame, 2);  // mp3音質
        lame_init_params(lame);
        
        do {
            read = (int)fread(pcm_buffer, 2*sizeof(short int), PCM_SIZE, pcm);
            if (read == 0)
                write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            else
                write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
            
            fwrite(mp3_buffer, write, 1, mp3);
            
        } while (read != 0);
        
        lame_close(lame);
        fclose(mp3);
        fclose(pcm);
        state = 1;
    }
    @catch (NSException *exception) {
        state = 0;
    }
    @finally {
        return state;
    }
}

#pragma mark - 通過名字及型別獲得檔案路徑
/**
 *  通過名字及型別獲得檔案路徑
 *
 *  @param fileName 檔名
 *  @param type     檔案型別
 *
 *  @return 檔案路徑
 */
+ (NSString *)getPathFromFileName:(NSString *)fileName ofType:(NSString *)type
{
    NSString *filePath = [[[self getCacheDirectory]stringByAppendingPathComponent:fileName]stringByAppendingPathExtension:type];
    NSFileManager *filemanager = [[NSFileManager alloc]init];
    if ([filemanager fileExistsAtPath:filePath]){ // 如果檔案已存在,刪除檔案
        [filemanager removeItemAtPath:filePath error:NULL];
    }
    return filePath;
}

#pragma mark - 獲得快取路徑
/**
 *  獲取快取路徑
 *
 *  @return 快取路徑
 */
+ (NSString *)getCacheDirectory
{
    NSString *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    NSString *voicePath = [cache stringByAppendingPathComponent:@"Voice"];
    NSFileManager *filemanager = [[NSFileManager alloc]init];
    if (![filemanager fileExistsAtPath:voicePath]){
        [filemanager createDirectoryAtPath:voicePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    return voicePath;
}

#pragma mark - 生成當前時間字串
+ (NSString *)getCurrentTimeString{
    NSDateFormatter *dateformat = [[NSDateFormatter  alloc]init];
    [dateformat setDateFormat:@"yyyyMMddHHmmss"];
    return [dateformat stringFromDate:[NSDate date]];
}

@end

說明:
+ (NSDictionary*)GetAudioRecorderSettingDict;//該函式返回錄音設定引數
+ (int)ConvertWavToMp3:(NSString *)aWavPath mp3SavePath:(NSString *)aSavePath;//該函式將wav檔案轉換為mp3檔案

由於在VoiceConverter.mm檔案中使用了第三方靜態庫,所以VoiceConverter.mm字尾是mm,用於混合編譯。


錄音完成以後,會彈出提交錄音對話方塊:


使用AFNetworking將錄音檔案提交到伺服器即可。

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:@"http://www.560315.com/MobileAPI/AudioAdd" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
    [formData appendPartWithFileData:[NSDatadataWithContentsOfFile:self.filePath] name:@"upname" fileName:[self.filePathlastPathComponent] mimeType:@"audio/mp3"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
    // 上傳成功
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];



未完待續。。。