OSDN Git Service

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