OSDN Git Service

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