using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Threading.Tasks;
-using FDK.同期;
+using System.Linq;
namespace FDK.メディア
{
- /// <remarks>
- /// 活性化後の最初の進行描画時に、再生が開始される。
- /// </remarks>
- public unsafe class 動画 : Activity
+ public class 動画 : FDK.Activity
{
- public enum 再生状態ID { 未再生, 再生中, 再生終了済み }
- public 再生状態ID 再生状態 { get; protected set; } = 再生状態ID.未再生;
- public bool 加算合成 { get; set; } = false;
- public float 不透明度0to1 { get; set; } = 1.0f;
- public bool ループ再生する { get; set; } = false;
- public int キューのサイズ { get; set; } = 16;
+ public string 動画ファイルパス
+ {
+ get;
+ protected set;
+ } = null;
+
+ public SharpDX.Size2F サイズdpx
+ {
+ get;
+ protected set;
+ }
+
+ public double 長さsec
+ {
+ get;
+ protected set;
+ } = 0.0;
+
+ public bool 加算合成
+ {
+ get;
+ set;
+ } = false;
+
+ /// <summary>
+ /// 0:透明 ~ 1:不透明
+ /// </summary>
+ public float 不透明度
+ {
+ get;
+ set;
+ } = 1.0f;
- public 動画( string 動画ファイルパス, int キューのサイズ )
+ public 動画( string 動画ファイルパス, int キューのサイズ = 16 )
{
this.動画ファイルパス = FDK.フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( 動画ファイルパス );
this.キューのサイズ = キューのサイズ;
}
- protected override void Onデバイス依存リソースの作成( デバイスリソース dr )
+
+ public void 再生を開始する( double 開始位置sec = 0.0, bool ループ再生する = false )
{
- // todo: デバイスがリサイズされると動画もリセットされてしまう。活性化とコードを分離すること(課題)。
+ this.ループ再生する = ループ再生する;
+
+ // タスクを起動する。
+ this.デコードタスク = System.Threading.Tasks.Task.Factory.StartNew( this._デコードタスクエントリ, (object) 開始位置sec );
+ this.デコードタスク起動完了.WaitOne();
+ }
+
+ public void 進行描画する( デバイスリソース dr, SharpDX.RectangleF 描画先矩形dpx, float 不透明度0to1 = 1.0f )
+ {
+ // Direct2D の行列は、設計単位じゃなく物理単位で計算するので注意。
+ var 変換行列2Dpx =
+ dr.拡大行列DPXtoPX // スケーリング(1) DPX → PX
+ * SharpDX.Matrix3x2.Scaling( 描画先矩形dpx.Width / this.サイズdpx.Width, 描画先矩形dpx.Height / this.サイズdpx.Height ) // スケーリング(2)
+ * SharpDX.Matrix3x2.Translation( 描画先矩形dpx.Left * dr.拡大率DPXtoPX横方向, 描画先矩形dpx.Top * dr.拡大率DPXtoPX縦方向 ); // 平行移動(物理単位)、
+
+ this.進行描画する( dr, 変換行列2Dpx, 不透明度0to1 );
+ }
+
+ public void 進行描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列, float 不透明度0to1 = 1.0f )
+ {
+ #region " 条件チェック。"
+ //----------------
+ if( null == this.デコードタスク || // 再生をまだ開始していないか、あるいはすでに再生を完了してデコードタスクを終了済みである。
+ null == this.SourceReaderEx || // 動画の準備に失敗した。
+ null == this.MediaType || // 同上
+ null == this.WicBitmap ) // 同上
+ {
+ return;
+ }
+ //----------------
+ #endregion
- this.再生状態 = 再生状態ID.未再生;
+ Action<FrameQueueItem> 次のフレームを表示する = ( frame ) => {
+ this._次のフレームを取り出す( out frame );
+ this.最後に表示したフレーム?.Dispose();
+ this.最後に表示したフレーム = frame;
+ this._D2DBitmapを描画する( dr, 変換行列, frame.D2DBitmap, 不透明度0to1 );
+ };
+ Action 前のフレームを表示する = () => {
+ this._D2DBitmapを描画する( dr, 変換行列, this.最後に表示したフレーム?.D2DBitmap, 不透明度0to1 );
+ };
- string 変数付きファイルパス = フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( this.動画ファイルパス ); // Log出力用。
+ var フレーム = (FrameQueueItem) null;
+ this._次のフレームを確認する( out フレーム );
+
+
+ if( null != フレーム ) // 次のフレームがある。
+ {
+ // (A) 次のフレームが前のフレームより過去 → ループしたので、タイマをリセットする。
+ if( ( null != this.最後に表示したフレーム ) &&
+ ( フレーム.表示時刻sec < this.最後に表示したフレーム.表示時刻sec ) )
+ {
+ this._再生タイマ.Value.リセットする( FDK.カウンタ.QPCTimer.秒をカウントに変換して返す( フレーム.表示時刻sec ) );
+ 次のフレームを表示する( フレーム );
+ }
+
+ // (B) 次のフレームの表示時刻に達した。
+ else if( フレーム.表示時刻sec <= this._再生タイマ.Value.現在のリアルタイムカウントsec )
+ {
+ 次のフレームを表示する( フレーム );
+ }
+
+ // (C) 次のフレームの表示時刻にはまだ達していない。
+ else
+ {
+ 前のフレームを表示する();
+ }
+ }
+
+ // (D) デコードが追い付いてない、またはループせず再生が終わっている。
+ else
+ {
+ // 何も表示しない → 真っ黒画像。デコードが追い付いてないなら点滅するだろう。
+ }
+ }
+
+ protected int キューのサイズ = 0;
+
+ protected class FrameQueueItem : IDisposable
+ {
+ public double 表示時刻sec = 0;
+ public SharpDX.Direct2D1.Bitmap D2DBitmap = null;
+
+ public void Dispose()
+ {
+ FDK.Utilities.解放する( ref this.D2DBitmap );
+ }
+ }
+ protected ConcurrentQueue<FrameQueueItem> フレームキュー = null;
+ protected bool ループ再生する = false;
+ protected System.Threading.Tasks.Task デコードタスク = null;
+ protected System.Threading.AutoResetEvent デコードタスク起動完了 = null;
+ protected System.Threading.ManualResetEvent キューが空いた = null;
+ protected System.Threading.AutoResetEvent デコードタスクを終了せよ = null;
+ protected SharpDX.MediaFoundation.SourceReaderEx SourceReaderEx = null;
+ protected SharpDX.MediaFoundation.MediaType MediaType = null;
+ protected SharpDX.WIC.Bitmap WicBitmap = null; // MediaFoundation は WICBitmap に出力する。
+ protected SharpDX.Direct2D1.DeviceContext1 デコードタスク用D2DDeviceContext参照 = null; // D2Dはスレッドセーフであること。
+ protected FrameQueueItem 最後に表示したフレーム = null;
+
+ protected override void On活性化( デバイスリソース dr )
+ {
+ this.デコードタスク = null; // タスクが起動していないときは null であることを保証する。
+ this.デコードタスク起動完了 = new System.Threading.AutoResetEvent( false );
+ this.キューが空いた = new System.Threading.ManualResetEvent( true );
+ this.デコードタスクを終了せよ = new System.Threading.AutoResetEvent( false );
+ this._再生タイマ.Value = new カウンタ.QPCTimer();
+ }
+
+ protected override void On非活性化( デバイスリソース dr )
+ {
+ this.キューが空いた.Close();
+ this.デコードタスクを終了せよ.Close();
+ }
+
+ protected override void Onデバイス依存リソースの作成( デバイスリソース dr )
+ {
+ FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
try
{
- this.動画の生成に成功した = false;
+ this.デコードタスク用D2DDeviceContext参照 = dr.D2DContext1;
+ this.フレームキュー = new ConcurrentQueue<FrameQueueItem>();
+ this.最後に表示したフレーム = null;
- #region " デコーダを生成する。"
- //----------------
- this.デコーダ = new 動画デコーダ( dr, this.動画ファイルパス, this.ループ再生する, this.キューのサイズ );
- this.デコーダ.現在の再生時刻100ns = () => {
- return this.タイマ.Value.現在のリアルタイムカウント100ns単位;
- };
- this.デコーダ.デコードキャッシング();
- //----------------
+ // 動画ファイルから、SourceReaderEx, MediaType, WicBitmap を生成する。
+
+ string 変数付きファイルパス = フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( 動画ファイルパス ); // Log出力用。
+
+ #region " 動画ファイルパスの有効性を確認する。"
+ //-----------------
+ if( string.IsNullOrEmpty( 動画ファイルパス ) )
+ {
+ Log.ERROR( $"動画ファイルパスが null または空文字列です。[{変数付きファイルパス}]" );
+ return;
+ }
+ if( false == System.IO.File.Exists( 動画ファイルパス ) )
+ {
+ Log.ERROR( $"動画ファイルが存在しません。[{変数付きファイルパス}]" );
+ return;
+ }
+ //-----------------
#endregion
+ #region " SourceReaderEx を生成する。"
+ //-----------------
+ try
+ {
+ using( var 属性 = new SharpDX.MediaFoundation.MediaAttributes() )
+ {
+ // DXVAに対応しているGPUの場合には、それをデコードに利用するよう指定する。
+ 属性.Set<SharpDX.ComObject>( SharpDX.MediaFoundation.SourceReaderAttributeKeys.D3DManager, dr.DXGIDeviceManager );
+
+ // 追加のビデオプロセッシングを有効にする。
+ 属性.Set<bool>( SharpDX.MediaFoundation.SourceReaderAttributeKeys.EnableAdvancedVideoProcessing, true ); // 真偽値が bool だったり
+
+ // 追加のビデオプロセッシングを有効にしたら、こちらは無効に。
+ 属性.Set<int>( SharpDX.MediaFoundation.SinkWriterAttributeKeys.ReadwriteDisableConverters, 0 ); // int だったり
- this.動画の生成に成功した = true;
- Log.Info( $"{Utilities.現在のメソッド名}: 動画を生成しました。[{変数付きファイルパス}]" );
+ // 属性を使って、SourceReaderEx を生成。
+ using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( 動画ファイルパス, 属性 ) ) // パスは URI 扱い
+ {
+ this.SourceReaderEx = sourceReader.QueryInterface<SharpDX.MediaFoundation.SourceReaderEx>();
+ }
+ }
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"SourceReaderEx の作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " 最初のビデオストリームを選択し、その他のすべてのストリームを非選択にする。"
+ //-----------------
+ try
+ {
+ this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
+ this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, true );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"ストリームの選択に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " 部分 MediaType を作成し、SourceReaderEx に登録する。"
+ //-----------------
+ try
+ {
+ using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
+ {
+ // フォーマットは ARGB32 で固定とする。(SourceReaderEx を使わない場合、H264 では ARGB32 が選べないので注意。)
+ mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Video );
+ mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.VideoFormatGuids.Argb32 );
- // 準備完了。以降は、サンプルを読み込みたいときに、適宜 ReadSample() する。
- // コールバックを使っていない(非同期である)ので、IMFSourceReader::ReadSample() はサンプルが用意できるまでブロックすることに注意。
+ // 部分メディアタイプを SourceReaderEx にセットする。SourceReaderEx は、必要なデコーダをロードするだろう。
+ this.SourceReaderEx.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, mediaType );
+ }
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"MediaType (Video, ARGB32) の設定または必要なデコーダの読み込みに失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " ビデオストリームが選択されていることを再度保証する。"
+ //-----------------
+ try
+ {
+ this.SourceReaderEx.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream, true );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"最初のビデオストリームの選択に失敗しました(MediaType 設定後)。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " 完全 MediaType と動画の情報を取得する。"
+ //-----------------
+ try
+ {
+ this.MediaType = this.SourceReaderEx.GetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"完全メディアタイプの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+
+ // フレームサイズを取得する。
+ try
+ {
+ // 動画の途中でのサイズ変更には対応しない。
+ var packedFrameSize = this.MediaType.Get<long>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.FrameSize );
+ this.サイズdpx = new SharpDX.Size2F( ( packedFrameSize >> 32 ) & 0xFFFFFFFF, ( packedFrameSize ) & 0xFFFFFFFF );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"フレームサイズの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+
+ // 動画の長さを取得する。
+ try
+ {
+ this.長さsec = this.SourceReaderEx.GetPresentationAttribute(
+ SharpDX.MediaFoundation.SourceReaderIndex.MediaSource,
+ SharpDX.MediaFoundation.PresentationDescriptionAttributeKeys.Duration
+ ) / ( 1000.0 * 1000.0 * 10.0 );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"動画の長さの取得に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " 描画先となる WicBitmap を作成する。"
+ //-----------------
+ try
+ {
+ this.WicBitmap = new SharpDX.WIC.Bitmap(
+ dr.WicImagingFactory2,
+ (int) this.サイズdpx.Width,
+ (int) this.サイズdpx.Height,
+ SharpDX.WIC.PixelFormat.Format32bppBGR,
+ SharpDX.WIC.BitmapCreateCacheOption.CacheOnLoad );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"描画先となる WICビットマップの作成に失敗しました。(0x{e.HResult:x8})[{変数付きファイルパス}]" );
+ throw;
+ }
+ //-----------------
+ #endregion
+
+ this._ストックする();
}
- catch
+ finally
{
- this.全リソースを解放する();
- //this.活性化していない = true; --> 失敗しても、一応活性化は成功とする。(進行描画はしない。)
+ FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
}
}
+
protected override void Onデバイス依存リソースの解放( デバイスリソース dr )
{
- // todo: デバイスがリサイズされると動画もリセットされてしまう。非活性化とコードを分離すること(課題)。
+ FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
+
+ #region " デコードタスクが起動していたら、終了する。"
+ //----------------
+ this.デコードタスクを終了せよ.Set();
+
+ if( !this.デコードタスク.Wait( 2000 ) )
+ FDK.Log.WARNING( "デコードタスクの終了待ちがタイムアウトしました。" );
- this.全リソースを解放する();
+ this.デコードタスク = null;
+ //----------------
+ #endregion
- Log.Info( $"{Utilities.現在のメソッド名}: 動画を解放しました。[{フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( this.動画ファイルパス )}]" );
+ FDK.Utilities.解放する( ref this.最後に表示したフレーム );
+ FDK.Utilities.解放する( ref this.WicBitmap );
+ FDK.Utilities.解放する( ref this.MediaType );
+ FDK.Utilities.解放する( ref this.SourceReaderEx );
+
+ this._キューをクリアする();
+ this.フレームキュー = null;
+ this.デコードタスク用D2DDeviceContext参照 = null;
+
+ FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
}
- public void 進行描画する( デバイスリソース dr, SharpDX.RectangleF 描画先矩形dpx, float 不透明度0to1 = 1.0f, bool ループ再生する = false )
+
+ private void _キューをクリアする()
{
- // Direct2D の行列は、設計単位じゃなく物理単位で計算するので注意。
- var 変換行列2Dpx =
- dr.拡大行列DPXtoPX // スケーリング(1) DPX → PX
- * SharpDX.Matrix3x2.Scaling( 描画先矩形dpx.Width / this.デコーダ.サイズdpx.Width, 描画先矩形dpx.Height / this.デコーダ.サイズdpx.Height ) // スケーリング(2)
- * SharpDX.Matrix3x2.Translation( 描画先矩形dpx.Left * dr.拡大率DPXtoPX横方向, 描画先矩形dpx.Top * dr.拡大率DPXtoPX縦方向 ); // 平行移動(物理単位)、
+ while( 0 < this.フレームキュー.Count )
+ {
+ var item = (FrameQueueItem) null;
+ if( this.フレームキュー.TryDequeue( out item ) )
+ item.Dispose();
+ }
- // 描画する。
- this.進行描画する( dr, 変換行列2Dpx );
+ this.キューが空いた?.Set();
}
- public void 進行描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列 )
+
+ private void _次のフレームを確認する( out FrameQueueItem フレーム )
{
- Debug.Assert( this.活性化している );
+ if( ( 0 == this.フレームキュー.Count ) ||
+ ( false == this.フレームキュー.TryPeek( out フレーム ) ) ) // キューから取り出さない
+ {
+ フレーム = null; // キューが空だったか、Peek が一歩遅かった?(ないはずだが
+ }
+ }
+
+ private void _次のフレームを取り出す( out FrameQueueItem フレーム )
+ {
+ if( ( 0 == this.フレームキュー.Count ) ||
+ ( false == this.フレームキュー.TryDequeue( out フレーム ) ) ) // キューから取り出す
+ {
+ フレーム = null; // キューが空だったか、Peek が一歩遅かった?(ないはずだが
+ }
+ else
+ {
+ this.キューが空いた.Set();
+ }
+ }
- // 活性化でエラーが発生している場合は、何もしない(何も描画しない)。
- if( false == this.動画の生成に成功した )
+ private void _D2DBitmapを描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列2Dpx, SharpDX.Direct2D1.Bitmap D2DBitmap, float 不透明度 )
+ {
+ if( null == D2DBitmap )
return;
- // 以下、再生状態とデコーダ状態に応じて処理分岐。
- switch( this.再生状態 )
- {
- case 再生状態ID.未再生:
- // (A) 未再生なら、デコードを開始する。(タイマはまだ開始しない。)
- this.再生状態 = 再生状態ID.再生中;
- this.デコーダ.デコードを開始する();
- this.タイマ.Value.リセットする();
- break;
+ FDK.Utilities.D2DBatchDraw( dr.D2DContext1, () => {
+ dr.D2DContext1.Transform = 変換行列2Dpx;
+ dr.D2DContext1.PrimitiveBlend = ( this.加算合成 ) ? SharpDX.Direct2D1.PrimitiveBlend.Add : SharpDX.Direct2D1.PrimitiveBlend.SourceOver;
+ dr.D2DContext1.DrawBitmap( D2DBitmap, 不透明度, SharpDX.Direct2D1.InterpolationMode.Linear );
+ } );
+ }
+
+ // 以下、デコードタスク用。
+
+ private FDK.同期.RWLock<FDK.カウンタ.QPCTimer> _再生タイマ = new 同期.RWLock<カウンタ.QPCTimer>();
+
+ private void _デコードタスクエントリ( object obj再生開始位置sec )
+ {
+ FDK.Log.Info( "デコードタスクを起動しました。" );
+
+ var 再生開始位置sec = (double) obj再生開始位置sec;
+
+ if( 0.0 < 再生開始位置sec )
+ this._再生位置までストリームを進める( 再生開始位置sec );
+
+ const int EVID_キューが空いた = 0;
+ const int EVID_デコードタスクを終了せよ = 1;
+ var events = new System.Threading.WaitHandle[ 2 ];
+ events[ EVID_キューが空いた ] = this.キューが空いた;
+ events[ EVID_デコードタスクを終了せよ ] = this.デコードタスクを終了せよ;
- case 再生状態ID.再生終了済み:
- // (B) 再生が終了している場合は、何もしない(何も描画しない)。
- break;
+ this.デコードタスク起動完了.Set();
- case 再生状態ID.再生中:
- // (C) 再生中なら、D2Dビットマップを描画する。
+ this._再生タイマ.Value.リセットする( FDK.カウンタ.QPCTimer.秒をカウントに変換して返す( 再生開始位置sec ) );
+
+ while( System.Threading.WaitHandle.WaitAny( events ) == EVID_キューが空いた )
+ {
+ // キューが空いてるので、サンプルを1つデコードする。
+ if( this._サンプルをひとつデコードしてフレームをキューへ格納する() )
+ {
+ // キューがいっぱいになったら、空くまで待つ。
+ if( キューのサイズ == this.フレームキュー.Count )
{
- var dummy = (SharpDX.Direct2D1.Bitmap) null;
- var 再生時刻sec = 0.0;
- if( this.デコーダ.次のビットマップの有無を確認する( out dummy, out 再生時刻sec ) &&
- ( this.タイマ.Value.現在のリアルタイムカウント秒単位 >= 再生時刻sec + this.デコーダ.ループした際の先頭時刻sec ) )
- {
- // (C-a) 確認できて、かつ再生時刻になった場合 → サンプルをD2Dビットマップに転写し、描画する。
- this.前回表示したD2Dビットマップ?.Dispose();
- this.デコーダ.キューからビットマップを取り出す( out this.前回表示したD2Dビットマップ, out 再生時刻sec ); // 正式に取り出す。
- this.D2Dビットマップを描画する( dr, 変換行列 );
- }
- else
- {
- // (C-b) 確認できなかった(キューが空だった)か、または再生時刻がまだの場合 → 現在のD2Dビットマップを再度表示する。
- this.D2Dビットマップを描画する( dr, 変換行列 );
- break;
- }
+ this.キューが空いた.Reset(); // 次の while で空くのを待つ。
}
- break;
+ }
+ else
+ {
+ break; // エラーあるいはストリーム終了 → デコードタスクを終了する。
+ }
}
+
+ this.デコードタスク = null;
+
+ FDK.Log.Info( "デコードタスクを終了しました。" );
}
- protected 動画デコーダ デコーダ = null;
+ /// <returns>
+ /// 格納できたかスキップした場合は true、エラーあるいはストリーム終了なら false。
+ /// </returns>
+ private bool _サンプルをひとつデコードしてフレームをキューへ格納する()
+ {
+ var sample = (SharpDX.MediaFoundation.Sample) null;
+ var bitmap = (SharpDX.Direct2D1.Bitmap) null;
- private string 動画ファイルパス = "";
- private bool 動画の生成に成功した = false; // 活性化時に成功の成否を設定。
- private SharpDX.Direct2D1.Bitmap 前回表示したD2Dビットマップ = null;
+ try
+ {
+ long サンプルの表示時刻100ns = 0;
+
+ #region " ソースリーダーから次のサンプルをひとつデコードする。"
+ //-----------------
+ var ストリームフラグ = SharpDX.MediaFoundation.SourceReaderFlags.None;
+ int 実ストリーム番号 = 0;
- private FDK.同期.RWLock<FDK.カウンタ.QPCTimer> タイマ = new RWLock<カウンタ.QPCTimer>( new カウンタ.QPCTimer() );
+ sample = this.SourceReaderEx.ReadSample(
+ SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream,
+ SharpDX.MediaFoundation.SourceReaderControlFlags.None,
+ out 実ストリーム番号,
+ out ストリームフラグ,
+ out サンプルの表示時刻100ns );
- private void D2Dビットマップを描画する( デバイスリソース dr, SharpDX.Matrix3x2 変換行列2Dpx )
+ if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Endofstream ) ) // BOX化コストとか気にしない
+ {
+ #region " ストリーム終了 "
+ //----------------
+ if( this.ループ再生する )
+ {
+ FDK.Log.Info( "動画をループ再生します。" );
+ this.SourceReaderEx.SetCurrentPosition( 0 );
+ return this._サンプルをひとつデコードしてフレームをキューへ格納する();
+ }
+ else
+ {
+ FDK.Log.Info( "動画の再生を終了します。" );
+ return false;
+ }
+ //----------------
+ #endregion
+ }
+ else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Error ) )
+ {
+ #region " エラー。"
+ //----------------
+ throw new SharpDX.SharpDXException( SharpDX.Result.Fail );
+ //----------------
+ #endregion
+ }
+ //else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Newstream ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Nativemediatypechanged ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Currentmediatypechanged ) )
+ //{
+ // 動画の途中でのサイズ変更には対応しない。
+ //}
+ //else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.StreamTick ) )
+ //{
+ //}
+ //else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.AllEffectsremoved ) )
+ //{
+ //}
+ //---------------------------------------------------
+ #endregion
+
+ //if( サンプルの表示時刻100ns < this.再生タイマ.Value.現在のリアルタイムカウント100ns単位 )
+ // return true; // もう表示時刻は通り過ぎてるのでスキップする。---> この実装だとループのし始めには常に true になってしまうので却下。
+
+ this._サンプルをビットマップに転送する( sample, out bitmap );
+
+ this.フレームキュー.Enqueue( new FrameQueueItem() {
+ D2DBitmap = bitmap,
+ 表示時刻sec = サンプルの表示時刻100ns / ( 10.0 * 1000.0 * 1000.0 ),
+ } );
+
+ bitmap = null; // キューに格納したので、ここでは Dispose しない。
+ }
+ catch( Exception e )
+ {
+ FDK.Log.Info( $"エラーが発生したので、動画の再生を終了します。[{e.Message}]" );
+ return false;
+ }
+ finally
+ {
+ bitmap?.Dispose();
+ sample?.Dispose();
+ }
+
+ return true;
+ }
+
+ private void _再生位置までストリームを進める( double 再生位置sec )
{
- if( null == this.前回表示したD2Dビットマップ )
+ #region " ストリームがシーク不可なら何もしない。"
+ //----------------
+ var flags = this.SourceReaderEx.GetPresentationAttribute(
+ SharpDX.MediaFoundation.SourceReaderIndex.MediaSource,
+ SharpDX.MediaFoundation.SourceReaderAttributeKeys.MediaSourceCharacteristics );
+ if( ( flags & (int) SharpDX.MediaFoundation.MediaSourceCharacteristics.CanSeek ) == 0 )
+ {
+ FDK.Log.WARNING( "この動画はシークできないようです。" );
return;
+ }
+ //----------------
+ #endregion
- Utilities.D2DBatchDraw( dr.D2DContext1, () => {
+ // ストリームの再生位置を移動する。
- // 変換行列とブレンドモードを設定する。
- dr.D2DContext1.Transform = 変換行列2Dpx;
- dr.D2DContext1.PrimitiveBlend = ( this.加算合成 ) ? SharpDX.Direct2D1.PrimitiveBlend.Add : SharpDX.Direct2D1.PrimitiveBlend.SourceOver;
+ this._キューをクリアする();
- // D2Dビットマップを描画する。
- dr.D2DContext1.DrawBitmap( this.前回表示したD2Dビットマップ, this.不透明度0to1, SharpDX.Direct2D1.InterpolationMode.Linear );
- } );
+ long 再生位置100ns = (long) ( 再生位置sec * 1000.0 * 1000.0 * 10.0 );
+ this.SourceReaderEx.SetCurrentPosition( 再生位置100ns );
+
+ // キーフレームから再生位置100nsまで ReadSample する。
+
+ var ストリームフラグ = SharpDX.MediaFoundation.SourceReaderFlags.None;
+ int 実ストリーム番号 = 0;
+ long サンプルの表示時刻100ns = 0;
+
+ while( サンプルの表示時刻100ns < 再生位置100ns )
+ {
+ // サンプルを取得。
+ var sample = this.SourceReaderEx.ReadSample(
+ SharpDX.MediaFoundation.SourceReaderIndex.FirstVideoStream,
+ SharpDX.MediaFoundation.SourceReaderControlFlags.None,
+ out 実ストリーム番号,
+ out ストリームフラグ,
+ out サンプルの表示時刻100ns );
+
+ // 即解放。
+ sample?.Dispose();
+
+ if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Endofstream ) )
+ {
+ // ストリーム終了。
+ return;
+ }
+ else if( ストリームフラグ.HasFlag( SharpDX.MediaFoundation.SourceReaderFlags.Error ) )
+ {
+ // エラー発生。
+ FDK.Log.ERROR( $"動画の再生位置を移動中に、エラーが発生しました。" );
+ return;
+ }
+ }
+
+ this._ストックする();
+
+ FDK.Log.Info( $"動画の再生位置を {再生位置sec}sec へ移動しました。" );
+ }
+
+ private void _ストックする()
+ {
+ for( int i = 0; i < this.キューのサイズ; i++ )
+ this._サンプルをひとつデコードしてフレームをキューへ格納する();
+
+ this.キューが空いた.Reset(); // 埋まった
+ }
+
+ private unsafe void _サンプルをビットマップに転送する( SharpDX.MediaFoundation.Sample Sample, out SharpDX.Direct2D1.Bitmap D2DBitmap )
+ {
+ var buffer = (SharpDX.MediaFoundation.MediaBuffer) null;
+ var buffer2d2 = (SharpDX.MediaFoundation.Buffer2D2) null;
+ try
+ {
+ #region " サンプルからサンプルバッファ (MediaBuffer) を取得する。"
+ //-----------------
+ try
+ {
+ buffer = Sample.ConvertToContiguousBuffer();
+ }
+ catch( Exception e )
+ {
+ Log.ERROR( $"サンプルバッファの取得に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " サンプルバッファを Buffer2D2 にキャストする。"
+ //-----------------
+ try
+ {
+ buffer2d2 = buffer.QueryInterface<SharpDX.MediaFoundation.Buffer2D2>();
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファから Buffer2D2 へのキャストに失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " サンプルバッファをロックする。"
+ //-----------------
+ byte[] scanLine0_bp = new byte[ 8 ]; // 「生ポインタ」が格納される。32bitなら[0~3]、64bitなら[0~7]が有効。(CPUではなく.NETに依存)
+ int pitch = 0;
+ byte[] bufferStart_bp = new byte[ 8 ]; // 「生ポインタ」が格納される。こちらは使わない。
+ int bufferLength;
+ try
+ {
+ buffer2d2.Lock2DSize(
+ SharpDX.MediaFoundation.Buffer2DLockFlags.Read,
+ scanLine0_bp,
+ out pitch,
+ bufferStart_bp,
+ out bufferLength );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファのロックに失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ try
+ {
+ #region " サンプルバッファのネイティブ先頭アドレスを取得する。"
+ //-----------------
+ byte* scanLine0 = null;
+ try
+ {
+ scanLine0 = (byte*) this._生ポインタを格納したbyte配列からポインタを取得して返す( scanLine0_bp );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"サンプルバッファのアドレスの取得に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //-----------------
+ #endregion
+ #region " サンプルから WicBitmap へ画像をコピーする。"
+ //-----------------
+ try
+ {
+ // 描画先である WICBitmap をロックする。
+ using( var bitmapLock = this.WicBitmap.Lock(
+ new SharpDX.Rectangle( 0, 0, this.WicBitmap.Size.Width, this.WicBitmap.Size.Height ),
+ SharpDX.WIC.BitmapLockFlags.Write ) )
+ {
+ // サンプルバッファからWICビットマップへ、ARGB32 を G8B8R8X8 に変換しながらコピーする。
+ int bitmapStride = bitmapLock.Stride;
+ byte* src = scanLine0;
+ byte* dest = (byte*) bitmapLock.Data.DataPointer.ToPointer();
+
+ if( pitch != bitmapStride )
+ {
+ for( int y = 0; y < this.サイズdpx.Height; y++ )
+ {
+ // ARGB32 to G8B8R8X8 ではデータ変換が不要なので、一行を一気にコピー。
+ 動画.CopyMemory( dest, src, (int) this.サイズdpx.Width * 4 ); // ARGB=4バイト。
+ src += pitch;
+ dest += bitmapStride; // bitmapStride は byte 単位
+ }
+ }
+ else
+ {
+ // ARGB32 to G8B8R8X8 ではデータ変換が不要、かつ pitch と bitmapStride が等しいので、全行を一括してコピー。
+ 動画.CopyMemory( dest, src, (int) ( this.サイズdpx.Width * this.サイズdpx.Height * 4 ) ); // ARGB=4バイト。
+ }
+ }
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"WicBitmap の Lock に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ catch( Exception e )
+ {
+ Log.ERROR( $"サンプルバッファから WIC ビットマップへのデータの転送に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //----------------
+ #endregion
+ #region " WicBitmap から D2DBitmap を生成する。"
+ //----------------
+ try
+ {
+ D2DBitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap( this.デコードタスク用D2DDeviceContext参照, this.WicBitmap );
+ }
+ catch( SharpDX.SharpDXException e )
+ {
+ Log.ERROR( $"D2Dビットマップの作成に失敗しました。(0x{e.HResult:x8})" );
+ throw;
+ }
+ //----------------
+ #endregion
+ }
+ finally
+ {
+ #region " サンプルバッファのロックを解除する。"
+ //-----------------
+ buffer2d2.Unlock2D();
+ //-----------------
+ #endregion
+ }
+ }
+ finally
+ {
+ FDK.Utilities.解放する( ref buffer2d2 );
+ FDK.Utilities.解放する( ref buffer );
+ }
}
- private void 全リソースを解放する()
+
+ private unsafe void* _生ポインタを格納したbyte配列からポインタを取得して返す( byte[] 生ポインタ )
{
- FDK.Utilities.解放する( ref this.前回表示したD2Dビットマップ );
+ if( ( 4 == IntPtr.Size ) && System.BitConverter.IsLittleEndian )
+ {
+ #region " (A) 32bit, リトルエンディアン "
+ //----------------
+ int 生アドレス32bit = 0;
+
+ for( int i = 0; i < 4; i++ )
+ 生アドレス32bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス32bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 8 == IntPtr.Size ) && System.BitConverter.IsLittleEndian )
+ {
+ #region " (B) 64bit, リトルエンディアン "
+ //----------------
+ long 生アドレス64bit = 0;
+ for( int i = 0; i < 8; i++ )
+ 生アドレス64bit += ( (int) 生ポインタ[ i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス64bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 4 == IntPtr.Size ) && ( false == System.BitConverter.IsLittleEndian ) )
+ {
+ #region " (C) 32bit, ビッグエンディアン "
+ //----------------
+ int 生アドレス32bit = 0;
+ for( int i = 0; i < 4; i++ )
+ 生アドレス32bit += ( (int) 生ポインタ[ 4 - i ] ) << ( i * 8 );
- this.デコーダ.Dispose();
+ return new IntPtr( 生アドレス32bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+ else if( ( 8 == IntPtr.Size ) && ( false == System.BitConverter.IsLittleEndian ) )
+ {
+ #region " (D) 64bit, ビッグエンディアン "
+ //----------------
+ long 生アドレス64bit = 0;
+ for( int i = 0; i < 8; i++ )
+ 生アドレス64bit += ( (int) 生ポインタ[ 8 - i ] ) << ( i * 8 );
+
+ return new IntPtr( 生アドレス64bit ).ToPointer();
+ //----------------
+ #endregion
+ }
+
+ throw new SharpDX.SharpDXException( SharpDX.Result.NotImplemented, "この .NET アーキテクチャには対応していません。" );
}
+
+ #region " Win32 API "
+ //-----------------
+ [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
+ private static extern unsafe void CopyMemory( void* dst, void* src, int size );
+ //-----------------
+ #endregion
}
}