結果だけでなく過程も見てください

日々の奮闘を綴る日記です。

C# DirectShowで動画を再生する方法

当方Windows 10でVisual Studio Community 2019を使用しています。

参照の追加

C#のプロジェクトを右クリック→追加→COM参照を選択します。
f:id:taiyakisun:20210617180605p:plain

[ActiveMovie control type library]にチェックを入れ、OKを押します。
f:id:taiyakisun:20210617180821p:plain

もしActiveMovie control type libraryが存在しない場合などは次の方法でdllを追加してください。

[参照]→[参照]ボタン→C:\Windows\System32\quartz.dllを選択して、③が追加されたことを確認します。
f:id:taiyakisun:20210617181041p:plain

必要なパッケージ

using QuartzTypeLib;

コード

C#でフォームアプリケーションでプロジェクトを作成します。

GUI

デザイナーで以下のコンポーネントを追加します。
・Panel (名前:panel1)
・Button (名前:button1)
ここでは、ボタンを押すとpanelに動画が再生されるようにコードを書きます。

C#のコード

private FilgraphManager objFilterGraph = null;
private IBasicAudio objBasicAudio = null;
private IVideoWindow objVideoWindow = null;
private IMediaEvent objMediaEvent = null;
private IMediaEventEx objMediaEventEx = null;
private IMediaPosition objMediaPosition = null;
private IMediaControl objMediaControl = null;

private const int WM_APP = 0x8000;
private const int WM_GRAPHNOTIFY = WM_APP + 1;
private const int EC_COMPLETE = 0x01;
private const int WS_CHILD = 0x40000000;
private const int WS_CLIPCHILDREN = 0x2000000;

enum MediaStatus { None, Stopped, Paused, Running };
private MediaStatus currentStatus = MediaStatus.None;

private void button1_Click(object sender, EventArgs e)
{
    objFilterGraph = new FilgraphManager();

    // ここに固定で再生したい動画ファイルのフルパスを書く
    objFilterGraph.RenderFile(@"C:\temp\midori.avi");

    objBasicAudio = objFilterGraph as IBasicAudio;
    objBasicAudio.Volume = -1000;    // 音量を少し下げておく

    try
    {
        objVideoWindow = objFilterGraph as IVideoWindow;
        objVideoWindow.Owner = (int)panel1.Handle;
        objVideoWindow.WindowStyle = WS_CHILD | WS_CLIPCHILDREN;        
        objVideoWindow.SetWindowPosition(panel1.ClientRectangle.Left,
                                            panel1.ClientRectangle.Top,
                                            panel1.ClientRectangle.Width,
                                            panel1.ClientRectangle.Height);
    }
    catch (Exception ex)
    {
        objVideoWindow = null;
    }

    objMediaEvent = objFilterGraph as IMediaEvent;

    // メッセージの傍受 DirectShowを親ウィンドウに送信します
    objMediaEventEx = objFilterGraph as IMediaEventEx;
    objMediaEventEx.SetNotifyWindow((int)this.Handle, WM_GRAPHNOTIFY, 0);

    // ビデオまたはオーディオトラックを開始および停止する
    objMediaControl = objFilterGraph as IMediaControl;

    // 再生開始
    objMediaControl.Run();
    currentStatus = MediaStatus.Running;
}

フォーム終了時などに実行する解放処理は以下の通りです。

if (objMediaControl != null)
{
    objMediaControl.Stop();
}

currentStatus = MediaStatus.Stopped;

if (objMediaEventEx != null)
{
    objMediaEventEx.SetNotifyWindow(0, 0, 0);
}

if (objVideoWindow != null)
{
    objVideoWindow.Visible = 0;
    objVideoWindow.Owner = 0;
}

if (objMediaControl != null) objMediaControl = null;
if (objMediaPosition != null) objMediaPosition = null;
if (objMediaEventEx != null) objMediaEventEx = null;
if (objMediaEvent != null) objMediaEvent = null;
if (objVideoWindow != null) objVideoWindow = null;
if (objBasicAudio != null) objBasicAudio = null;
if (objFilterGraph != null) objFilterGraph = null;

一時停止する場合は以下のコードを実行します。

objMediaControl.Pause();

一時停止を再開する場合は以下のコードを実行します。

objMediaControl.Run();

動画を末尾まで再生したことを検知して止めるためのコードは以下の通りです。
メッセージプロシージャをオーバーライドして特定のメッセージだけ処理を追加する形にしています。

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_GRAPHNOTIFY)
    {
        int lEventCode;
        int lParam1, lParam2;

        while (true)
        {
            try
            {
                objMediaEventEx.GetEvent(out lEventCode,
                    out lParam1,
                    out lParam2,
                    0);

                objMediaEventEx.FreeEventParams(lEventCode, lParam1, lParam2);

                if (lEventCode == EC_COMPLETE)
                {
                    objMediaControl.Stop();
                    objMediaPosition.CurrentPosition = 0;
                    currentStatus = MediaStatus.Stopped;
                }
            }
            catch (Exception)
            {
                break;
            }
        }
    }

    base.WndProc(ref m);
}

トラックバー(シークバー)の設置

どうせなのでトラックバーも設置してしまいましょう。
トラックバー設置により、再生位置をクリックしたりドラッグしたりして、好きな位置に飛べるようにします。

GUIの設置

デザイナ画面でTrackbarを設置します。変数名はデフォルトのtrackBar1とします。

コード

button1_Click関数で再生を開始するときにトラックバーの設定コードを追加します。
まずトラックバーの取りうる値をMinimumとMaximumに設定します。
ここでは動画の秒数をMaximumに設定します。

private void button8_Click(object sender, EventArgs e)
{
    :
    // 再生開始
    objMediaControl.Run();
    currentStatus = MediaStatus.Running;

    // トラックバーの設定
    this.trackBar1.Minimum = 0;
    this.trackBar1.Maximum = (int)objMediaPosition.Duration;
    this.trackBar1.Value = 0;
    this.trackBar1.TickFrequency = 1;

再生中にトラックバーを更新する関数を作成します。

private void updateTrackBar()
{
    if (this.currentStatus == MediaStatus.Running && this.objMediaPosition != null)
    {
        this.trackBar1.Value = (int)objMediaPosition.CurrentPosition;
    }
    else
    {
        this.trackBar1.Minimum = 0;
        this.trackBar1.Maximum = 0;
        this.trackBar1.Value = 0;
    }
}

上述したトラックバー更新関数をコールするコードを追加します。
まずは動画の再生が完了したときにトラックバーを0に戻すため、WndProc関数にコードを追加します。

protected override void WndProc(ref Message m)
{
    :
    if (lEventCode == EC_COMPLETE)
    {
        :
        updateTrackBar();
    }
}

次に、1秒に1度トラックバーを更新するためにタイマーを設定し、そこからupdateTrackBar関数を呼び出します。

private void Form1_Load(object sender, EventArgs e)
{
    :
    // トラックバーの更新
    Timer trackbar_timer = new Timer();
    trackbar_timer.Interval = 1000;
    trackbar_timer.Enabled = true;
    trackbar_timer.Start();
    trackbar_timer.Tick += (sender, e) =>
    {
        if (this.currentStatus == MediaStatus.Running)
        {
            this.updateTrackBar();
        }
    };

続いて、マウスクリックとマウスの移動でシークバーを更新するようにします。
MouseDownとMouseMoveイベントハンドラを作成してください。

MouseDownについては、クリックされた場所とトラックバーの幅から比率を計算して、その場所に再生位置を設定するだけです。

private void trackBar1_MouseDown(object sender, MouseEventArgs e)
{
    if (this.currentStatus == MediaStatus.Running && this.objMediaPosition != null)
    {
        if (e.Button == MouseButtons.Left)
        {
            double pos = ((double)e.X / this.trackBar1.Width) * (this.trackBar1.Maximum - this.trackBar1.Minimum);

            // トラックバー
            this.trackBar1.Value = (int)(pos);

            // 動画再生位置
            this.objMediaPosition.CurrentPosition = pos;
        }
    }
}

MouseMoveについては、基本的にMouseDownと同じですが、範囲外にマウスをドラッグされた場合の対応コードを追加したものとなります。

private void trackBar1_MouseMove(object sender, MouseEventArgs e)
{
    if (this.currentStatus == MediaStatus.Running && this.objMediaPosition != null)
    {
        if (e.Button == MouseButtons.Left)
        {
            double pos = ((double)e.X / this.trackBar1.Width) * (this.trackBar1.Maximum - this.trackBar1.Minimum);

            try
            {
                if ((int)(pos) >= this.trackBar1.Minimum && (int)pos <= this.trackBar1.Maximum)
                {
                    // トラックバー
                    this.trackBar1.Value = (int)(pos);

                    // 動画再生位置
                    this.objMediaPosition.CurrentPosition = pos;
                }
            }
            catch(ArgumentException)
            {
                // 位置を超えて設定してしまった場合は例外が飛ぶが無視する
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message, "error");
            }
        }
    }
}
プライバシーポリシー お問い合わせ