生成 LivePhoto 探索记录

1,900 阅读4分钟

背景

LivePhoto 是 iOS 9.0 之后系统相机提供的拍摄动态照片的功能,在 iPhone 6S+,iOS 9.0+ 设备可用。拍摄完LivePhoto 之后,只需要在相册长按 LivePhoto 相片即可动态的播放,LivePhoto还可以设置为动态壁纸。

最近 iOS 微信更新后,朋友圈支持发布 LivePhoto 。尝试在系统相册之外生成 LivePhoto ,开始了解 LivePhoto 的技术原理。

导演导演导演导演导演

通过搜索资料,了解到 LivePhoto 是由一张图片和一个视频,两个文件组合而成。在系统相册中默认看到的是图片,当长按 LivePhoto 则开始播放视频内容。

一张图片和一个视频为什么会被系统相册识别成 LivePhoto ?弄清楚这里面的原理,是不是不用系统相册,也可以自己构造 LivePhoto ?

原理

将 iPhone 设备的 LivePhoto 通过 AirDrop 发送到 MAC 设备,在 Finder 中可以查看到 LivePhoto 中的图片和视频文件,文件名相同。简单猜测只有文件名相同,就会被当做 LivePhoto 吗?答案是否定的。

继续搜索资料,了解到这两个文件中的元数据包含了 LivePhoto 需要的相关信息。一共三个元数据信息,分别记录图片和视频的唯一标识 identifier,以及图片在视频中的时间戳。

  1. 图片元数据(Metadata)
# 记录一个唯一标识 identifier
{
    "{MakerApple}" : {
        "17" : "<Identifier>"
    }
}

2. 视频元数据(Metadata)

  • 记录一个唯一标识 identifier
{"com.apple.quicktime.content.identifier" : "<Identifier>"}

3. 图片元数据轨道(Metadata Track)

{
    "MetadataIdentifier" : "mdta/com.apple.quicktime.still-image-time",
    "MetadataDataType" : "com.apple.metadata.datatype.int8"
}
  • 元数据轨道中的元数据记录图片在视频中的时间戳
{"com.apple.quicktime.still-image-time" : 0}

验证

  1. 查看图片的元数据的确有 MakerApple->17 中记录了一串字符,类似 UUID 。

  1. 查看视频的元数据和 Tracks

    • 元数据中出现了 com.apple.quicktime.content.identifier 字段中记录了与图片中相同值的 identifier
    • 元数据轨道也存在且有两个,但格式与封面时间戳如何对应,没太看懂。

实践

知道了基本原理并且通过查看 LivePhoto 元数据验证了原理的准确性,接下来尝试如何在剪映内实现生成系统相册能识别的 LivePhoto。

再次搜索资料,很好,找到可以直接使用的代码,拷贝之。

github.com/genadyo/Liv…

在工程中加入对应代码,运行,导出。系统相册查看,的确识别成 LivePhoto 。搞定。

使用系统相册编辑 LivePhoto ,为啥封面的时间戳显示是在视频起始位置?哦!没写入封面时间戳信息。继续看代码,封面时间戳信息如何写入,找到时间戳生成方法,原理是 0 ,修改之,单位多少,不知道,先写个 1000 ,当毫秒试试,不行,继续换秒,还是不行,总之是不行。

    fileprivate func metadataForStillImageTime() -> AVMetadataItem {
        let item = AVMutableMetadataItem()
        item.key = kKeyStillImageTime as (NSCopying & NSObjectProtocol)?
        item.keySpace = AVMetadataKeySpace(rawValue: kKeySpaceQuickTimeMetadata)
        item.value = 0 as (NSCopying & NSObjectProtocol)?
        item.dataType = "com.apple.metadata.datatype.int8"
        return item
    }

继续搜索资料。又发现一个项目 github.com/LimitPoint/…

原来是要构造一个 timeRange 设置在 AVTimedMetadataGroup 中,timeRange 中的 start 则是封面时间戳,duration 则是视频中一帧的时长。

 static func makeStillImageTimeRange(duration: CMTime, percent: Float, fps: Int32) -> CMTimeRange {
    let frameDuration = Int64(1000.0 / Float(fps))
    var start = duration
    start.value = Int64(Float(start.value) * percent)
    return CMTimeRangeMake(start: start, duration: CMTimeMake(value: frameDuration, timescale: 1000))
}

这会真搞定了,生成的 LivePhoto 图片效果和系统相机生成的基本一样。

image.png

优化

试试导出 1 分钟时长的 LivePhoto ,试试就逝逝了。导出进度卡在 99% 不走了。

过了 10 多秒,有成功了,查看相册,确实成功了,去看代码。使用了 AVAssetWriter 生成 LivePhoto 的视频文件,走了解码再编码的过程,这个耗时那就非常客观了,导出耗时等于翻倍了。

有同学建议用 AVAssetExportSession 生成 LivePhoto 中的视频文件,将元数据 metadata 和元数据轨道写入文件中。

继续搜索资料,AVAssetExportSession 能写入元数据 metadata 和元数据轨道,但元数据轨道的数据如何写入,没找到接口和可参考的代码。

那么试试不写入元数据轨道,是否能被识别成 LivePhoto,验证成功,被识别成 LivePhoto 。尝试系统相册编辑封面,无法拖动,有体验损失,但导出耗时减少非常多,收益很大。可以对时长大于 30秒使用,毕竟目前时长较长的 LivePhoto 还比较少。

继续尝试写入封面时间戳信息。

如果先用 AVAssetWriter 生成一个只包含元数据轨道的视频文件,那么在用 AVAssetExportSession 生成 LivePhoto 的视频文件时,就有了元数据轨道的数据来源。

继续试试,确实可行。由于生成只包含元数据轨道的视频不涉及解码编码,耗时减少也非常多,但因为 AVAssetExportSession 还是需要生成一个新视频文件,涉及整个视频文件数据的 IO ,还是会增加一些耗时。

该方案在将一个已有视频转换成 LivePhoto 的场景基本够用 但在多轨编辑器中应该还是在原有导出生成视频的过程中,同时写入 LivePhoto 的信息,减少多一次生成视频的时间会更优。