u 文章的目的为了记录使用Objective-C 进行IOS app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 Objective-C IOS 应用开发(一)macOS 的使用
开源 Objective-C IOS 应用开发(二)Xcode安装
开源 Objective-C IOS 应用开发(三)第一个iPhone的APP
开源 Objective-C IOS 应用开发(四)Xcode工程文件结构
开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)
推荐链接:
开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客
开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客
开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客
开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客
开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客
开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客
开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客
开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客
开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客
开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客
开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客
开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客
开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客
开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客
开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
本章内容主要是实现CAF音频的录制。
目录:
1.手机演示
2.所有源码
3.源码分析
一、手机演示

二、所有源码
AppDelegate.h文件
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
AppDelegate.m文件
#import "AppDelegate.h"
#import "MainViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建窗口
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// 创建主视图控制器
MainViewController *mainViewController = [[MainViewController alloc] init];
// 创建导航控制器(可选,提供更好的导航体验)
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
navController.navigationBar.prefersLargeTitles = YES;
mainViewController.navigationItem.title = @"CAF音频录制";
// 设置根视图控制器
self.window.rootViewController = navController;
// 设置窗口背景色并显示
self.window.backgroundColor = [UIColor systemBackgroundColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
AudioRecorder.h文件
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
NS_ASSUME_NONNULL_BEGIN
@interface AudioRecorder : NSObject
@property (nonatomic, readonly) BOOL isRecording;
@property (nonatomic, copy) NSString *currentFilePath;
- (BOOL)startRecording;
- (void)stopRecording;
- (void)pauseRecording;
- (void)resumeRecording;
@end
NS_ASSUME_NONNULL_END
AudioRecorder.m文件
#import "AudioRecorder.h"
#define kNumberBuffers 3
typedef struct AQRecorderState {
AudioStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
AudioFileID mAudioFile;
UInt32 bufferByteSize;
SInt64 mCurrentPacket;
bool mIsRunning;
} AQRecorderState;
static void HandleInputBuffer(void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc) {
AQRecorderState *pAqData = (AQRecorderState *)inUserData;
if (inNumPackets == 0 && pAqData->mDataFormat.mBytesPerPacket != 0) {
inNumPackets = inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
}
if (AudioFileWritePackets(pAqData->mAudioFile,
false,
inBuffer->mAudioDataByteSize,
inPacketDesc,
pAqData->mCurrentPacket,
&inNumPackets,
inBuffer->mAudioData) == noErr) {
pAqData->mCurrentPacket += inNumPackets;
}
if (pAqData->mIsRunning) {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}
@implementation AudioRecorder {
AQRecorderState _aqData;
}
- (instancetype)init {
self = [super init];
if (self) {
[self setupAudioFormat:&_aqData.mDataFormat];
_aqData.mIsRunning = false;
}
return self;
}
- (void)dealloc {
if (_aqData.mIsRunning) {
[self stopRecording];
}
}
- (void)setupAudioFormat:(AudioStreamBasicDescription *)format {
format->mSampleRate = 44100.0;
format->mFormatID = kAudioFormatLinearPCM;
format->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
format->mFramesPerPacket = 1;
format->mChannelsPerFrame = 1;
format->mBitsPerChannel = 16;
format->mBytesPerPacket = 2;
format->mBytesPerFrame = 2;
format->mReserved = 0;
}
- (void)deriveBufferSize:(AudioQueueRef)audioQueue
format:(AudioStreamBasicDescription *)format
seconds:(Float64)seconds
outBufferSize:(UInt32 *)outBufferSize {
static const int maxBufferSize = 0x50000;
int maxPacketSize = format->mBytesPerPacket;
if (maxPacketSize == 0) {
UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
AudioQueueGetProperty(audioQueue,
kAudioQueueProperty_MaximumOutputPacketSize,
&maxPacketSize,
&maxVBRPacketSize);
}
Float64 numBytesForTime = format->mSampleRate * maxPacketSize * seconds;
*outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize);
}
- (BOOL)startRecording {
if (_aqData.mIsRunning) {
return NO;
}
// 创建录音文件路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"recording.caf"];
_currentFilePath = [filePath copy];
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
(CFStringRef)filePath,
kCFURLPOSIXPathStyle,
false);
// 创建音频队列
OSStatus status = AudioQueueNewInput(&_aqData.mDataFormat,
HandleInputBuffer,
&_aqData,
NULL,
kCFRunLoopCommonModes,
0,
&_aqData.mQueue);
if (status != noErr) {
NSLog(@"Error creating audio queue: %d", (int)status);
return NO;
}
// 创建音频文件
status = AudioFileCreateWithURL(fileURL,
kAudioFileCAFType,
&_aqData.mDataFormat,
kAudioFileFlags_EraseFile,
&_aqData.mAudioFile);
CFRelease(fileURL);
if (status != noErr) {
NSLog(@"Error creating audio file: %d", (int)status);
AudioQueueDispose(_aqData.mQueue, true);
return NO;
}
// 设置缓冲区大小
[self deriveBufferSize:_aqData.mQueue
format:&_aqData.mDataFormat
seconds:0.5
outBufferSize:&_aqData.bufferByteSize];
// 创建缓冲区
for (int i = 0; i < kNumberBuffers; ++i) {
AudioQueueAllocateBuffer(_aqData.mQueue,
_aqData.bufferByteSize,
&_aqData.mBuffers[i]);
AudioQueueEnqueueBuffer(_aqData.mQueue,
_aqData.mBuffers[i],
0,
NULL);
}
_aqData.mCurrentPacket = 0;
_aqData.mIsRunning = true;
// 开始录音
status = AudioQueueStart(_aqData.mQueue, NULL);
if (status != noErr) {
NSLog(@"Error starting audio queue: %d", (int)status);
return NO;
}
return YES;
}
- (void)stopRecording {
if (!_aqData.mIsRunning) {
return;
}
_aqData.mIsRunning = false;
AudioQueueStop(_aqData.mQueue, true);
AudioFileClose(_aqData.mAudioFile);
AudioQueueDispose(_aqData.mQueue, true);
}
- (void)pauseRecording {
if (_aqData.mIsRunning) {
AudioQueuePause(_aqData.mQueue);
}
}
- (void)resumeRecording {
if (_aqData.mIsRunning) {
AudioQueueStart(_aqData.mQueue, NULL);
}
}
- (BOOL)isRecording {
return _aqData.mIsRunning;
}
@end
MainViewController.h文件
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface MainViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
MainViewController.m文件
#import "MainViewController.h"
#import "AudioRecorder.h"
@interface MainViewController ()
@property (nonatomic, strong) AudioRecorder *audioRecorder;
@property (nonatomic, strong) UIButton *recordButton;
@property (nonatomic, strong) UIButton *stopButton;
@property (nonatomic, strong) UIButton *pauseButton;
@property (nonatomic, strong) UILabel *statusLabel;
@property (nonatomic, strong) UILabel *filePathLabel;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self setupAudioRecorder];
}
- (void)setupUI {
self.view.backgroundColor = [UIColor systemBackgroundColor];
// 状态标签
self.statusLabel = [[UILabel alloc] init];
self.statusLabel.text = @"准备就绪";
self.statusLabel.textAlignment = NSTextAlignmentCenter;
self.statusLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.statusLabel];
// 文件路径标签
self.filePathLabel = [[UILabel alloc] init];
self.filePathLabel.text = @"文件将保存到 Documents 目录";
self.filePathLabel.textAlignment = NSTextAlignmentCenter;
self.filePathLabel.font = [UIFont systemFontOfSize:14];
self.filePathLabel.textColor = [UIColor secondaryLabelColor];
self.filePathLabel.numberOfLines = 0;
self.filePathLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.filePathLabel];
// 录音按钮
self.recordButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.recordButton setTitle:@"开始录音" forState:UIControlStateNormal];
[self.recordButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.recordButton.backgroundColor = [UIColor systemRedColor];
self.recordButton.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
self.recordButton.layer.cornerRadius = 8;
self.recordButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.recordButton addTarget:self action:@selector(recordButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.recordButton];
// 暂停按钮
self.pauseButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.pauseButton setTitle:@"暂停" forState:UIControlStateNormal];
[self.pauseButton setTitle:@"继续" forState:UIControlStateSelected];
self.pauseButton.backgroundColor = [UIColor systemOrangeColor];
[self.pauseButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.pauseButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
self.pauseButton.layer.cornerRadius = 6;
self.pauseButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.pauseButton addTarget:self action:@selector(pauseButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.pauseButton];
// 停止按钮
self.stopButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.stopButton setTitle:@"停止录音" forState:UIControlStateNormal];
[self.stopButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.stopButton.backgroundColor = [UIColor systemBlueColor];
self.stopButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
self.stopButton.layer.cornerRadius = 6;
self.stopButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.stopButton addTarget:self action:@selector(stopButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.stopButton];
// 设置约束
[NSLayoutConstraint activateConstraints:@[
// 状态标签约束
[self.statusLabel.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:40],
[self.statusLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20],
[self.statusLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20],
// 文件路径标签约束
[self.filePathLabel.topAnchor constraintEqualToAnchor:self.statusLabel.bottomAnchor constant:20],
[self.filePathLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20],
[self.filePathLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20],
// 录音按钮约束
[self.recordButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.recordButton.topAnchor constraintEqualToAnchor:self.filePathLabel.bottomAnchor constant:60],
[self.recordButton.widthAnchor constraintEqualToConstant:200],
[self.recordButton.heightAnchor constraintEqualToConstant:50],
// 暂停按钮约束
[self.pauseButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.pauseButton.topAnchor constraintEqualToAnchor:self.recordButton.bottomAnchor constant:20],
[self.pauseButton.widthAnchor constraintEqualToConstant:120],
[self.pauseButton.heightAnchor constraintEqualToConstant:40],
// 停止按钮约束
[self.stopButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.stopButton.topAnchor constraintEqualToAnchor:self.pauseButton.bottomAnchor constant:20],
[self.stopButton.widthAnchor constraintEqualToConstant:120],
[self.stopButton.heightAnchor constraintEqualToConstant:40]
]];
[self updateButtonStates];
}
- (void)setupAudioRecorder {
self.audioRecorder = [[AudioRecorder alloc] init];
}
- (void)recordButtonTapped {
if ([self.audioRecorder startRecording]) {
self.statusLabel.text = @"录音中...";
self.statusLabel.textColor = [UIColor systemRedColor];
} else {
self.statusLabel.text = @"开始录音失败";
self.statusLabel.textColor = [UIColor systemRedColor];
}
[self updateButtonStates];
}
- (void)stopButtonTapped {
[self.audioRecorder stopRecording];
self.statusLabel.text = @"录音已停止";
self.statusLabel.textColor = [UIColor systemBlueColor];
if (self.audioRecorder.currentFilePath) {
self.filePathLabel.text = [NSString stringWithFormat:@"文件已保存至:\n%@", self.audioRecorder.currentFilePath];
}
[self updateButtonStates];
}
- (void)pauseButtonTapped {
if (self.pauseButton.selected) {
[self.audioRecorder resumeRecording];
self.statusLabel.text = @"录音中...";
self.pauseButton.selected = NO;
} else {
[self.audioRecorder pauseRecording];
self.statusLabel.text = @"已暂停";
self.pauseButton.selected = YES;
}
[self updateButtonStates];
}
- (void)updateButtonStates {
BOOL isRecording = self.audioRecorder.isRecording;
self.recordButton.enabled = !isRecording;
self.recordButton.alpha = isRecording ? 0.5 : 1.0;
self.stopButton.enabled = isRecording;
self.stopButton.alpha = isRecording ? 1.0 : 0.5;
self.pauseButton.enabled = isRecording;
self.pauseButton.alpha = isRecording ? 1.0 : 0.5;
}
@end
info.plist中,添加以下权限,再删除原来的窗口配置
<key>NSMicrophoneUsageDescription</key>
<string>此应用需要访问麦克风以进行音频录制</string>

三、源码分析
- AudioRecorder 类分析
1.1 数据结构定义
objc
typedef struct AQRecorderState {
AudioStreamBasicDescription mDataFormat; // 音频格式描述
AudioQueueRef mQueue; // 音频队列引用
AudioQueueBufferRef mBuffers[kNumberBuffers]; // 音频缓冲区数组
AudioFileID mAudioFile; // 音频文件ID
UInt32 bufferByteSize; // 缓冲区字节大小
SInt64 mCurrentPacket; // 当前包编号
bool mIsRunning; // 录制状态标志
} AQRecorderState;
作用:封装音频录制所需的所有状态信息,作为录音器的核心数据结构。
1.2 核心回调函数
objc
static void HandleInputBuffer(void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc)
参数分析:
-
inUserData: 用户自定义数据指针,指向AQRecorderState结构体 -
inAQ: 触发回调的音频队列 -
inBuffer: 包含录音数据的缓冲区 -
inStartTime: 缓冲区时间戳 -
inNumPackets: 包数量 -
inPacketDesc: 包描述数组
函数执行流程:
-
获取状态数据:将void指针转换为AQRecorderState结构体指针
-
计算包数量:如果包数为0且每包字节数固定,则计算实际包数
-
写入文件:使用
AudioFileWritePackets将缓冲区数据写入音频文件 -
更新包计数:递增当前包编号
-
重新入队:如果仍在录制,将缓冲区重新加入队列循环使用
1.3 音频格式设置函数
objc
- (void)setupAudioFormat:(AudioStreamBasicDescription *)format
参数配置详解:
-
mSampleRate = 44100.0: 采样率44.1kHz(CD音质) -
mFormatID = kAudioFormatLinearPCM: 使用线性PCM格式 -
mFormatFlags: 格式标志-
kAudioFormatFlagIsSignedInteger: 有符号整数采样 -
kAudioFormatFlagIsPacked: 数据包紧凑排列
-
-
mFramesPerPacket = 1: 每包1帧 -
mChannelsPerFrame = 1: 单声道 -
mBitsPerChannel = 16: 16位采样深度 -
mBytesPerPacket = 2: 每包2字节(16位 × 1通道) -
mBytesPerFrame = 2: 每帧2字节
1.4 缓冲区大小计算函数
objc
- (void)deriveBufferSize:(AudioQueueRef)audioQueue
format:(AudioStreamBasicDescription *)format
seconds:(Float64)seconds
outBufferSize:(UInt32 *)outBufferSize
计算逻辑:
-
设置最大限制:
maxBufferSize = 0x50000(约320KB) -
获取包大小:如果格式中未指定,从音频队列属性获取
-
计算理论大小:
采样率 × 最大包大小 × 时间 -
取较小值:理论大小与最大限制的较小值作为最终缓冲区大小
1.5 开始录制函数
objc
- (BOOL)startRecording
执行步骤:
步骤1:文件路径创建
objc
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"recording.caf"];
-
获取Documents目录路径
-
构建完整的文件路径
步骤2:音频队列创建
objc
AudioQueueNewInput(&_aqData.mDataFormat,
HandleInputBuffer,
&_aqData,
NULL,
kCFRunLoopCommonModes,
0,
&_aqData.mQueue);
-
参数1:音频格式描述
-
参数2:回调函数指针
-
参数3:传递给回调的用户数据
-
参数4:运行循环(NULL表示使用音频队列内部线程)
-
参数5:运行循环模式
-
参数6:保留参数(必须为0)
-
参数7:输出的音频队列引用
步骤3:音频文件创建
objc
AudioFileCreateWithURL(fileURL,
kAudioFileCAFType,
&_aqData.mDataFormat,
kAudioFileFlags_EraseFile,
&_aqData.mAudioFile);
-
参数2:
kAudioFileCAFType指定CAF文件格式 -
参数4:
kAudioFileFlags_EraseFile表示如果文件存在则覆盖
步骤4:缓冲区分配和入队
objc
for (int i = 0; i < kNumberBuffers; ++i) {
AudioQueueAllocateBuffer(_aqData.mQueue,
_aqData.bufferByteSize,
&_aqData.mBuffers[i]);
AudioQueueEnqueueBuffer(_aqData.mQueue,
_aqData.mBuffers[i],
0,
NULL);
}
-
创建3个缓冲区(kNumberBuffers = 3)
-
每个缓冲区大小为之前计算的结果
-
立即将缓冲区加入队列开始接收数据
步骤5:启动录制
objc
AudioQueueStart(_aqData.mQueue, NULL);
- 启动音频队列,开始录制流程
1.6 停止录制函数
objc
- (void)stopRecording
清理流程:
-
设置运行标志为false
-
停止音频队列:
AudioQueueStop(_aqData.mQueue, true)- 第二个参数true表示同步停止,立即停止
-
关闭音频文件:
AudioFileClose(_aqData.mAudioFile) -
释放音频队列:
AudioQueueDispose(_aqData.mQueue, true)- 释放队列及其所有资源
-
MainViewController 类分析
2.1 界面设置函数
objc
- (void)setupUI
UI组件创建:
状态标签 (statusLabel)
-
显示当前录制状态
-
使用系统字体,中等字重
文件路径标签 (filePathLabel)
-
显示录音文件保存路径
-
支持多行显示
录音按钮 (recordButton)
-
红色背景,开始录音功能
-
圆角设计,点击触发
recordButtonTapped
暂停按钮 (pauseButton)
-
橙色背景,暂停/继续功能
-
使用
selected状态切换显示文字
停止按钮 (stopButton)
- 蓝色背景,停止录音功能
2.2 按钮状态管理
objc
- (void)updateButtonStates
状态逻辑:
-
录制中:开始按钮禁用,停止和暂停按钮启用
-
未录制:开始按钮启用,停止和暂停按钮禁用
-
通过alpha值变化提供视觉反馈
2.3 按钮事件处理
开始录音
objc
- (void)recordButtonTapped
-
调用
audioRecorder的startRecording方法 -
更新状态标签显示"录音中..."
-
刷新按钮状态
停止录音
objc
- (void)stopButtonTapped
-
调用
stopRecording停止录制 -
显示文件保存路径
-
重置界面状态
暂停/继续
objc
- (void)pauseButtonTapped
-
根据当前状态调用
pauseRecording或resumeRecording -
切换按钮的selected状态
-
更新状态标签显示
- AppDelegate 配置分析
objc
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
窗口创建流程:
-
创建窗口:
[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] -
创建视图控制器:
MainViewController实例 -
包装导航控制器:提供导航栏和大标题支持
-
设置根控制器:
window.rootViewController = navController -
显示窗口:
[window makeKeyAndVisible] -
关键技术点分析
4.1 音频队列工作流程
-
初始化:设置格式、创建队列、分配缓冲区
-
回调循环:缓冲区填满 → 回调处理 → 重新入队
-
数据流:麦克风 → 音频队列 → 回调函数 → 文件写入
-
资源清理:停止队列、关闭文件、释放资源
4.2 内存管理
-
缓冲区重用:3个缓冲区循环使用,避免频繁分配
-
及时释放:在dealloc和stopRecording中正确释放资源
-
ARC兼容:使用Objective-C的自动引用计数
4.3 错误处理
-
每个Core Audio函数调用后检查返回值
-
在关键步骤提供错误日志输出
-
通过BOOL返回值向调用方报告成功状态