--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using SharpDX;
+using SharpDX.Direct2D1;
+using SharpDX.MediaFoundation;
+using FDK.カウンタ;
+using FDK.同期;
+
+namespace FDK.メディア
+{
+ public class 動画 : Activity
+ {
+ public string 動画ファイルパス
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._動画ファイルパス;
+ }
+ }
+ protected set
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._動画ファイルパス = value;
+ }
+ }
+ }
+
+ public Size2F サイズ
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._サイズ;
+ }
+ }
+ protected set
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._サイズ = value;
+ }
+ }
+ }
+
+ public double 長さsec
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._長さsec;
+ }
+ }
+ protected set
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._長さsec = value;
+ }
+ }
+ }
+
+ public bool 加算合成
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._加算合成;
+ }
+ }
+ set
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._加算合成 = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// 0:透明 ~ 1:不透明
+ /// </summary>
+ public float 不透明度
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._不透明度;
+ }
+ }
+ set
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._不透明度 = value;
+ }
+ }
+ }
+
+ public bool 動画がエラーまたは再生を終了した
+ {
+ get
+ {
+ lock( this._スレッド間同期 )
+ {
+ return this._動画がエラーまたは再生を終了した;
+ }
+ }
+ }
+
+
+ public 動画( string 動画ファイルパス, int キューのサイズ = 16 )
+ {
+ this.動画ファイルパス = Folder.絶対パスに含まれるフォルダ変数を展開して返す( 動画ファイルパス );
+ this._キューのサイズ = キューのサイズ;
+ }
+
+ public void 再生を開始する( double 開始位置sec = 0.0, bool ループ再生する = false )
+ {
+ this._ループ再生する = ループ再生する;
+ this._動画がエラーまたは再生を終了した = false;
+
+ // タスクを起動する。
+ this._デコードタスク = Task.Factory.StartNew( this._デコードタスクエントリ, (object) 開始位置sec );
+ this._デコードタスク起動完了.WaitOne();
+ }
+
+ public void 進行描画する( グラフィックデバイス gd, RectangleF 描画先矩形, float 不透明度0to1 = 1.0f )
+ {
+ lock( this._スレッド間同期 )
+ {
+ var 変換行列2D =
+ Matrix3x2.Scaling( 描画先矩形.Width / this.サイズ.Width, 描画先矩形.Height / this.サイズ.Height ) // スケーリング
+ * Matrix3x2.Translation( 描画先矩形.Left, 描画先矩形.Top ); // 平行移動
+
+ this.進行描画する( gd, 変換行列2D, 不透明度0to1 );
+ }
+ }
+
+ public void 進行描画する( グラフィックデバイス gd, Matrix3x2 変換行列, float 不透明度0to1 = 1.0f )
+ {
+ lock( this._スレッド間同期 )
+ {
+ #region " 条件チェック。"
+ //----------------
+ if( null == this._デコードタスク || // 再生をまだ開始していないか、あるいはすでに再生を完了してデコードタスクを終了済みである。
+ null == this._SourceReaderEx || // 動画の準備に失敗した。
+ null == this._MediaType || // 同上
+ null == this._WicBitmap || // 同上
+ this._動画がエラーまたは再生を終了した )
+ {
+ return;
+ }
+ //----------------
+ #endregion
+
+ if( this._デコードタスク.IsCompleted && ( 0 == this._フレームキュー.Count ) ) // タスクが終わってもキューにまだフレームが残っている場合がある。
+ {
+ this._動画がエラーまたは再生を終了した = true;
+ return;
+ }
+
+ this._次のフレームを確認する( out FrameQueueItem フレーム );
+
+ if( null != フレーム ) // 次のフレームがある。
+ {
+ // (A) 次のフレームが前のフレームより過去 → ループしたので、タイマをリセットする。
+ if( ( null != this._最後に表示したフレーム ) &&
+ ( フレーム.表示時刻sec < this._最後に表示したフレーム.表示時刻sec ) )
+ {
+ this._再生タイマ.リセットする( QPCTimer.秒をカウントに変換して返す( フレーム.表示時刻sec ) );
+ _次のフレームを表示する( フレーム );
+ }
+
+ // (B) 次のフレームの表示時刻に達した。
+ else if( フレーム.表示時刻sec <= this._再生タイマ.現在のリアルタイムカウントsec )
+ {
+ _次のフレームを表示する( フレーム );
+ }
+
+ // (C) 次のフレームの表示時刻にはまだ達していない。
+ else
+ {
+ this.前のフレームを描画する( gd, 変換行列, 不透明度0to1 );
+ }
+
+ #region " ローカル関数 "
+ //----------------
+ void _次のフレームを表示する( FrameQueueItem frame )
+ {
+ this._次のフレームを取り出す( out frame );
+ this._最後に表示したフレーム?.Dispose();
+ this._最後に表示したフレーム = frame;
+ this._D2DBitmapを描画する( gd, 変換行列, frame.D2DBitmap, 不透明度0to1 );
+ };
+ //----------------
+ #endregion
+ }
+
+ // (D) デコードが追い付いてない、またはループせず再生が終わっている。
+ else
+ {
+ // 何も表示しない → 真っ黒画像。デコードが追い付いてないなら点滅するだろう。
+ }
+ }
+ }
+
+ public void 前のフレームを描画する( グラフィックデバイス gd, RectangleF 描画先矩形, float 不透明度0to1 = 1.0f )
+ {
+ lock( this._スレッド間同期 )
+ {
+ var 変換行列2D =
+ Matrix3x2.Scaling( 描画先矩形.Width / this.サイズ.Width, 描画先矩形.Height / this.サイズ.Height ) // スケーリング
+ * Matrix3x2.Translation( 描画先矩形.Left, 描画先矩形.Top ); // 平行移動
+
+ this.前のフレームを描画する( gd, 変換行列2D, 不透明度0to1 );
+ }
+ }
+
+ public void 前のフレームを描画する( グラフィックデバイス gd, Matrix3x2 変換行列, float 不透明度0to1 = 1.0f )
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._D2DBitmapを描画する( gd, 変換行列, this._最後に表示したフレーム?.D2DBitmap, 不透明度0to1 );
+ }
+ }
+
+ protected override void On活性化( グラフィックデバイス gd )
+ {
+ this._デコードタスク = null; // タスクが起動していないときは null であることを保証する。
+ this._デコードタスク起動完了 = new AutoResetEvent( false );
+ this._キューが空いた = new ManualResetEvent( true );
+ this._デコードタスクを終了せよ = new AutoResetEvent( false );
+ this._再生タイマ = new QPCTimer();
+
+ using( Log.Block( FDKUtilities.現在のメソッド名 ) )
+ {
+ lock( this._スレッド間同期 )
+ {
+ this._デコードタスク用D2DDeviceContext参照 = gd.D2DDeviceContext;
+ this._フレームキュー = new Queue<FrameQueueItem>();
+ this._最後に表示したフレーム = null;
+
+ // 動画ファイルから、SourceReaderEx, MediaType, WicBitmap を生成する。
+
+ string 変数付きファイルパス = Folder.絶対パスに含まれるフォルダ変数を展開して返す( 動画ファイルパス ); // Log出力用。
+
+ #region " 動画ファイルパスの有効性を確認する。"
+ //-----------------
+ if( 動画ファイルパス.Nullまたは空である() )
+ {
+ Log.ERROR( $"動画ファイルパスが null または空文字列です。[{変数付きファイルパス}]" );
+ return;
+ }
+ if( false == File.Exists( 動画ファイルパス ) )
+ {
+ Log.ERROR( $"動画ファイルが存在しません。[{変数付きファイルパス}]" );
+ return;
+ }
+ //-----------------
+ #endregion
+
+ #region " SourceReaderEx を生成する。"
+ //-----------------
+ try
+ {
+ using( var 属性 = new MediaAttributes() )
+ {
+ // DXVAに対応しているGPUの場合には、それをデコードに利用するよう指定する。
+ 属性.Set( SourceReaderAttributeKeys.D3DManager, gd.DXGIDeviceManager );
+
+ // 追加のビデオプロセッシングを有効にする。
+ 属性.Set( SourceReaderAttributeKeys.EnableAdvancedVideoProcessing, true ); // 真偽値が bool だったり
+
+ // 追加のビデオプロセッシングを有効にしたら、こちらは無効に。
+ 属性.Set( SinkWriterAttributeKeys.ReadwriteDisableConverters, 0 ); // int だったり
+
+ // 属性を使って、SourceReaderEx を生成。
+ using( var sourceReader = new SourceReader( 動画ファイルパス, 属性 ) ) // パスは URI 扱い
+ {
+ this._SourceReaderEx = sourceReader.QueryInterface<SourceReaderEx>();
+ }
+ }
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"SourceReaderEx の作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " 最初のビデオストリームを選択し、その他のすべてのストリームを非選択にする。"
+ //-----------------
+ try
+ {
+ this._SourceReaderEx.SetStreamSelection( SourceReaderIndex.AllStreams, false );
+ this._SourceReaderEx.SetStreamSelection( SourceReaderIndex.FirstVideoStream, true );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"ストリームの選択に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " 部分 MediaType を作成し、SourceReaderEx に登録する。"
+ //-----------------
+ try
+ {
+ using( var mediaType = new MediaType() )
+ {
+ // フォーマットは ARGB32 で固定とする。(SourceReaderEx を使わない場合、H264 では ARGB32 が選べないので注意。)
+ mediaType.Set( MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video );
+ mediaType.Set( MediaTypeAttributeKeys.Subtype, VideoFormatGuids.Argb32 );
+
+ // 部分メディアタイプを SourceReaderEx にセットする。SourceReaderEx は、必要なデコーダをロードするだろう。
+ this._SourceReaderEx.SetCurrentMediaType( SourceReaderIndex.FirstVideoStream, mediaType );
+ }
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"MediaType (Video, ARGB32) の設定または必要なデコーダの読み込みに失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " ビデオストリームが選択されていることを再度保証する。"
+ //-----------------
+ try
+ {
+ this._SourceReaderEx.SetStreamSelection( SourceReaderIndex.FirstVideoStream, true );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"最初のビデオストリームの選択に失敗しました(MediaType 設定後)。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " 完全 MediaType と動画の情報を取得する。"
+ //-----------------
+ try
+ {
+ this._MediaType = this._SourceReaderEx.GetCurrentMediaType( SourceReaderIndex.FirstVideoStream );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"完全メディアタイプの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+
+ // フレームサイズを取得する。
+ try
+ {
+ // 動画の途中でのサイズ変更には対応しない。
+ long packedFrameSize = this._MediaType.Get( MediaTypeAttributeKeys.FrameSize );
+ this.サイズ = new Size2F( ( packedFrameSize >> 32 ) & 0xFFFFFFFF, ( packedFrameSize ) & 0xFFFFFFFF );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"フレームサイズの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+
+ // 動画の長さを取得する。
+ try
+ {
+ this.長さsec = this._SourceReaderEx.GetPresentationAttribute(
+ SourceReaderIndex.MediaSource,
+ PresentationDescriptionAttributeKeys.Duration
+ ) / ( 1000.0 * 1000.0 * 10.0 );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"動画の長さの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " 描画先となる WicBitmap を作成する。"
+ //-----------------
+ try
+ {
+ this._WicBitmap = new SharpDX.WIC.Bitmap(
+ gd.WicImagingFactory,
+ (int) this.サイズ.Width,
+ (int) this.サイズ.Height,
+ SharpDX.WIC.PixelFormat.Format32bppBGR,
+ SharpDX.WIC.BitmapCreateCacheOption.CacheOnLoad );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"描画先となる WICビットマップの作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ this._ストックする();
+ }
+ }
+ }
+
+ protected override void On非活性化( グラフィックデバイス gd )
+ {
+ using( Log.Block( FDKUtilities.現在のメソッド名 ) )
+ {
+ #region " デコードタスクが起動していたら、終了する。"
+ //----------------
+ if( null != this._デコードタスク )
+ {
+ this._デコードタスクを終了せよ.Set();
+
+ if( false == this._デコードタスク.Wait( 2000 ) )
+ Log.WARNING( "デコードタスクの終了待ちがタイムアウトしました。" );
+
+ this._デコードタスク = null;
+ }
+ //----------------
+ #endregion
+
+ lock( this._スレッド間同期 )
+ {
+ FDKUtilities.解放する( ref this._最後に表示したフレーム );
+ FDKUtilities.解放する( ref this._WicBitmap );
+ FDKUtilities.解放する( ref this._MediaType );
+ FDKUtilities.解放する( ref this._SourceReaderEx );
+
+ this._キューをクリアする();
+ this._フレームキュー = null;
+ this._デコードタスク用D2DDeviceContext参照 = null;
+ }
+ }
+
+ this._キューが空いた.Close();
+ this._デコードタスクを終了せよ.Close();
+ }
+
+
+ private string _動画ファイルパス;
+
+ private Size2F _サイズ;
+
+ private double _長さsec = 0.0;
+
+ private bool _加算合成 = false;
+
+ private float _不透明度 = 1.0f;
+
+ private int _キューのサイズ = 0;
+
+ private class FrameQueueItem : IDisposable
+ {
+ public double 表示時刻sec = 0;
+ public Bitmap D2DBitmap = null;
+
+ public void Dispose()
+ {
+ FDKUtilities.解放する( ref this.D2DBitmap );
+ }
+ }
+
+ private Queue<FrameQueueItem> _フレームキュー = null;
+
+ private FrameQueueItem _最後に表示したフレーム = null;
+
+ private bool _ループ再生する = false;
+
+ private Task _デコードタスク = null;
+
+ private AutoResetEvent _デコードタスク起動完了 = null;
+
+ private ManualResetEvent _キューが空いた = null;
+
+ private AutoResetEvent _デコードタスクを終了せよ = null;
+
+ private SourceReaderEx _SourceReaderEx = null;
+
+ private MediaType _MediaType = null;
+
+ private SharpDX.WIC.Bitmap _WicBitmap = null; // MediaFoundation は WICBitmap に出力する。
+
+ private DeviceContext1 _デコードタスク用D2DDeviceContext参照 = null; // D2Dはスレッドセーフであること。
+
+ private QPCTimer _再生タイマ = null;
+
+ private bool _動画がエラーまたは再生を終了した = false;
+
+ private readonly object _スレッド間同期 = new object();
+
+
+ private void _キューをクリアする()
+ {
+ lock( this._スレッド間同期 )
+ {
+ foreach( var frame in this._フレームキュー )
+ frame.Dispose();
+
+ this._フレームキュー.Clear();
+ this._キューが空いた?.Set();
+ }
+ }
+
+ private void _次のフレームを確認する( out FrameQueueItem フレーム )
+ {
+ lock( this._スレッド間同期 )
+ {
+ if( ( 0 == this._フレームキュー.Count ) ||
+ ( null == ( フレーム = this._フレームキュー.Peek() ) ) ) // キューから取り出さない
+ {
+ フレーム = null; // キューが空だったか、Peek が一歩遅かった?(ないはずだが
+ }
+ }
+ }
+
+ private void _次のフレームを取り出す( out FrameQueueItem フレーム )
+ {
+ lock( this._スレッド間同期 )
+ {
+ フレーム = null;
+
+ if( 0 < this._フレームキュー.Count )
+ {
+ フレーム = this._フレームキュー.Dequeue();
+ }
+
+ this._キューが空いた.Set();
+ }
+ }
+
+ private void _D2DBitmapを描画する( グラフィックデバイス gd, Matrix3x2 変換行列2D, Bitmap D2DBitmap, float 不透明度 )
+ {
+ if( null == D2DBitmap )
+ return;
+
+ gd.D2DBatchDraw( ( dc ) => {
+
+ dc.Transform = ( 変換行列2D ) * dc.Transform;
+ dc.PrimitiveBlend = ( this.加算合成 ) ? PrimitiveBlend.Add : PrimitiveBlend.SourceOver;
+
+ dc.DrawBitmap( D2DBitmap, 不透明度, InterpolationMode.Linear );
+
+ } );
+ }
+
+
+ // 以下、デコードタスク用
+
+ private void _デコードタスクエントリ( object obj再生開始位置sec )
+ {
+ Log.Info( "デコードタスクを起動しました。" );
+
+ var 再生開始位置sec = (double) obj再生開始位置sec;
+
+ if( 0.0 < 再生開始位置sec )
+ {
+ this._再生位置までストリームを進める( 再生開始位置sec );
+ }
+
+ const int EVID_キューが空いた = 0;
+ const int EVID_デコードタスクを終了せよ = 1;
+ var events = new WaitHandle[ 2 ];
+ events[ EVID_キューが空いた ] = this._キューが空いた;
+ events[ EVID_デコードタスクを終了せよ ] = this._デコードタスクを終了せよ;
+
+ this._デコードタスク起動完了.Set();
+
+ lock( this._スレッド間同期 )
+ {
+ this._再生タイマ.リセットする( QPCTimer.秒をカウントに変換して返す( 再生開始位置sec ) );
+ }
+
+ while( WaitHandle.WaitAny( events ) == EVID_キューが空いた )
+ {
+ lock( this._スレッド間同期 )
+ {
+ // キューが空いてるので、サンプルを1つデコードする。
+ if( this._サンプルをひとつデコードしてフレームをキューへ格納する() )
+ {
+ // キューがいっぱいになったら、空くまで待つ。
+ if( _キューのサイズ == this._フレームキュー.Count )
+ {
+ this._キューが空いた.Reset(); // 次の while で空くのを待つ。
+ }
+ }
+ else
+ {
+ break; // エラーあるいはストリーム終了 → デコードタスクを終了する。
+ }
+ }
+ }
+
+ //this._デコードタスク = null; --> メインスレッド側でスレッド終了時にチェックしてるので、ここでnullにしてはダメ。
+
+ Log.Info( "デコードタスクを終了しました。" );
+ }
+
+ /// <returns>
+ /// 格納できたかスキップした場合は true、エラーあるいはストリーム終了なら false。
+ /// </returns>
+ private bool _サンプルをひとつデコードしてフレームをキューへ格納する()
+ {
+ lock( this._スレッド間同期 )
+ {
+ var sample = (Sample) null;
+ var bitmap = (Bitmap) null;
+
+ try
+ {
+ long サンプルの表示時刻100ns = 0;
+
+ #region " ソースリーダーから次のサンプルをひとつデコードする。"
+ //-----------------
+ sample = this._SourceReaderEx.ReadSample(
+ SourceReaderIndex.FirstVideoStream,
+ SourceReaderControlFlags.None,
+ out int 実ストリーム番号,
+ out var ストリームフラグ,
+ out サンプルの表示時刻100ns );
+
+ if( ストリームフラグ.HasFlag( SourceReaderFlags.Endofstream ) ) // BOX化コストとか気にしない
+ {
+ #region " ストリーム終了 "
+ //----------------
+ if( this._ループ再生する )
+ {
+ Log.Info( "動画をループ再生します。" );
+ this._SourceReaderEx.SetCurrentPosition( 0 );
+ return this._サンプルをひとつデコードしてフレームをキューへ格納する();
+ }
+ else
+ {
+ Log.Info( "動画の再生を終了します。" );
+ return false;
+ }
+ //----------------
+ #endregion
+ }
+ else if( ストリームフラグ.HasFlag( SourceReaderFlags.Error ) )
+ {
+ #region " エラー。"
+ //----------------
+ throw new SharpDXException( Result.Fail );
+ //----------------
+ #endregion
+ }
+ //else if( ストリームフラグ.HasFlag( SourceReaderFlags.Newstream ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SourceReaderFlags.Nativemediatypechanged ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SourceReaderFlags.Currentmediatypechanged ) )
+ //{
+ // 動画の途中でのサイズ変更には対応しない。
+ //}
+ //else if( ストリームフラグ.HasFlag( SourceReaderFlags.StreamTick ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SourceReaderFlags.AllEffectsremoved ) )
+ //{
+ //}
+ //---------------------------------------------------
+ #endregion
+
+ //if( サンプルの表示時刻100ns < this.再生タイマ.現在のリアルタイムカウント100ns単位 )
+ // return true; // もう表示時刻は通り過ぎてるのでスキップする。---> この実装だとループのし始めには常に true になってしまうので却下。
+
+ bitmap = this._サンプルからビットマップを取得する( sample );
+
+ this._フレームキュー.Enqueue( new FrameQueueItem() {
+ D2DBitmap = bitmap,
+ 表示時刻sec = サンプルの表示時刻100ns / 10_000_000.0,
+ } );
+
+ bitmap = null; // キューに格納したので、ここでは Dispose しない。
+ }
+ catch( Exception e )
+ {
+ Log.Info( $"エラーが発生したので、動画の再生を終了します。[{e.Message}]" );
+ return false;
+ }
+ finally
+ {
+ bitmap?.Dispose();
+ sample?.Dispose();
+ }
+ }
+ return true;
+ }
+
+ private void _再生位置までストリームを進める( double 再生位置sec )
+ {
+ #region " ストリームがシーク不可なら何もしない。"
+ //----------------
+ var flags = this._SourceReaderEx.GetPresentationAttribute(
+ SourceReaderIndex.MediaSource,
+ SourceReaderAttributeKeys.MediaSourceCharacteristics );
+
+ if( ( flags & (int) MediaSourceCharacteristics.CanSeek ) == 0 )
+ {
+ Log.WARNING( "この動画はシークできないようです。" );
+ return;
+ }
+ //----------------
+ #endregion
+
+ // ストリームの再生位置を移動する。
+
+ this._キューをクリアする();
+
+ long 再生位置100ns = (long) ( 再生位置sec * 10_000_000 );
+ this._SourceReaderEx.SetCurrentPosition( 再生位置100ns );
+
+ // キーフレームから再生位置100nsまで ReadSample する。
+
+ long サンプルの表示時刻100ns = 0;
+
+ while( サンプルの表示時刻100ns < 再生位置100ns )
+ {
+ // サンプルを取得。
+ var sample = this._SourceReaderEx.ReadSample(
+ SourceReaderIndex.FirstVideoStream,
+ SourceReaderControlFlags.None,
+ out int 実ストリーム番号,
+ out var ストリームフラグ,
+ out サンプルの表示時刻100ns );
+
+ // 即解放。
+ sample?.Dispose();
+
+ if( ストリームフラグ.HasFlag( SourceReaderFlags.Endofstream ) )
+ {
+ // ストリーム終了。
+ return;
+ }
+ else if( ストリームフラグ.HasFlag( SourceReaderFlags.Error ) )
+ {
+ // エラー発生。
+ Log.ERROR( $"動画の再生位置を移動中に、エラーが発生しました。" );
+ return;
+ }
+ }
+
+ this._ストックする();
+
+ Log.Info( $"動画の再生位置を {再生位置sec}sec へ移動しました。" );
+ }
+
+ private void _ストックする()
+ {
+ lock( this._スレッド間同期 )
+ {
+ for( int i = 0; i < this._キューのサイズ; i++ )
+ {
+ this._サンプルをひとつデコードしてフレームをキューへ格納する();
+ }
+
+ this._キューが空いた.Reset(); // 埋まった
+ }
+ }
+
+ private unsafe Bitmap _サンプルからビットマップを取得する( Sample Sample )
+ {
+ var d2dBitmap = (Bitmap) null;
+ var buffer = (MediaBuffer) null;
+ var buffer2d2 = (Buffer2D2) null;
+ try
+ {
+ #region " サンプルからサンプルバッファ (MediaBuffer) を取得する。"
+ //-----------------
+ try
+ {
+ buffer = Sample.ConvertToContiguousBuffer();
+ }
+ catch( Exception e )
+ {
+ Log.ERROR( $"サンプルバッファの取得に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " サンプルバッファを Buffer2D2 にキャストする。"
+ //-----------------
+ try
+ {
+ buffer2d2 = buffer.QueryInterface<Buffer2D2>();
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファから Buffer2D2 へのキャストに失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " サンプルバッファをロックする。"
+ //-----------------
+ byte[] scanLine0_bp = new byte[ 8 ]; // 「生ポインタ」が格納される。32bitなら[0~3]、64bitなら[0~7]が有効。(CPUではなく.NETに依存)
+ int pitch;
+ byte[] bufferStart_bp = new byte[ 8 ]; // 「生ポインタ」が格納される。こちらは使わない。
+ int bufferLength;
+ try
+ {
+ buffer2d2.Lock2DSize(
+ Buffer2DLockFlags.Read,
+ scanLine0_bp,
+ out pitch,
+ bufferStart_bp,
+ out bufferLength );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファのロックに失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ try
+ {
+ #region " サンプルバッファのネイティブ先頭アドレスを取得する。"
+ //-----------------
+ byte* scanLine0 = null;
+ try
+ {
+ scanLine0 = (byte*) this._生ポインタを格納したbyte配列からポインタを取得して返す( scanLine0_bp );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファのアドレスの取得に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ #region " サンプルから WicBitmap へ画像をコピーする。"
+ //-----------------
+ try
+ {
+ // 描画先である WICBitmap をロックする。
+ using( var bitmapLock = this._WicBitmap.Lock(
+ new Rectangle( 0, 0, this._WicBitmap.Size.Width, this._WicBitmap.Size.Height ),
+ SharpDX.WIC.BitmapLockFlags.Write ) )
+ {
+ // サンプルバッファからWICビットマップへ、ARGB32 を G8B8R8X8 に変換しながらコピーする。
+ int bitmapStride = bitmapLock.Stride;
+ byte* src = scanLine0;
+ byte* dest = (byte*) bitmapLock.Data.DataPointer.ToPointer();
+
+ if( pitch != bitmapStride )
+ {
+ for( int y = 0; y < this.サイズ.Height; y++ )
+ {
+ // ARGB32 to G8B8R8X8 ではデータ変換が不要なので、一行を一気にコピー。
+ 動画.CopyMemory( dest, src, (int) this.サイズ.Width * 4 ); // ARGB=4バイト。
+ src += pitch;
+ dest += bitmapStride; // bitmapStride は byte 単位
+ }
+ }
+ else
+ {
+ // ARGB32 to G8B8R8X8 ではデータ変換が不要、かつ pitch と bitmapStride が等しいので、全行を一括してコピー。
+ 動画.CopyMemory( dest, src, (int) ( this.サイズ.Width * this.サイズ.Height * 4 ) ); // ARGB=4バイト。
+ }
+ }
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"WicBitmap の Lock に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ catch( Exception e )
+ {
+ Log.ERROR( $"サンプルバッファから WIC ビットマップへのデータの転送に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //----------------
+ #endregion
+
+ #region " WicBitmap から D2DBitmap を生成する。"
+ //----------------
+ try
+ {
+ d2dBitmap = Bitmap.FromWicBitmap( this._デコードタスク用D2DDeviceContext参照, this._WicBitmap );
+ }
+ catch( SharpDXException e )
+ {
+ Log.ERROR( $"D2Dビットマップの作成に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //----------------
+ #endregion
+ }
+ finally
+ {
+ #region " サンプルバッファのロックを解除する。"
+ //-----------------
+ buffer2d2.Unlock2D();
+ //-----------------
+ #endregion
+ }
+ }
+ finally
+ {
+ FDKUtilities.解放する( ref buffer2d2 );
+ FDKUtilities.解放する( ref buffer );
+ }
+
+ return d2dBitmap;
+ }
+
+ private unsafe void* _生ポインタを格納したbyte配列からポインタを取得して返す( byte[] 生ポインタ )
+ {
+ if( ( 4 == IntPtr.Size ) && BitConverter.IsLittleEndian )
+ {
+ #region " (A) 32bit, リトルエンディアン "
+ //----------------
+ int 生アドレス32bit = 0;
+
+ for( int i = 0; i < 4; i++ )
+ 生アドレス32bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス32bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 8 == IntPtr.Size ) && BitConverter.IsLittleEndian )
+ {
+ #region " (B) 64bit, リトルエンディアン "
+ //----------------
+ long 生アドレス64bit = 0;
+ for( int i = 0; i < 8; i++ )
+ 生アドレス64bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス64bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 4 == IntPtr.Size ) && ( false == BitConverter.IsLittleEndian ) )
+ {
+ #region " (C) 32bit, ビッグエンディアン "
+ //----------------
+ int 生アドレス32bit = 0;
+ for( int i = 0; i < 4; i++ )
+ 生アドレス32bit += ( (int) 生ポインタ[ 4 - i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス32bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 8 == IntPtr.Size ) && ( false == BitConverter.IsLittleEndian ) )
+ {
+ #region " (D) 64bit, ビッグエンディアン "
+ //----------------
+ long 生アドレス64bit = 0;
+ for( int i = 0; i < 8; i++ )
+ 生アドレス64bit += ( (int) 生ポインタ[ 8 - i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス64bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+
+ throw new SharpDXException( Result.NotImplemented, "この .NET アーキテクチャには対応していません。" );
+ }
+
+
+ #region " Win32 API "
+ //-----------------
+ [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
+ private static extern unsafe void CopyMemory( void* dst, void* src, int size );
+ //-----------------
+ #endregion
+ }
+}