OSDN Git Service

動画クラスを復元。
authorくまかみ工房 <kumakamikoubou@gmail.com>
Wed, 31 May 2017 06:26:46 +0000 (15:26 +0900)
committerくまかみ工房 <kumakamikoubou@gmail.com>
Wed, 31 May 2017 06:26:46 +0000 (15:26 +0900)
FDK/FDK.csproj
FDK/メディア/動画.cs [new file with mode: 0644]
SSTFEditor/SSTFEditor.csproj

index db9fdd4..abd8b88 100644 (file)
     <Compile Include="メディア\テクスチャ.cs" />
     <Compile Include="メディア\テクスチャフォント.cs" />
     <Compile Include="メディア\ビットマップ付きテクスチャ.cs" />
+    <Compile Include="メディア\動画.cs" />
     <Compile Include="メディア\描画可能画像.cs" />
     <Compile Include="メディア\文字列画像.cs" />
     <Compile Include="メディア\画像.cs" />
diff --git a/FDK/メディア/動画.cs b/FDK/メディア/動画.cs
new file mode 100644 (file)
index 0000000..915f7a6
--- /dev/null
@@ -0,0 +1,993 @@
+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
+       }
+}
index a052aff..15d7e15 100644 (file)
     <Content Include="Resources\りらちょー.png" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\FDK\FDK.csproj">
+      <Project>{0e35c4ac-6e28-4a09-a6ce-05d177800051}</Project>
+      <Name>FDK</Name>
+    </ProjectReference>
     <ProjectReference Include="..\SSTFormat\SSTFormat.csproj">
       <Project>{f6bd5fad-8cbd-4df4-89bf-bbdbe6c5c6fe}</Project>
       <Name>SSTFormat</Name>
     </ProjectReference>
-    <ProjectReference Include="..\_FDK\_FDK.csproj">
-      <Project>{693493de-4adc-4ad4-92a5-a92980a89886}</Project>
-      <Name>_FDK</Name>
-    </ProjectReference>
     <ProjectReference Include="..\_StrokeStyleT\_StrokeStyleT.csproj">
       <Project>{f23ad6fe-6d0d-4a4b-96e8-ab07f4c526cf}</Project>
       <Name>_StrokeStyleT</Name>