OSDN Git Service

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