You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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   

解决方案

问题根源

  1. 元数据标识符不匹配:使用通用的commonIdentifierTitle无法被解析为章节标题,苹果生态需要专用的iTunesMetadataChapterName标识。
  2. 无效的时间范围:CMTime.invalid作为时长会导致播放器无法正确识别章节的结束边界。
  3. 轨道类型未明确:默认元数据轨道未标记为章节类型,导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 01:10:54