ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
AVFoundationとVideoToolboxを使ったDecode ProRes
Mac AppでProResコンテンツをより簡単にデコードおよび表示する方法: AVFoundationとVideoToolboxのデコード機能を活用して、最適なグラフィックパイプラインを実装する方法について学びます。Appのベストプラクティスとパフォーマンスに関する考慮事項を共有し、Afterburnerカードをパイプラインに統合する方法およびMetalを使用してデコードされたフレームを表示する方法について説明します。
リソース
-
ダウンロード
こんにちは ようこそWWDCへ “AVFoundationとVideoToolboxを使った Decode ProRes” セッションへ ようこそ 今日の目標は ProResなどの映像ファイルから― アプリケーションへのパスを 最適化することです
例えばMetalレンダリングエンジンに 映像編集アプリケーションがあるとします ユーザに最高のProResコンテンツを アプリケーション内で提供したいと考えた時
目指すべきゴールは2つです Afterburnerのような ハードウェアデコーダの活用と 圧縮データのフローや デコーダから出力されたフレーム用に― 最適で効率的なパスを持つことです まずはアプリケーションへ映像を統合する際の いくつかの概要の説明です 次にAVFoundationの機能について話しますが
この方法が合わない人もいるでしょう そこで圧縮サンプルを 構築する方法を説明します Video Toolboxで 独自にデコードさせる場合です
そしてVTDecompressionSessionの 使い方の説明 最後はデコードしたビデオフレームを Metalと統合する方法です
それでは我々のプラットフォームで 映像操作の基本を説明します まず映像デコーダについてお話しします 映像デコーダは様々なソースの ビットストリームの解析を行います ユーザによる管理は完全ではなく 不正な形式のメディアで アプリケーションが不安定になったり ぜい弱性が原因で セキュリティ問題が発生します
防御策としてVideo Toolboxは sandboxのサーバ内でデコードを実行
特別なプロセスで実行することで セキュリティが向上し― アプリケーションの安定性も向上します クラッシュ時もアプリケーション全体は壊れず デコードエラーのみで済みます それではmacOSのメディアスタック― 特に映像についてお話しします
まずはAVKitがあります AVKitはアプリケーションに映像機能を入れる ハイレベルなオプションを提供してくれます 我々は既存のレンダーパイプラインを 使うのでAVKitは使いません AVFoundationが様々なメディアのために 優秀なインターフェイスを提供してくれます いくつかのインターフェイスを紹介する予定です
引き続きVideo Toolboxについてですが 映像のデコードとエンコードの際に インターフェイスを提供します
Core Mediaフレームワークは基本的な ビルディングブロックを提供してくれます Core Videoは映像に特化した ビルディングブロックを提供してくれます 今回は3つのフレームワークの インターフェイスに注目します AVFoundationとVideo Toolbox そしてCore Videoです AVFoundationではAVAssetReaderと AVSampleBufferGenerator Video Toolboxでは VTDecompressionSessionに注目します 最後にCore Videoでは CVPixelBuffersと― Metalを使ったCVPixelBufferPoolsに注目します
異なるAPIレベルを機能させる際の 懸念点を見てみましょう
現在のOSのバージョンでは― インターフェイスによる自動的な ハードウェアデコードが可能です Afterburnerの有効化も同様です デコードの有効化と無効化は 選択可能ですが― デフォルトではすべて自動なのです
デコーダの異なるプロセスでの 実行方法は前述しましたが CMSampleBufferが AVFoundationで作成された場合 自動的にフォームになります RPCの境界を越えて 最適化され生成されるのです VTDecompressionSessionを 直接使用する場合 RPCとは関係なくCMSampleBuffersの 生成方法に依存することになります CMSampleBufferの生成については あとで詳しく説明します
次は用語の説明に移りましょう まずはCVPixelBufferですが 解凍されたラスタ画像データに対する ラッパーとしての役目があります ピクセル形式 高さ 幅 行バイト ピッチなどのプロパティを持つデータです さらにカラータグのような画像データの アタッチメントも含みます
次にCMBlockBuffer これはCore Mediaフレームワークですが 圧縮されたサンプルデータなど 任意のデータをラップする― 基礎的な役割があります
そしてCMSampleBuffer これは主に3つの特色を持っています 1つ目は圧縮された映像または 音声データを含む― CMBlockBufferをラップできるということ
2つ目は非圧縮のラスタ画像データを含む― CVPixelBufferをラップできること
見てのとおり どちらもCMTimeを含んでいます CMTimeはデコードのタイムスタンプなどを 表すものです さらにSampleBuffer内のフォーマットを示す CMFormatDescriptionもあります
3つ目の特色は アタッチメントが保持できることで 3種類目のCMSampleBufferとなります CMBlockBufferもCVPixelBufferも 含まない標準のCMSampleBufferです 特殊な条件のメディアパイプラインを通じて 時限化されたアタッチメントが存在します
次はIOSurfaceです IOSurfaceは画像データに使われる メモリに関する巧妙な抽象化です 先ほどラスタデータの話をしましたが このデータは IOSurfaceのフォームに存在しています
IOSurfaceはMetalにおける メモリ構造の基礎としても使用可能です
さらにフレームワーク間のメモリの移動を 効率化することもできます 例えばsandbox化されたデコーダと 皆さんのアプリケーション間の移動 またはGPUが異なるVRAMなど 異なるメモリ領域間でも移動が可能です
そして最後の用語はCVPixelBufferPoolです これはCore Video由来のオブジェクトで 画像データ用のバッファを 効率的にリサイクルしてくれます
多くの場合CVPixelBufferは IOSurfaceをラップします CVPixelBufferがプールからリリースされ 使用されない場合 IOSurfaceはCVPixelBufferPoolに戻ります ゆえに次に移動してきたCVPixelBufferが そのメモリを使用できるのです つまりCVPixelBufferPoolはCVPixelBufferと 同様の特徴を持つということです ピクセル形式 高さ 幅 そして行バイトとピッチです
次にAVFoundationで できることをお話ししましょう やりたいことは何でしたか? ProResの映像ファイルと Metalレンダリングエンジンがあり― 映像からフレームを取得して レンダラーへ移したい AVAssetReaderがすべてを実現します まずサンプルを読み込み― Video Toolbox内で RPC用に最適化します そしてsandbox内で映像データをデコードし― デコードされたCVPixelBufferを 希望のフォーマットで提供します
AVAssetReaderの構築は とても簡単です まずはURLを使いローカルにある映像への AVAssetを作成
そのAVAssetを用いて AVAssetReaderを作りますが この状態ではまだ使えません
AVAssetReaderでデコードデータを 要求するには AVAssetReaderTrackOutputを設定します まず映像トラックが必要です ここではすべての配列を取得しており― 最初のトラックを選択しています この設定は各自で異なるでしょう
その選択に基づき AVAssetReaderTrackOutputを構築します ここでは16ビット 4:4:4:4 YCbCrアルファ もしくは Y416へ戻すように アウトプットの設定をします これはProRes 4444と共に使用する 優れたネイティブフォーマットです 次にサンプルを戻す際のコピーの回避を AVAssetReaderTrackOutputに設定 これにより効率化を望めますが― 戻されたCMSampleBufferが 別の場所に保管されることもあります これは変更することができません
最後に このアウトプットを AVAssetReaderに追加します 動作はとてもシンプルです 最も簡単な設定でお見せしましょう まずは 読み込み開始です
次にcopyNextSampleBufferを 繰り返し実行 デコード済みのアウトプットを設定したので CVImageBufferへの CMSampleBufferデータをチェック 画像バッファのないCMSampleBufferが あっても問題ありません AVAssetReaderを使えば このような単純な反復より― より高度な動作が可能になります もしレンダラーがデコーダに適した フォーマットのバッファを消費できれば 映像パイプラインは最も効率的になります AVAssetReaderはデコーダが サポートしていないフォーマットを指定した場合 アウトプットを 希望のフォーマットに変換します ただしバッファコピーを回避すれば アプリケーションは大幅に改善します 変換不能なアウトプットの選択に関する ガイドラインを紹介します 先ほどのAVAssetReaderOutputの 例を見てみましょう 16ビット 4:4:4:4 YCbCrアルファ またはY416のバッファを元に戻すことにします ProRes 4444を使用する際の 最適なフォーマットです ProRes 422に対しては16ビット 4:2:2 YCbCrもしくはv216が― 最も標準的なデコーダフォーマットです さらにProRes RAWにはRGBA half-float もしくはRGhAが標準的です
AVAssetReaderにすべてを 委ねたくない場合もあります その場合はCMSampleBufferを Video Toolboxにパスする必要があります 3つのオプションを紹介します 1つ目は先ほどの説明どおり AVAssetReaderを使います しかしデコードせずに 圧縮データを得ることができます 編集とフレームの関係と共に トラックレベルのアクセスが得られます
2つ目はAVSampleBufferGeneratorです 編集とフレームとは無関係に メディアレベルでサンプルにアクセスできます 3つ目はCMSampleBufferを 自ら構築することです
AVAssetReaderにサンプルを生成させ AVAssetから圧縮データを得るのです AVAssetReaderでトラックレベルの 読み込みを行い― フレームを表示させるために必要な すべてのサンプルを取得します さらにAVAssetReaderは RPC用に最適化されたサンプルを提供し フレーム送信時の sandboxのオーバーヘッドを削減します
AVAssetReaderから生の圧縮アウトプットを 得るためには AVAssetReaderを構築するだけでよいのです しかしAVAssetReaderTrackOutputを 構築する際は ピクセル形式のディクショナリは指定せず 出力設定をnilに設定します
AVSampleBufferGeneratorはAVAssetTrackの メディアからサンプルを提供します AVSampleBufferCursorを使い メディアを読み取る位置を制御します フレームの依存関係は関係ないため ProResの利用も可能になります しかしHEVCやH.264など フレーム間依存のあるコンテンツで― 使用する場合は注意が必要です AVSampleBufferGeneratorの作成過程を 簡単なコードで示します まず サンプルの実行用に AVSampleCursorを作成します AVSampleBufferRequestも 作成しなければなりません 実際のサンプルリクエストを記述します
AVAssetのソースを使い AVSampleBufferGeneratorを作成
ここでタイムベースをnilに設定すると 同期が実行されます AVSampleBufferGeneratorの 最適なパフォーマンスには― タイムベースを指定し リクエストを非同期で実行します 最後にcreateSampleBufferForRequestで ループさせます 同時にカーソルを1フレーム進めます 繰り返しますが これは最も単純な同期操作で― 最適なパフォーマンスを得るには 非同期版を使います 最後は自らCMSampleBufferを作成する方法です 独自のファイルを読み込んだり 別のソースからデータを得る場合 サンプルデータはsandbox RPCの変換の際には 最適化されません 先ほどCMSampleBufferの構成要素を 説明しました CMBlockBufferにデータが存在しており CMFormatDescriptionと タイムスタンプがあります まずCMBlockBufferで サンプルデータをパックします
次にデータ記述のため CMVideoFormatDescriptionを作成 ここに拡張ディクショナリの カラータグを含めます ビデオの色の管理を適切に行うためです
次にCMSampleTimingInfoの構造に タイムスタンプを作成 最後にCMBlockBufferと CMVideoFormatDescription― CMSampleTimingInfoを使い CMSampleBufferを作成します
以上で皆さん自身でCMSampleBufferのための ソースを作成できました 次はVideo Toolboxです VTDecompressionSessionの構造を見てみましょう 当然ながら映像デコーダがあります そして別々のsandboxプロセスを実行します このCVPixelBufferPoolは― デコードされたフレームの バッファ作成用に使用されています
デコーダが提供できる形式と異なる 形式での出力を指定した時のために 変換を行うVTPixelTransferSessionが存在します もしアプリケーションが特殊なデコーダに アクセスする必要がある場合 VTRegisterProfessionalVideoWorkflow VideoDecodersを呼び出す必要があります この操作は1回のみで結構です VTDecompressionSessionの使用はシンプルです まずVTDecompressionSessionを作成 次にVTSessionSetPropertyを通じて VTDecompressionSessionに― 必要なすべての設定を実施します 必要ない場合もあります
最後にVTDecompressionSessionDecode FrameWithOutputHandlerを記述 単純にVTDecompression SessionDecodeFrameでも結構です
DecodeFrameの呼び出しで 非同期デコードを有効にすることをお勧めします VTDecompressionSession作成の詳細です 注目すべき3つのオプションがあります まずはvideoFormatDescriptionです VTDecompressionSessionに コーデックを伝える役目を持ち CMSampleBuffer内のフォーマットの 詳細を提供します このフォーマットはCMSampleBufferの― CMVideoFormatDescriptionと 一致させる必要があります
次にdestinationImageBufferAttributesです pixelBufferの要求を示しています Video Toolboxでサイズ設定する場合に ここに含めることが可能です レンダリングエンジンの要求があれば 特定のピクセル形式も含めることができます 8ビットRGBサンプルの消費方法しか 分からない場合もここで指定します
Core Animationの出力の要求など 高レベルのディレクティブにもなります
次はvideoDecoderSpecificationです デコーダの選択にヒントを与えてくれます デフォルト以外のハードウェアデコーダも 指定可能です
ハードウェアデコーダと言えば 現行バージョンのOSでは― ハードウェアデコーダの使用は サポートされるすべてのフォーマットで有効です これはオプトインだった数年前とは異なります 現在のOSではハードウェアコーデックが オプトインの必要なく使用可能です VTDecompressionSessionに確実に ハードウェアデコーダをセットしたい場合 また失敗した際にエラーを出したい場合は RequireHardwareAcceleratedVideoDecoderの オプションをtrueに設定します
ハードウェアを無効にして ソフトウェアデコーダを使用する場合は EnableHardwareAcceleratedVideoDecoderの オプションをfalseに設定します 酷似していますね RequireHardwareAcceleratedVideoDecoderと EnableHardwareAcceleratedVideoDecoderです サンプルでVTDecompressionSession作成の 基本を見ましょう まず希望のデータの種類を伝えるための formatDescriptionが必要です CMSampleBufferから 取り出してsessionにパスしましょう 特定のピクセル形式の出力には― pixelBufferAttributesディクショナリの 作成が必要です 先ほどのAVAssetReaderと同様に 16ビット 4:4:4:4 YCbCrアルファを 指定するとします そしてVTDecompressionSessionを作成 3番目のパラメータにはNULLを設定します videoDecoderSpecificationの部分です Video Toolboxがデフォルトで ハードウェアデコーダを選択します
VTDecompressionSessionができたら DecodeFrameの呼び出しに進みます 最適なパフォーマンスのために デコードフラグとしてKVTDecodeFrameEnable AsynchronousDecompressionフラグを設定します
VTDecompressionSession DecodeFrameWithOutputHandlerは 圧縮されたサンプルバッファを取得します inFlagsをデコーダの挙動管理として記述し デコード動作の結果として 出力ブロックが呼び出されます VTDecompressionSessionDecodeFrame WithOutputHandlerがエラーにならない限り 出力ブロックはデコードの結果として 呼び出されます CVImageBufferもしくはデコーダエラーです 圧縮アウトプットの注意点ですが デコードのアウトプットはシリアル化されます 一度にデコーダから戻される フレームは1つだけです デコーダアウトプット内のブロックは フレームのアウトプットにも影響があり デコーダを通じて バックプレッシャーが起きます
動作の実行はアウトプットブロックや コールバックの外で実施してください それではMetalを使った CVPixelBufferの説明に移ります その前にCVPixelBufferPoolの機能を おさらいしましょう 前述のとおりCVPixelBufferPoolから CVPixelBufferがリリースされた時に IOSurfaceはCVPixelBufferPoolに戻ります 次にCVPixelBufferがPoolから移動すると IOSurfaceは再利用され 新たなCVPixelBuffer用に使われます
Metalで実行する際の失敗を避けるため このことを念頭に置くとよいでしょう Metalで利用する場合 IOSurfaceはリサイクルされません
アプローチの方法は2通りあります まずはIOSurfaceを介して行う方法です MetalにはIOSurfaceを テクスチャリングに使用する方法があります そのためには特殊なケアが必要です
次にCore Videoの CVMetalTextureCacheを経由する方法です 確実ではありませんが 簡単で安全な方法なので― パフォーマンス上の利点があります
MetalでCVPixelBufferからの IOSurfaceを使うのは簡単に見えます しかしMetalで利用した場合 IOSurfaceが再利用されないトリックが あることを認識しましょう
まずはCVPixelBufferから IOSurfaceを取得します それからMetalのテクスチャを作成します Metalの使用中は IOSurfaceは リサイクルされないことは分かっているので IOSurfaceIncrementUseCountを使います
Metalが外れ IOSurfaceを リリースしてプールに戻すには MTLCommandBufferを設定し CommandBufferの完了後に実行します さらにIOSurfaceUseCountを デクリメントします
インターフェイスを処理するために CVMetalTextureCacheを使います IOSurfaceとIOSurfaceUseCountを 手動で追跡する必要性を排除します
そのため最初にCVMetalTextureCacheを 作成する必要があります ここで関連付けるMetalデバイスを指定
CVPixelBufferに関連付けられた オブジェクトを作成するため CVMetalTextureCacheCreateTextureFromImageを 呼び出すことができます
さらにCVMetalTextureCacheから 実際のMetalテクスチャを取得するには CVMetalTextureGetTextureを 呼び出すだけです
最後にもう一度 注意してほしいのは― Metalコマンドバッファの完了時に ハンドラを設定することです CVMetalTextureをリリースする前に Metalが外れていることを確認します CVMetalTextureCacheは IOSurfaceテクスチャ結合の繰り返しを防ぎます CVPixelBufferPool由来のIOSurfaceが― 再利用され出現することで 少し効率的になります
トピックのおさらいです ハードウェアデコードの取得タイミングと その制御法 AVAssetReaderを使うことにより― 映像デコーディングが容易に レンダリングパイプラインに統合できること CMSampleBufferの構築方法 AVAssetReaderが適していない場合 Video Toolboxで使用する方法
最後にMetalでのCVPixelBufferの 使用方法を説明しました 皆さんの映像アプリケーションの向上に つながることを期待します ご視聴ありがとうございました
-
-
7:41 - Creating an AVAssetReader is pretty easy
// Constructing an AVAssetReader // Create an AVAsset with an URL pointing at a local asset AVAsset *sourceMovieAsset = [AVAsset assetWithURL:sourceMovieURL]; // Create an AVAssetReader for the asset AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:sourceMovieAsset error:&error];
-
7:58 - // Configuring AVAssetReaderTrackOutput
// Configuring AVAssetReaderTrackOutput // Copy the array of video tracks from the source movie NSArray<AVAssetTrack*> *tracks = [sourceMovieAsset tracksWithMediaType:AVMediaTypeVideo]; // Get the first video track AVAssetTrack *track = [sourceMovieVideoTracks objectAtIndex:0]; // Create the asset reader track output for this video track, requesting ‘y416’ output NSDictionary *outputSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_4444AYpCbCr16) }; AVAssetReaderTrackOutput* assetReaderTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:outputSettings]; // Set the property to instruct the track output to return the samples // without copying them assetReaderTrackOutput.alwaysCopiesSampleData = NO; // Connect the the AVAssetReaderTrackOutput to the AVAssetReader [assetReader addOutput:assetReaderTrackOutput];
-
8:57 - Running AVAssetReader
// Running AVAssetReader BOOL success = [assetReader startReading]; if (success) { CMSampleBufferRef sampleBuffer = NULL; // output is a AVAssetReaderOutput while ((sampleBuffer = [output copyNextSampleBuffer])) { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if (imageBuffer) { // Use the image buffer here // if imageBuffer is NULL, this is likely a marker sampleBuffer } } }
-
11:40 - Prepareing CMSampleBuffers for optimized RPC transfer
AVAssetReaderTrackOutput* assetReaderTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:nil];
-
12:24 - How an AVSampleBufferGenerator is created
AVSampleCursor* cursor = [assetTrack makeSampleCursorAtFirstSampleInDecodeOrder]; AVSampleBufferRequest* request = [[AVSampleBufferRequest alloc] initWithStartCursor:cursor]; request.direction = AVSampleBufferRequestDirectionForward; request.preferredMinSampleCount = 1; request.maxSampleCount = 1; AVSampleBufferGenerator* generator = [[AVSampleBufferGenerator alloc] initWithAsset:srcAsset timebase:nil]; BOOL notDone = YES; while(notDone) { CMSampleBufferRef sampleBuffer = [generator createSampleBufferForRequest:request]; // do your thing with the sampleBuffer [cursor stepInDecodeOrderByCount:1]; }
-
13:40 - Pack your sample data into a CMBlockBuffer
CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, sampleData, sizeof(sampleData), kCFAllocatorMalloc, NULL, 0, sizeof(sampleData), 0, &blockBuffer); CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AppleProRes4444, 1920, 1080, extensionsDictionary, &formatDescription); CMSampleTimingInfo timingInfo; timingInfo.duration = CMTimeMake(10, 600); timingInfo.presentationTimeStamp = CMTimeMake(frameNumber * 10, 600); CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, formatDescription, 1, 1, &timingInfo, 1, &sampleSize, &sampleBuffer);
-
17:47 - VTDecompressionSession Creation
// VTDecompressionSession Creation CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); CFDictionaryRef pixelBufferAttributes = (__bridge CFDictionaryRef)@{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_4444AYpCbCr16) }; VTDecompressionSessionRef decompressionSession; OSStatus err = VTDecompressionSessionCreate(kCFAllocatorDefault, formatDesc, NULL, pixelBufferAttributes, NULL, &decompressionSession);
-
18:30 - Running a VTDecompressionSession
// Running a VTDecompressionSession uint32_t inFlags = kVTDecodeFrame_EnableAsynchronousDecompression; VTDecompressionOutputHandler outputHandler = ^(OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDurationVTDecodeInfoFlags) { // Handle decoder output in this block // Status reports any decoder errors // imageBuffer contains the decoded frame if there were no errors }; VTDecodeInfoFlags outFlags; OSStatus err = VTDecompressionSessionDecodeFrameWithOutputHandler(decompressionSession, sampleBuffer, inFlags, &outFlags, outputHandler);
-
20:54 - CVPixelBuffer to Metal texture: IOSurface
// CVPixelBuffer to Metal texture: IOSurface IOSurfaceRef surface = CVPixelBufferGetIOSurface(imageBuffer); id <MTLTexture> metalTexture = [metalDevice newTextureWithDescriptor:descriptor iosurface:surface plane:0]; // Mark the IOSurface as in-use so that it won’t be recycled by the CVPixelBufferPool IOSurfaceIncrementUseCount(surface); // Set up command buffer completion handler to decrement IOSurface use count again [cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { IOSurfaceDecrementUseCount(surface); }];
-
21:42 - Create a CVMetalTextureCacheRef
// Create a CVMetalTextureCacheRef CVMetalTextureCacheRef metalTextureCache = NULL; id <MTLDevice> metalDevice = MTLCreateSystemDefaultDevice(); CVMetalTextureCacheCreate(kCFAllocatorDefault, NULL, metalDevice, NULL, &metalTextureCache); // Create a CVMetalTextureRef using metalTextureCache and our pixelBuffer CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, metalTextureCache, pixelBuffer, NULL, pixelFormat, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer), 0, &cvTexture); id <MTLTexture> texture = CVMetalTextureGetTexture(cvTexture); // Be sure to release the cvTexture object when the Metal command buffer completes!
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。