ハイライト
WWDC20 で AVAssetWriter は HLS 用の segmented MPEG-4 データを直接出力できるようになりました。App は delegate 経由で初期化セグメントと分離可能なメディアセグメントを受け取り、segment ファイルと playlist を自前で書き込みます。
主要内容
HLS 制作パイプラインでは、エンコードは第一歩にすぎません。動画サンプルはオンデマンドファイルからもライブカメラからも来ます。HEVC や AAC などにエンコードしたあとも、クライアントが逐次リクエストできるメディアセグメントに切り、対応する playlist を用意する必要があります。
これまでは別の segmenter が担うことが多かった部分です。AVAssetWriter はメディアサンプルを指定コンテナのファイルに書くのが得意でしたが、この WWDC20 セッションは HLS の segmenter 段階まで進めます。書き込み先は 1 つの出力 URL に固定されず、書き込み中に segmented MPEG-4 データを App に返します。
能力の境界も明確です。AVAssetWriter は HLS または CMAF 制約を満たす fragmented MP4 データを出力し、App は受け取ったデータを初期化 segment、メディア segment、単一または複数ファイル形態に整理し、AVAssetSegmentReport の情報でメディア playlist と I-frame playlist を作ります。
最後の焦点は分割品質です。passthrough、エンコードモード、カスタム分割、オーディオ sample dependency、AAC priming はいずれも segment 境界とタイムスタンプに影響します。ファイルを書くだけでは不十分で、最終的に HLS 要件に沿って segment ファイルと playlist を検証する必要があります。
詳細
1. 出力 URL に結び付かない AVAssetWriter を作る
(05:36)このセッションの最初の変化は、content type で AVAssetWriter を初期化することです。講演者は writer が 1 ファイルを直接書かずデータを出力するため、output URL を渡さないと明言しています。
// Instantiate asset writer
let assetWriter = AVAssetWriter(contentType: UTType(AVFileType.mp4.rawValue)!)
// Add inputs
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: compressionSettings)
assetWriter.add(videoInput)
キーポイント:
contentTypeは fragmented MP4 が MP4 ファミリーに属するため、MP4 系の型を使います。AVAssetWriterInputはメディアサンプルを受け取り、compressionSettingsは書き込み時にサンプルをエンコードすることを意味します。outputSettingsにnilを渡すと passthrough モードになり、受け取ったサンプルをそのまま書き出します。assetWriter.add(videoInput)の後は、従来どおり start writing と append sample の流れです。
2. HLS profile と segment 間隔で fMP4 出力を有効にする
(06:28)設定段階では output file type profile、目標 segment 間隔、初期 segment 起点、delegate を指定します。講演者は HLS の target segment duration は整数秒である必要があるため、例では 6 秒を使うと補足しています。
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
assetWriter.preferredOutputSegmentInterval = CMTime(seconds: 6.0, preferredTimescale: 1)
assetWriter.initialSegmentStartTime = myInitialSegmentStartTime
assetWriter.delegate = myDelegateObject
キーポイント:
.mpeg4AppleHLSで writer は Apple HLS profile の fragmented MP4 データを出力します。preferredOutputSegmentIntervalは目標間隔です。実際の境界は sync sample 制約にも影響されます。initialSegmentStartTimeは interval の起点で、後段の audio priming 処理でも同じ時間オフセットに合わせて動かす必要があります。delegateが segment データを受け取る入口で、ファイルと playlist の作成はこれに依存します。
3. delegate コールバックで初期化セグメントとメディアセグメントを区別する
(08:00、08:37)AVAssetWriter は delegate 経由で Data を返し、AVAssetSegmentType で種別を付けます。2 つ目の delegate メソッドは playlist と I-frame playlist 用に AVAssetSegmentReport も返します。
optional func assetWriter(_ writer: AVAssetWriter, didOutputSegmentData segmentData: Data, segmentType: AVAssetSegmentType)
optional func assetWriter(_ writer: AVAssetWriter, didOutputSegmentData segmentData: Data, segmentType: AVAssetSegmentType, segmentReport: AVAssetSegmentReport?)
public enum AVAssetSegmentType : Int {
case initialization = 1
case separable = 2
}
キーポイント:
segmentDataは writer が出力する fragmented media data です。initializationには file type box と movie box が含まれ、HLS の initialization segment になります。separableには movie fragment box 群と media data box が含まれ、メディア segment として書けます。- AVAssetWriter は playlist を書きません。受け取った segment と
AVAssetSegmentReportから App が作成します。
4. passthrough とエンコードモードでは境界の扱いが異なる
(10:53)passthrough モードはサンプルをエンコードしません。講演者は、目標間隔に達するか超えたあと、次の sync sample の手前まで待ってメディアデータを出力すると述べています。CMAF は各 segment を sync sample から始めることを要求し、Apple HLS もその構造を好みます。
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: nil)
キーポイント:
outputSettings: nilは passthrough で、受け取ったサンプルをそのまま書き出します。- passthrough では
AVAssetWriterInputは 1 つだけ追加できます。 - sync sample が目標間隔から遠いと、実際の segment duration は設定よりかなり長くなり、HLS に不向きな場合があります。
- エンコードモードでは、動画サンプルが目標間隔に達した位置で sync sample を強制生成でき、出力は設定間隔に近づきます。
- 複数ビットレート、多言語、音声動画分離には master playlist で複数 AVAssetWriter インスタンスを使います。
5. カスタム分割は passthrough にのみ適用できる
(13:45)目標間隔より前に segment を切りたい場合は、preferredOutputSegmentInterval を .indefinite にし、自前で flushSegment を呼びます。講演者はこれが passthrough にのみ有効で、エンコードモードとは併用できないと強調しています。
// Set properties
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
assetWriter.preferredOutputSegmentInterval = .indefinite
assetWriter.delegate = myDelegateObject
// Passthrough
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: nil)
キーポイント:
.indefiniteは固定 interval での自動分割を行いません。- カスタム分割では固定 interval がないため
initialSegmentStartTimeは不要です。 flushSegmentは前回呼び出し以降に append したサンプルをすべて出力します。flushSegmentは sync sample の手前で呼び、次の fragmented media data が sync sample から始まるようにします。- 音声と動画の両方に sample dependency があると均等な境界合わせは難しく、separate streams を検討します。
6. audio dependency を確認してから priming オフセットを扱う
(15:17)AVAssetTrack には、USAC など、オーディオトラックに sample dependency があるか確認するプロパティが追加されました。これは passthrough、カスタム分割、音声と動画を同一 muxed stream に載せるかどうかに影響します。
extension AVAssetTrack {
/* indicates whether this audio track has dependencies (e.g. kAudioFormatMPEGD_USAC) */
open var hasAudioSampleDependencies: Bool { get }
}
キーポイント:
hasAudioSampleDependenciesはパッケージ前に、オーディオサンプルが前後サンプルに依存するかを App に伝えます。- 依存のあるオーディオも sync sample 開始の segment ルールに従う必要があります。
- 音声と動画の両方に依存があると、単一 muxed stream の切点は安定しにくく、separate streams の方が制御しやすいです。
(16:16)AAC エンコードは最初の本物のオーディオサンプルの前に priming を入れます。一般的な AAC priming は 2,112 audio samples で、44,100Hz では約 48ms です。CMAF は edit list box で補償します。Apple HLS profile は旧プレイヤー互換のため edit list を使わず、audio media の baseMediaDecodeTime を priming 分だけ前方にずらします。
制限があります。baseMediaDecodeTime は符号なしで 0 より前にはできません。セッションのやり方は、音声と動画サンプルを同じオフセット分だけ後方に平行移動し、audio の decode time を priming 分だけ前方補償することです。initialSegmentStartTime も同じオフセットを使います。数秒で足りることが多く、Apple の Media File Segmenter は 10 秒オフセットを使います。
重要ポイント
1. VOD から fMP4 HLS へのトランスコーダ
やること: ローカル映画ファイルを fMP4 segment ファイルと HLS playlist に変換する。
価値: セッションのオンデマンドフローは AVAssetReader でソース movie からサンプルを読み、AVAssetWriter に渡して fragmented MP4 segment data を出力します。
始め方: AVAssetWriter(contentType:) で writer を作り、.mpeg4AppleHLS と 6 秒の preferredOutputSegmentInterval を設定し、delegate で initialization と separable データをファイルに書き、AVAssetSegmentReport から playlist を作ります。
2. ライブキャプチャから HLS 配信
やること: カメラとマイクのサンプルをリアルタイムに HLS segment にパッケージする。
価値: transcript は AVCaptureVideoDataOutput と AVCaptureAudioDataOutput をライブ入力として挙げ、AVAssetWriter がその sample data を受け取れます。
始め方: capture output からサンプルを受け取ったら writer input に append します。動画はエンコードモードにし、目標間隔付近で sync sample を生成させ、delegate で segment を受け取ったらすぐファイル化またはアップロードします。
3. ビジネスマーカーでのカスタムスライス
やること: レッスン章、試合ラウンド、広告境界の前に segment を切る。
価値: セッションは固定間隔が不正確な場面向けに .indefinite と flushSegment を提供しています。
始め方: このパスは passthrough のみで使い、preferredOutputSegmentInterval を .indefinite にします。目標 sync sample の手前で flushSegment を呼び、音声と動画の依存が複雑なら separate streams に切り替えます。
4. マルチビットレート・多言語 HLS 出力器
やること: 同一コンテンツの異なるビットレート、言語、音声動画分離の HLS variant を生成する。
価値: セッションは master playlist で複数 video/audio stream を配信し、複数 AVAssetWriter インスタンスで生成できると説明しています。
始め方: variant ごとに writer を作り、HLS profile、segment interval、delegate を設定します。playlist 層で variant stream と audio rendition を組み合わせ、Apple HLS profile ではサンプルと initialSegmentStartTime に同じ priming offset を適用します。
5. HLS パッケージ品質チェッカー
やること: 生成した segment ファイルと playlist を自動検証する。
価値: セッション末尾は HLS の要件と推奨を強調し、AVAssetWriter で書いた segment と playlist の検証を勧めています。
始め方: delegate で一批の segment を書いたあと出力ディレクトリを残し、HLS 検証ツールで playlist、duration、segment 境界、互換性を確認し、失敗をパッケージ設定に戻します。
関連セッション
- Improve stream authoring with HLS Tools — HLS authoring ツール、低遅延テストストリーム、オーディオ codec、master playlist の制作フロー。
- What’s new in Low-Latency HLS — 低遅延 HLS プロトコル拡張と 2 秒以内の遅延。
- Deliver a better HLS audio experience — HLS オーディオ codec、帯域制限ネットワーク、authoring specification の文脈。
- Export HDR media in your app with AVFoundation — AVFoundation における AVAssetExportSession と AVAssetWriter のエクスポートパスを対照。
- Discover how to download and play HLS offline — 再生側の AVFoundation API で HLS のダウンロード、永続化、オフライン再生を理解。
コメント
GitHub Issues · utterances