在iOS上做视频录制、剪辑导出或者相机推流时,编码环节的效率直接决定了CPU占用、发热和功耗。AVAssetWriter是AVFoundation框架中专门负责媒体写入的高层接口,它内部调用了VideoToolbox硬编码器,能在保证画质的前提下获得远优于软编码的性能。这篇文章将从零开始搭建一个完整的编码链路,并重点讨论H.264与H.265的编码参数配置以及关键帧间隔的控制策略。

一、AVAssetWriter硬编码的基本工作原理
AVAssetWriter本身并不直接执行编码,它更像一个容器写入器加编码调度器。当你创建一个AVAssetWriter实例并指定输出文件路径后,需要为其添加一个或多个AVAssetWriterInput。视频输入通道接受CVPixelBufferRef类型的像素缓冲区,音频输入通道接受CMSampleBufferRef。数据在内部经过格式转换后被送入VideoToolbox的硬件编码器,编码完成后按目标容器格式(MP4或MOV)封装写入文件。
整个链路是异步的。AVAssetWriterInput通过requestMediaDataWhenReadyOn:usingBlock:告知开发者何时可以继续喂数据,这种背压机制可以防止向编码器塞入过多数据导致内存暴涨。理解这一点很重要,很多初学者直接在相机回调线程里同步调用appendPixelBuffer:withPresentationTime:,结果遇到返回NO或者写入中断,就是因为忽略了输入通道的就绪状态。
硬编码的优势在于编码工作由专门的ASIC或GPU完成,A9之后的芯片对H.264 1080p实时编码的功耗只有软编码的几分之一,同时不会占用主线程CPU时间。对于长时间录制场景,这个差距会直接体现在设备温度上。
二、搭建完整的编码写入链路
第一步是创建writer并配置视频输入。这里的关键是输出文件类型与编码格式的匹配,MP4容器对应AVFileTypeMPEG4,如果使用H.265则需要注意容器兼容性问题,后文会展开。
NSURL *outputURL = [NSURL fileURLWithPath:@"/tmp/output.mp4"];
NSError *error = nil;
AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:outputURL
fileType:AVFileTypeMPEG4
error:&error];
NSDictionary *videoSettings = @{
AVVideoCodecKey: AVVideoCodecTypeH264,
AVVideoWidthKey: @(1920),
AVVideoHeightKey: @(1080),
AVVideoCompressionPropertiesKey: @{
AVVideoAverageBitRateKey: @(6000000),
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoMaxKeyFrameIntervalKey: @(60),
AVVideoMaxKeyFrameIntervalDurationKey: @(2.0),
AVVideoExpectedSourceFrameRateKey: @(30)
}
};
AVAssetWriterInput *videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings];
videoInput.expectsMediaDataInRealTime = YES;
if ([writer canAddInput:videoInput]) {
[writer addInput:videoInput];
}
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];
expectsMediaDataInRealTime这个属性在实时录制场景必须设置为YES,它会让编码器按照实时速率调整缓冲策略。如果是离线转码(比如从已有文件转格式),保持默认的NO即可,编码器会尽量利用缓冲提高压缩效率。
接下来是喂数据部分。正确的做法是在就绪回调中循环写入,直到isReadyForMoreMediaData变为NO:
[videoInput requestMediaDataWhenReadyOn:dispatch_get_main_queue() usingBlock:^{
while (videoInput.isReadyForMoreMediaData && self.pendingBuffers.count > 0) {
CVPixelBufferRef pixelBuffer = self.pendingBuffers.firstObject;
[self.pendingBuffers removeObjectAtIndex:0];
CMTime time = CMTimeMake(self.frameIndex, 30);
self.frameIndex++;
if (![videoInput appendPixelBuffer:pixelBuffer withPresentationTime:time]) {
NSLog(@"写入失败: %@", writer.error);
}
CVPixelBufferRelease(pixelBuffer);
}
}];
时间戳的管理是这里最容易出错的地方。PTS必须是严格单调递增的,如果出现回退或者重复,append会直接失败。用帧序号配合CMTimeMake生成时间戳是最稳妥的方式,CMTimeMake(frameIndex, fps)得到的每帧间隔精确等于1/fps秒,避免了用CACurrentMediaTime累加带来的浮点漂移。
录制结束时必须按顺序调用markAsFinished和finishWritingWithCompletionHandler:,前者告诉输入通道不会再有数据,后者触发尾部数据刷新和文件索引写入。漏掉任何一个都会导致文件无法播放。
三、编码参数详解与关键帧间隔控制
AVVideoCompressionPropertiesKey下的字典是控制编码质量的核心。AVVideoAverageBitRateKey设定平均码率,单位是比特每秒,1080p 30fps的场景一般建议4到8Mbps;如果希望画质优先,可以改用恒定质量模式,设置AVVideoQualityKey并移除码率键,让编码器自行分配码率。
关键帧间隔由两个参数共同约束:AVVideoMaxKeyFrameIntervalKey限制两个关键帧之间的最大帧数,AVVideoMaxKeyFrameIntervalDurationKey限制最大时间间隔。编码器会在两个条件中先满足的那个插入关键帧。这两个参数的取值需要根据用途权衡:
- 本地播放或下载场景:间隔可以设大,比如每5秒一个关键帧(150帧),压缩率最高,文件最小。
- 网络流媒体或需要快速seek:间隔要缩短到1到2秒,否则拖动进度条后需要等待解码到下一个关键帧才能出画面。
- 直播推流:通常固定为1到2秒,与GOP对齐,保证弱网下快速恢复。
需要注意关键帧的数据量通常是普通P帧的5到10倍,间隔太短会造成码率曲线出现周期性尖峰,在网络传输时引发卡顿。如果同时设置AVVideoAllowFrameReorderingKey为YES启用B帧,压缩效率还能进一步提升,但会增加解码端的延迟,实时场景慎用。
四、H.265编码配置与兼容性注意事项
切换到H.265只需要把编码器键值替换为AVVideoCodecTypeHEVC,但实际使用前必须检查设备支持情况。A9芯片之前的设备不支持HEVC硬件编码,iOS 11之前的系统也没有对应API,推荐用AVAssetWriter的类方法做能力检测:
NSArray *types = [AVAssetWriter availableVideoCodecTypesForFileType:AVFileTypeMPEG4]; BOOL hevcSupported = [types containsObject:AVVideoCodecTypeHEVC];
H.265的profile设置与H.264略有不同,常用的是AVVideoProfileLevelHEVCMainAutoLevel。在同等画质下H.265能比H.264节省大约30%到40%的码率,这对移动端非常有吸引力。但兼容性代价必须考虑:部分老旧浏览器和播放器不支持HEVC,微信内嵌浏览器在iOS上可以播放,安卓端则表现不一。如果是面向分发的视频,稳妥做法是服务端提供H.264和H.265两个版本,或者干脆只在本地存储场景使用HEVC。
另一个细节是录制过程中动态调整码率。虽然AVAssetWriter没有直接暴露实时码率控制接口,但可以在创建input时通过设置较大的AVVideoAverageBitRateKey配合AVVideoBitRateLimitKey来约束峰值码率,避免复杂运动场景下码率飙升。对于需要真正动态码率的直播场景,建议直接使用VideoToolbox的VTCompressionSession,它提供了VTCompressionSessionSetProperty可以在编码中途修改码率,灵活度高于AVAssetWriter,代价是需要自己处理像素格式转换和输出封装。
总结来说,AVAssetWriter为绝大多数录制和导出场景提供了足够的控制能力:常规录制用它一行C代码都不用碰;只有需要直播中动态调参或获取编码后的裸NALU数据时,才需要下沉到VideoToolbox。掌握好关键帧间隔与码率的平衡,是产出高质量视频文件同时控制体积的关键。
AVAssetWriteriOS视频硬编码H.265编码修改时间:2026-08-31 18:53:12