オーディオ・キャプチャと、PCM形式での保存

2010年10月15日(金)
PROJECT KySS

ロジック・コードの記述

以上を踏まえて、ロジック・コード(MainPage.xaml.vb)を記述していきます。

名前空間の読み込み

入出力を可能にする、System.IO名前空間をインポートしておきます。

1<!--//--><![CDATA[// ><!--
2 
3Option Strict On
4Imports System.IO
5 
6//--><!

クラスの作成

Silverlightでオーディオ情報を取得するには、AudioSinkからカスタムオーディオシンクを派生させ、AudioSink.CaptureSource からオーディオ情報を受け取ります。そこで、「Inherits System.Windows.Media.AudioSink」と入力します。すると、下記のOnCaptureStarted、OnCaptureStopped、OnFormatChange、OnSamples メソッドのコードの大半が、自動的に追加されます。基本的な処理だけであれば、必要な作業は、パラメータや多少のコードを変更したり追加する程度です。

OnCaptureStartedメソッドは、オーディオ・データのキャプチャを開始した時に呼び出されます。この時点で、新規MemoryStreamを生成しておきます。MemoryStreamの引数に、内部配列のサイズを指定して初期化する場合は、32bit型整数を指定します。

OnCaptureStoppedは停止した時に、OnFormatChangeは、オーディオ形式が変更された時に、呼び出されます。ただし、オーディオのキャプチャを開始すると、OnFormatChangeが、最初のキャプチャに対して1回以上呼び出されることになっています。そこで、OnFormatChangeで取得するオーディオ形式を利用します。

OnSamplesは、オーディオ・サンプルのキャプチャを完了したときに呼び出されます。Stream.Writeメソッドに引数を指定してストリームに書き込みます。

01<!--//--><![CDATA[// ><!--
02 
03Public Class savePcmDataClass
04  Inherits System.Windows.Media.AudioSink
05 
06  Dim myStream As MemoryStream
07  Dim myFormat As AudioFormat
08 
09  Protected Overrides Sub OnCaptureStarted()
10    myStream = New MemoryStream()
11  End Sub
12 
13  Protected Overrides Sub OnCaptureStopped()
14  End Sub
15 
16  Protected Overrides Sub OnFormatChange(ByVal audioFormat As System.Windows.Media.AudioFormat)
17    myFormat = audioFormat
18  End Sub
19 
20  Protected Overrides Sub OnSamples(ByVal sampleTime As Long, ByVal sampleDuration As Long, ByVal sampleData() As Byte)
21    myStream.Write(sampleData, 0, sampleData.Length)
22  End Sub
23 
24//--><!

音声データを生成するには、ストリームとフォーマット形式を取得しておく必要があるので、上記に続けて、次の処理を記述します。

01<!--//--><![CDATA[// ><!--
02 
03Public ReadOnly Property myAudioDataForSave() As Stream
04  Get
05    Return myStream
06  End Get
07End Property
08 
09Public ReadOnly Property myFormatForSave() As AudioFormat
10  Get
11    Return myFormat
12  End Get
13End Property
14End Class
15 
16//--><!

変数の宣言

キャプチャの開始と停止に利用する、新規CaptureSourceを宣言します。CaptureSource は、オーディオキャプチャを、オーディオキャプチャ・デバイスから利用するために使うクラスです。また、先に作成したクラスを録音と保存の際に利用するため宣言しておきます。

01<!--//--><![CDATA[// ><!--
02 
03Partial Public Class MainPage<br />
04  Inherits UserControl
05 
06  Public Sub New()
07    InitializeComponent()
08  End Sub
09 
10  Dim recordingSound As New CaptureSource
11  Dim myCreateData As New savePcmDataClass
12 
13//--><!

ページがロードされた時の処理

CaptureDeviceConfiguration を用いて、システムで利用可能なオーディオデバイスの情報を取得し、デバイスからキャプチャにアクセスする際の、アクセス許可を与えます。GetAvailableAudioCaptureDevices で、システム上で利用可能なオーディオデバイスのコレクションを取得します。

1<!--//--><![CDATA[// ><!--
2 
3  Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
4recordingSound.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice
5  End Sub
6 
7//--><!

[録音]ボタンがクリックされた時の処理

「録音」「停止」「保存」の3つのボタンをクリックした時の処理を、順次記述していきます。あらかじめ、各々のボタンについて、MainPage.xaml.vbの「MainPage」と「Declarations」を選択して、イベントのコードを追加しておきます(図6)。

図6:MainPage.xaml.vbの画面で、3つのボタンについてイベントを追加しておく(クリックで拡大)

まず、「録音」ボタンがクリックされた時の処理を記述します。
このサンプルは、Trustedモードのアウト・オブ・ブラウザで実行させますので、インストールを促すメッセージを表示します。

先に作成したCaptureSourceに、オーディオをキャプチャします。CaptureSource.Startメソッドで、キャプチャを開始します。

'■「録音ボタン」がクリックされた時の処理

01<!--//--><![CDATA[// ><!--
02 
03  Private Sub RecordButton_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles RecordButton.Click
04    If App.Current.IsRunningOutOfBrowser = False AndAlso App.Current.HasElevatedPermissions = False Then
05      MessageBox.Show("TrustedモードのOut-Of-Browserで実行してください。")
06      Exit Sub
07    Else
08  ‘************If CaptureDeviceConfiguration.RequestDeviceAccess Then
09      myCreateData.CaptureSource = recordingSound
10      recordingSound.Start()
11      TextBlock.Text = "録音中です"
12      RecordButton.IsEnabled = False
13      StopButton.IsEnabled = True
14  ‘************End If
15    End If
16  End Sub
17 
18//--><!

なお、今回はTrusteモードで実行させていますが、Trustedモードを指定しない場合は、キャプチャ関連のAPIを呼び出すためにユーザのアクセス許可を得る必要があるため、前回と同様、CaptureDeviceConfiguration.RequestDeviceAccess メソッドを用いて、キャプチャデバイスに対するアクセスを要求するように記述しなければなりません(リスト中のコメントアウト部分)。その場合、前掲の図1の実行時には、アクセス許可を問うメッセージが表示されます(図7)。

図7:Trustedモードを指定しない場合は、アクセス許可の確認メッセージが表示される(クリックで拡大)

[停止]ボタンがクリックされた時の処理

「停止」ボタンがクリックされた時は、CaptureSource.Stop メソッドで、キャプチャデバイスからのキャプチャを停止します。

'■「停止ボタン」がクリックされた時の処理

01<!--//--><![CDATA[// ><!--
02 
03  Private Sub StopButton_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles StopButton.Click
04    recordingSound.Stop()
05    TextBlock.Text = "録音中断しました"
06    StopButton.IsEnabled = False
07    SaveButton.IsEnabled = True
08  End Sub
09 
10//--><!

[保存]ボタンがクリックされた時の処理

最後に「保存」ボタンがクリックされた時の処理を記述します。このサンプルでは、拡張子はwavとして保存しますので、ファイルの種類を決定するフィルター文字列に"Audio Files (*.wav)|*.wav"を指定して、新しいファイルダイアログを開きます。

音声データの入出力には、文字列用のStreamWriterではなく、バイトの入出力用のStream クラスを使います。

キャプチャしたデータをもとに、PCM形式のデータを生成する処理を実行します。この生成処理は、取得した多数のデータを利用するため、引数を渡して別プロシジャ(後述のsaveMyDataプロシジャ)として記述します。

'■「保存ボタン」がクリックされた時の処理

01<!--//--><![CDATA[// ><!--
02 
03  Private Sub SaveButton_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles SaveButton.Click
04    Dim mySaveFileDialog As New SaveFileDialog
05    mySaveFileDialog.Filter = "Audio files (*.wav)|*.wav"
06    If mySaveFileDialog.ShowDialog Then
07      Dim myFileStream As Stream = mySaveFileDialog.OpenFile
08      saveMyData(myCreateData.myAudioDataForSave, myFileStream, myCreateData.myFormatForSave)
09    End If
10  End Sub
11 
12//--><!

音声データを生成して書き込む処理

先のSaveButton_Clickプロシジャから引数を受け取り、キャプチャしたデータやフォーマット形式をもとにして、PCM形式のデータを生成する処理を記述していきます。生成するデータの内容は、前ページの表1の通りです。

まず、保存する音声データはバイナリ形式ですから、BinaryWriterクラスを使い、新しいインスタンスを生成します。

次に、データ生成の際の計算に利用する4つの変数を、あらかじめ宣言しておきます。チャンネル数、サンプリングレート、量子化ビット数、データ長です。Option Strict Onを指定していますので、型指定を間違えないように注意します。

さらに、3つのチャンクの順に、PCM形式データを順次生成して書き込んでいきます。fmtの後には半角スペースが必要ですので、注意してください。文字列を出力する際には、文字をUnicode 文字配列へコピーする、ToCharArray メソッドを使います。このメソッドを使わなければ正しいデータが生成されません。

dataチャンクについては、音声の実データを生成して書き込みます。

まず、データ長から1減算した数の、バイト配列を作成します。音声を順次読み取るためのカウンタ用変数を宣言します。FileStream.Seek メソッド で、音声データの走査を開始する起点を、0の位置に設定します。構文は、returnValue = instance.Seek(offset, origin)です。

データ長の数だけ、データの読み取りと書き込みを繰り返します。データを読み取るStream.Readメソッド の構文は、returnValue = instance.Read(buffer, offset, count) です。bufferにはバイト配列を指定します。offsetには、ストリームから読み取ったデータを格納する開始点の位置を指定します。この位置は0から始まるため、ここでは0を指定しています。countには、ストリームから読み取る最大バイト数、ここではデータ長を指定しています。

これで、指定したバイト配列の、offset から (offset + count -1) までの値が、キャプチャしたソースから読み取られたバイトに置き換わります。以上の順序で、音声データを生成します。

一連の処理が完了した時点で、「保存しました」というメッセージを表示します。

'■音声データを生成して書き込む処理

01<!--//--><![CDATA[// ><!--
02 
03  Public Sub saveMyData(ByVal mySoundData As Stream, ByRef mySoundOutput As Stream, ByRef mySoundFormat As AudioFormat)
04 
05    Dim pcmData As New BinaryWriter(mySoundOutput)
06 
07    Dim channels As Integer = mySoundFormat.Channels
08    Dim samplingRate As Integer = mySoundFormat.SamplesPerSecond
09    Dim samplingBitRate As Integer = mySoundFormat.BitsPerSample
10    Dim dataLength As Long = mySoundData.Length
11 
12    pcmData.Write("RIFF".ToCharArray())
13    pcmData.Write(CUInt(36 + dataLength))
14    pcmData.Write("WAVE".ToCharArray())
15    pcmData.Write("fmt ".ToCharArray())
16    pcmData.Write(CUInt(16))
17    pcmData.Write(CUShort(1))
18    pcmData.Write(CUShort(channels))
19    pcmData.Write(CUInt(samplingRate))
20    pcmData.Write(CUInt(samplingRate * samplingBitRate * channels / 8))
21    pcmData.Write(CUShort(samplingBitRate * channels / 8))
22    pcmData.Write(CUShort(samplingBitRate))
23    pcmData.Write("data".ToCharArray())
24    pcmData.Write(CUInt(dataLength))
25 
26    Dim dataSize As Integer = CInt(dataLength)
27    Dim myBuffer(dataSize - 1) As Byte
28    Dim soundRead As Integer
29    mySoundData.Seek(0, SeekOrigin.Begin)
30 
31    For soundRead = 0 To dataSize - 1
32      pcmData.Write(myBuffer, 0, mySoundData.Read(myBuffer, 0, dataSize))
33      soundRead = soundRead + 1
34    Next
35    TextBlock.Text = "保存しました"
36  End Sub
37End Class
38 
39//--><!

以上のコードが書けたら、デバッグして動作を確認してみてください。出力結果は音声なので、画面上では結果をお伝えできません。読者の皆さまそれぞれで試してください。

もし、タイプミスなどで正しいデータを生成できていない場合は、再生時に、「ファイルを再生できません。プレーヤーがそのファイルの種類をサポートしていないか、そのファイルの圧縮に使用したコーデックをサポートしていない可能性があります」というメッセージが表示されます(図8)。正しいデータを保存した場合は、前掲図3のように、問題なく再生されます。

図8:正しいデータを生成できていない場合は、再生時にメッセージが表示される

次回は、センサーから入力されたデータの取得と利用について紹介します。

四国の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メルマガ会員のサービス内容を見る

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