2015-12-10 17:35:32 hyplcf 阅读数 1829

在iOS设备中进行录音,录音文件的格式为wav。但这种格式文件会很大,上传到服务器会消耗大量流量。为了适应终端的播放功能以及文件大小的要求,特将wav转换为mp3格式文件来使用。

注意:
在录制wav文件时,需要使用双通道,否则在转换为MP3格式时,声音不对。

首先,下载lame源码:http://sourceforge.net/projects/lame/files/lame/3.99/,怎么编译可以查看这篇文章编译完成后,将fat-lame文件夹下的lame.hlibmp3lame.a文件导入项目中。如图:



我们打开物流唐山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) {
}];



未完待续。。。
2018-12-13 19:04:13 u012478759 阅读数 189

填坑记录

最近做语音项目,需要上传语音文件。因为需要iOS,Android两端都要能够在线播放,保存格式为aac,Android自带编码器MediaCodec,很轻松实现。

但是测试过程中遇到了问题,华为mate9不能播放,播放时解码失败,无奈啊,只能找别的办法,最后将音频转码成MP3再上传,解决了问题。

分享一下,用的是Lame实现的,实现过程,悉数奉上。
github地址 https://github.com/suntiago/lame4androiddemo

第一步需要将原始pcm流保存为wav文件。
只需要机上pcm协议头部,尾部即可。
切记需要修改,如果设置不正确,转码会失败。

//采样率 
private static int frequency = 16000;
//CHANNEL_IN_STEREO;//双声道
private static int channelConfiguration = AudioFormat.CHANNEL_IN_MONO;
//采样声道
private static int channels = 1;
//音频数据格式:脉冲编码调制(PCM)每个样品16位
private static int EncodingBitRate = AudioFormat.ENCODING_PCM_16BIT;
private static final int RECORDER_BPP = 16;
    //PCM  原始文件传wav
    // 为wav文件添加头,尾
    private void copyWaveFile(String inFilename, String outFilename) {
        Log.d(TAG, "copyWaveFile");
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = frequency;
        long byteRate = RECORDER_BPP * frequency * channels / 8;
        byte[] data = new byte[1280];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void WriteWaveFileHeader(
            FileOutputStream out, long totalAudioLen,
            long totalDataLen, long longSampleRate, int channels,
            long byteRate) throws IOException {
        Log.d(TAG, "WriteWaveFileHeader");
        byte[] header = new byte[44];

        header[0] = 'R';  // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f';  // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1;  // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (channels * 16 / 8);  // block align
        header[33] = 0;
        header[34] = RECORDER_BPP;  // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }

加载androidlame库,我的so库是在 https://github.com/flyingfishes/LameForAndroid基础之上编译的。

static {
        System.loadLibrary("androidlame");
    }
    public AndroidLame() {
        initializeDefault();
    }

    public AndroidLame(LameBuilder builder) {
        initialize(builder);
    }

    private void initialize(LameBuilder builder) {
        initialize(builder.inSampleRate, builder.outChannel, builder.outSampleRate,
                builder.outBitrate, builder.scaleInput, getIntForMode(builder.mode), getIntForVbrMode(builder.vbrMode), builder.quality, builder.vbrQuality, builder.abrMeanBitrate,
                builder.lowpassFreq, builder.highpassFreq, builder.id3tagTitle, builder.id3tagArtist,
                builder.id3tagAlbum, builder.id3tagYear, builder.id3tagComment);
    }

    public int encode(short[] buffer_l, short[] buffer_r,
                      int samples, byte[] mp3buf) {

        return lameEncode(buffer_l, buffer_r, samples, mp3buf);
    }

    public int encodeBufferInterLeaved(short[] pcm, int samples,
                                       byte[] mp3buf) {
        return encodeBufferInterleaved(pcm, samples, mp3buf);
    }

    public int flush(byte[] mp3buf) {
        return lameFlush(mp3buf);
    }

    public void close() {
        lameClose();
    }


    ///////////NATIVE
    private static native void initializeDefault();

    private static native void initialize(int inSamplerate, int outChannel,
                                          int outSamplerate, int outBitrate, float scaleInput, int mode, int vbrMode,
                                          int quality, int vbrQuality, int abrMeanBitrate, int lowpassFreq, int highpassFreq, String id3tagTitle,
                                          String id3tagArtist, String id3tagAlbum, String id3tagYear,
                                          String id3tagComment);

    private native static int lameEncode(short[] buffer_l, short[] buffer_r,
                                         int samples, byte[] mp3buf);


    private native static int encodeBufferInterleaved(short[] pcm, int samples,
                                                      byte[] mp3buf);


    private native static int lameFlush(byte[] mp3buf);


    private native static void lameClose();


    ////UTILS
    private static int getIntForMode(LameBuilder.Mode mode) {
        switch (mode) {
            case STEREO:
                return 0;
            case JSTEREO:
                return 1;
            case MONO:
                return 3;
            case DEFAULT:
                return 4;
        }
        return -1;
    }

    private static int getIntForVbrMode(LameBuilder.VbrMode mode) {
        switch (mode) {
            case VBR_OFF:
                return 0;
            case VBR_RH:
                return 2;
            case VBR_ABR:
                return 3;
            case VBR_MTRH:
                return 4;
            case VBR_DEFAUT:
                return 6;
        }
        return -1;
    }
2017-02-09 16:22:58 sg_zxw 阅读数 3400

最近在项目中用到lame转码将wav转换成MP3格式,其中遇到了不少的坑,最大的一个就是转码完成后, 用audioPlayer.duration获取的时间不准,而且获取的时间一直在变,最后在一位大神的帮助下解决了这个问题,下面就分享下解决过程(把整个转码过程都说一遍)!

我从录制开始讲:

首先在录音时要设置录音参数    记住了,通道数目必须设置为2,否则转码后声音不对


//录音设置  
NSMutableDictionary *recordSettings = [[NSMutableDictionary alloc] init];  
//录音格式 无法使用  
[recordSettings setValue :[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey: AVFormatIDKey];  
//采样率  
[recordSettings setValue :[NSNumber numberWithFloat:11025.0] forKey: AVSampleRateKey];//44100.0  
//通道数  
[recordSettings setValue :[NSNumber numberWithInt:2] forKey: AVNumberOfChannelsKey];  
//线性采样位数  
//[recordSettings setValue :[NSNumber numberWithInt:16] forKey: AVLinearPCMBitDepthKey];  
//音频质量,采样质量  
[recordSettings setValue:[NSNumber numberWithInt:AVAudioQualityMin] forKey:AVEncoderAudioQualityKey];  

接着是转码了   注意:注意了:录音的采样率和转码的采样率必须一样   必须一样

- (void)audio_PCMtoMP3  
{  
  
    NSString *mp3FileName = [self.audioFileSavePath lastPathComponent];  
    mp3FileName = [mp3FileName stringByAppendingString:@".mp3"];  
    NSString *mp3FilePath = [self.audioTemporarySavePath stringByAppendingPathComponent:mp3FileName];  
      
    @try {  
        int read, write;  
          
        FILE *pcm = fopen([self.audioFileSavePath cStringUsingEncoding:1], "rb");  //source 被转换的音频文件位置  
        fseek(pcm, 4*1024, SEEK_CUR);                                   //skip file header  
        FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:1], "wb");  //output 输出生成的Mp3文件位置  
          
        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_in_samplerate(lame, 11025.0);  
        lame_set_VBR(lame, vbr_default);  
        lame_init_params(lame);  
          
        do {  
            read = 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);  
    }  
    @catch (NSException *exception) {  
        NSLog(@"%@",[exception description]);  
    }  
    @finally {  
        self.audioFileSavePath = mp3FilePath;  
        NSLog(@"MP3生成成功: %@",self.audioFileSavePath);  
    }  
  
} 


(上面的代码都是网上copy的,重点看参数的设置)转码成功后,如果你用 audioPlayer.duration获取播放时间,你会发现时间不准 

看解决方案:

//只有这个方法获取时间是准确的 audioPlayer.duration获得的时间不准
    AVURLAsset* audioAsset =[AVURLAsset URLAssetWithURL:fileUrl options:nil];
    CMTime audioDuration = audioAsset.duration;
    float audioDurationSeconds =CMTimeGetSeconds(audioDuration);

用上面这个方法,你就会发现获取的时间准了,哈哈 解决问题!!!!!



2014-09-10 20:57:38 u012576807 阅读数 1016
音频转换工具
平常使用.wav或.mp3格式的音频文件
而为了性能考虑,推荐使用.caf格式,通过工具afconvert可方便转换。
.caf  CoreAudio Format

打开终端,输入:avconvert


然后,在终端输入如下命令,查看支持哪些格式的音频。
afconvert -hf (意思是help formats)


比较重要的如下:






afconvert 命令的的使用格式如下:


afconvert [option...] input_file [output_file]

其中[option...]主要有以下几个操作:

-f 指定文件格式

-d 数据格式

-c 声道

-b 比特率

比如命令:
afconvert -f caff -d 'ima4' -c 1 in.mp3out.caf
用于将 in.mp3 转换为 out.caf。

其中 -f 指定文件格式为caff,即采用ima4压缩的.caf文件格式;

其中- d 指定数据格式;

其中 -c 就是声道数了。


改变当前目录及子目录中所有 .mp3 文件的数据格式为 ima4


find . -name '*.mp3' -exec afconvert -f caff -d 'ima4' {} \;
改变当前目录下单个.
mp3文件的数据格式为 ima4
find . -name '*.mp3' -execafconvert-f caff -d aac {} \;
改变当前目录下单个.mp3文件的数据格式为 aac


用于将sample.mp3 转换为 out.caf
afconvert -f caff -d 'ima4' -c 1 sample.mp3 out.caf

更多命令用法请输入以下命令:
afconvert -h
转换为AIFF格式(未压缩,文件变大了):
afconvert -f AIFF -d I8  (i8必须大写)



2013-04-25 14:54:26 looffer 阅读数 644

First of all, the preferred sound format for iPhone is LE formatted CAF, or mp3 for music. You can convert a wav to caf with the built in terminal utility:

afconvert -f caff -d LEI16 crash.wav crash.caf 
OR directly
afconvert sound1.wav sound1.caf

Then the easiest away to play a sound is to use the AVAudioPlayer... this quick function can help you load a sound resource:

- (AVAudioPlayer *) soundNamed:(NSString *)name {
    NSString * path;
    AVAudioPlayer * snd;
    NSError * err;

    path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name];

    if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
        NSURL * url = [NSURL fileURLWithPath:path];
        snd = [[[AVAudioPlayer alloc] initWithContentsOfURL:url 
                                                      error:&err] autorelease];
        if (! snd) {
            NSLog(@"Sound named '%@' had error %@", name, [err localizedDescription]);
        } else {
            [snd prepareToPlay];
        }
    } else {
        NSLog(@"Sound file '%@' doesn't exist at '%@'", name, path);
    }

    return snd;
}


iOS播放提示音

阅读数 3134

没有更多推荐了,返回首页