フォトアルバム

他のアカウント

更新ブログ

Powered by Six Apart
Member since 03/2005

スレッドセーフな? Form へのEvent 渡し

ひっさびさにBlog 更新!!

FB に移行してからもう、コッチは・・・

ですが^^; プログラムメモとしてはやはりBlog でしょう!!

 

さて、本題!

私は、Form に機能を持たせません。

殆ど、クラスに機能を持たせ、そのクラスだけで実動作を行うように作ります。

なので、メインのプログラムは、クラス内でスレッドでグルグル回ってる作り方が多いです。

で、そのクラスをForm に渡し、メソッドを叩いたり、プロパティを参照する訳ですが、

クラスからForm へ通知したい時にイベントやデリゲートを使います。

所が・・・

クラスからのスレッドでイベントやデリゲートを発生させて、Form で受けて、

Form 内のコントロールにアクセスすると、「スレッドセーフがなにがし山」って言う例外が発生します!!

おっしゃる事は分かりますが、別に良いじゃんって思うのに・・・

で、良くForm 内に変数を作りイベントで内容を一時的にソイツに保存し、

Form 内でRefresh タイマーかなんかを仕掛けて、ソイツで読んだりしていましたが、

今回試したのは、ウィンドウメッセージを使って、処理をして見ました。

そうすると、そのForm でのスレッドでイベントが走るので、

無事に同期がとれた訳です。

Form の上にコイツを一応^^;

Imports System.Runtime.InteropServices

適当な場所に、メッセージ送信用の関数を準備

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
    End Function

メッセージを受ける方は、下記のクラスをNew します。

Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.Runtime.Serialization.Formatters.Binary

Public Class clsWindowMessage
    Inherits NativeWindow

    Private prvtargetForm As Form = Nothing

    Public Sub New(ByVal targetForm As Form)

        prvtargetForm = targetForm

        AddHandler prvtargetForm.HandleCreated, AddressOf Me.OnHandleCreated
        AddHandler prvtargetForm.HandleDestroyed, AddressOf Me.OnHandleDestroyed
    End Sub

    Public Sub OnHandleCreated(ByVal sender As Object, ByVal e As EventArgs)

        Me.AssignHandle(prvtargetForm.Handle)

    End Sub

    Public Sub OnHandleDestroyed(ByVal sender As Object, ByVal e As EventArgs)

        Me.ReleaseHandle()

    End Sub

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

        MyBase.WndProc(m)

    End Sub

End Class

上記クラスをForm のNew の時に、

        prvWindowMessage = New clsWindowMessage(Me)

します。
※因みに、Form 内に

    Private prvWindowMessage As clsWindowMessage
と変数を定義してあります。

受信は、下記のメソッドが呼ばれます。

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If ReciveWindowMessage(m) Then
            Exit Sub
        End If
        MyBase.WndProc(m)
    End Sub

    Private Function ReciveWindowMessage(ByRef m As System.Windows.Forms.Message) As Boolean
        If ref***** Is Nothing Then
            Return False
        End If
        Select Case m.Msg
            Case WM_USER_EXEC_INSPECTION : ref*****.ReciveWindowMessage(m)
            Case WM_USER_WORK_DIRECTION : ref*****.ReciveWindowMessage(m)
            Case WM_USER_CHANGE_RECIPE : ref*****.ReciveWindowMessage(m)
            Case WM_USER_CHANGE_CAMERA : ref*****.ReciveWindowMessage(m)
            Case WM_USER_CHANGE_USER_LEVEL : ref*****.ReciveWindowMessage(m)
            Case WM_USER_SETTING_MODE : ref*****.ReciveWindowMessage(m)
            Case WM_USER_DISPLAY_REFRESH : ref*****.ReciveWindowMessage(m)
            Case Else
                Return False
        End Select
        Return True
    End Function

コイツで受けてます。

んで、ソレを上のクラスにぶっ飛ばしてます。

    Public Const WM_USER As UInteger = &H400
    Public Const WM_APP As UInteger = &H8000

    Public Const WM_USER_EXEC_INSPECTION As UInteger = WM_USER + 1000
    Public Const WM_USER_WORK_DIRECTION As UInteger = WM_USER + 1001
    Public Const WM_USER_CHANGE_RECIPE As UInteger = WM_USER + 1002
    Public Const WM_USER_CHANGE_CAMERA As UInteger = WM_USER + 1003
    Public Const WM_USER_CHANGE_USER_LEVEL As UInteger = WM_USER + 1100
    Public Const WM_USER_SETTING_MODE As UInteger = WM_USER + 1101

    Public Const WM_USER_DISPLAY_REFRESH As UInteger = WM_USER + 1090

メッセージは数字なので、私はConst で上記のように定義しました。

ココで注意は、XP ならWM_USER でそのまま受けれますが、

XP 以降のWindows はフィルタがかかってるため、解除する必要が有ります。

#Region "DllImport"
    Private Const MSGFLT_ADD As Integer = 1
    Private Const MSGFLT_REMOVE As Integer = 2
    <DllImport("user32.dll", EntryPoint:="ChangeWindowMessageFilter", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function ChangeMessageFileter(ByVal message As UInteger, ByVal dwflag As Int32) As Boolean
    End Function
#End Region

下記のコードをForm のNew に追加し、XP の場合はCatch し、ソレ以降のWindows の場合は、フィルタが解除されます。

        Try
            ChangeMessageFileter(WM_USER_EXEC_INSPECTION, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_WORK_DIRECTION, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_CHANGE_RECIPE, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_CHANGE_CAMERA, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_CHANGE_USER_LEVEL, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_SETTING_MODE, MSGFLT_ADD)
            ChangeMessageFileter(WM_USER_DISPLAY_REFRESH, MSGFLT_ADD)
        Catch ex As Exception
            '
        End Try

基本的には、コレで準備完了となり、後は、そのForm へメッセージを送ります。

メッセージ送信には、そのForm のHandle が必要ですので、

    Private prvMainFormHndl As IntPtr
この様な変数に、

        prvMainForm = New frmMain(refMasterSetting, Me, refLogClass)
        prvMainFormHndl = prvMainForm.Handle

ハンドルを渡しておきます。

そして、スレッドでも何処からでも、

                PostMessage(prvMainFormHndl, WM_USER_DISPLAY_REFRESH, 0, 0)
コレを打てば、Form 側が受信してForm 側でイベント処理を行い始めます。

また、上の受信部から飛ばすref*****.ReciveWindowMessage(m) にも、

        Dim m As System.Windows.Forms.Message = New System.Windows.Forms.Message()
        m.Msg = WM_USER_CHANGE_USER_LEVEL
        m.WParam = 0
        ref*****.ReciveWindowMessage(m)

こんな感じでSystem.Windows.Forms.Message を作って渡せば、

メッセージを送らなくても動作が出来ます。

こんな感じで、メッセージのやり取りでスレッド間の同期をとる方法も有るなと言う事でした。

また多分使うだろうから、ココにメモっときました!!

昔のノート見てたら・・・

懐かしい処理が出てきました。

コレは、ワークを回転させながらラインスキャンカメラで撮った映像を円形補正し、更にフィルタ処理をかけたモノです。

このフィルタで色々と試行錯誤していた記憶が有ります。

当時のメモによると・・・

                    3*3
X0 = S0 + (( ∑MiSi ) / 4) * Amp
                    i=1

X =   処理結果画素
M =   マスクパターン
S =   元画像画素
Amp = 強調パラメータ

マスクパターン
----------------
|  0 |  0 |  0 |
----------------
| -1 |  0 |  1 |
----------------
|  0 |  0 |  0 |
----------------

ベースはエンボス処理のアルゴリズム(マスクパターン)を利用し、
その微分値を強調パラメータで増幅し、元画像にフィードバックかけております。
 

との事・・・

ソコで得られた画像はコチラ↓

Maxview

 

ハッキリ言うと、この後の処理は大変やりにくい画像になりますが、

Viewer 画像としては最適な絵になります。

※人が目で見てワークの特徴をつかみやすい絵

人は目で見て直ぐに画像内の特徴や傾向等を処理する事が出来るのですが、

コレをソフトで行うには色々なアルゴリズムを駆使しなければならずなかなかメンドクサイもんです^^;

ちなみに、ソフト的にはこういった絵の方が処理し易い↓

Sobel

 

我々の扱う画像処理は、ほとんどが工業向けなので、光学系でなるべくソフト側をカバーするように設計します。

しかし、最近のワークはとにかく厄介(笑)

こういった厄介なワークを処理するのが画像処理アルゴリズムで有る訳ですが、

コレがまた面白かったりする訳ですよ^^;

 

時間が有ったらまたこういうテーマをやってみたいなぁ~

この前、DTMF 解析の時に使ったFFT で少し実験したい映像が有る。

※ DTMF 解析 http://s14.dcnblog.jp/pri/2011/02/dtmf-0ac9.html

バックグランドの輝度ムラや表面ムラと欠陥との判別とか・・・

低周波成分、高周波成分の分離で出来そうな気がするんだけどなぁ~

 

たまに昔のノートを開くとその当時やってた仕事の思い出とかよみがえって良いですね。

やっと実機で動いた!!

iPhone アプリ・・・

ようやく実機で動作確認出来ました^^;

って言うか、証明や認証が大変!!

iOS Developer Program の登録から何からめんどくさい事・・・

まぁ、とりあえず実機で動いて良かった良かった。

とは言うモノの・・・

iPhone が真っ暗闇になってみたり(笑)

電源OFF って復活でしたが・・・

まだまだ格闘中です!!

Img_1115

 

真っ暗闇状態

Img_1113

Img_1114 

本日より勉強開始!!

iPhone アプリ^^;

ようやくMac ちゃんが来たので、早速iOS SDK をインストール。

んでもって、まずはどんなもんか、実験用のソフトを作ってみましたが・・・

ナカナカ時間がかかったなぁ~(笑)

午後の8時から作って、完成が12時・・・

先が思いやられますなぁ^^;

一応、こんな感じのアプリですと言う事で、動画を撮ってみました。

http://www.youtube.com/watch?v=fnP-j5vbCGo

 

画面下にUIToolBar を配置し、UIBarButtonItem を二つ配置。

画面上半分が、UIImageView で、下半分がUIWebView です。

ソース内で、UIImage を二つこしらえて、それぞれにS14 の写真をセットする。

各Button のOutlet と、ソース内のIBAction を設定。

ボタン1が押された場合、ImageView にImage1 を設定。

WebView に、http://s14.dcnblog.jp をリクエスト。

ボタン2が押された場合、ImageView にImage2 を設定。

WebView に、http://www.s14.nu をリクエスト。

プログラム終了時に、Image1 とImage2 を解放。

 

たったコレだけです^^;

まぁ、なんとなく感覚がつかめてきました。

本格的なアプリ開発まではもう少し時間がかかりそうですが、

何事も一歩一歩進んで行けば良いかなと・・・

届いたど〜

届いたど〜
久々にワクワクするお買い物!!
コレ、どう料理しようかな〜

続DTMF 解析

また例の奴を書き続けていたのですが・・・

今度はもう少し数値的にDTMF を解析するように変更しました。

ソレと、各種オーディオフォーマットに対応しました。

 

まず、チャンネル数が1 の場合はモノラルで、2 の場合はステレオです。

そして、ビットレートが8Bit か16Bit かを選択が出来ます。

その為、データ格納変数を4種類用意ました。

    Private _AudioIn_M8_ReadData() As Byte
    Private _AudioIn_M16_ReadData() As Int16
    Private _AudioIn_S8_ReadData() As Int16
    Private _AudioIn_S16_ReadData() As Int32

コレは、そのままM8 がモノラル8Bit で、S16 はステレオ16 Bit と言う意味ですが・・・

モノラルの8Bit はByte で、16Bit はInt16 でそのままの配列ですね。

ステレオの場合、1 データに対して、左チャンネル、右チャンネルとデータが書かれますので、

8Bit の場合、左と右用で16Bit を用意します。

16Bit の場合は、同じ事で32Bit ですね。

ちなみに、1つの配列の要素に、左右でデータ上位下位に分かれています。

Byte ストリーミングは左右左右左右とデータが流れるので、

配列要素には、下位8Bit(16Bit) 左音声、上位8Bit(16Bit) 右音声となっております。

実際のデータは、8Bit データの場合 0 ~ 255 の値で、128 が無音(0) として扱われます。

16Bit データの場合 -32768 ~ 32767 で、0 が無音として扱われます。

コレを、FFT 用のバッファに移して音声データを周波数変換します。

 

        If _WAVFMTEX.nChannels = 1 And _WAVFMTEX.wBitsPerSample = 8 Then
            'Monaural 8 Bit
            For i = 0 To AudioInBufferNum - 1
                _AudioIn_M_FftData(i) = (CType(_AudioIn_M8_ReadData(i), Double) - 128.0)
            Next
            Four1(_AudioIn_M_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 1 And _WAVFMTEX.wBitsPerSample = 16 Then
            'Monaural 16 Bit
            For i = 0 To AudioInBufferNum - 1
                _AudioIn_M_FftData(i) = CType(_AudioIn_M16_ReadData(i), Double)
            Next
            Four1(_AudioIn_M_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 2 And _WAVFMTEX.wBitsPerSample = 8 Then
            'Stereo 8 Bit
            For i = 0 To AudioInBufferNum - 1
                Dim R_HiBit As Integer = _AudioIn_S8_ReadData(i) And &HFF
                Dim L_LowBit As Integer = (_AudioIn_S8_ReadData(i) >> 8) And &HFF
                _AudioIn_R_FftData(i) = (CType(R_HiBit, Double) - 128.0)
                _AudioIn_L_FftData(i) = (CType(L_LowBit, Double) - 128.0)
            Next
            Four1(_AudioIn_R_FftData, FFT_FORWARD)
            Four1(_AudioIn_L_FftData, FFT_FORWARD)
        ElseIf _WAVFMTEX.nChannels = 2 And _WAVFMTEX.wBitsPerSample = 16 Then
            'Stereo 16 Bit
            For i = 0 To AudioInBufferNum - 1
                Dim R_HiBit As Integer = _AudioIn_S16_ReadData(i) And &HFFFF
                Dim L_LowBit As Integer = (_AudioIn_S16_ReadData(i) >> 16) And &HFFFF
                _AudioIn_R_FftData(i) = CType(R_HiBit, Double)
                _AudioIn_L_FftData(i) = CType(L_LowBit, Double)
            Next
            Four1(_AudioIn_R_FftData, FFT_FORWARD)
            Four1(_AudioIn_L_FftData, FFT_FORWARD)
        Else
            Return False
        End If

 

基本的に、普通のwaveIn 関係の処理に、上記の様な処理を追加すれば複数フォーマットに対応できると思われます。

 

さて、サンプリングレートがメンドクサイのですが・・・

入力デバイスのサンプリングレートによって、周波数分解能が変わります。

私のPC では、下記三種類のサンプリングレートが選択できます。

 11.025 kHz

 22.050 kHz

 44.100 kHz

計算上では、この半分の数値が再生周波数になると思うのですが、

※本来はフィルタが有る為、上下20Hz 程切られてると思うが・・・

11.025 kHz の場合、一秒間に11025 データです。

コレに対し、waveIn で取得するデータ数は下記で定義してあります。

    Private Const AudioInBufferNum As Integer = 1024

1024 個のデータをFFT にかけると、実数部512 、虚数部512 個のデータが出来上がります。

512 個のデータの内、半分が対象のデータとなるので、256 個のデータが周波数成分になります。

1024 / 256 = 4 ですので、周波数レンジは、11025 / 4 = 2756.25 ≒ 2.8 kHz

2800 / 256 = 10.93 Hz の分解能と言う事になります。

DTMF の各周波数に合わせて、データ分解能を計算します。

    Private Const Hz697 As Integer = 697
    Private Const Hz770 As Integer = 770
    Private Const Hz852 As Integer = 852
    Private Const Hz941 As Integer = 941

    Private Const Hz1209 As Integer = 1209
    Private Const Hz1336 As Integer = 1336
    Private Const Hz1477 As Integer = 1477

    Private _HiHz As Integer
    Private _LowHz As Integer
    Private _WaveDataRange As Double

    Private _Hz697Column As Integer
    Private _Hz770Column As Integer
    Private _Hz852Column As Integer
    Private _Hz941Column As Integer

    Private _Hz1209Column As Integer
    Private _Hz1336Column As Integer
    Private _Hz1477Column As Integer

        _WaveDataNum = AudioInBufferNum / 4
        _HiHz = _WAVFMTEX.nSamplesPerSec / 4
        _LowHz = 0
        _WaveDataRange = _WaveDataNum / (_HiHz - _LowHz)

        _Hz697Column = Hz697 * _WaveDataRange
        _Hz770Column = Hz770 * _WaveDataRange
        _Hz852Column = Hz852 * _WaveDataRange
        _Hz941Column = Hz941 * _WaveDataRange

        _Hz1209Column = Hz1209 * _WaveDataRange
        _Hz1336Column = Hz1336 * _WaveDataRange
        _Hz1477Column = Hz1477 * _WaveDataRange
 

コレで、FFT 後、値を周波数変換したデータ配列(この場合は256 個の配列)内の、

何処の値がDTMF のどの周波数かが解る。

んで、その値を使って下記の様に配列からデータを取り込む。

※この場合は、ターゲットの上下1個ずつ、計3個のデータを取って、その平均値を使って解析している。

        Dim Hz697 As Double = (_WaveDataM(_Hz697Column - 1) + _WaveDataM(_Hz697Column) + _WaveDataM(_Hz697Column + 1)) / 3.0
        Dim Hz770 As Double = (_WaveDataM(_Hz770Column - 1) + _WaveDataM(_Hz770Column) + _WaveDataM(_Hz770Column + 1)) / 3.0
        Dim Hz852 As Double = (_WaveDataM(_Hz852Column - 1) + _WaveDataM(_Hz852Column) + _WaveDataM(_Hz852Column + 1)) / 3.0
        Dim Hz941 As Double = (_WaveDataM(_Hz941Column - 1) + _WaveDataM(_Hz941Column) + _WaveDataM(_Hz941Column + 1)) / 3.0
        Dim Hz1209 As Double = (_WaveDataM(_Hz1209Column - 1) + _WaveDataM(_Hz1209Column) + _WaveDataM(_Hz1209Column + 1)) / 3.0
        Dim Hz1336 As Double = (_WaveDataM(_Hz1336Column - 1) + _WaveDataM(_Hz1336Column) + _WaveDataM(_Hz1336Column + 1)) / 3.0
        Dim Hz1477 As Double = (_WaveDataM(_Hz1477Column - 1) + _WaveDataM(_Hz1477Column) + _WaveDataM(_Hz1477Column + 1)) / 3.0

後は、それぞれの周波数の数値が、基準より上かどうかを判断し、DTMF が入力されたか判断している。

※この場合は、全周波数の平均値を取り、その値に倍率をかけたモノを閾値としている。

 図の中の赤線がその値。

        If Hz697 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz697Flag = True
        End If
        If Hz770 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz770Flag = True
        End If
        If Hz852 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz852Flag = True
        End If
        If Hz941 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz941Flag = True
        End If
        If Hz1209 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1209Flag = True
        End If
        If Hz1336 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1336Flag = True
        End If
        If Hz1477 > _WaveDataM_AveVal * JudgeAveAmp Then
            Hz1477Flag = True
        End If

        _Flag_1 = Hz697Flag And Hz1209Flag
        _Flag_2 = Hz697Flag And Hz1336Flag
        _Flag_3 = Hz697Flag And Hz1477Flag
        _Flag_4 = Hz770Flag And Hz1209Flag
        _Flag_5 = Hz770Flag And Hz1336Flag
        _Flag_6 = Hz770Flag And Hz1477Flag
        _Flag_7 = Hz852Flag And Hz1209Flag
        _Flag_8 = Hz852Flag And Hz1336Flag
        _Flag_9 = Hz852Flag And Hz1477Flag
        _Flag_KM = Hz941Flag And Hz1209Flag
        _Flag_0 = Hz941Flag And Hz1336Flag
        _Flag_SP = Hz941Flag And Hz1477Flag

 

ソース内の特定部位しか書いて無いので意味不明な記事ですが・・・

後は適当にグラフはPictureBox に

        Dim BMPImage As Bitmap
        Dim WaveGraphics As Graphics
        BMPImage = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        WaveGraphics = Graphics.FromImage(BMPImage)

        For i = 0 To _WaveDataNum - 1
            Dim XScl As Integer = (PictureBox1.Width / _WaveDataNum) * i
            Dim YLen As Integer = (PictureBox1.Height / _WaveDataM_MaxVal) * _WaveDataM(i)
            If YLen > PictureBox1.Height Then
                YLen = PictureBox1.Height
            End If
            WaveGraphics.DrawLine(Pens.Blue, XScl, PictureBox1.Height - 10, XScl, PictureBox1.Height - YLen - 10)
        Next
        Dim AveLen As Integer = (PictureBox1.Height / _WaveDataM_MaxVal) * (_WaveDataM_AveVal * JudgeAveAmp)
        WaveGraphics.DrawLine(Pens.Red, 0, PictureBox1.Height - AveLen - 10, PictureBox1.Width, PictureBox1.Height - AveLen - 10)

こんな事して絵を描いているだけですね^^;

ちなみに、縦線は、それぞれDTMF の周波数を緑と紫で線をひいています。

サンプリングレートを上げる場合は、もちろん周波数分解能が低くなるので、

取得データ数を増やす必要が有ります。

処理速度を考えると、やはり11.025 kHz が安定してDTMF 検出出来る感じです。

 

結局、このプログラムで週末を完全に潰してしまった^^;

なんだかなぁ~

この部分だけソース公開するか、全部合わせてフリーソフトにするかはまた検討します。

※なにがしAPI の関係でこのソフトをどうするか検討中です^^;

 

さて、寝るぞぉ~

Scskpbx2

ようやくDTMF 解析が可能に!!

ははは~

趣味プログラムで作っていた奴、

とあるAPI から音声を取り出して、その音声にDTMF が含まれてるか確認する奴。

Scskpbx

「とあるAPI」が何なのか判明出来る画像デスが^^;

とりあえずデバッグで、マイク入力からDTMF が解析出来ました。

※この画像では「5」を判別しました

実は、とあるAPI はP2P の接続時のみAPI 標準のメソッドでDTMF が読めるのですが、

「なにがしIn」 のサービスを使っての接続時には標準のメソッドではDTMF が読めません。

その為、音声を解析してDTMF を判別しなければなりません。

コレは、まさにそのプログラム。

waveIn を利用し、オーディオデバイスから音声データを取得。

そのデータをFFT で周波数分析し、特定の周波数成分でDTMF を判断する。

さて!!

勘の良い方はもう既に何のためにコレを作っているかお分かり頂けると思うのですが^^;

このプログラムは、既にP2P 接続では下記の様な動作を行っております。

 

1.クライアント1が、マスタークライアント(等ソフトと「なにがし」が起動しているマシン)に接続する。

2.等ソフトがなにがしAPI 経由でクライアント1の接続に応答

3.クライアント1がDTMF で電話番号の様なモノを打ち込む

4.マスタークライアントが、クライアント1から入力されたDTMF の番号に何かを転送接続する

5.クライアント1と転送接続が確立される

6.クライアント1は転送接続先と何かが出来る(コミュニケーション的な・・・)

 

ココまで出来ているので、後は、P2P 接続で無い場合、自作DTMF 解析を使って同じ事をすれば・・・

上記「クライアント1」の部分が、なにがしIn の接続に変わって・・・

と、言う事が可能になる訳です。

このソフト名が「SCSKPBX」って言うんで、まさにソレですが(笑)

コレ、厳密に言うと違法らしいですね^^;

個人で楽しむ分には許してもらえないかしら~

 

ま、今回一番の努力ポイントは、waveIn の部分かな・・・

なんせVB.NET の資料が少なくて^^;

しかし、コレのおかげで、以前から作りたかった

SCPTH(ただ、マイクの音を拾ってスピーカーに垂れ流す)が作成可能になった。

コレは、イヤホンで音楽聞いてる時に、周りの音が聞こえなくなるから、

ソコにPC のマイクから音を拾ってタレ流しておけば良いかなぁ~と言う発想^^;

 

週末につまらんソフトを作ってたらこんな時間に・・・

さみしい生活だなぁ^^;

さて、もうひと頑張りしちゃおうかな!!

Twitter API 仕様書

Twitter API 仕様書

http://apiwiki.twitter.com/Twitter-API-Documentation

 

やっぱり、プログラムを作って行くと、必ず仕様書をみる事になる・・・

 

戻り値のアイテム詳細

http://apiwiki.twitter.com/Return-Values

 

個々のメソッドに対して、引数と戻り値がしっかり書かれているので、

コレを見れば後は何をやりたいか、何の情報が欲しいかはクライアントソフトを作る人次第。

 

通信部位は、前回の奴で一応そのまま使える筈ですが、

やはり、受信データのクラスは少し工夫して高機能化した方がよさそう・・・

 

色々みていると、受信データが抜けてたり、後ろが足りなかったりする事もあるらしい。

受信データのクラスは、例外処理を入れておいた方がよさそう。

 

さて・・・

明日帰国だけど、もう少し書いてみよう・・・

ステータス取得

たぶん、status って言うのが、一個の投稿アイテムだと思います。

タイムラインの取得をすると、XML のデータが送られてきます。

※"http://twitter.com/statuses/friends_timeline.xml" から取得すればXML

それを解析すると・・・

<ststus>から</ststus> までが一つのメッセージで、内容が、

 

    '<created_at>Tue Mar 09 05:15:03 +0000 2010</created_at>    
    '<id>102071*****</id>    
    '<text>**************</text>    
    '<source>&lt;a href="http://mobile.twitter.com" rel="nofollow"&gt;mobile web&lt;/a&gt;</source>    
    '<truncated>false</truncated>    
    '<in_reply_to_status_id>102071******</in_reply_to_status_id>    
    '<in_reply_to_user_id>592*****</in_reply_to_user_id>    
    '<favorited>false</favorited>    
    '<in_reply_to_screen_name>LivingForDemi</in_reply_to_screen_name>    

 

こんなん書かれてます。

また、この中に、<user> </user> の階層?もありました。

 

    '<id>914******</id>      
    '<name>**************</name>      
    '<screen_name>**************</screen_name>      
    '<location>Earth</location>      
    '<description>**************</description>      
    '<profile_image_url>**************_normal.jpg</profile_image_url>      
    '<url></url>      
    '<protected>false</protected>      
    '<followers_count>468</followers_count>      
    '<profile_background_color>352726</profile_background_color>      
    '<profile_text_color>3E4415</profile_text_color>      
    '<profile_link_color>D02B55</profile_link_color>      
    '<profile_sidebar_fill_color>99CC33</profile_sidebar_fill_color>      
    '<profile_sidebar_border_color>829D5E</profile_sidebar_border_color>      
    '<friends_count>101</friends_count>      
    '<created_at>Sat Nov 21 03:01:16 +0000 2009</created_at>      
    '<favourites_count>124</favourites_count>      
    '<utc_offset>-32400</utc_offset>      
    '<time_zone>Alaska</time_zone>      
    '<profile_background_image_url**************bg.gif</profile_background_image_url>      
    '<profile_background_tile>false</profile_background_tile>      
    '<notifications></notifications>      
    '<geo_enabled>false</geo_enabled>      
    '<verified>false</verified>      
    '<following></following>      
    '<statuses_count>6667</statuses_count>      
    '<lang>en</lang>      
    '<contributors_enabled>false</contributors_enabled>    

 

こんな感じで、ユーザーのデータが載ってます。

一個の投稿の中には、

<status>

  <id> </id>

  ・・・ 投稿情報、省略

  <user>

    <id> </id>

    ・・・ ユーザー情報、省略

  </user>

</status>

ってなってると思います。

たぶん・・・

送られてきたデータを見るからには、こんな感じでした。

 

なんで、後はタグ毎に必要なデータをピックアップするだけ。

 

手順的には、

前回の通信関数からByRef で戻って来るHttpWebResponse から System.IO.Stream を取得する。

Stream = _WebResponce.GetResponseStream

そこからByte データを取得する。

Do
                result = Stream.ReadByte()
                If result = -1 Then Exit Do ' ReadByte returns -1 at EOF.
                DownDataAL.Add(CByte(result))
Loop

コレで、データ取り込みは完了。

Byte 配列のデータをエンコードし、 System.Xml.Linq.XElement データにする。

AllStatusXElement = System.Xml.Linq.XElement.Parse(System.Text.Encoding.UTF8.GetString(DownData))

コレで、プログラム内で、statusがXElement で利用可能になる。

私の場合は、XElement ではいまいちピンとこない為、

※使い方も良く解らんし、意味すら分からないから・・・

このデータをクラス化して、一つのstatus をクラスに分割してます。

            TwitterStatusAL.Clear()
            For Each StatusXElement As XElement In AllStatusXElement...<status>
                TwitterStatusAL.Add(New clsTwitterStatus(StatusXElement))
            Next
※StatusXElement が一つのstatus のXElement

  AllStatusXElement...<status>  コレで、分けてる。

コレで作ったclsTwitterStatus の配列を、ListBox 等に表示すれば、とりあえずタイムラインは表示できます。

        If _TeitterCom.GetTimeline(TwitterStatus) Then
            For ii = 0 To TwitterStatus.Length - 1
                ListBox.Items.Add(TwitterStatus(ii).User.Name & " , " & _
                                  TwitterStatus(ii).Text & " , " & _
                                  TwitterStatus(ii).CreatedAt)
            Next
        End If
 

では、ソース・・・

 

Ststus Class と、User Class

#Region "Status Class"
Public Class clsTwitterStatus
    Private _StatusXElement As XElement
    Private _User As clsTwitterUser

    Public Sub New(ByVal __XElement As XElement)
        _StatusXElement = __XElement
        _User = New clsTwitterUser(UserXElement)
    End Sub
    Public Function StatusXElement() As XElement
        Return _StatusXElement
    End Function
    Public Function UserXElement() As XElement
        Return _StatusXElement.Element("user")
    End Function
    Public Function User() As clsTwitterUser
        Return _User
    End Function

    Public ReadOnly Property CreatedAt() As String
        Get
            Return _StatusXElement...<created_at>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property StatusID() As String
        Get
            Return _StatusXElement...<id>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property Text() As String
        Get
            Return _StatusXElement...<text>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property InReplyToStatusId() As String
        Get
            Return _StatusXElement...<in_reply_to_status_id>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property InReplyToUserId() As String
        Get
            Return _StatusXElement...<in_reply_to_user_id>.Value.ToString()
        End Get
    End Property
End Class
#End Region
#Region "User Class"
Public Class clsTwitterUser
    Private _UserXElement As XElement

    Public Sub New(ByVal __XElement As XElement)
        _UserXElement = __XElement
    End Sub
    Public Function UserXElement() As XElement
        Return _UserXElement
    End Function

    Public ReadOnly Property UserID() As String
        Get
            Return _UserXElement...<id>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property Name() As String
        Get
            Return _UserXElement...<name>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property ScreenName() As String
        Get
            Return _UserXElement...<screen_name>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property Location() As String
        Get
            Return _UserXElement...<location>.Value.ToString()
        End Get
    End Property
    Public ReadOnly Property ProfileImageUrl() As String
        Get
            Return _UserXElement...<profile_image_url>.Value.ToString()
        End Get
    End Property
End Class
#End Region

 

ステータスを受信・解析する関数

    Private Function ReciveStatus(ByVal _WebResponce As System.Net.HttpWebResponse) As clsTwitterStatus()
        Dim Stream As System.IO.Stream
        Dim result As Integer
        Dim DownDataAL As ArrayList
        Dim DownData As Byte()
        Dim AllStatusXElement As System.Xml.Linq.XElement
        Dim TwitterStatusAL As ArrayList
        Dim TwitterStatus As clsTwitterStatus()

        If _WebResponce Is Nothing Then
            Return Nothing
        End If

        Try
            Stream = _WebResponce.GetResponseStream
            If Stream Is Nothing Then
                Return Nothing
            End If
            DownDataAL = New ArrayList
            DownDataAL.Clear()
            Do
                result = Stream.ReadByte()
                If result = -1 Then Exit Do ' ReadByte returns -1 at EOF.
                DownDataAL.Add(CByte(result))
            Loop
            ReDim DownData(DownDataAL.Count - 1)
            Array.Copy(DownDataAL.ToArray, DownData, DownDataAL.Count)
            AllStatusXElement = System.Xml.Linq.XElement.Parse(System.Text.Encoding.UTF8.GetString(DownData))
            TwitterStatusAL = New ArrayList
            TwitterStatusAL.Clear()
            For Each StatusXElement As XElement In AllStatusXElement...<status>
                TwitterStatusAL.Add(New clsTwitterStatus(StatusXElement))
            Next
            ReDim TwitterStatus(TwitterStatusAL.Count - 1)
            Array.Copy(TwitterStatusAL.ToArray, TwitterStatus, TwitterStatusAL.Count)
            Return TwitterStatus
        Catch ex As Exception
            Return Nothing
        End Try
    End Function

 

通信関数と、ステータス受信関数を合わせて、タイムラインの取得を行う為の関数。

    Public Function GetTimeline(ByRef _TwitterStatus As clsTwitterStatus()) As Boolean
        Dim WebResponce As System.Net.HttpWebResponse
        If TwitterComm(__Friends_Timeline_URL, enumHttpReqMode.HTRM_GET, WebResponce) Then
            _TwitterStatus = ReciveStatus(WebResponce)
            If _TwitterStatus Is Nothing Then
                Return False
            End If
            Return True
        Else
            Return False
        End If
        Return False
    End Function
 

ソレの利用法(フォームのボタンでListBox にタイムラインを表示させる)

    Private Sub btnRefersh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefersh.Click
        Dim TwitterStatus As clsTwitterStatus()
        Dim ii As Integer
        If _TeitterCom.GetTimeline(TwitterStatus) Then
            For ii = 0 To TwitterStatus.Length - 1
                ListBox.Items.Add(TwitterStatus(ii).User.Name & " , " & _
                                  TwitterStatus(ii).Text & " , " & _
                                  TwitterStatus(ii).CreatedAt)
            Next
        End If
    End Sub

 

とりあえず、こんな感じでTwitter API からのデータ取得は出来ると思います。

私はデータをクラスにしましたが、本来はそのままXElement で使っても良いと思いますが、

いかんせん、XML なんて使った事無いんでクラスにしました。

また、クラス化すると、コーディング時にプロパティやメソッドが選択出来るようになるので楽ですよね。

 

後は、このクラスをどうやって保存しておくか・・・

データベースに入れればUser のID とか、Status ID とか検索しやすくなるかなぁ???

ま、また適当に考えてみて書いてみます。

Twitter API 通信部位

自作Twitter クライアントは、「s14twi」と言う名前で作ります。

Twitter ID そのままですが・・・

 

さて、通信部位。

通常のタイムライン読み込みや、ステータスアップデートは、Basic 認証 で可能ですが、

「via s14twi」を出させるには、OAuth プロトコルでステータスアップデートしなければなりません。

そこで、OAuthLib(http://oauthlib.codeplex.com/)を使ってみる事にしました。

このAuth を利用するには、Consumer key とConsumer secret をTwitter から発行して貰わなければなりません。

ココ(http://twitter.com/apps/new)からアプリケーション登録の手続きをするともらえます。

その後、このConsumer key とConsumer secret を実装してAccessToken を取得します。

コレは、OAuthLib でRequestAccessToken を行います。

 

            PostConsumer = New OAuthLib.Consumer(_Setting.ConsumerKey, _Setting.ConsumerSecret)
            ReqToken = PostConsumer.ObtainUnauthorizedRequestToken(__RequestTokenURL, __AuthorizationRealm)
            Process.Start(Consumer.BuildUserAuthorizationURL(__AuthorizeURL, ReqToken))
 

ココの、処理で、ブラウザにKey が表示されるので、

そのKey を(TempStr がソレ)

AccToken = PostConsumer.RequestAccessToken(TempStr, ReqToken, __AccessTokenURL, __AuthorizationRealm)

この様に関数に渡すとAccessToken が取得できます。

 

AccessTokenは、TokenSecret と、TokenValue の二つの値で構成されています。

よって、OAuthLib でTwitter と通信するには、

 

Consumer key

Consumer secret

TokenSecret

TokenValue

 

この四つの値(String 型)を何処かに保存しておかないと不便です。

(AccessToken を毎回取得するのもめんどくさいし・・・)

 

なんか、Blog に上手くソースを表示させる手段は無いのだろうか???

 

とりあえず、AccessToken 取得関数

#Region "アクセストークン取得"
    Public Function SetAccTokenKey() As Boolean
        Dim PostConsumer As OAuthLib.Consumer
        Dim ReqToken As OAuthLib.RequestToken

        'Consumer 系の値が設定されているかチェック
        If _Setting.ConsumerKey = "" Or _Setting.ConsumerSecret = "" Then
            Return False
        End If

        Try
            PostConsumer = New OAuthLib.Consumer(_Setting.ConsumerKey, _Setting.ConsumerSecret)
            ReqToken = PostConsumer.ObtainUnauthorizedRequestToken(__RequestTokenURL, __AuthorizationRealm)
            Process.Start(Consumer.BuildUserAuthorizationURL(__AuthorizeURL, ReqToken))
        Catch ex As Exception
            Return False
        End Try

        'Web ページが開かれて、Access Token Key が表示されているはず。
        'InputBox でそのKey を入れてもらい、Setting に保存する。
        Dim TempStr As String = InputBox("Plese Input Access Token Key", "Input Key")
        If TempStr <> "" Then
            Dim AccToken As OAuthLib.AccessToken
            Try
                AccToken = PostConsumer.RequestAccessToken(TempStr, ReqToken, __AccessTokenURL, __AuthorizationRealm)
            Catch ex As Exception
                Return False
            End Try
            _Setting.AccTokenSecret = AccToken.TokenSecret
            _Setting.AccTokenValue = AccToken.TokenValue
        Else
            Return False
        End If
        Return True
    End Function
#End Region

 

Const 系 とEnum 系

#Region "Const"
    Private Const __Public_Timeline_URL As String = "http://twitter.com/statuses/public_timeline.xml"
    Private Const __Friends_Timeline_URL As String = "http://twitter.com/statuses/friends_timeline.xml"
    Private Const __Replies_URL As String = "http://twitter.com/statuses/replies.xml"
    Private Const __Direct_Messages_URL As String = "http://twitter.com/direct_messages.xml"
    Private Const __Rate_Limit_Status_URL As String = "http://twitter.com/account/rate_limit_status.xml"
    Private Const __Status_Update_URL As String = "http://twitter.com/statuses/update.xml"

    Private Const __RequestTokenURL As String = "http://twitter.com/oauth/request_token"
    Private Const __AccessTokenURL As String = "http://twitter.com/oauth/access_token"
    Private Const __AuthorizeURL As String = "http://twitter.com/oauth/authorize"
    Private Const __AuthorizationRealm As String = "http://twitter.com"

    Private Const __DummyParamName As String = "since_id"
    Private Const __DummyParamValue As String = "your since id"
#End Region

#Region "Enum"
Public Enum enumAuthorizationType As Integer
    AUTH_Unknown = 0
    AUTH_OAuth = 1
    AUTH_Basic = 2
End Enum
Public Enum enumHttpReqMode As Integer
    HTRM_Unknown = 0
    HTRM_GET = 1
    HTRM_POST = 2
End Enum
#End Region

 

そして、通信本体。

 

#Region "通信本体"
    Private Function TwitterComm(ByVal _URLStr As String, ByVal _ReqMode As enumHttpReqMode, ByRef _WebResponse As System.Net.HttpWebResponse) As Boolean
        Dim Params() As OAuthLib.Parameter
        ReDim Params(0)
        Params(0) = New OAuthLib.Parameter(__DummyParamName, __DummyParamValue)
        Return TwitterComm(_URLStr, _ReqMode, _WebResponse, Params)
    End Function
    Private Function TwitterComm(ByVal _URLStr As String, ByVal _ReqMode As enumHttpReqMode, ByRef _WebResponse As System.Net.HttpWebResponse, ByVal _Params() As OAuthLib.Parameter) As Boolean
        Dim ReqStr As String
        Dim ReqMessage As String

        '値のチェックもココで
        ReqStr = ""
        ReqMessage = ""
        Select Case _ReqMode
            Case enumHttpReqMode.HTRM_GET
                ReqStr = "GET"
                'OAuth 認証の場合、ココでメッセージを削除
                If _Setting.AuthorizationType = enumAuthorizationType.AUTH_OAuth Then
                    _Params = Nothing
                End If
            Case enumHttpReqMode.HTRM_POST
                ReqStr = "POST"
                'Params が有るかどうか???
                If _Params Is Nothing Then
                    Return False
                End If
                If _Params(0).Name = "__DummyParamName" Then    '値がDummy だった場合
                    Return False
                End If

                'Basic 認証の場合、ココでメッセージを作成
                If _Setting.AuthorizationType = enumAuthorizationType.AUTH_Basic Then
                    Dim ii As Integer
                    ReqMessage = ""
                    For ii = 0 To _Params.Length - 1
                        ReqMessage = ReqMessage & _Params(ii).Name & "=" & _Params(ii).Value & "&"
                    Next
                    ReqMessage = ReqMessage.Remove(ReqMessage.Length - 1, 1)
                End If
            Case enumHttpReqMode.HTRM_Unknown
                Return False
            Case Else
                Return False
        End Select

        '認証タイプ
        Select Case _Setting.AuthorizationType
            Case enumAuthorizationType.AUTH_OAuth
                Dim PostConsumer As OAuthLib.Consumer
                Dim AccToken As OAuthLib.AccessToken

                'Consumer 系の値が設定されているかチェック
                If _Setting.ConsumerKey = "" Or _Setting.ConsumerSecret = "" Then
                    Return False
                End If
                '取得したAccessToken が有るかどうかチェック
                If _Setting.AccTokenValue = "" Or _Setting.AccTokenSecret = "" Then
                    Return False
                End If
                PostConsumer = New OAuthLib.Consumer(_Setting.ConsumerKey, _Setting.ConsumerSecret)
                AccToken = New OAuthLib.AccessToken(_Setting.AccTokenValue, _Setting.AccTokenSecret)
                Try
                    _WebResponse = PostConsumer.AccessProtectedResource(AccToken, _URLStr, ReqStr, __AuthorizationRealm, _Params)
                Catch ex As System.Net.WebException
                    'Web 系エラー発生
                    Return False
                Catch ex As Exception
                    'エラー発生
                    Return False
                End Try

            Case enumAuthorizationType.AUTH_Basic
                Dim Request As System.Net.WebRequest
                Try
                    Request = System.Net.HttpWebRequest.Create(_URLStr)
                    Request.ContentType = "application/x-www-form-urlencoded"
                    Request.Headers.Add("Authorization: Basic " & Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(_Setting.UserID & ":" & _Setting.Password)))
                    Request.Method = ReqStr

                    'Twitterのサーバーが「100-Continue 」ヘッダをサポートしていないので、これを設定しないと「417」ステータスコードが返ってくるらしい
                    System.Net.ServicePointManager.Expect100Continue = False
                    If _ReqMode = enumHttpReqMode.HTRM_GET Then
                        _WebResponse = Request.GetResponse
                    ElseIf _ReqMode = enumHttpReqMode.HTRM_POST Then
                        Dim PostStream As System.IO.Stream
                        Dim PostBytes() As Byte
                        PostBytes = System.Text.Encoding.ASCII.GetBytes(Uri.EscapeUriString(ReqMessage))
                        Request.ContentLength = PostBytes.Length
                        PostStream = Request.GetRequestStream
                        PostStream.Write(PostBytes, 0, PostBytes.Length)
                        _WebResponse = Request.GetResponse
                    End If
                Catch ex As System.Net.WebException
                    'Web 系エラー発生
                    Return False
                Catch ex As Exception
                    'エラー発生
                    Return False
                End Try
            Case enumAuthorizationType.AUTH_Unknown
                Return False
            Case Else
                Return False
        End Select

        '最後にWebResponse の確認
        If _WebResponse.StatusCode = Net.HttpStatusCode.OK Then
            Return True
        End If
        Return False
    End Function
#End Region

また変更が有ったらUP してみます。