【iOS】iOS音频录制并转码 MP3 格式

2022/08/18 iOS 共 15666 字,约 45 分钟

导读: 此处记录一下原生音频录制,及录制的音频文件转码问题。

音频录制

iOS 核心录制音频的类是 AVAudioRecorder

/// 核心代码 ------------------------------

/// 导入头文件
import AVFoundation
/// 系统录音类
private var recorder: AVAudioRecorder?

// 获取路径
var urlString = ""
if let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last {
    urlString = filePath.jjc_appendPath("audio.caf")
}
var url = URL(fileURLWithPath: urlString)

if !isStop {
    // 开始录音
    // 如果不初始化 AVAudioSession 这段代码,会出现 .caf 录音时长和文件大小不正确,同时转成 .mp3 后时长和文件大小也不正确
    let session = AVAudioSession.sharedInstance()
    do {
        try session.setCategory(.playAndRecord)
    } catch let error {
        debugPrint("初始化 AVAudioSession 失败 ----- \(error)")
    }
    
    let dict: [String:Any] = [AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.medium.rawValue),
                                            AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM),
                                    AVEncoderBitRateKey: NSNumber(value: 16),
                                        AVSampleRateKey: NSNumber(value: 11025),
                                    AVNumberOfChannelsKey: NSNumber(value: 2)]
    do {
        debugPrint("开始录音 ----------")
        self.recorder = try AVAudioRecorder(url: url, settings: dict)
        self.recorder?.record()
    } catch let error {
        HUD.showFailure("录音失败", error.localizedDescription)
    }
} else {
    // 结束录音
    self.recorder?.stop()
    // 将 .caf 转 .mp3 格式
    let mp3Path: String = LameTool.audio(toMP3: urlString, isDeleteSourchFile: false)
}
/// 核心方法参数

/// 创建一个录音文件并准备系统录制
func prepareToRecord() -> Bool
/// 开始录制音频
func record() -> Bool
/// 从一个具体的时间点开始录制音频
func record(atTime: TimeInterval) -> Bool
/// 录制音频持续时长
func record(forDuration: TimeInterval) -> Bool
/// 开始于某个时间录制音频并录制持续设定时长
func record(atTime: TimeInterval, forDuration: TimeInterval) -> Bool
/// 暂停录制音频
func pause()
/// 停止录制音频
func stop()
/// 判断当前是否处于录制中
var isRecording: Bool
/// 删除录制的音频文件
func deleteRecording() -> Bool
/// AVFoundation - AVAudioSettings.h - 所有的参数值都是 NSNumber 类型
/// property keys - values for all keys defined below are NSNumbers

/* keys for all formats */
/// 音频格式
public let AVFormatIDKey: String
/// 采样率
public let AVSampleRateKey: String
/// 通道数
public let AVNumberOfChannelsKey: String

/* linear PCM keys */
/// 采样位数, one of: 8, 16, 24, 32
public let AVLinearPCMBitDepthKey: String
/// 大端还是小端,内存的组织方式
public let AVLinearPCMIsBigEndianKey: String
/// 采样信号是整数还是浮点数
public let AVLinearPCMIsFloatKey: String
/// 是否允许音频交叉他的值
public let AVLinearPCMIsNonInterleaved: String

/* audio file type key */
/// 音频文件类型
public let AVAudioFileTypeKey: String

/* encoder property keys */
/// 音频编码质量
public let AVEncoderAudioQualityKey: String
/// 动态比特率编码时候的音质值
public let AVEncoderAudioQualityForVBRKey: String

/* only one of AVEncoderBitRateKey and AVEncoderBitRatePerChannelKey should be provided. */
/// 解码率
public let AVEncoderBitRateKey: String
/// 声道采样率
public let AVEncoderBitRatePerChannelKey: String
/// 编码时比特率策略
public let AVEncoderBitRateStrategyKey: String
/// 位深度,取值 8~32
public let AVEncoderBitDepthHintKey: String

/* sample rate converter property keys */
/// 采样率转换器的算法
public let AVSampleRateConverterAlgorithmKey: String
/// 采样率转换器的音质值
public let AVSampleRateConverterAudioQualityKey: String

/* channel layout */
/// 通道布局值
public let AVChannelLayoutKey: String

/* property values */
/* values for AVEncoderBitRateStrategyKey */
/// 常数
public let AVAudioBitRateStrategy_Constant: String
/// 平均数
public let AVAudioBitRateStrategy_LongTermAverage: String
/// 有限制的
public let AVAudioBitRateStrategy_VariableConstrained: String
/// 可变的
public let AVAudioBitRateStrategy_Variable: String

/* values for AVSampleRateConverterAlgorithmKey */
/// 普通
public let AVSampleRateConverterAlgorithm_Normal: String
/// 母带处理
public let AVSampleRateConverterAlgorithm_Mastering: String
/// 
public let AVSampleRateConverterAlgorithm_MinimumPhase: String
/// 音频文件质量
public enum AVAudioQuality : Int, @unchecked Sendable {
    case min = 0        // 最低
    case low = 32       // 较低
    case medium = 64    // 中等
    case high = 96      // 较高
    case max = 127      // 最高
}

音频文件转码

由于 iOS 录制音频的时候,一般默认都是使用 .caf 类型格式文件作为音频存储对象,虽然 .caf 文件在 iOS 系统中是可以通用使用,但是往往项目开发过程中为了兼容 Android 系统,都是需要将录音文件转码成 .mp3 通用格式才方便使用。

由于 Apple 官方提供的转码方式不是很方便,所以在此就比较推荐一个开源库:

Lame 是一个开源的 mp3 音频压缩软件,根据官网来看,全称是 Lame Aint an MP3 Encoder。当前最新版本为 2017-10-13 的 v3.100

主要的处理方式就是将这个 Lame 开源库下载并编译打包成静态库,供项目工程中使用。

下载 Lame

由于当下官方最新版本是 2017-10-13 更新的 v3.100 版本。

编译打包静态库

由于下载下来的 Lame 解压包无法直接应用在项目中,所以需要编译打包成静态库使用。此处借助一 .sh 脚本进行打包。

将下载好的 lame-ios-build 压缩包解压后,将其中的 build-lame.sh 文件移动到 Lame 解压包中,并进行部分参数的更改。

build-lame.sh 文件参数更改好后,终端执行以下指令:

/// cd 到 lame-3.100 文件夹根目录下
cd /Users/mxgx/Desktop/lame-3.100
/// 执行脚本文件
./build-lame.sh


/// 如果执行报 -bash:./build-lame.sh:Permission denied 错误,是由于没有权限,执行如下指令即可
chmod a+x build-lame.sh

正常情况下,该脚本会执行几十秒左右。

/// 执行结果如下
{22-08-18 15:11}[ruby-3.0.0]jcji:~/Desktop/lame-3.100@master✗✗✗✗✗✗ mxgx% ./build-lame.sh
building arm64...
checking build system type... x86_64-apple-darwin21.5.0
checking host system type... arm-apple-darwin
checking for a BSD-compatible install... /usr/local/bin/ginstall -c
checking whether build environment is sane... yes
checking for arm-apple-darwin-strip... no
checking for strip... strip
checking for a thread-safe mkdir -p... /usr/local/bin/gmkdir -p
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... yes
......
......
......
Making install in vc_solution
make[2]: Nothing to be done for `install-exec-am'.
make[2]: Nothing to be done for `install-data-am'.
make[2]: Nothing to be done for `install-exec-am'.
make[2]: Nothing to be done for `install-data-am'.
building fat binaries...

正常编译成功后,在 lame-3.100 文件夹下会生成 fat-lamethin-lame 两个文件夹,而在 fat-lame 文件夹下就有我们所需的 .a.h 文件了。

之后将这两个文件引入到项目中,尽量封装一个工具类用于转码成 .mp3 文件,比如文章后面的 LameTool 类。

/// 查看该静态库支持的架构(结果支持:armv7 armv7s x86_64 arm64)
{22-08-18 20:39}[ruby-3.0.0]jcji:~/Desktop/lame-3.100@master✗✗✗✗✗✗ mxgx% /Users/mxgx/Desktop/lame-3.100/fat-lame/lib
{22-08-18 20:40}[ruby-3.0.0]jcji:~/Desktop/lame-3.100/fat-lame/lib@master✗✗✗✗✗✗ mxgx% lipo -info libmp3lame.a
Architectures in the fat file: libmp3lame.a are: armv7 armv7s x86_64 arm64 
#!/bin/sh

CONFIGURE_FLAGS="--disable-shared --disable-frontend"

ARCHS="arm64 armv7s x86_64 i386 armv7"

# directories
# SOURCE 是下载的 lame 源码包解压后的目录,可以把 sh 脚本放到这个目录,source 改为 ""
SOURCE=""
# FAT 是所有指令集 build 后输出的目录,所有静态库被合并成一个静态库,用的就是这个文件夹下的 .h 和 .a 两个文件
FAT="fat-lame"

# SCRATCH="scratch-lame"
# SCRATCH 是下载的 lame 源码包解压后的目录,必须是绝对路径
SCRATCH="/Users/mxgx/Desktop/lame-3.100"
# must be an absolute path

# THIN 各自指令集 build 后输出的静态库所在的目录,每个指令集为一个静态库,目录下对用不同的指令有不同的文件夹,里面分别是对应指令的 .h 和 .a 两个文件
THIN=`pwd`/"thin-lame"

COMPILE="y"
LIPO="y"

if [ "$*" ]
then
	if [ "$*" = "lipo" ]
	then
		# skip compile
		COMPILE=
	else
		ARCHS="$*"
		if [ $# -eq 1 ]
		then
			# skip lipo
			LIPO=
		fi
	fi
fi

if [ "$COMPILE" ]
then
	CWD=`pwd`
	for ARCH in $ARCHS
	do
		echo "building $ARCH..."
		mkdir -p "$SCRATCH/$ARCH"
		cd "$SCRATCH/$ARCH"

		if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]
		then
		    PLATFORM="iPhoneSimulator"
		    if [ "$ARCH" = "x86_64" ]
		    then
		    	SIMULATOR="-mios-simulator-version-min=7.0"
                        HOST=x86_64-apple-darwin
		    else
		    	SIMULATOR="-mios-simulator-version-min=5.0"
                        HOST=i386-apple-darwin
		    fi
		else
		    PLATFORM="iPhoneOS"
		    SIMULATOR=
                    HOST=arm-apple-darwin
		fi

		XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'`
		CC="xcrun -sdk $XCRUN_SDK clang -arch $ARCH"
		#AS="$CWD/$SOURCE/extras/gas-preprocessor.pl $CC"
		CFLAGS="-arch $ARCH $SIMULATOR"
		if ! xcodebuild -version | grep "Xcode [1-6]\."
		then
			CFLAGS="$CFLAGS -fembed-bitcode"
		fi
		CXXFLAGS="$CFLAGS"
		LDFLAGS="$CFLAGS"

		CC=$CC $CWD/$SOURCE/configure \
		    $CONFIGURE_FLAGS \
                    --host=$HOST \
		    --prefix="$THIN/$ARCH" \
                    CC="$CC" CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS"

		make -j3 install
		cd $CWD
	done
fi

if [ "$LIPO" ]
then
	echo "building fat binaries..."
	mkdir -p $FAT/lib
	set - $ARCHS
	CWD=`pwd`
	cd $THIN/$1/lib
	for LIB in *.a
	do
		cd $CWD
		lipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIB
	done

	cd $CWD
	cp -rf $THIN/$1/include $FAT
fi
/// 脚本注释
#disable-shared 关闭动态链接库
#disable-frontend 不编译出Lame的可执行文件

#prefix  指定编译好的库放在哪个目录
#CC 指定编译工具
#CFLAGS  指定 编译 过程中的参数  是否开启比特code,App最低版本
#LDFLAGS 指定 链接 过程中的参数  是否开启bitcode,App支持的最低版本
#host 指定最终库要运行的平台

Lame 转码 mp3 工具类

/// LameTool.h -----------------------------------
/// 音频文件转码 MP3(由于方法内部多以 C 语言为主,用 Swift 语言写的时候有些问题,此处仍以 OC 文件)
#import <Foundation/Foundation.h>

@interface LameTool : NSObject

+ (NSString *)audioToMP3: (NSString *)sourcePath samplerate:(int)samplerate bitRate:(int)bitRate isDeleteSourchFile:(BOOL)isDelete;

@end



/// LameTool.m -----------------------------------
/// https://www.likecs.com/show-246141.html
/// https://www.jianshu.com/p/21eaf8504728
/// https://juejin.cn/post/6844903704181604365

#import "LameTool.h"
#import "lame.h"

@implementation LameTool

+ (NSString *)audioToMP3: (NSString *)sourcePath samplerate:(int)samplerate bitRate:(int)bitRate isDeleteSourchFile:(BOOL)isDelete {
    
    // 输入路径
    NSString *inPath = sourcePath;
    // 判断输入路径是否存在
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:sourcePath]) {
        NSLog(@"文件不存在");
    }
    // 输出路径
    NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
    @try {
        int read, write;
        
        // 被转换的音频文件位置
        FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb"); 
        // skip file header 跳过 PCM header 能保证录音的开头没有噪音
        fseek(pcm, 4*1024, SEEK_CUR); 
        // 输出生成的Mp3文件位置(这里注意打开文件的方式wb 只写方式打开或新建一个二进制文件,只允许写数据;wb+ 读写方式打开或建立一个二进制文件,允许读和写
        FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb+");
        
        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();

        // 设置1为单通道,默认为2双通道(录音时是双通道)
//        lame_set_num_channels(lame,2);
        // 设置最终mp3编码输出的声道模式,如果不设置则和输入声道数一样。参数是枚举,STEREO代表双声道,MONO代表单声道
//        lame_set_mode(lame,STEREO);

        // 设置被输入编码器的原始数据的采样率,单位是HZ 通常设置成44100 44.1k
        lame_set_in_samplerate(lame, samplerate);

        // 设置比特率控制模式,默认是CBR,但是通常我们都会设置VBR。参数是枚举,vbr_off代表CBR,vbr_abr代表ABR(因为ABR不常见,所以本文不对ABR做讲解)vbr_mtrh代表VBR,vbr_default = vbr_mtrh 代表 VBR
//        lame_set_VBR(lame, vbr_default);
        // 设置VBR的比特率,只有在VBR模式下才生效
//        lame_set_VBR_mean_bitrate_kbps(lame, 16);

        // CBR 固定比特率;ABR 平均比特率
        // VBR 动态比特率,以质量为前提兼顾文件大小的方式

        // 设置 CBR 模式和比特率
        lame_set_VBR(lame, vbr_off);
        // 设置CBR的比特率,只有在CBR模式下才生效
        lame_set_brate(lame, bitRate);
        // 根据上面设置好的参数建立编码器
        lame_init_params(lame);
        
        do {
            size_t size = (size_t)(2 * sizeof(short int));
            // fread是一个函数。从一个文件流中读数据,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0。
            read = (int)fread(pcm_buffer, size, PCM_SIZE, pcm);
            if (read == 0) {
                // 刷新编码器缓冲,获取残留在编码器缓冲里的数据。这部分数据也需要写入mp3文件
                write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
            } else {
                // 将PCM数据送入编码器,获取编码出的mp3数据。这些数据写入文件就是mp3文件
                write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
            } 
            // 写入文件
            fwrite(mp3_buffer, write, 1, mp3);
            
        } while (read != 0);
        
        // 将VBR/INFO tags封装到一个MP3 Frame中,写到文件开头。如果输出流没有办法回溯,那么必须在第3步设置lame_set_bWriteVbrTag(gfp,0),这一步调用lame_mp3_tags_fid(lame_global_flags *,FILE* fid)将fid参数=NULL。这样的话那个开头的信息帧(MP3 FRAME)的所有字节都是0
        // 在lame_close之前一定要调用lame_mp3_tags_fid()这个方法,不然时间计算不准确,此处是针对 VBR 模式
        // 编码的MP3会预留 VBR tag的空间,如果不插入,会影响计算时间。非VBR模式也会空出 VBR tag。这也是前面文件读写那设置‘wb+‘的原因。 进源码看吧,有详细注释!
//        lame_mp3_tags_fid(lame, mp3);
        
        // 销毁编码器,释放资源
        lame_close(lame);
        fclose(mp3);
        fclose(pcm);
    }
    @catch (NSException *exception) {
        NSLog(@"%@",[exception description]);
    }
    @finally {
        NSLog(@"MP3生成成功:");
        if (isDelete) {
            
            NSError *error;
            [fm removeItemAtPath:sourcePath error:&error];
            if (error == nil) {
                NSLog(@"删除源文件成功");
            }
        }
        return outPath;
    }
}

@end
/// CBR 模式,固定比特率
+ (NSString *)audioToMP3: (NSString *)sourcePath samplerate:(int)samplerate bitRate:(int)bitRate isDeleteSourchFile:(BOOL)isDelete {
    
    NSString *inPath = sourcePath;
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:sourcePath]) {
        NSLog(@"文件不存在");
    }
    NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
    
    @try {
        int read, write; 
        
        FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb");
        fseek(pcm, 4*1024, SEEK_CUR);
        FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb");
        
        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_mode(lame, STEREO);
        lame_set_in_samplerate(lame, samplerate);
        lame_set_VBR(lame, vbr_off);
        lame_set_brate(lame, bitRate);
        lame_init_params(lame);
        
        do {
            size_t size = (size_t)(2 * sizeof(short int));
            read = (int)fread(pcm_buffer, size, 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 {
        NSLog(@"MP3生成成功 ------------------");
        if (isDelete) {
            NSError *error;
            [fm removeItemAtPath:sourcePath error:&error];
            if (error == nil) {
                NSLog(@"删除源文件成功");
            }
        }
        return outPath;
    }
}
/// VBR 模式,动态比特率
+ (NSString *)audioToMP3: (NSString *)sourcePath samplerate:(int)samplerate bitRate:(int)bitRate isDeleteSourchFile:(BOOL)isDelete {
    
    NSString *inPath = sourcePath;
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:sourcePath]) {
        NSLog(@"文件不存在");
    }
    NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
    
    @try {
        int read, write;
        
        FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb");
        fseek(pcm, 4*1024, SEEK_CUR);
        FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb");
        
        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_mode(lame, STEREO);
        lame_set_in_samplerate(lame, samplerate);
        lame_set_VBR(lame, vbr_default);
        lame_set_VBR_mean_bitrate_kbps(lame, bitRate);
        lame_set_VBR_max_bitrate_kbps(lame, bitRate * 2);
        lame_set_VBR_min_bitrate_kbps(lame, bitRate);
        lame_init_params(lame);
        
        do {
            size_t size = (size_t)(2 * sizeof(short int));
            read = (int)fread(pcm_buffer, size, 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_mp3_tags_fid(lame, mp3);
        
        lame_close(lame);
        fclose(mp3);
        fclose(pcm);
    }
    @catch (NSException *exception) {
        NSLog(@"%@",[exception description]);
    }
    @finally {
        NSLog(@"MP3生成成功 ------------------");
        if (isDelete) {
            NSError *error;
            [fm removeItemAtPath:sourcePath error:&error];
            if (error == nil) {
                NSLog(@"删除源文件成功");
            }
        }
        return outPath;
    }
}

音频录制 + 转码 MP3 完整代码

/// 桥接头文件
DDYEducation-Bridging-Header.h

/// 桥接头文件内容
#import "LameTool.h"
/// 导入头文件
import AVFoundation

/// 系统录音类
private var recorder: AVAudioRecorder?
/// 获取录音权限
fileprivate func getRecordAudioAuthorization() {
    // 获取原生麦克风权限
    let status = AVCaptureDevice.authorizationStatus(for: .audio)
    if status == .notDetermined {
        // 未授权
        AVCaptureDevice.requestAccess(for: .audio) { granted in
            if granted {
                self.recordAudio()
            }
        }
    } else if status == .denied || status == .restricted {
        // 用户拒绝 || 未授权,家长限制
        let alertVC = JJC_Alert(title: "温馨提示", message: "录音权限受限,请前往设置界面进行开启", leftTitle: "取消", rightTitle: "确定", rightAction: {
            if let url = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(url)
            }
        })
        present(alertVC, animated: true, completion: nil)
    } else {
        recordAudio()
    }
}


/// 录音功能
fileprivate func recordAudio(_ isStop: Bool = false) {
    // 获取路径
    var urlString = ""
    if let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last {
        urlString = filePath.jjc_appendPath("audio.caf")
    }
    var url = URL(fileURLWithPath: urlString)
    
    if !isStop {
        // 开始录音
        // 如果不初始化 AVAudioSession 这段代码,会出现 .caf 录音时长和文件大小不正确,同时转成 .mp3 后时长和文件大小也不正确
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playAndRecord)
        } catch let error {
            debugPrint("初始化 AVAudioSession 失败 ----- \(error)")
        }
        
        let dict: [String:Any] = [AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.medium.rawValue),
                                                AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM),
                                        AVEncoderBitRateKey: NSNumber(value: 16),
                                            AVSampleRateKey: NSNumber(value: 11025),
                                        AVNumberOfChannelsKey: NSNumber(value: 2)]
        do {
            debugPrint("开始录音 ----------")
            self.recorder = try AVAudioRecorder(url: url, settings: dict)
            self.recorder?.record()
        } catch let error {
            HUD.showFailure("录音失败", error.localizedDescription)
        }
    } else {
        // 结束录音
        self.recorder?.stop()
        // 将 .caf 转 .mp3 格式
        let mp3Path: String = LameTool.audio(toMP3: urlString, samplerate: 11025, bitRate: 16, isDeleteSourchFile: false)

        // 执行上传等操作
    }
}

参考链接

版权声明

原文作者苜蓿鬼仙(苜蓿、jijiucheng)

原文链接GitHub.io - 苜蓿鬼仙 - 【iOS】iOS音频录制并转码 MP3 格式

发表日期:2022/08/18 21:00:00

更新日期:2022/08/26 17:00:00

-

GitHubGitHub - jijiucheng

个人博客GitHub.io - 苜蓿鬼仙

小专栏小专栏 - 苜蓿鬼仙

掘金掘金 - 苜蓿鬼仙

微博微博 - 苜蓿鬼仙

公众号微信 - 苜蓿小站

小程序微信 - 苜蓿小站

文档信息

Search

    Table of Contents