ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
AVFoundationの新機能
オーディオビジュアルプレゼンテーションを検査、再生、作成するためのAppleのフレームワークであるAVFoundationの最新のアップデート内容を紹介します。AVFoundationを使用して、オーディオビジュアルアセットの属性を照会したり、時間指定のメタデータでカスタムビデオコンポジションをさらにカスタマイズしたり、キャプションファイルをオーサリングしたりする方法を検証します。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
♪ (AVFoundationの新機能)
こんにちはアダムです 今日はAVFoundationの 3つの新機能について ご紹介します 特にAVAssetの検証で 新しくなった 部分について 時間をかけて説明し その後その他2つの 新機能である メタデータを使った ビデオコンポジットや キャプションファイルの オーサリングに簡潔に触れます では単刀直入に 最初のトピックに入りましょう AVAssetの 非同期検証です しかしまずは AVAssetを おさらいして背景を つかみましょう AVAssetとはAVFoundationの コアモデルオブジェクトで ユーザーのデバイスに 保存された 動画ファイルや リモートサーバーなど 別の場所に保存された動画ファイル HTTP Live Streams など他のフォーマットの オーディオビジュアルコンテンツの 表示に使われます アセットがあると視聴したく なるのはもちろんのこと 検証したくなることも ありますよね 動画の長さや オーディオや動画に 含まれるフォーマットは何かなど 質問をぶつけたくなります そしてそれこそが このトピックの テーマ つまり アセットの検証です そしてアセットの検証を いつ行うにせよ 2つの注意点があります まず アセットの検証は オンデマンドで発生します 動画ファイルは容量が膨大に なるのが主な理由です 長編映画なら数GBに及ぶ こともあります 後で動画の長さを 尋ねる場合を考慮すると アセットにファイル全部を ダウンロードさせたくは ないものです その代わりアセットは プロパティ値の読み込みが 命令されるまで待ってから その値を与えるために 必要な 情報だけを ダウンロードします もう1つ注意したいのは アセットの検証は 非同期プロセスだ という点です ネットワークI/Oには時間が かかるのでこれはとても重要です アセットがネットワークに 保存される場合 AVAssetが同期ネットワーク リクエストを出す間 Appのメインスレッドは ブロックしたくないですが AVAssetは非同期で 準備が整い次第結果を 送信してくれます この2つを念頭に入れて アセットプロパティ検証用の 新APIを見てみると こんな感じになっています 主に気づくのは この新しいLoadメソッドで これはどのプロパティ値を 読み込むか 伝えるため プロパティ識別子 ここでは .durationを取り込みます 各プロパティ識別子は コンパイル時の結果型に 関連付けられています これによりLoadメソッドの 戻り型が決まります このケースでは 動画の長さはCMTimeなので 結果はCMTimeです awaitキーワードは見たことが ないかもしれません これはSwiftの新機能で 呼び出し場所で Loadメソッドが非同期であると マークするのに使われます async/awaitや Swiftのより幅広い 並行性への取り組みに ついて詳しくは 「Swiftのasync/awaitについて」 というセッションを チェックしてみてください 今のところは 新しいプロパティ プロパティローディングメソッド関数の使い方を 手っ取り早く理解するため awaitキーワードを 呼び出し機能を 2つに分けるものとして 考えてみようと思います まず非同期処理が 開始される 前に起こる部分があります ここではアセットの作成と 動画の長さの読み込み依頼です この時点ではアセットは早速 動画の長さの決定に 必要なI/Oと構文解析を 行い 私たちはその結果を 待機します その間その呼び出し関数は 一時停止されます awaitの後に書かれた コードが すぐ実行されないと いうことです しかし実行中のスレッドは ブロックされておらず 待機中でもより多くの仕事を こなせます 動画の長さの非同期読み込みが 完了すると もう半分の関数の実行が スケジュールされます ここでは動画の長さが正常に 読み込まれた場合 それをローカル定数に保管し 別の関数に送ります または処理が失敗した場合 その呼び出し関数が再開され次第 エラーが投げられます これが非同期でプロパティ値 を読み込む際の基本です また 複数のプロパティの値を 同時に読み込むこともできます Loadメソッドに 1つ以上のプロパティ 識別子を 渡すだけです このケースでは 動画の長さとトラックを 同時に読み込んでいます これは便利なだけでなく 効率的でもあります 興味のある全プロパティを アセットが知っている場合 その容量の読み込みに必要な 作業をバッチしてくれます 複数のプロパティ値 読み込みの結果は プロパティ識別子と同じ順番の 読み込まれた値で タプルになります 1つのプロパティ値の 読み込みと同様 型安全です このケースではタプルの結果の 最初の要素はCMTimeで 2番目の要素はAVAssetTracksの 配列です またもちろん1つの値で 読み込むのと同じく これは非同期処理です 非同期でプロパティ値を 読み込むのに加え プロパティのステータスを 新しいstatus(of: )メソッドで 値の読み込みを 待つことなく いつでもチェックすることも できます Loadメソッドで 使ったのと 同じプロパティ識別子 を渡すと enumを4つの考えられる ケースと共に返します 各プロパティは.notYetLoaded から始まります アセットの検証は オンデマンドで発生するので プロパティ値の読み込みを 指示するまでは アセットは全く読み込みを しないと覚えておいてください 読み込みの進行中に ステータスをチェックすると .loading caseが 得られます またはプロパティが既に 読み込まれていると 読み込まれた値と 一緒に.loadedケースを 取得します 最後に ネットワークの 障害などで 失敗が発生すると 何が失敗したのかを示す エラーと一緒に .failed caseを 取得します これは読み込みリクエストの 失敗を 引き起こしたloadメソッドの 呼び出しに 投げられたのと 同じエラーです これが非同期プロパティ読み込みと ステータスチェックのための 新しいAPIです AVAssetには 値を非同期で 読み込める沢山の プロパティがあります これらのほとんどは 自身を内包する値ですが .tracksと.metadata プロパティは アセットの階層構造を 下るのに使える より複雑なオブジェクトとなります .tracksプロパティの このケースでは AVAssetTrakcsの 配列が取得されます AVAssetTracksは 同じloadメソッドで 値を非同期に 読み込み可能な 独自のプロパティ群を 持ちます 同様に.metadata プロパティも AVMetadataItem 配列と AVMetadataItemの プロパティをいくつか与えます これもloadメソッドで 非同期で読み込めます このようなAPIの最後のパートは あるプロパティ値の 特定のサブセット 取得に使える非同期メソッドの コレクションです 従って例えば全トラックを 読み込む代わりに 最初の3つのメソッドを 使って オーディオトラック のみなどを読み込めます AVAssetと AVAssetTrack にもこうした新しい メソッドがあります ここまでがアセットを 非同期で 検証するために用意した 新APIの全てです しかしここで1つお伝え しなければなりません 実はこの機能は全て 新しくはないのです APIは新しいですが これらのクラスは常に 非同期で プロパティ値を読み込む ことはできました 単に古いAPIでは こんな風にコードを書かなければ ならなかったというだけです 従来は3段階の プロセスでした まずloadValuesAsynchronously メソッドを呼び出して どのプロパティを読み込むか 伝える文字列を与えます 次に各プロパティが正常に 読み込んだか確認しなければ なりませんでした そしてそこまで済んだら 対応する同期プロパティを クエリするか 同期フィルタリングメソッド をどれか呼び出し やっと読み込まれた値を フェッチできていました これは冗長で繰り返しが 多いだけでなく 誤用しやすいです 例えばこうした基本的な 読み込みや ステータスチェックステップを 忘れやすくなります これら非同期プロパティと いつでも呼び出せる メソッドは手元に 残りますが 最初にプロパティ値を 読み込まずに呼び出すと I/Oがブロックされてしまいます メインスレッドでやったら Appが予測しない タイミングで 止まってしまいます よって新しいAPIは ただより使いやすい というだけでなく こうしたよくある誤用 を排除できるので 今後のリリースでは Swiftクライアントのこの古いAPIを 非推奨にする予定です こうしたインターフェイスの 新し非同期バージョンに 移行し そのサポートのため 私たちは簡潔な 移行ガイドをご用意しました 値の読み込みやステータスチェック 同期プロパティの取得という スリーポイントシュートを めがけているなら loadメソッドの呼び出しを 全て1つの非同期なステップで 行えます 同様にこの3ステップ プロセスを行いつつも 同期フィルタリングメソッドを プロパティの代わりに使っても 1ステップで フィルタリングメソッドの 非同期版の等価なものを 呼び出せるようになりました 古いstatusOfValue(forKey: ) メソッドで プロパティのステータスを 切り替え中で .loaded caseにいると 認識している時に 同期プロパティ値を 取得中なら 新しいステータスenumの .loadedケースに その.loaded値が ついてくるという 事実を活用できます コードの一部でプロパティの 値を読み込んで 読み込んだ値をコードの 別の部分にフェッチするなど Appで何か 興味深い動きがある場合 それはこの新インターフェイスで できます loadメソッドを再び 呼ぶだけでよいでしょう これが最も簡単で安全な やり方で またプロパティが既に 読み込まれている場合 完了した作業が重複 することはありません 代わりにキャッシュした値を 返すだけです しかし1つ注意点があります loadメソッドは非同期メソッド なので 非同期コンテキストからのみ 呼び出し可能ということです そのため本当にプロパティの値を 純粋な非同期コンテキスト から取得したい場合 プロパティのステータスを 取得して 読み込み済みと表明することで プロパティの値を同期で 取得することが可能です それでもまだ注意が 必要です 既に読み込み済みであっても プロパティが 失敗することは あり得るからです 最後に 結果が出るまで ブロックするということで 読み込みとステータスチェックを 飛ばし 現在のプロパティの動作と メソッドのみを使う場合 これについては代替を 用意していません これは今までもAPIのおすすめの 使い方とは言えず 推奨もしていません 新しいプロパティ読み込みAPIは 単純なプロパティフェッチ並みに 使いやすくなるように設計したので 新APIへの移行はストレートで あるべきです 1番目のトピックは ここまでです Swiftの新しい非同期機能で アセット検証の 新方法を紹介できて とても嬉しいです 私と同じように楽しんで 暮れたらと思います では後半の小トピックの 1番目に入りましょう メタデータを使った ビデオコンポジットです ここではコンポジット つまり複数のビデオトラックを ビデオフレームの1つのストリームに 編集する方法について話します そして特に カスタムのビデオ コンポジタ向けに 編集を行うコードを 提供する 強化ツールを 用意しました 今年からはカスタムコンポジタの フレームコンポジット コールバックで提供 されるフレームごとメタデータを 取得できるようになりました 例えばGPSデータの シーケンスがあるとします そのデータはタイムスタンプされ ビデオと同期していて そのGPSデータを使って フレームのコンポーズに 影響を及ぼしたいとします これは現在可能で まずGPSデータを ソースビデオのタイムドメタデータ トラックに書くだけです AVAssetWriteで 行うには 既存のクラス AVAssetWriter InputMetadataAdaptorをチェック では新しいAPIを 見てみましょう あるトラックのコレクションを 持つ ソースビデオで始めるとします 1つのオーディオトラックと 2つのビデオトラック 3つのタイムドメタデータトラックが あるとします しかしトラック4と5には ビデオコンポジットに 便利なメタデータが 含まれますが トラック6は無関係です 2つの行うべきセットアップ ステップがあります まず新しいソース SampleDataTrackIDsプロパティに ビデオコンポジットオブジェクトに ビデオコンポジット全体に関連する タイムドメタデータトラックの 全てのIDを 伝えることです 終わったら 次は ビデオコンポジット指示のそれぞれを 受け取って 似たように操作しますが 今回は requiredSourceSampleDataTrackIDs プロパティを 特定の指示に関連するIDに 伝えるように設定します このセットアップは両方とも 行わないと コンポジションコールバックで メタデータを 得られなくなります ではコールバックそのものに ついてです 非同期のビデオコンポジットリクエスト オブジェクトを コールバックに取得すると 2つの新APIで ビデオコンポジット用のメタデータを 取得することができます 1つ目はsourceSampleDataTrackIDs プロパティで リクエストに関連する メタデータトラックの トラックIDを 再生します そして各トラックIDには sourceTimedMetadata(byTrackID :) メソッドがあり そのトラックの 現在のタイムドメタデータ グループ取得に使えます さてAVTimedMetadataGroupは 文字列や日付その他 ハイレベルオブジェクトに 構文解析された値と共に ハイレベルな表示です メタデータのRAWバイトを 使いたいなら sourceSampleBuffer(byTrackID: ) メソッドで AVTimedMetadataGroup の代わりに CMSampleBufferを得られます メタデータが揃ったら それをソースビデオフレームで 使い 出力ビデオフレームを生成して リクエストを終了できます ここまでが カスタムのビデオコンポジタ コールバックに メタデータを得る方法です これで ビデオコンポジットで さらに面白いことができます 最後のトピックはキャプション ファイルオーサリングです macOSでは今年から AVFoundationで2つのファイル 形式にサポートを追加しました まずはiTunes Timed Text または.ittファイルで 字幕を含みます もう1つは Scenarist Closed Captions または.sccファイルで クローズドキャプションを含みます AVFoundationではこの2つの ファイル形式用オーサリングや これらのファイルからの キャプション取り込み 実行時にキャプションを プレビューし 再生時の状態を確認できる サポートを追加中です オーサリングには 1つのキャプションを表示する モデルオブジェクトの AVCaptionを筆頭に テキストや位置 1つの キャプションの 他の属性のもののための プロパティが入っています AVCaptionを自作し AVAssetWriterInputCaptionAdaptor を使って 2つのファイル形式のいずれかに 書くこともできます さらにAVCaptionConversion Validatorクラスに 新しい検証サービスも 追加しました 書いているキャプションが 実際に 選択したファイル形式と互換性が あるか確認するのに使えます これが重要である一例に .sccファイルがあります これにはCEA-608 キャプションが含まれ 一定の時間内で持てる キャプション数から 個々の文字やスタイリング を表示するデータの 割当量の固定に 至るまで 非常に細かい制限のある フォーマットです 検証サービスは キャプションストリームが ファイル形式に互換性があるか 確認するだけでなく タイムスタイプの調整など 互換させるために キャプションに行える 微調整もおすすめしてくれます キャプション取り込み用の 新APIには AVAssetReader OutputCaptionAdaptorがあり これらファイルのいずれかを 取り出して それからAVCaptionオブジェクトを 読み込めるようにします 最後はAVCaptionRenderer クラスで 1つのキャプションや キャプショングループを 取ってCGCContext をレンダリングし 再生中の状態を プレビューできるように してくれます これは私たちの新しい キャプションファイルオーサリング APIの一部にすぎません 導入に興味のある方は フォーラムや conference labで お問い合わせいただければ そこでご質問に お答えします 最後のトピックが終わったので まとめます 今回の大トピックは AVAssetプロパティの検証 それをオンデマンドかつ非同期で 行うべき理由 この分野の新API 古いAPIからの移行についての アドバイスでした その後タイムドメタデータ を使って カスタムビデオコンポジットをさらに カスタマイズする方法も触れました 最後にキャプションファイル オーサリングと その新APIについても 簡潔に触れました 今日はここまでです ご視聴ありがとうございました WWDCをお楽しみください ♪
-
-
2:16 - AVAsset property loading
func inspectAsset() async throws { let asset = AVAsset(url: movieURL) let duration = try await asset.load(.duration) myFunction(thatUses: duration) }
-
4:02 - Load multiple properties
func inspectAsset() async throws { let asset = AVAsset(url: movieURL) let (duration, tracks) = try await asset.load(.duration, .tracks) myFunction(thatUses: duration, and: tracks) }
-
4:52 - Check status
switch asset.status(of: .duration) { case .notYetLoaded: // This is the initial state after creating an asset. case .loading: // This means the asset is actively doing work. case .loaded(let duration): // Use the asset's property value. case .failed(let error): // Handle the error. }
-
6:32 - Async filtering methods
let asset: AVAsset let trk1 = try await asset.loadTrack(withTrackID: 1) let atrs = try await asset.loadTracks(withMediaType: .audio) let ltrs = try await asset.loadTracks(withMediaCharacteristic: .legible) let qtmd = try await asset.loadMetadata(for: .quickTimeMetadata) let chcl = try await asset.loadChapterMetadataGroups(withTitleLocale: .current) let chpl = try await asset.loadChapterMetadataGroups(bestMatchingPreferredLanguages: ["en-US"]) let amsg = try await asset.loadMediaSelectionGroup(for: .audible) let track: AVAssetTrack let seg0 = try await track.loadSegment(forTrackTime: .zero) let spts = try await track.loadSamplePresentationTime(forTrackTime: .zero) let ismd = try await track.loadMetadata(for: .isoUserData) let fbtr = try await track.loadAssociatedTracks(ofType: .audioFallback)
-
7:16 - Async loading: Old API
asset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) { var error: NSError? guard asset.statusOfValue(forKey: "duration", error: &error) == .loaded else { ... } guard asset.statusOfValue(forKey: "tracks", error: &error) == .loaded else { ... } let duration = asset.duration let audioTracks = asset.tracks(withMediaType: .audio) // Use duration and audioTracks. }
-
8:09 - This is the equivalent using the new API:
let duration = try await asset.load(.duration) let audioTracks = try await asset.loadTracks(withMediaType: .audio) // Use duration and audioTracks.
-
8:36 - load(_:)
let tracks = try await asset.load(.tracks)
-
8:51 - Async filtering method
let audioTracks = try await asset.loadTracks(withMediaType: .audio)
-
8:58 - status(of:)
switch status(of: .tracks) { case .loaded(let tracks): // Use tracks.
-
9:18 - load(_:) again (returns cached value)
let tracks = try await asset.load(.tracks)
-
9:49 - Assert status is .loaded()
guard case .loaded (let tracks) = asset.status(of: .tracks) else { ... }
-
11:49 - Custom video composition with metadata: Setup
/* Source movie: - Track 1: Audio - Track 2: Video - Track 3: Video - Track 4: Metadata - Track 5: Metadata - Track 6: Metadata */ // Tell AVMutableVideoComposition about all the metadata tracks. videoComposition.sourceSampleDataTrackIDs = [4, 5] // For each AVMutableVideoCompositionInstruction, specify the metadata track ID(s) to include. instruction1.requiredSourceSampleDataTrackIDs = [4] instruction2.requiredSourceSampleDataTrackIDs = [4, 5]
-
12:44 - Custom video composition with metadata: Compositing
// This is an implementation of a AVVideoCompositing method: func startRequest(_ request: AVAsynchronousVideoCompositionRequest) { for trackID in request.sourceSampleDataTrackIDs { let metadata: AVTimedMetadataGroup? = request.sourceTimedMetadata(byTrackID: trackID) // To get CMSampleBuffers instead, use sourceSampleBuffer(byTrackID:). } // Compose input video frames, using metadata, here. request.finish(withComposedVideoFrame: composedFrame) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。