撮影した動画を専用サーバーに保存し、再生する

2012年1月20日(金)
PROJECT KySS

次に、MainPage.xamlを展開して表示される、MainPage.xaml.vbをダブルクリックしてリスト3のコードを記述します。

ロジックコードを記述する

リスト3 (MainPage.xaml.vb)

Option Strict On

Silverlight ベースのアプリケーション内の描画、テキスト、オーディオ コンテンツ、ビデオ コンテンツなどのメディアを提供するクラスの含まれる、System.Windows.Media名前空間をインポートします。
Imports System.Windows.Media

仮想ファイルシステムを作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。
Imports System.IO.IsolatedStorage
Imports System.IO

XML to LINQでXMLを処理するクラスの含まれる、System.Xml.Linqをインポートします。
Imports System.Xml.Linq
Partial Public Class MainPage
  Inherits PhoneApplicationPage

  ' コンストラクター
  Public Sub New()
    InitializeComponent()
  End Sub

ビデオ キャプチャに関連付けられている、キャプチャ デバイスから使用するためのメソッドを提供する、新しいCaptureSourceクラスのインスタンス、mySourceをメンバ変数として宣言します。
  Dim mySource As New CaptureSource

ビデオキャプチャに Windows PhoneのSilverlight をファイルに保存するために使用する、新しいFileSinkクラスのインスタンス、fsをメンバ変数として宣言します。
  Dim fs As New FileSink

ビデオコンテンツで領域を塗りつぶす、VideoBrushクラス型のメンバ変数myVideoBrushを宣言します。
  Dim myVideoBrush As VideoBrush

mp4のファイル名を格納するメンバ変数videoFileNameを宣言します。
  Dim videoFileName As String
 
  Dim readSize As Integer

ユーザーの専用サーバーに配置したASP.NETのDefault.aspxファイル(後ほど作成します)にfileNameを引数として渡すURLを定数変数として宣言します。
  Const UploadUri As String = "ユーザーの専用サーバーURL/VideoFileUpload/Default.aspx?fileName={0}"

ページがアクティブになった時の処理

新しいWebClientのインスタンスmyWebClientオブジェクトを作成します。WebClientクラスは、データの送受信用のメソッドを提供するクラスです。
String またはUriとして指定したリソースをダウンロードする、DownloadStringAsyncメソッドで、サーバー上のVideoInfo.xmlをダウンロードします。キャッシュから読み込まないように、常に新しいデータを読み込むよう、引数に現在の時間、分、秒を指定しています。
AddHandlerステートメントで、非同期のリソース ダウンロード操作の完了時に発生する、DownloadStringCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理を実行します。
ダウンロードが成功しなかった場合は、警告メッセージを出して、処理を抜けます。成功した場合は、XElement.Parseメソッドでダウンロードした結果(resultArgs.Result)を読み込みます。読み込んだ結果はxmldoc変数に格納されます。
読み込んだVideoInfo.xmlから<fileName>要素を取得するクエリを定義します。Countプロパティで<fileName>要素の個数を取得します。その個数が0より大きい場合、つまり<fileName>要素が存在する場合は、[一覧]ボタンの使用を可能とします。それ以外は[一覧]ボタンの使用は不可となります。
  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Dim myWebClient As New WebClient
 
    AddHandler myWebClient.DownloadStringCompleted, Sub(resultSender As Object, resultArgs As DownloadStringCompletedEventArgs)
      If resultArgs.Error Is Nothing = False Then
        MessageBox.Show("XMLファイルが見つかりません")
        Exit Sub
      Else
        Dim xmldoc As XElement = XElement.Parse(resultArgs.Result)
 
                       Dim query = From c In xmldoc.Descendants("fileName") Select c
                                   If query.Count > 0 Then
                                       ichiranButton.IsEnabled = True
                                   Else
                                       ichiranButton.IsEnabled = False
                                   End If
                     End If
                 End Sub
    myWebClient.DownloadStringAsync(New Uri(String.Format(ユーザーの専用サーバーURL/VideoFileUpload/VideoData/VideoInfo.xml?myTime={0}", DateTime.Now.ToLongTimeString), UriKind.Absolute))
    MyBase.OnNavigatedTo(e)
  End Sub

[開始]ボタンがタップされた時の処理

保存するビデオのファイル名(変数videoFileNameに格納)を、現在の「年月日時間分秒.mp4」とします。DateTime.Now.ToString("yyyyMMddHHmmmmss")と指定します。

CaptureDeviceConfiguration.GetDefaultVideoCaptureDeviceメソッドで、クライアント上の既定のビデオキャプチャデバイスを表すVideoCaptureDevice オブジェクトを取得します。
CaptureDeviceConfiguration.GetDefaultAudioCaptureDeviceメソッドで、クライアント上の既定のオーディオキャプチャデバイスを表すAudioCaptureDevice オブジェクトを取得します。
CaptureSourceのVideoCaptureDeviceプロパティに既定のビデオキャプチャデバイス(myVideo)を指定します。
CaptureSourceのAudioCaptureDeviceプロパティに既定のオーディオキャプチャデバイス(myAudio)を指定します。
VideoBrushクラスの新しいインスタンス、myVideoBrushオブジェクトを作成します。VideoBrush.SetSourceメソッドで、CaptureSourceを指定し、VideoBrushのソースを設定します。
RectangleのFillプロパティに、VideoBrushソースの設定されたmyVideoBrushを指定し塗りつぶします。これで、Rectangle内にビデオが表示されます。
変数isostoreを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。FileExistsメソッドでvideoFileNameに格納されたファイル名が既に存在する場合は、DeleteFileメソッドで変数videoFileNameに格納されたファイルをいったん削除します。
分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStream変数を用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内に、変数videoFilNameに格納されているファイルを作成します。
FileSinkクラスのIsolatedStorageFileNameプロパティに、変数videoFileNameに格納されているファイル名を指定します。IsolatedStorageFileNameプロパティには、関連付けられている分離ストレージファイルの名前を指定します。
FileSinkクラスのCaptureSourceプロパティに、mySourceオブジェクトを指定します。CaptureSourceプロパティは、この FileSink が関連付けられているキャプチャソースを取得します。
CaptureSourceのStartメソッドで、CaptureSource に関連した全てのキャプチャデバイスからのキャプチャを開始します。[サーバーに保存終了]ボタンの使用を可能にします。
今回の、サーバーにmp4ビデオファイルを保存する処理は、いったん分離ストレージにmp4ファイルを保存し、その後専用サーバーに保存します。専用サーバーに保存した後、分離ストレージ内のmp4関連のファイルを削除しています。
  Private Sub startButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles startButton.Click
    videoFileName = DateTime.Now.ToString("yyyyMMddHHmmmmss") & ".mp4"
  
    Dim myVideo As VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice
    Dim myAudio As AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice

    mySource.VideoCaptureDevice = myVideo
    mySource.AudioCaptureDevice = myAudio
 
    myVideoBrush = New VideoBrush
    myVideoBrush.SetSource(mySource)
 
    Rectangle1.Fill = myVideoBrush
    Using isostore As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
        If isostore.FileExists(videoFileName) = True Then
           isostore.DeleteFile(videoFileName)
    Else
        Dim myStream As IsolatedStorageFileStream = isostore.CreateFile(videoFileName)
 
        myStream.Close()
    End If
  End Using
  fs.IsolatedStorageFileName = videoFileName
  fs.CaptureSource = mySource
 
  mySource.Start()
  endButton.IsEnabled = True
End Sub

[サーバーに保存終了]ボタンがタップされた時の処理

CaptureSourceのStopメソッドで、CaptureSourceに関連した全てのキャプチャデバイスからのキャプチャを停止します。
変数myUploadUriを宣言し、定数変数UploadUriに指定した専用サーバーに、引数としてビデオのファイル名を持たし、myUploadUriに格納しておきます。
新しいWebClientのインスタンスmyWebClientオブジェクトを作成します。WebClientクラスは、データの送受信用のメソッドを提供するクラスです。
OpenWriteAsyncメソッドに、専用サーバーのUriとビデオのファイル名で初期化された新しいUriのオブジェクト、myUriを指定します。OpenWriteAsyncメソッドは、指定したリソースにデータを書き込むためのストリームを開くメソッドです。Headersにはvideo/jpegと指定します。
AddHandlerステートメントで、リソースにデータを書き込むためにストリームを開き、非同期操作の完了時に発生する、OpenWriteCompletedイベントにイベントハンドラを指定します。イベントハンドラ内では以下の処理を実行します。
非同期操作中にエラーが発生しなかった場合は、変数myStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStreamを用意し、IsolatedStorageFileクラスのOpenFileメソッドでビデオのファイルを指定したモード、アクセスで開きます。
ビデオファイルと、データをサーバーに送信するために使用される書き込み可能なストリームを引数にPushDataプロシージャを実行します。ストリームを閉じ、保存した旨のメッセージを表示します。
変数isostorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内にビデオファイルが存在すれば、DeleteFileメソッドで削除します。また分離ストレージ内には.mp4ファイルと同時に.mp4.jpgファイルも作成されますので、.mp4.jpgファイルも削除します。
FileSinkのCaptureSource、IsolatedStorageFileNameプロパティにNothingを指定し、全てのオブジェクトの関連付けを破棄します。[一覧]ボタンの使用を可能にします。
  Private Sub endButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles endButton.Click
    mySource.Stop()
    Dim myUploadUri As String = String.Empty
    myUploadUri = String.Format(UploadUri, videoFileName)
    Dim myWebClient As New WebClient
 
    AddHandler myWebClient.OpenWriteCompleted, Sub(mySender As Object, myArgs As OpenWriteCompletedEventArgs)
      If myArgs.Error Is Nothing = True Then
        Dim myStorage = IsolatedStorageFile.GetUserStoreForApplication
        Dim myStream As IsolatedStorageFileStream = myStorage.OpenFile(videoFileName, FileMode.Open, FileAccess.Read)
          PushData(myStream, myArgs.Result)
          myArgs.Result.Close()
          myStream.Close()
          MessageBox.Show(videoFileName & "を保存しました。")
            Using isostorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
              If isostorage.FileExists(videoFileName) = True Then
                 isostorage.DeleteFile(videoFileName)
              End If
                 If isostorage.FileExists(videoFileName & ".jpg") = True Then
                    isostorage.DeleteFile(videoFileName & ".jpg")
              End If
            End Using
        End If
    End Sub

 
  myWebClient.Headers(HttpRequestHeader.ContentType) = "video/mpeg"
  Dim myUri As Uri = New Uri(myUploadUri, UriKind.Absolute)
  myWebClient.OpenWriteAsync(myUri)
 
  fs.CaptureSource = Nothing
  fs.IsolatedStorageFileName = Nothing
  ichiranButton.IsEnabled = True
End Sub

サーバーに送るためのビデオファイルの情報をストリームに書き込む処理

バイト単位で取得したストリームの長さで初期化された新しいByte型配列変数bufferを作成します。
FileStreamクラスのFileStream.Readメソッドで、リソースに格納されているストリームを読み取りWriteメソッドで書き込みます。InlineAssignHelperヘルパー関数(後述)を使っています。 
Stream.Write メソッドの書式は下記の通りです。 

Stream.Write(バイト配列(現在のストリームに書き込むバイト数だけコピーします), 現在のストリームへのバイトのコピーを開始する位置を示すbuffer 内のバイトオフセット。インデックス番号は0から始まる,現在のストリームに書き込むバイト数) 

  Private Sub PushData(input As FileStream, output As Stream)
    Dim buffer () As Byte = New Byte(CInt(input.Length)) {}
    Dim bytesRead As Integer
    While (InlineAssignHelper(bytesRead, input.Read(buffer, 0, buffer.Length))) <> 0
      output.Write(buffer, 0, bytesRead)
    End While
  End Sub

[一覧]ボタンがタップされた時の処理

IchiranPage.xamlに遷移します。
  Private Sub ichiranButton_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles ichiranButton.Click
    NavigationService.Navigate(New Uri("/IchiranPage.xaml", UriKind.Relative))
  End Sub

MSDNのドキュメントで下記のように定義されているInlineAssignHelperヘルパー関数

  Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
    target = value
    Return value
  End Function
End Class
  • 「撮影した動画を専用サーバーに保存し、再生する」サンプルプログラム

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています