使用AVAssetWriter写入视频章节标记遇异常:标题不显示、未被识别
实时录屏APP章节写入问题
我正在开发一款可在运行时实时录制视频的应用,用户可在录制过程中插入书签。我尝试将这些书签作为章节写入录制的视频文件,但遇到问题:ffplay能显示章节的正确起止时间,但无法显示标题;同时QuickTime Player和AVAsset都无法识别这些章节。
实现代码(assetWriter和videoInput未展示)
let metadataItem = AVMutableMetadataItem() metadataItem.identifier = AVMetadataIdentifier.commonIdentifierTitle metadataItem.value = "title" as (NSCopying & NSObjectProtocol) metadataItem.dataType = kCMMetadataBaseDataType_UTF8 as String metadataItem.extendedLanguageTag = "en" let dummyGroup = AVTimedMetadataGroup(items: [metadataItem], timeRange: CMTimeRange(start: CMTime.zero, end: CMTime.invalid)) let metaFmt = dummyGroup.copyFormatDescription() textWriterInput = AVAssetWriterInput(mediaType: .metadata, outputSettings: nil, sourceFormatHint: metaFmt) textWriterInput.languageCode = "en" textWriterAdaptor = AVAssetWriterInputMetadataAdaptor(assetWriterInput: textWriterInput) assetWriter.add(textWriterInput) videoInput.addTrackAssociation(withTrackOf: textWriterInput, type: AVAssetTrack.AssociationType.chapterList.rawValue)
插入书签处理逻辑
let metadataItem = AVMutableMetadataItem() metadataItem.identifier = AVMetadataIdentifier.commonIdentifierTitle metadataItem.value = title as (NSCopying & NSObjectProtocol) metadataItem.dataType = kCMMetadataBaseDataType_UTF8 as String metadataItem.extendedLanguageTag = "en" let group = AVTimedMetadataGroup(items: [metadataItem], timeRange: CMTimeRange(start: time, duration: CMTime.invalid)) if textWriterInput.isReadyForMoreMediaData { self.textWriterAdaptor.append(group) }
ffplay识别信息
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/path/to/video.mp4': Metadata: major_brand : mp42 minor_version : 1 compatible_brands: isommp41mp42 creation_time : 2024-02-01T16:37:28.000000Z Duration: 00:00:14.48, start: 0.142993, bitrate: 13226 kb/s Chapters: Chapter #0:0: start 6.727000, end 12.020000 Metadata: title : Chapter #0:1: start 12.020000, end 14.622000 Metadata: title : Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 2060x1080, 13268 kb/s, 57.81 fps, 300 tbr, 600 tbn (default) Metadata: creation_time : 2024-02-01T16:37:28.000000Z handler_name : Core Media Video vendor_id : [0][0][0][0] Stream #0:1[0x3](und): Audio: aac (HE-AAC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 57 kb/s (default) Metadata: creation_time : 2024-02-01T16:37:28.000000Z handler_name : Core Media Audio vendor_id : [0][0][0][0] Stream #0:2[0x4](eng): Data: bin_data (mebx / 0x7862656D), 0 kb/s (default) Metadata: creation_time : 2024-02-01T16:37:28.000000Z handler_name : Core Media Metadata 2.05 A-V: -0.001 fd= 24 aq= 35KB vq= 6576KB sq= 0B f=0/0
解决方案
问题根源
- 元数据标识符不匹配:使用通用的
commonIdentifierTitle无法被解析为章节标题,苹果生态需要专用的iTunesMetadataChapterName标识。 - 无效的时间范围:
CMTime.invalid作为时长会导致播放器无法正确识别章节的结束边界。 - 轨道类型未明确:默认元数据轨道未标记为章节类型,导致QuickTime/AVAsset无法识别。
修复后的代码
1. 初始化章节元数据轨道
let metadataItem = AVMutableMetadataItem() // 替换为iTunes章节专用标题标识 metadataItem.identifier = AVMetadataIdentifier.iTunesMetadataChapterName metadataItem.value = "title" as (NSCopying & NSObjectProtocol) metadataItem.dataType = kCMMetadataBaseDataType_UTF8 as String metadataItem.extendedLanguageTag = "en" // 使用有效时间范围生成格式描述(避免invalid时间) let dummyGroup = AVTimedMetadataGroup(items: [metadataItem], timeRange: CMTimeRange(start: .zero, duration: CMTime(seconds: 1, preferredTimescale: 600))) let metaFmt = dummyGroup.copyFormatDescription() textWriterInput = AVAssetWriterInput(mediaType: .metadata, outputSettings: nil, sourceFormatHint: metaFmt) textWriterInput.languageCode = "en" // 明确标记轨道为章节类型 textWriterInput.metadataType = .chapters textWriterAdaptor = AVAssetWriterInputMetadataAdaptor(assetWriterInput: textWriterInput) assetWriter.add(textWriterInput) videoInput.addTrackAssociation(withTrackOf: textWriterInput, type: AVAssetTrack.AssociationType.chapterList.rawValue)
2. 插入书签与章节写入逻辑(需记录上一章节时间)
// 新增变量记录上一个章节的开始时间 private var lastChapterStartTime: CMTime = .zero func addChapter(at currentTime: CMTime, title: String) { let metadataItem = AVMutableMetadataItem() metadataItem.identifier = AVMetadataIdentifier.iTunesMetadataChapterName metadataItem.value = title as (NSCopying & NSObjectProtocol) metadataItem.dataType = kCMMetadataBaseDataType_UTF8 as String metadataItem.extendedLanguageTag = "en" // 处理非第一个章节:计算上一章节的时长并写入 if lastChapterStartTime != .zero { let chapterDuration = currentTime - lastChapterStartTime let chapterRange = CMTimeRange(start: lastChapterStartTime, duration: chapterDuration) let chapterGroup = AVTimedMetadataGroup(items: [metadataItem], timeRange: chapterRange) if textWriterInput.isReadyForMoreMediaData { textWriterAdaptor.append(chapterGroup) } } // 更新上一章节时间为当前时间 lastChapterStartTime = currentTime } // 录制结束时,处理最后一个章节 func finalizeLastChapter(with title: String) { guard let totalDuration = assetWriter.duration, lastChapterStartTime != .zero else { return } let metadataItem = AVMutableMetadataItem() metadataItem.identifier = AVMetadataIdentifier.iTunesMetadataChapterName metadataItem.value = title as (NSCopying & NSObjectProtocol) metadataItem.dataType = kCMMetadataBaseDataType_UTF8 as String metadataItem.extendedLanguageTag = "en" let finalDuration = totalDuration - lastChapterStartTime let finalRange = CMTimeRange(start: lastChapterStartTime, duration: finalDuration) let finalGroup = AVTimedMetadataGroup(items: [metadataItem], timeRange: finalRange) if textWriterInput.isReadyForMoreMediaData { textWriterAdaptor.append(finalGroup) } }
关键说明
- 标识符切换:
AVMetadataIdentifier.iTunesMetadataChapterName是苹果播放器识别章节标题的标准,通用标题标识不会被归类为章节元数据。 - 有效时间范围:每个章节必须有明确的开始和结束时间,播放器依赖此划分章节区间,
CMTime.invalid会导致解析失败。 - 轨道类型标记:设置
metadataType = .chapters会让系统将该元数据轨道识别为章节轨道,而非普通元数据,这是AVAsset和QuickTime识别章节的核心。
内容的提问来源于stack exchange,提问作者kiw
相关产品推荐
相关产品推荐

