导读:
此处记录一下原生音频录制,及录制的音频文件转码问题。
音频录制
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 – https://sourceforge.net/projects/lame/files/lame/
Lame
是一个开源的 mp3 音频压缩软件,依据官网来看,全称是 Lame Aint an MP3 Encoder。当时最新版别为 2017-10-13 的 v3.100
。
主要的处理办法就是将这个 Lame 开源库下载并编译打包成静态库,供项目工程中运用。
下载 Lame
由于当下官方最新版别是 2017-10-13
更新的 v3.100
版别。
- Lame 下载链接
编译打包静态库
由于下载下来的 Lame 解压包无法直接应用在项目中,所以需求编译打包成静态库运用。此处借助一 .sh
脚本进行打包。
- GitHub – kewlbear – lame-ios-build
将下载好的 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-lame
和 thin-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:///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, isDeleteSourchFile: false)
// 履行上传等操作
}
}
参考链接
- 简书 – 翀鹰精灵 – iOS中录音功用
- 简书 – iOS程序媛ing – iOS 录音文件caf转mp3
- 简书 – 精神病患者link常 – iOS录音–caf转MP3
- 简书 – sweetpf – AVFoundation结构解析看这儿 – 目录
- 简书 – sweetpf – AVFoundation结构解析看这儿(1)- 概论
- 简书 – sweetpf – AVFoundation结构解析看这儿(2)- 媒体捕捉与视频拍摄
- 简书 – sweetpf – AVFoundation结构解析看这儿(3)- 音频AVAudio
- 简书 – sweetpf – AVFoundation结构解析看这儿(4)- CMTime
- 简书 – sweetpf – AVFoundation结构解析看这儿(6)- AVAssetExportSession
- 简书 – sweetpf – AVFoundation结构解析看这儿(7)- AVAssetImageGenerator
- Likecs – smileberry – MP3 Lame 转化 参数 设置(转)
- 简书 – aven_kang – iOS 运用lame库进行音频转mp3
- – YY一块腹肌 – (一)音视频学习理论知识【引荐】
版权声明
原文作者:苜蓿鬼仙(苜蓿、jijiucheng)
原文链接:GitHub.io – 苜蓿鬼仙 – 【iOS】iOS音频录制并转码 MP3 格局
宣布日期:2022/08/18 21:00:00
更新日期:2022/08/26 17:00:00
GitHub:GitHub – jijiucheng
个人博客:GitHub.io – 苜蓿鬼仙
小专栏:小专栏 – 苜蓿鬼仙
: – 苜蓿鬼仙
微博:微博 – 苜蓿鬼仙
公众号:微信 – 苜蓿小站
小程序:微信 – 苜蓿小站