Landscape トップページ | < 前の日 2006-05-01 2006-05-03 次の日 2006-05-04 >

Landscape - エンジニアのメモ 2006-05-03

C# と DirectX で動画を再生する


* C# と DirectX で動画を再生する

この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [.net] [C#] [Windows]

2006-04-27 「C# でスクリーンセーバーを作る」の続き。C# から DirectX を使って MPEG2 の動画を再生する。

- DirectX SDK のダウンロード

動画再生部分は DirectX を使って作ることにしたので、DirectX SDK をダウンロードした。サイズが390.2MB もあるので、気長に。

DirectX SDK - (April 2006)
http://www.microsoft.com/downloads/details.aspx?FamilyId=7AB ...

ダウンロードしたファイルを実行。インストーラではいつも通り「次へ」を連打してインストール完了。

- Microsoft.DirectX.AudioVideoPlayback で動画を再生

動画再生部分は Microsoft.DirectX.AudioVideoPlayback を使って簡単に作れた。

string path = GetNextClipPath();
fileIndex++;
if (videoClip == null) {
    videoClip = new Video(path);
} else {
    videoClip.Ending -= this.ClipEnded;
    videoClip.Open(path);
}
videoClip.Ending += new System.EventHandler(this.ClipEnded);
videoClip.Owner = this;
videoClip.Fullscreen = false;
Bounds = new Rectangle(0, 20, 1280, 960);
videoClip.Play();

これをベースとしている Visual Studio 2005 のスクリーンセーバースタートキットに組み込めば、動画を再生するスクリーンセーバーのできあがり。とっても簡単だ。

プログラムに名前を付けよう。スタートキットがデフォルトで付ける ScreenSaver1.exe だと味気ないし、「画面のプロパティ」で選択するときも何か名前が表示されてほしいし。とくに思いつかなかったので、とりあえず SaveTheQueen (セーブザクイーン) にした。FF12 に出てきた剣の名前が元ねただ。FF9 にも出てきたかな? もともと 2006-04-20 の「FF12 のムービーをリッピング (ripping) して再生」でリップしたファイルを再生するスクリーンセーバーを作るのが目的だから、save という単語が入っていて、FF っぽい名前であればなんでも良かった。

- SaveTheQueen のソース

後の私も含めて、誰かの参考になるかもしれないので、メインフォームとオプションフォームのコードを全部載せておく。私の環境以外を考慮していないコードだし、エラー処理やオプションの処理も入ってない。たとえばビデオクリップのパスに対して FileExists() もしてない。

ソース一式とバイナリをパッケージしたものはそのうち作ろうかな。Suversion から 自動でチェックアウトしてビルドして ClickOnce としてパッケージするようにしよう。

ScreenSaverForm.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Microsoft.DirectX.AudioVideoPlayback;

namespace SaveTheQueen {
    partial class ScreenSaverForm : Form {

        private bool isActive = false;
        private Point mouseLocation;
        private Video videoClip;
        private DefaultTraceListener dtl;

        /// <summary>
        /// 再生するリスト上の現在位置。始点は0。
        /// </summary>
        private int fileIndex = 0;

        public ScreenSaverForm() {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            dtl = (DefaultTraceListener)Trace.Listeners["Default"];
            dtl.LogFileName = @"c:\trace.txt";

            InitializeComponent();
            SetupScreenSaver();
            PlayVideoClip();
        }

        private void SetupScreenSaver() {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            // ダブル バッファを使用して、表示パフォーマンスを改善します。
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

            // マウスをキャプチャします。
            this.Capture = true;

            // アプリケーションを全画面表示モードに設定して、マウスを表示しません。
            Cursor.Hide();
            // Bounds = Screen.PrimaryScreen.Bounds;
            ShowInTaskbar = false;
            DoubleBuffered = true;

            ResumeVideoClip();
        }

        /// <summary>
        /// 現在再生しているビデオクリップの情報を保存します。
        /// </summary>
        private void SaveVideoClip() {
            Properties.Settings.Default.LastClipIndex = fileIndex - 1;
            Properties.Settings.Default.LastClipPosision = videoClip.CurrentPosition;
            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// 前回終了時に再生していたビデオクリップの情報を復元します。
        /// </summary>
        private void ResumeVideoClip() {
            int lastClipIndex = Properties.Settings.Default.LastClipIndex;
            if (0 <= lastClipIndex && lastClipIndex < Properties.Settings.Default.videoClipPath.Length) {
                fileIndex = lastClipIndex;
            }
      }

        private void PlayVideoClip() {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name );

            string path = GetNextClipPath();
            fileIndex++;
            if (videoClip == null) {
                videoClip = new Video(path);
            } else {
                videoClip.Ending -= this.ClipEnded;
                videoClip.Open(path);
            }
            videoClip.Ending += new System.EventHandler(this.ClipEnded);
            videoClip.Owner = this;
            videoClip.Fullscreen = false;
            // WindowState = FormWindowState.Normal;
            Bounds = new Rectangle(0, 20, 1280, 960);
            videoClip.Play();
        }

        private void ClipEnded(object sender, System.EventArgs e) {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            PlayVideoClip();
        }

        private string GetNextClipPath() {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            if (Properties.Settings.Default.videoClipPath.Length == 0) {
                return "";
            }
            if (Properties.Settings.Default.videoClipPath.Length - 1 < fileIndex) {
                fileIndex = 0;
            }
            if (fileIndex < 0) {
                fileIndex = Properties.Settings.Default.videoClipPath.Length - 1;
            }

            string path = Properties.Settings.Default.videoClipPath[fileIndex];
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name + " " + path);

            return path;
        }

        private void ScreenSaverForm_MouseMove(object sender, MouseEventArgs e) {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            // IsActive および MouseLocation を、このイベントが最初に呼び出されるときにのみ設定します。
            if (!isActive) {
                mouseLocation = MousePosition;
                isActive = true;
            } else {
                // 最初の呼び出し以来マウスが著しく移動した場合、閉じます。
                if ((Math.Abs(MousePosition.X - mouseLocation.X) > 10) ||
                    (Math.Abs(MousePosition.Y - mouseLocation.Y) > 10)) {
                    Close();
                }
            }
        }

        private void ScreenSaverForm_KeyDown(object sender, KeyEventArgs e) {
            Trace.WriteLine(DateTime.Now.ToString() + System.Reflection.MethodBase.GetCurrentMethod().Name);

            if (e.KeyData == Keys.F) {
                // 次のクリップ
                PlayVideoClip();
            } else if (e.KeyData == Keys.B) {
                // 前のクリップ
                fileIndex -= 2;
                PlayVideoClip();
            } else if (e.KeyData == Keys.R) {
                // 現在再生中のクリップを頭からもう一度再生
                fileIndex--;
                PlayVideoClip();
            } else {
                Close();
            }
        }

        private void ScreenSaverForm_MouseDown(object sender, MouseEventArgs e) {
            Trace.WriteLine(DateTime.Now.ToString() + System.Reflection.MethodBase.GetCurrentMethod().Name);

            Close();
        }

        private void ScreenSaverForm_FormClosing(object sender, FormClosingEventArgs e) {
            Trace.WriteLine(DateTime.Now.ToString() + " " + System.Reflection.MethodBase.GetCurrentMethod().Name);

            if (videoClip != null) {
                SaveVideoClip();
                videoClip.Dispose();
                videoClip = null;
            }
        }
    }
}

ダブルバッファなどはベースとしたスクリーンセーバースタートキットに最初から入ってたもの。効果のほどは不明。コメントアウトしても問題なく再生できてたし。CPU 負荷や GPU の負荷が違うのかな。タスクマネージャで見てたけど、コメントアウトしても負荷が十分に低かったので違いがわからなかった。

OptionsForm.cs
using System;
using System.Configuration;
using System.Drawing;
using System.Windows.Forms;


namespace SaveTheQueen {
    partial class OptionsForm : Form {
        public OptionsForm() {
            InitializeComponent();

            // 現在の設定からテキスト ボックスを読み込みます。
            try {
                backgroundImageFolderTextBox.Lines = Properties.Settings.Default.videoClipPath;
            //  rssFeedTextBox.Text = Properties.Settings.Default.RssFeedUri;
            } catch {
                MessageBox.Show("スクリーン セーバーの設定での読み取り中に問題が発生しました。");
            }
        }

        // [適用] ボタンが最後に押されてから変更が行われた場合にのみ、
        // [適用] ボタンをアクティブな状態に更新します。
        private void UpdateApply() {
            /*
            if (Properties.Settings.Default.BackgroundImagePath != backgroundImageFolderTextBox.Text
                  || Properties.Settings.Default.RssFeedUri != rssFeedTextBox.Text)
                applyButton.Enabled = true;
            else
                applyButton.Enabled = false;
            */
        }

        // [適用] ボタンが最後に押されてからの、すべての変更を適用します。
        private void ApplyChanges() {
            Properties.Settings.Default.videoClipPath = backgroundImageFolderTextBox.Lines;
            Properties.Settings.Default.Save();
        }

        private void btnOK_Click(object sender, EventArgs e) {
            try {
                ApplyChanges();
            } catch (ConfigurationException) {
                MessageBox.Show("設定を保存できませんでした。スクリーン セーバーと同じディレクトリ内に .config ファイルがあることを確認してください。", "設定を保存できませんでした。", MessageBoxButtons.OK, MessageBoxIcon.Error);
            } finally {
                Close();
            }
        }

        private void btnCancel_Click(object sender, EventArgs e) {
            Close();
        }

        private void btnApply_Click(object sender, EventArgs e) {
            ApplyChanges();
            applyButton.Enabled = false;
        }

        // ユーザー指定された URI が有効な RSS フィードにポイントするかどうかを確認します。
        private void validateButton_Click(object sender, EventArgs e) {
            try {
                // RssFeed.FromUri(rssFeedTextBox.Text);
            } catch {
                MessageBox.Show("有効な RSS フィードではありません。", "有効な RSS フィードではありません。", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            MessageBox.Show("有効な RSS フィードです。", "有効な RSS フィードです。", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        private void browseButton_Click(object sender, EventArgs e) {
            // [ファイルを開く] ダイアログを開いて、イメージを選択します。

            DialogResult result = backgroundImageFolderBrowser.ShowDialog();
            if (result == DialogResult.OK) {
                backgroundImageFolderTextBox.Text = backgroundImageFolderBrowser.SelectedPath;
                UpdateApply();
            }
        }

        private void rssFeedTextBox_TextChanged(object sender, EventArgs e) {
            UpdateApply();
        }

        private void backgroundImageFolderTextBox_TextChanged(object sender, EventArgs e) {
            UpdateApply();
        }

        private void backgroundImageOpenFileDialog_FileOk(object sender, System.ComponentModel.CancelEventArgs e) {

        }

        private void backgroundImageFolderBrowser_HelpRequest(object sender, EventArgs e) {

        }

        private void button1_Click(object sender, EventArgs e) {
            DialogResult result = videClipOpenFileDialog.ShowDialog();
            if (result == DialogResult.OK) {
                backgroundImageFolderTextBox.Lines = videClipOpenFileDialog.FileNames;
                UpdateApply();
            }
        }

        private void rssGroupBox_Enter(object sender, EventArgs e) {

        }
    }
}

OptionsForm.cs は不要なコードを消してないので冗長。

C#でムービースクリーンセーバー
http://d.hatena.ne.jp/bellbind/20060428/1146200768
ムービースクリーンセーバーを作ったってはなしだけど、作るのに必要な情報は入って無いんで自分で調べて書いてみた。

  public SimpleScreenSaver() {
    this.FormBorderStyle = FormBorderStyle.None;
    this.WindowState = FormWindowState.Maximized;
    this.KeyDown += new KeyEventHandler(this.MyKeyDown);
    this.MouseDown += new MouseEventHandler(this.MyMouseDown);
    this.Load += new EventHandler(this.MyLoad);
    this.ClientSize = new Size(640, 480);
    this.player = new Video("movie.avi");
    this.player.Owner = this;
  }

(略)

cscでのビルドには/r:Microsoft.DirectX.AudioVideoPlayback.dllが必要だけど、これはc:\WINDOWS\Microsoft.NET\DirectX for Managed Code\*\以下にあるのでそれを使う。

私もとりえあず再生さえできれば良かったので AudioVideoPlayback を使いました。

あれ? もしかして DirectX の dll って最初からインストールされてたのかな? わざわざ SDK をダウンロードする必要ってなかった? まあ、スクリーンセーバーを作るためだけにわざわざ Visual Studio 2005 をインストールしてるわけだから、DirextX SDK を追加で入れるくらい誤差みたいなものということで。それにそもそも .NET Framework 2.0 をインストールする必要がある。スクリーンセーバーのためだけに .NET Framework 2.0 を要求するって結構すごいよなあ。

本当はもっといろいろ DirectX の機能を使ってみようかと思った。ビデオクリップの解像度にあわせてディスプレイの解像度を変更したり、マルチディスプレイ環境では再生するディスプレイを選択できるようにしたりとかね。でも、結局液晶ディスプレイで再生してるので、解像度を落としても dot-by-dot で再生できるわけじゃなく、スケーリングが入るので美しさは変わらない。高い解像度のままでも CPU や GPU の負荷もとくに問題にならなかった。マルチディスプレイ対応については、「あったらいいな」くらいで必須じゃない。というわけで今の私の環境ではあんまり意味がなくなったので見送った。

すべての記事の見出し (全1029件)
全カテゴリの一覧と記事の数
カテゴリごとに記事をまとめ読みできます。記事の表題だけを見たい場合は、すべての記事の見出し (カテゴリ別表示) へ。

直近30日分の記事
2007-04-23 (Mon)
2007-03-07 (Wed)
2007-02-27 (Tue)
2007-01-17 (Wed)
2007-01-15 (Mon)
2007-01-14 (Sun)
2007-01-08 (Mon)
2006-12-01 (Fri)
2006-11-22 (Wed)
2006-11-20 (Mon)
2006-11-19 (Sun)
2006-09-30 (Sat)
2006-08-29 (Tue)
2006-08-04 (Fri)
2006-07-27 (Thu)
2006-07-23 (Sun)
2006-07-17 (Mon)
2006-07-10 (Mon)
2006-07-06 (Thu)
2006-07-03 (Mon)
2006-06-29 (Thu)
2006-06-28 (Wed)
2006-06-27 (Tue)
2006-06-25 (Sun)
2006-06-19 (Mon)
2006-06-18 (Sun)
2006-06-15 (Thu)
2006-06-11 (Sun)
2006-06-01 (Thu)
2006-05-30 (Tue)
プロファイル
斎藤 宏明。エンジニアです。宇都宮市に住んでいます。
リンク
RSS
スポンサードリンク
Powered by
さくらインターネット

© 斎藤 宏明 Saito Hiroaki Gmail Address
Landscape - エンジニアのメモ http://sonic64.com/
Landscape はランドスケープと読みます。
ひらがなだと らんどすけーぷ です。