大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 AVAssetWriter 创作 片段 MPEG-4 内容
转换你的音频和视频内容至分段式 MPEG-4 文件,从而获得更快、更流畅的 HLS 流媒体体验。了解如何使用分段式 MPEG-4 格式,如何从电影中获得分段式内容以及如何设置 AVAssetWriter,从而创建用于 HLS 输出的片段。
资源
-
下载
(你好 WWDC 2020) 你好 欢迎来到 WWDC
(使用 AVAssetWriter 创作 片段 MPEG-4 内容) 你好 我叫 Takayuki Mizuno 我是 Apple 公司的 CoreMedia 工程师 本次讲座主要关于 AVFoundation 的新功能 可用于为 HLS 编写片段 MP4 文件 下列图表显示出 HLS 的典型工作流 有一个来源材料 可以是视频点播素材 也可以是直播素材 有一部分对媒体数据进行编码 其中 假设视频样本被编码到 HEVC 而音频样本被编码到 AAC (工作流) 这里有一个分段器
它以特定格式分段媒体数据
分段器还创建了播放列表 能同时列出这些片段
最后 还有一个承载内容的 web 服务器 AVFoundation 提供的新功能 主要是用于分段器之中
AVFoundation 具有 AVAssetWriter 允许媒体创作 app 以便将媒体数据编写指定容器类型 如 MP4 的新文件 我们对它进一步增强 使其能够以 片段 MP4 格式输出用于 HLS 的媒体数据
Apple HLS 可支持其他格式 如 MPEG-2 传输流 ADTS 和 MPEG 音频 但这个新特性主要用于片段 MP4 来看看其中一个用例 (用例) 我以视频点播素材的来源为例
AVFoundation 具有 AVAssetReader 它是从媒体文件读取示例数据的 对象
你可以从源电影中读取示例数据 然后将示例推送到 AVAssetWriter 接着编写片段 MP4 分段文件
再比如 来源是直播材料
AVFoundation 具有 AVCaptureVideoDataOutput 和 AVCaptureAudioDataOutput 它们可以为你提供从连接设备捕获的 样本数据的对象
你从设备接收示例数据 将示例推送到 AVAssetWriter 并编写片段 MP4 分段文件 片段 MP4 是一种流媒体格式 基于 ISO 基本媒体文件格式 Apple HTTP Live Streaming 自 2016 年开始就支持片段 MP4 下面是一个标准 MP4 文件的基本图 它以文件类型框开始 该框指示此文件符合哪种文件格式 (格式结构) 有一个电影框正在组织 关于所有示例数据的信息 然后有一个媒体数据框包含了 所有示例数据 电影框包含与整个示例相关的信息 例如音频和视频编解码器
它还包含对示例数据的引用 例如示例数据的位置 那些框的顺序大多是任意排序的
必须先显示“文件类型”框 然后其他框就可以任意排序 如果你使用过 AVAssetWriter 就会知道 AVAssetWriter 具有 movieFragmentInterval 属性 这指定了电影片段的编写频率
由此产生的电影就是所谓的片段电影 这里是一个片段电影文件的基本图
在这个电影中有电影片段框 它引用了跟在后面的媒体数据框中保存的 所有示例数据
这种格式结构很有用 比如在实时捕获中 即使编写是因为崩溃而意外中断 或者诸如此类 部分编写的数据仍然可以访问和播放
请注意 这个片段电影场景 如果编写成功完成 AVAssetWriter 就将作为最后一步 对文件进行片段整理 这就最终形成了标准的电影文件
这里是片段 MP4 文件的基本图 首先显示“文件类型”框 接着在文件类型框之后的是电影框 然后是电影片段框 跟在后面的是媒体数据框 然后是两个电影片段框 以及媒体数据框 那些框的顺序理应如下 片段电影与片段 MP4 的主要区别 在于片段 MP4 电影框 不包含对示例数据的引用 它只包含与整个样本相关的信息 而电影片段框和媒体数据框的顺序 则是倒转过来
现在我将讨论如何使用 AVAssetWriter 这里展示了如何创建 AVAssetWriter 的实例 你不需要提供输出 URL 因为 AVAssetWriter 不编写文件而是输出数据 你只需指定输出内容类型 片段 MP4 符合 MP4 系列 因此 你应该始终指定 UTType 与 AVFileType MP4 创建 AVAssetWriterInput 在本例中 以作为代码字典的压缩设置 来对媒体样本进行编码 或者 可以将输出设置设置为”空值“ 这称为”通过“模式 在通过模式下 AVAssetWriter 只按给定的方式编写示例 然后将输入添加到 AVAssetWriter 下面讲述了如何配置 AVAssetWriter 你必须指定 outputFileTypeProfile (配置 AVASSETWRITER) 你应该指定 Apple HLS 或 CMAF 兼容配置文件 以片段 MP4 格式输出媒体数据
你需要指定 preferredOutputSegmentInterval
AVAssetWriter 以该间隔输出媒体数据 该间隔时间可以是合理的时间 但是 HLS 的目标片段持续时间 必须是以秒为单位的整数 因此这里不使用更高的精度 因为这对 HLS 来说是正确的选择 你还可以指定初始段开始时间
这是间隔的起点
然后指定实现指定协议的委托对象
我稍后会再讲述协议 在此之后 继续进行与正常文件 编写相同的过程
开始编写 然后使用 AVAssetWriterInput 追加示例
我不打算在这次讲座上再谈这个了 但你可以查看 2011 年 WWDC 讲座 “在 AV Foundation 中与媒体合作” 那就能看到如何执行那些过程了 你可以在 Apple 的开发者网站上 找到讲座视频 下面是委托方法的协议 有两种方法 这两种委托方法都能传递 片段媒体数据的 NS 数据 (委托方法) 它们还能传递 AVAssetSegmentType
可指示分段媒体数据的类型
以其中一种方法实现即可 不同之处在于 第二种方法 传递 AVAssetSegmentReport 对象 该对象包含有关片段媒体数据的信息 如果你不需要这些信息 就要实现第一种
AVAssetSegmentType 有两种类型 初始化和可分离 具有初始化类型的分段媒体数据 包含“文件类型”框和“电影”框 具有可分离类型的片段媒体数据 分别包含电影片段框和媒体数据框
你将成功地接收具有可分离类型的数据
(编写文件) 你将接收到的媒体数据打包成所谓的片段 然后编写该文件片段
一对文件类型框和电影框
应该是初始化段
还有那对电影片段框和媒体数据框
可以是一个片段
对于 HLS 来说 包含所有这些片段的单个文件 可用于流式传输
或者可以将每个片段划分为多个片段文件
此外 多对电影片段框 和媒体数据框可以是一个片段 并存储在一个片段文件中 HLS 使用播放列表 播放列表是客户端发现片段的主要途径 (播放列表) 这里有一个播放列表的例子 有几个标记指示有关片段的信息 例如 URL 和持续时间 AVAssetWriter 不编写播放列表 而是该由你来编写播放列表 这就是为什么其中一个委托方法 要交付 AVAssetSegmentReport 你可以基于 AVAssetSegmentReport 提供的信息 创建播放列表和 I-frame 播放列表
只要看到我们的示例代码 fmp4Writer 你就能看到如何创建播放列表 此外 关于播放列表的文档 可以在这里的开发者流页面找到 在通过模式中 即不对样本进行编码的模式 AVAssetWriter 输出的媒体数据包括 在达到或超过优选 输出段间隔之后的 下一个同步样本之前的所有样本 (片段间隔) 这里的同步样本 是一个不依赖于其他样本的样本 这是因为 CMAF 要求每个数据段 以同步样本开始 而 Apple HLS 也更喜欢这样 此规则不仅适用于视频 而且还适用于具有示例依赖项的音频 如 USAC 音频 因此 对于通过模式 只能添加一个 AVAssetWriterInput 如果同步样本不在间隔附近布局 则媒体数据可能在 远超过指定持续时间的时间内输出 因此 它将不适合 HLS 解决方案之一是同时对视频样本进行编码 正如我前面提到的 如果你在 AVAssetWriterInput 中 为压缩指定输出设置 则示例将被编码 在编码模式下 刚刚到达 或超过首选输出段间隔的视频样本 将强制编码为同步样本
换句话说 同步样本是自动生成的 使得分段媒体数据 将以指定的间隔 或非常接近指定的间隔输出 如果同时编码音频和视频 可以为每个媒体添加一个 AVAssetWriterInput 我在前面说过 对于通过模式 只能添加一个 AVAssetWriterInput 但你可以使用主播放列表 将视频和音频作为单独的流传送 你不仅可以传送一对音频和视频流 还可以传送各种比特率或不同语言的流
你需要创建 AVAssetWriter 的 多个实例 才能创建多个流 同样 关于主播放列表的文档 和 HLS 编码视频和音频的建议 可在这里的开发者流页面中找到
这是一个高级用例 但如果你需要有别于 preferredOutputSegmentInterval 的 其他分段 你可以自己来实践一下 例如 你可能希望不是在达到间隔之后 而是在达到间隔之前以同步采样 输出媒体数据 下面是我前面展示的代码 它设置了 AVAssetWriter 的属性 要发出自定义分段的信号 请将 preferredOutputSegment- Interval 设置为 CMTime indefinite 不需要设置 initialSegmentStartTime 因为没有间隔 其他设置 与 preferredOutputSegment- Interval 的设置相同 这只用于通过 不能使用自定义分段进行编码 所以当你创建 AVAssetWriterInput 时
你必须将输出设置设置为“空值”
调用 flushSegment方法输出媒体数据
FlushSegment 输出媒体数据 它包括自上次调用后追加的所有示例
必须在同步示例之前调用 flushSegment 这样下一个分段媒体数据 就可以从同步示例开始
否则将出现错误 提示说 出现以非同步样本开始的片段媒体数据
在此模式下 你可以多路复用音频和视频 但如果音频和视频都具有示例依赖项 那么将很难均匀地对齐这两种媒体 在这种情况下 你应该考虑 将视频和音频打包为单独的流 (音频具有依赖性) AVAssetTrack 现在有了一个新属性 能指示出音轨是否具有样本依赖项
你可以使用此属性预先检查曲目是否具有 示例依赖项 我之前提到过 你应该指定 Apple HLS 或 CMAF 兼容配置文件 以片段 MP4 格式输出媒体数据 (通用媒体应用格式 即CMAF) CMAF是一组关于 如何构造片段 MP4 分段的约束
它是使用片段 MP4 进行流式传输的标准
它得到了包括 Apple HLS 在内的 众多播放机的支持 如果你只想应用于 Apple HLS 则不需要某些约束 但如果你希望媒体库有更广泛的受众 那么就可以考虑 CMAF
现在我将谈谈 HLS 是如何处理音频启动的 此图表示 AAC 音频的音频波形 (音频启动) AAC 音频编解码器需要源 PCM 音频样本以外的数据 来正确编码和解码音频样本 全因编码算法的性质 因此 编码器在第一个真实音频样本之前 添加静音
这叫做启动 AAC 最常见的启动是 2112 个音频样本 大约是 48 毫秒 假定采样速率为 44100 的情况下
当音频和视频作为单独的流 编写入单独的文件时 媒体数据是这样布局的
假定两种媒体都从时间零点开始 你可能已经注意到了 如果音频和视频同时开始
音频将被启动延迟 这样音频和视频就会稍微不匹配 CMAF 在音轨中采用编辑列表框 来弥补这个问题 启动被剪辑掉了
使得音频和视频开始时间匹配
从前面来看 Apple HLS 并没有使用编辑列表 所以如果指定 Apple HLS 配置文件 编辑列表框就不会用于与旧播放机兼容 相反 音频媒体 baseMediaDecodeTime 将启动量向后移动
然而 baseMediaDecodeTime 不能早于零 因为它被定义为无符号整数
一种解决方案是将音频和视频向前移动 相同的时间偏移 这样 音频媒体的 baseMediaDecodeTime 可以向后移动启动量
即使开始时间不为零 在 HLS 中 回放从视频样本的最早呈现时间开始
因此回放立即开始 而无需等待时间偏移
这不仅对精确的视频和音频同步 非常重要 而且对于具有不同启动持续时间的 音频变体之间的时间戳精确匹配 也非常重要 因此我们建议你通过向所有样本 添加一定量的时间来转移媒体时间 若是要指定 Apple HLS 配置文件的话
初始段开始时间也应偏移 相同的时间偏移
正如我前面所说的 最常见的启动时间不到一秒 所以几秒钟的时间偏移就足够了 而 Media File Segmenter 是 Apple 的分段器工具 具有 10 秒的偏移 这样就可以选择同样的 10 秒 我们的示例代码展示了 如何为所有示例添加时间偏移 在本次演示中 我将使用示例命令行 app 来创建片段 MP4 分段文件和播放列表
然后我将使用 Safari 播放结果内容
此演示场景预先设置为本地服务器 以便托管内容
我在终端 app 中输入了命令行 app
此命令行 app 从预先制作的源电影中 读取媒体数据 并对视频和音频进行编码 这就是源电影 长度只有 30 秒 此命令行 app 使用 6 秒钟 作为首选输出片段间隔 片段文件和播放列表将在此目录中创建 我们开始吧 源电影的视频帧率为每秒 30 帧 你可以看到有五个片段文件 并创建了一个播放列表 我打开播放列表
每个片段的持续时间为 6 秒
我输入本地主机作为 URL 然后使用 Safari 播放它
我们开始吧
HLS 有几个要求 它也有几个建议来改善用户的体验 我们建议你使用 AVAssetWriter 验证 你所编写的片段文件和播放列表 从而确保这些文件符合要求和建议 2016 年 WWDC 讲座 “验证 HTTP Live Streams” 是实现这一目标的优秀来源 你可以在 Apple 的开发者站点 中找到相应的讲座视频
AVAssetWriter 现在为 HLS 输出分段 MP4 格式的媒体数据 感谢你参加本次讲座
-
-
5:36 - Instantiate AVAssetWriter and input
// Instantiate asset writer let assetWriter = AVAssetWriter(contentType: UTType(AVFileType.mp4.rawValue)!) // Add inputs let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: compressionSettings) assetWriter.add(videoInput)
-
6:28 - Configure AVAssetWriter
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS assetWriter.preferredOutputSegmentInterval = CMTime(seconds: 6.0, preferredTimescale: 1) assetWriter.initialSegmentStartTime = myInitialSegmentStartTime assetWriter.delegate = myDelegateObject
-
8:00 - Delegate methods
optional func assetWriter(_ writer: AVAssetWriter, didOutputSegmentData segmentData: Data, segmentType: AVAssetSegmentType) optional func assetWriter(_ writer: AVAssetWriter, didOutputSegmentData segmentData: Data, segmentType: AVAssetSegmentType, segmentReport: AVAssetSegmentReport?)
-
8:37 - AVAssetSegmentType
public enum AVAssetSegmentType : Int { case initialization = 1 case separable = 2 }
-
13:45 - Custom segmentation
// Set properties assetWriter.outputFileTypeProfile = .mpeg4AppleHLS assetWriter.preferredOutputSegmentInterval = .indefinite assetWriter.delegate = myDelegateObject // Passthrough let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: nil)
-
15:17 - Audio has dependencies
extension AVAssetTrack { /* indicates whether this audio track has dependencies (e.g. kAudioFormatMPEGD_USAC) */ open var hasAudioSampleDependencies: Bool { get } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。