OSDN Git Service

動画クラスのデコーダ状態フラグの設定個所を変更。
[strokestylet/CsWin10Desktop3.git] / FDK24 / メディア / 動画.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Threading.Tasks;
5 using FDK.同期;
6
7 namespace FDK.メディア
8 {
9         /// <remarks>
10         /// 活性化後の最初の進行描画時に、再生が開始される。
11         /// </remarks>
12         public unsafe class 動画 : Activity
13         {
14                 public enum 再生状態ID { 未再生, 再生中, 再生終了済み }
15                 public FDK.同期.RWLock<再生状態ID> 再生状態 { get; } = new RWLock<再生状態ID>( 再生状態ID.未再生 );
16                 public bool 加算合成 { get; set; } = false;
17                 public float 不透明度0to1 { get; set; } = 1.0f;
18                 public bool ループ再生する { get; set; } = false;
19
20                 public 動画( string 動画ファイルパス )
21                 {
22                         this.動画ファイルパス = FDK.フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( 動画ファイルパス );
23                 }
24                 protected override void Onデバイス依存リソースの作成( デバイスリソース dr )
25                 {
26                         // todo: デバイスがリサイズされると動画もリセットされてしまう。活性化とコードを分離すること(課題)。
27
28                         this.再生状態.Value = 再生状態ID.未再生;
29                         this.前フレームの時刻 = FDK.カウンタ.QPCTimer.未使用;
30                         this.ループした際の先頭時刻 = 0;
31
32                         string 変数付きファイルパス = フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( this.動画ファイルパス );       // Log出力用。
33                         var result = SharpDX.Result.Ok;
34                         try
35                         {
36                                 this.動画の生成に成功した = false;
37
38                                 // 動画ファイルパスを基に、SourceReaderEx, MediaType, フレームサイズ, 描画先WICビットマップを準備する。
39
40                                 #region " 動画ファイルパスの有効性を確認する。"
41                                 //-----------------
42                                 if( string.IsNullOrEmpty( this.動画ファイルパス ) )
43                                 {
44                                         Log.ERROR( $"動画ファイルパスが null または空文字列です。[{変数付きファイルパス}]" );
45                                         return;
46                                 }
47                                 if( false == System.IO.File.Exists( this.動画ファイルパス ) )
48                                 {
49                                         Log.ERROR( $"動画ファイルが存在しません。[{変数付きファイルパス}]" );
50                                         return;
51                                 }
52                                 //-----------------
53                                 #endregion
54                                 #region " 動画のファイルパス(URI扱い)から SourceReaderEx を作成する。"
55                                 //-----------------
56                                 try
57                                 {
58                                         using( var 属性 = new SharpDX.MediaFoundation.MediaAttributes() )
59                                         {
60                                                 // DXVAに対応しているGPUの場合にはそれをデコードに利用するよう指定する。
61                                                 属性.Set<SharpDX.ComObject>( SharpDX.MediaFoundation.SourceReaderAttributeKeys.D3DManager, dr.DXGIDeviceManager );
62
63                                                 // 追加のビデオプロセッシングを有効にする。
64                                                 属性.Set<bool>( SharpDX.MediaFoundation.SourceReaderAttributeKeys.EnableAdvancedVideoProcessing, true );  // bool だったり
65
66                                                 // 追加のビデオプロセッシングを有効にしたら、こちらは無効に。
67                                                 属性.Set<int>( SharpDX.MediaFoundation.SinkWriterAttributeKeys.ReadwriteDisableConverters, 0 );           // int だったり
68
69                                                 // 属性を使って、SourceReaderEx を生成。
70                                                 using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( this.動画ファイルパス, 属性 ) )
71                                                 {
72                                                         this.SourceReaderEx = sourceReader.QueryInterface<SharpDX.MediaFoundation.SourceReaderEx>();
73                                                 }
74                                         }
75                                 }
76                                 catch( SharpDX.SharpDXException e )
77                                 {
78                                         Log.ERROR( $"SourceReaderEx の作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
79                                         result = e.ResultCode;
80                                         return;
81                                 }
82                                 //-----------------
83                                 #endregion
84                                 #region " 最初のビデオストリームを選択し、その他のすべてのストリームを非選択にする。"
85                                 //-----------------
86                                 try
87                                 {
88                                         this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
89                                 }
90                                 catch( SharpDX.SharpDXException e )
91                                 {
92                                         Log.ERROR( $"すべてのストリームの選択解除に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
93                                         return;
94                                 }
95                                 try
96                                 {
97                                         this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, true );
98                                 }
99                                 catch( SharpDX.SharpDXException e )
100                                 {
101                                         Log.ERROR( $"最初のビデオストリームの選択に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
102                                         result = e.ResultCode;
103                                         return;
104                                 }
105                                 //-----------------
106                                 #endregion
107                                 #region " ARGB32 フォーマットに合わせたメディアタイプを作成し、SourceReaderEx に登録してデコーダを準備する。"
108                                 //-----------------
109                                 try
110                                 {
111                                         using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
112                                         {
113                                                 // フォーマットを部分メディアタイプの属性に設定。
114                                                 mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Video );
115                                                 mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.VideoFormatGuids.Argb32 ); // ARGB32 フォーマットで固定。SourceReaderEx を使わない場合、H264 では NV12 しか選べないので注意。
116
117                                                 // 部分メディアタイプを SourceReaderEx にセットする。SourceReaderEx は、必要なデコーダをロードするだろう。
118                                                 this.SourceReaderEx.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, mediaType );
119                                         }
120                                 }
121                                 catch( SharpDX.SharpDXException e )
122                                 {
123                                         Log.ERROR( $"MediaType (Video, ARGB32) の設定または必要なデコーダの読み込みに失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
124                                         result = e.ResultCode;
125                                         return;
126                                 }
127                                 //-----------------
128                                 #endregion
129                                 #region " ビデオストリームが選択されていることを再度保証する。"
130                                 //-----------------
131                                 try
132                                 {
133                                         this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, true );
134                                 }
135                                 catch( SharpDX.SharpDXException e )
136                                 {
137                                         Log.ERROR( $"最初のビデオストリームの選択に失敗しました(MediaType 設定後)。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
138                                         result = e.ResultCode;
139                                         return;
140                                 }
141                                 //-----------------
142                                 #endregion
143                                 #region " デコーダの読み込みにより完成した完全メディアタイプを取得する。"
144                                 //-----------------
145                                 try
146                                 {
147                                         this.MediaType = this.SourceReaderEx.GetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream );
148                                 }
149                                 catch( SharpDX.SharpDXException e )
150                                 {
151                                         Log.ERROR( $"完全メディアタイプの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
152                                         result = e.ResultCode;
153                                         return;
154                                 }
155                                 //-----------------
156                                 #endregion
157                                 #region " フレームサイズを取得する。動画の途中でのサイズ変更には対応しない。"
158                                 //-----------------
159                                 try
160                                 {
161                                         var packedFrameSize = this.MediaType.Get<long>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.FrameSize );
162                                         this.サイズdpx.Width = (int) ( ( packedFrameSize >> 32 ) & 0xFFFFFFFF );
163                                         this.サイズdpx.Height = (int) ( ( packedFrameSize ) & 0xFFFFFFFF );
164                                 }
165                                 catch( SharpDX.SharpDXException e )
166                                 {
167                                         Log.ERROR( $"フレームサイズの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
168                                         result = e.ResultCode;
169                                         return;
170                                 }
171                                 //-----------------
172                                 #endregion
173                                 #region " 描画先WICビットマップを作成する。"
174                                 //-----------------
175                                 try
176                                 {
177                                         this.WICビットマップ = new SharpDX.WIC.Bitmap(
178                                                 dr.WicImagingFactory2,
179                                                 this.サイズdpx.Width,
180                                                 this.サイズdpx.Height,
181                                                 SharpDX.WIC.PixelFormat.Format32bppBGR,
182                                                 SharpDX.WIC.BitmapCreateCacheOption.CacheOnLoad );
183                                 }
184                                 catch( SharpDX.SharpDXException e )
185                                 {
186                                         Log.ERROR( $"描画先 WIC ビットマップの作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
187                                         result = e.ResultCode;
188                                         return;
189                                 }
190                                 //-----------------
191                                 #endregion
192
193                                 this.動画の生成に成功した = true;
194                                 Log.Info( $"{Utilities.現在のメソッド名}: 動画を生成しました。[{変数付きファイルパス}]" );
195
196                                 #region " デコードの前処理を行う。"
197                                 //----------------
198                                 this.デコードキャッシング();
199                                 Log.Info( $"{Utilities.現在のメソッド名}: 動画のセットアップ(フレームキャッシング)を行いました。[{変数付きファイルパス}]" );
200                                 //----------------
201                                 #endregion
202
203                                 // 準備完了。以降は、サンプルを読み込みたいときに、適宜 ReadSample() する。
204                                 // コールバックを使っていない(非同期である)ので、IMFSourceReader::ReadSample() はサンプルが用意できるまでブロックすることに注意。
205                         }
206                         finally
207                         {
208                                 if( result.Failure )
209                                         this.全リソースを解放する();
210
211                                 //this.活性化していない = true; --> 失敗しても、一応活性化は成功とする。(進行描画はしない。)
212                         }
213                 }
214                 protected override void Onデバイス依存リソースの解放( デバイスリソース dr )
215                 {
216                         // todo: デバイスがリサイズされると動画もリセットされてしまう。非活性化とコードを分離すること(課題)。
217
218                         // デコード中なら、タスクが終了するまで待つ。
219                         while( ( this.再生状態.Value == 再生状態ID.再生中 ) && ( this.デコーダ状態.Value == デコーダ状態ID.デコード中 ) )
220                                 System.Threading.Thread.Sleep( 500 );
221
222                         this.全リソースを解放する();
223
224                         Log.Info( $"{Utilities.現在のメソッド名}: 動画を解放しました。[{フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( this.動画ファイルパス )}]" );
225                 }
226                 public void 進行描画する( デバイスリソース dr, SharpDX.RectangleF 描画先矩形dpx, float 不透明度0to1 = 1.0f, bool ループ再生する = false )
227                 {
228                         // Direct2D の行列は、設計単位じゃなく物理単位で計算するので注意。
229                         var 変換行列2Dpx =
230                                 dr.拡大行列DPXtoPX // スケーリング(1) DPX → PX
231                                 * SharpDX.Matrix3x2.Scaling( 描画先矩形dpx.Width / this.サイズdpx.Width, 描画先矩形dpx.Height / this.サイズdpx.Height )  // スケーリング(2)
232                                 * SharpDX.Matrix3x2.Translation( 描画先矩形dpx.Left * dr.拡大率DPXtoPX横方向, 描画先矩形dpx.Top * dr.拡大率DPXtoPX縦方向 );  // 平行移動(物理単位)、
233
234                         // 描画する。
235                         this.進行描画する( dr, 変換行列2Dpx );
236                 }
237                 public void 進行描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列 )
238                 {
239                         Debug.Assert( this.活性化している );
240
241                         // 活性化でエラーが発生している場合は、何もしない(何も描画しない)。
242                         if( false == this.動画の生成に成功した )
243                                 return;
244
245                         // 以下、再生状態とデコーダ状態に応じて処理分岐。
246                         switch( this.再生状態.Value )
247                         {
248                                 case 再生状態ID.未再生:
249                                         // (A) 未再生なら、デコードタスクを開始する。
250                                         this.再生状態.Value = 再生状態ID.再生中;
251                                         this.デコードタスクを開始する();
252                                         break;
253
254                                 case 再生状態ID.再生終了済み:
255                                         // (B) 再生が終了している場合は、何もしない(何も描画しない)。
256                                         break;
257
258                                 case 再生状態ID.再生中:
259                                         // (C) 再生中なら、D2Dビットマップを描画する。
260                                         switch( this.デコーダ状態.Value )
261                                         {
262                                                 case デコーダ状態ID.デコード中:
263                                                         // (C-a) タスクが次のサンプルフレームをデコード中なら、現在のD2Dビットマップ(直前のフレーム画像が入ってる)をもう一度描画する。
264                                                         this.D2Dビットマップを描画する( dr, 変換行列 );
265                                                         break;
266
267                                                 case デコーダ状態ID.完了:
268                                                         // (C-b) タスクの次のサンプルフレームのデコード処理が完了しているなら、サンプルをD2Dビットマップに転送し、それを描画し、デコードタスクを再び起動する。
269                                                         this.サンプルをD2Dビットマップに転送する( dr );
270                                                         this.D2Dビットマップを描画する( dr, 変換行列 );
271                                                         this.デコードタスクを開始する();
272                                                         break;
273
274                                                 case デコーダ状態ID.スキップ:
275                                                         // (C-c) タスクの次のサンプルフレームをスキップしたなら、現在のD2Dビットマップ(直前のフレーム画像が入ってる)をもう一度描画し、デコードタスクを再び起動する。
276                                                         this.D2Dビットマップを描画する( dr, 変換行列 );
277                                                         this.デコードタスクを開始する();
278                                                         break;
279                                         }
280                                         break;
281                         }
282                 }
283
284                 private enum デコーダ状態ID { 完了, デコード中, スキップ }
285                 private readonly FDK.同期.RWLock<デコーダ状態ID> デコーダ状態 = new RWLock<デコーダ状態ID>( デコーダ状態ID.完了 );
286                 private string 動画ファイルパス = "";
287                 private bool 動画の生成に成功した = false;    // 活性化時に成功の成否を設定。
288                 private SharpDX.Result LastSharpDXResult = SharpDX.Result.Ok;
289                 private SharpDX.MediaFoundation.Sample Sample = null;
290                 private SharpDX.MediaFoundation.SourceReaderEx SourceReaderEx = null;
291                 private SharpDX.WIC.Bitmap WICビットマップ = null;    // MediaFoundation は WICBitmap に出力する。
292                 private SharpDX.Direct2D1.Bitmap D2Dビットマップ = null;  // WICBitmap をこれに転送して D2D に Drawする。
293                 private SharpDX.MediaFoundation.MediaType MediaType = null;
294                 private SharpDX.Size2 サイズdpx = new SharpDX.Size2( 1, 1 );  // CopyMemory()で使うので、Size2F じゃなく Size2 を使う。
295                 private FDK.カウンタ.QPCTimer タイマ = new FDK.カウンタ.QPCTimer();
296                 private long ループした際の先頭時刻 = 0;
297                 private long 前フレームの時刻 = FDK.カウンタ.QPCTimer.未使用;  // 未使用 = 先頭のフレームである
298
299                 private unsafe void サンプルをD2Dビットマップに転送する( デバイスリソース dr )
300                 {
301                         var buffer = (SharpDX.MediaFoundation.MediaBuffer) null;
302                         var buffer2d2 = (SharpDX.MediaFoundation.Buffer2D2) null;
303                         try
304                         {
305                                 #region " サンプルからサンプルバッファ (MediaBuffer) を取得する。"
306                                 //-----------------
307                                 try
308                                 {
309                                         buffer = this.Sample.ConvertToContiguousBuffer();
310                                 }
311                                 catch( SharpDX.SharpDXException e )
312                                 {
313                                         Log.ERROR( $"サンプルバッファの取得に失敗しました。(0x{e.HResult:x8})" );
314                                         this.LastSharpDXResult = e.ResultCode;
315                                         return;
316                                 }
317                                 catch( Exception e )
318                                 {
319                                         Log.ERROR( $"サンプルバッファの取得に失敗しました。(0x{e.HResult:x8})" );
320                                         return;
321                                 }
322                                 //-----------------
323                                 #endregion
324                                 #region " サンプルバッファを Buffer2D2 にキャストする。"
325                                 //-----------------
326                                 try
327                                 {
328                                         buffer2d2 = buffer.QueryInterface<SharpDX.MediaFoundation.Buffer2D2>();
329                                 }
330                                 catch( SharpDX.SharpDXException e )
331                                 {
332                                         Log.ERROR( $"サンプルバッファから Buffer2D2 へのキャストに失敗しました。(0x{e.HResult:x8})" );
333                                         this.LastSharpDXResult = e.ResultCode;
334                                         return;
335                                 }
336                                 //-----------------
337                                 #endregion
338                                 #region " サンプルバッファをロックする。"
339                                 //-----------------
340                                 byte[] scanLine0_bp = new byte[ 8 ];    // 「生ポインタ」が格納される。32bitなら[0~3]、64bitなら[0~7]が有効。(CPUではなく.NETに依存)
341                                 int pitch = 0;
342                                 byte[] bufferStart_bp = new byte[ 8 ];  // 「生ポインタ」が格納される。こちらは使わない。
343                                 int bufferLength;
344                                 try
345                                 {
346                                         buffer2d2.Lock2DSize(
347                                                 SharpDX.MediaFoundation.Buffer2DLockFlags.Read,
348                                                 scanLine0_bp,
349                                                 out pitch,
350                                                 bufferStart_bp,
351                                                 out bufferLength );
352                                 }
353                                 catch( SharpDX.SharpDXException e )
354                                 {
355                                         Log.ERROR( $"サンプルバッファのロックに失敗しました。(0x{e.HResult:x8})" );
356                                         this.LastSharpDXResult = e.ResultCode;
357                                         return;
358                                 }
359                                 //-----------------
360                                 #endregion
361                                 try
362                                 {
363                                         #region " サンプルバッファのネイティブ先頭アドレスを取得する。"
364                                         //-----------------
365                                         byte* scanLine0 = null;
366                                         try
367                                         {
368                                                 scanLine0 = (byte*) this.生ポインタを格納したbyte配列からポインタを取得して返す( scanLine0_bp );
369                                         }
370                                         catch( SharpDX.SharpDXException e )
371                                         {
372                                                 Log.ERROR( $"サンプルバッファのアドレスの取得に失敗しました。(0x{e.HResult:x8})" );
373                                                 this.LastSharpDXResult = e.ResultCode;
374                                                 return;
375                                         }
376                                         //-----------------
377                                         #endregion
378                                         #region " サンプルバッファから描画先WICビットマップへ、ARGB32 を G8B8R8X8 に変換しながらコピーする。"
379                                         //-----------------
380                                         if( null == this.D2Dビットマップ )
381                                         {
382                                                 // (A) 初回は WICBitmap 経由で D2Dビットマップを新規生成する。
383                                                 #region " サンプルバッファから描画先WICビットマップへ、ARGB32 を G8B8R8X8 に変換しながらコピーする。"
384                                                 //-----------------
385                                                 try
386                                                 {
387                                                         // 描画先WICビットマップをロックする。
388                                                         using( var bitmapLock = this.WICビットマップ.Lock(
389                                                                 new SharpDX.Rectangle( 0, 0, this.WICビットマップ.Size.Width, this.WICビットマップ.Size.Height ),
390                                                                 SharpDX.WIC.BitmapLockFlags.Write ) )
391                                                         {
392                                                                 // サンプルバッファからWICビットマップへ、ARGB32 を G8B8R8X8 に変換しながらコピー。
393                                                                 int bitmapStride = bitmapLock.Stride;
394                                                                 byte* src = scanLine0;
395                                                                 byte* dest = (byte*) bitmapLock.Data.DataPointer.ToPointer();
396
397                                                                 for( int y = 0; y < this.サイズdpx.Height; y++ )
398                                                                 {
399                                                                         // ARGB32 to G8B8R8X8 ではデータ変換が不要なので、一行を一気にコピー。
400                                                                         動画.CopyMemory( dest, src, this.サイズdpx.Width * 4 );   // ARGB=4バイト。
401                                                                         src += pitch;
402                                                                         dest += bitmapStride;   // bitmapStride は byte 単位
403                                                                 }
404                                                         }
405                                                 }
406                                                 catch( SharpDX.SharpDXException e )
407                                                 {
408                                                         Log.ERROR( $"WICビットマップの Lock に失敗しました。(0x{e.HResult:x8})" );
409                                                         this.LastSharpDXResult = e.ResultCode;
410                                                         return;
411                                                 }
412                                                 catch( Exception e )
413                                                 {
414                                                         Log.ERROR( $"サンプルバッファから WIC ビットマップへのデータの転送に失敗しました。(0x{e.HResult:x8})" );
415                                                         this.LastSharpDXResult = SharpDX.Result.GetResultFromException( e );
416                                                         return;
417                                                 }
418                                                 //-----------------
419                                                 #endregion
420                                                 #region " 描画先WICビットマップからD2Dビットマップを生成する。 "
421                                                 //-----------------
422                                                 try
423                                                 {
424                                                         this.D2Dビットマップ = SharpDX.Direct2D1.Bitmap.FromWicBitmap(
425                                                                 dr.D2DContext1,
426                                                                 this.WICビットマップ );
427                                                 }
428                                                 catch( SharpDX.SharpDXException e )
429                                                 {
430                                                         Log.ERROR( $"D2Dビットマップの作成に失敗しました。(0x{e.HResult:x8})" );
431                                                         this.LastSharpDXResult = e.ResultCode;
432                                                         return;
433                                                 }
434                                                 //-----------------
435                                                 #endregion
436                                         }
437                                         else
438                                         {
439                                                 // (B) 2回目以降は直接 D2Dビットマップへコピーする。
440                                                 this.D2Dビットマップ.CopyFromMemory( new IntPtr( scanLine0 ), pitch );
441                                         }
442                                         //-----------------
443                                         #endregion
444                                 }
445                                 finally
446                                 {
447                                         #region " サンプルバッファのロックを解除する。"
448                                         //-----------------
449                                         buffer2d2.Unlock2D();
450                                         //-----------------
451                                         #endregion
452                                 }
453                         }
454                         finally
455                         {
456                                 FDK.Utilities.解放する( ref buffer2d2 );
457                                 FDK.Utilities.解放する( ref buffer );
458                                 if( this.LastSharpDXResult.Failure )
459                                 {
460                                         // 描画に失敗したら、全リソースを解放して再生終了。
461                                         this.再生状態.Value = 再生状態ID.再生終了済み;
462                                         this.全リソースを解放する();
463                                 }
464                         }
465                 }
466                 private void D2Dビットマップを描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列2Dpx )
467                 {
468                         if( null == this.D2Dビットマップ )
469                                 return;
470
471                         Utilities.D2DBatchDraw( dr.D2DContext1, () => {
472
473                                 // 変換行列とブレンドモードを設定する。
474                                 dr.D2DContext1.Transform = 変換行列2Dpx;
475                                 dr.D2DContext1.PrimitiveBlend = ( this.加算合成 ) ? SharpDX.Direct2D1.PrimitiveBlend.Add : SharpDX.Direct2D1.PrimitiveBlend.SourceOver;
476
477                                 // D2Dビットマップを描画する。
478                                 dr.D2DContext1.DrawBitmap( this.D2Dビットマップ, this.不透明度0to1, SharpDX.Direct2D1.InterpolationMode.Linear );
479                         } );
480                 }
481                 private void デコードタスクを開始する()
482                 {
483                         // 再生中なら、デコードタスクをタスクとして起動する(タスクの完了を待たず、このメソッドはすぐ抜ける)。
484                         if( this.再生状態.Value == 再生状態ID.再生中 )
485                         {
486                                 this.デコーダ状態.Value = デコーダ状態ID.デコード中;   // タスクの起動前に状態を変更する。→ タスクの起動前に再び進行描画が実行されたときに、(無駄で重たい)ビットマップのコピーは行われなくなる。
487                                 Task.Run( () => {
488                                         this.デコードタスク( this.ループ再生する );
489                                 } );
490                         }
491                 }
492                 private void デコードタスク( bool ループ再生する )
493                 {
494                         //  サンプル(フレーム)を1つ取得するタスク。
495
496                         //this.デコーダ状態.Value = デコーダ状態ID.デコード中; --> タスクの起動後に設定するのは遅い。
497                         this.LastSharpDXResult = SharpDX.Result.Ok;
498                         try
499                         {
500                                 var ストリームフラグ = SharpDX.MediaFoundation.SourceReaderFlags.None;
501                                 long サンプルの時刻 = 0;
502
503                                 #region " SourceReader から次のサンプル(フレーム)を1つ取得する。"
504                                 //-----------------
505                                 {
506                                         int 実ストリーム番号 = 0;
507                                         try
508                                         {
509                                                 this.Sample?.Dispose();
510                                                 this.Sample = this.SourceReaderEx.ReadSample(
511                                                         SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream,
512                                                         SharpDX.MediaFoundation.SourceReaderControlFlags.None,
513                                                         out 実ストリーム番号,
514                                                         out ストリームフラグ,
515                                                         out サンプルの時刻 );
516                                         }
517                                         catch( SharpDX.SharpDXException e )
518                                         {
519                                                 Log.ERROR( $"SourceReaderEx.ReadSample() に失敗しました。(0x{e.HResult:x8})" );
520                                                 this.LastSharpDXResult = e.ResultCode;
521                                                 return;
522                                         }
523                                 }
524                                 //-----------------
525                                 #endregion
526                                 #region " 取得結果フラグに応じて、必要な処理があれば行なう。"
527                                 //---------------------------------------------------
528                                 if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Endofstream ) )  // BOX化コストとか気にしない
529                                 {
530                                         #region " ストリーム終了 "
531                                         //----------------
532                                         if( ループ再生する )   // メンバじゃなくパラメータのほう。
533                                         {
534                                                 // (A) ループ再生する場合
535                                                 FDK.Log.Info( "動画をループ再生します。" );
536                                                 this.ループした際の先頭時刻 = this.前フレームの時刻;   // このフラグがセットされるときは、ReadSample() で返されるサンプル時刻は 0 になってるので使えない。
537                                                 this.SourceReaderEx.SetCurrentPosition( 0 );  // ストリーム再生位置を先頭へ。
538                                                 this.デコードタスク( ループ再生する ); // 再帰で先頭サンプルを取得して終了。
539                                                 return;
540                                         }
541                                         else
542                                         {
543                                                 // (B) ループ再生しない場合
544                                                 FDK.Log.Info( "動画の再生を終了します。" );
545                                                 this.再生状態.Value = 再生状態ID.再生終了済み;
546                                                 this.LastSharpDXResult = SharpDX.Result.Ok; // SharpDX のエラーによる終了じゃないよと。
547                                                 return;
548                                         }
549                                         //----------------
550                                         #endregion
551                                 }
552                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Newstream ) )
553                                 {
554                                         // 未対応。
555                                 }
556                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Nativemediatypechanged ) )
557                                 {
558                                         // 未対応。
559                                 }
560                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Currentmediatypechanged ) )
561                                 {
562                                         // 未対応。
563                                 }
564                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.StreamTick ) )
565                                 {
566                                         // 未対応。
567                                 }
568                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.AllEffectsremoved ) )
569                                 {
570                                         // 未対応。
571                                 }
572                                 else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Error ) )
573                                 {
574                                         #region " エラー "
575                                         //----------------
576                                         FDK.Log.Info( "エラーが発生したので、動画の再生を終了します。" );
577                                         this.再生状態.Value = 再生状態ID.再生終了済み;
578                                         this.LastSharpDXResult = SharpDX.Result.Fail;
579                                         return;
580                                         //----------------
581                                         #endregion
582                                 }
583                                 //---------------------------------------------------
584                                 #endregion
585                                 #region " 前フレームの時刻を、先頭時刻+サンプルの時刻 に設定する。"
586                                 //-----------------
587                                 if( FDK.カウンタ.QPCTimer.未使用 == 前フレームの時刻 )    // 最初のフレームである場合(ループ時は除く)
588                                 {
589                                         // 最初のフレームの取得には時間がかかることが多いので、最初のフレームを取得した場合のみ、タイマをサンプルの時刻にリセットする。
590                                         this.タイマ.リセットする( サンプルの時刻 );
591                                 }
592
593                                 サンプルの時刻 += this.ループした際の先頭時刻;
594                                 this.前フレームの時刻 = サンプルの時刻;
595                                 //-----------------
596                                 #endregion
597                                 #region " サンプルの表示時刻までの時刻調整を行う。"
598                                 //-----------------
599                                 var 現在時刻 = this.タイマ.現在のリアルタイムカウント100ns単位;
600                                 if( 現在時刻 > サンプルの時刻 )
601                                 {
602                                         // (A) サンプルの時刻が過去である → このサンプルとタスクは捨てる(さっさと次のサンプルへ移る)。
603                                         this.デコーダ状態.Value = デコーダ状態ID.スキップ;
604                                         return;
605                                 }
606                                 else
607                                 {
608                                         // (B) サンプルの時刻が未来である → その時刻を過ぎるまで Sleep する。
609                                         while( 現在時刻 < サンプルの時刻 )
610                                         {
611                                                 System.Threading.Thread.Sleep( 2 );      // 500fps の動画まで対応(理論上(汗
612                                                 現在時刻 = タイマ.現在のリアルタイムカウント100ns単位;
613                                         }
614                                 }
615                                 //-----------------
616                                 #endregion
617                         }
618                         finally
619                         {
620                                 #region " 失敗した、または EndOfStream に達した場合は、Sample を無効化する。"
621                                 //----------------
622                                 if( this.LastSharpDXResult.Failure )
623                                 {
624                                         FDK.Log.Info( "SharpDX でエラーが発生したので、動画の再生を終了します。" );
625                                         this.再生状態.Value = 再生状態ID.再生終了済み;
626                                         FDK.Utilities.解放する( ref this.Sample );
627                                 }
628                                 //----------------
629                                 #endregion
630
631                                 this.デコーダ状態.Value = デコーダ状態ID.完了;
632                         }
633                 }
634                 private void デコードキャッシング()
635                 {
636                         // 最初のほうのサンプルのデコードには 100~200 ms ほどかかってしまうので、あらかじめ少しデコードしてメモリにキャッシュさせることでこれを緩和する。
637
638                         this.デコーダ状態.Value = デコーダ状態ID.デコード中;
639                         try
640                         {
641                                 // 先頭から複数のフレームを読み込む。
642                                 for( int i = 0; i < 60; i++ )   // 60フレームもあれば、2つめのキーフレームくらいには届くだろう……
643                                 {
644                                         #region " SourceReader から次のサンプル(フレーム)を1つ取得しては解放する。"
645                                         //-----------------
646                                         var sample = (SharpDX.MediaFoundation.Sample) null;
647                                         int actualStreamIndex = 0;
648                                         var streamFlags = SharpDX.MediaFoundation.SourceReaderFlags.None;
649                                         long サンプルの時刻 = 0;
650                                         try
651                                         {
652                                                 sample = this.SourceReaderEx.ReadSample(
653                                                         SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream,
654                                                         SharpDX.MediaFoundation.SourceReaderControlFlags.None,
655                                                         out actualStreamIndex,
656                                                         out streamFlags,
657                                                         out サンプルの時刻 );
658                                         }
659                                         catch( SharpDX.SharpDXException e )
660                                         {
661                                                 Log.ERROR( $"SourceReaderEx.ReadSample() に失敗しました。(0x{e.HResult:x8})" );
662                                                 this.LastSharpDXResult = e.ResultCode;
663                                                 return;
664                                         }
665                                         finally
666                                         {
667                                                 // すぐ解放。
668                                                 sample?.Dispose();
669                                         }
670                                         //-----------------
671                                         #endregion
672                                 }
673                                 // SourceReader を先頭へリセット。
674                                 this.SourceReaderEx.SetCurrentPosition( 0 );
675                         }
676                         finally
677                         {
678                                 this.デコーダ状態.Value = デコーダ状態ID.完了;
679                         }
680                 }
681                 private void 全リソースを解放する()
682                 {
683                         FDK.Utilities.解放する( ref this.D2Dビットマップ );
684                         FDK.Utilities.解放する( ref this.WICビットマップ );
685                         FDK.Utilities.解放する( ref this.Sample );
686                         FDK.Utilities.解放する( ref this.MediaType );
687                         FDK.Utilities.解放する( ref this.SourceReaderEx );
688                 }
689                 private unsafe void* 生ポインタを格納したbyte配列からポインタを取得して返す( byte[] 生ポインタ )
690                 {
691                         if( ( 4 == IntPtr.Size ) && System.BitConverter.IsLittleEndian )
692                         {
693                                 // (A) 32bit, リトルエンディアン
694                                 int 生アドレス32bit = 0;
695                                 for( int i = 0; i < 4; i++ )
696                                         生アドレス32bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
697                                 return new IntPtr( 生アドレス32bit ).ToPointer();
698                         }
699                         else if( ( 8 == IntPtr.Size ) && System.BitConverter.IsLittleEndian )
700                         {
701                                 // (B) 64bit, リトルエンディアン
702                                 long 生アドレス64bit = 0;
703                                 for( int i = 0; i < 8; i++ )
704                                         生アドレス64bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
705                                 return new IntPtr( 生アドレス64bit ).ToPointer();
706                         }
707                         else if( ( 4 == IntPtr.Size ) && ( false == System.BitConverter.IsLittleEndian ) )
708                         {
709                                 // (C) 32bit, ビッグエンディアン
710                                 int 生アドレス32bit = 0;
711                                 for( int i = 0; i < 4; i++ )
712                                         生アドレス32bit += ( (int) 生ポインタ[ 4 - i ] ) << ( i * 8 );
713                                 return new IntPtr( 生アドレス32bit ).ToPointer();
714                         }
715                         else if( ( 8 == IntPtr.Size ) && ( false == System.BitConverter.IsLittleEndian ) )
716                         {
717                                 // (D) 64bit, ビッグエンディアン
718                                 long 生アドレス64bit = 0;
719                                 for( int i = 0; i < 8; i++ )
720                                         生アドレス64bit += ( (int) 生ポインタ[ 8 - i ] ) << ( i * 8 );
721                                 return new IntPtr( 生アドレス64bit ).ToPointer();
722                         }
723
724                         throw new SharpDX.SharpDXException( SharpDX.Result.NotImplemented, "この .NET アーキテクチャには対応していません。" );
725                 }
726
727                 #region " Win32 API "
728                 //-----------------
729                 [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
730                 private static extern unsafe void CopyMemory( void* dst, void* src, int size );
731                 //-----------------
732                 #endregion
733         }
734 }