読者です 読者をやめる 読者になる 読者になる

すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

oFでVoIPっぽいもの

こんにちは.1月もそろそろ終わりです. また卒論の季節ですねぇ.どうも1000chです.

大量のマイクを有線でつなげんのめんどくせーなー.ということでiPhoneをワイヤレスマイクっぽく使えないかな?と思いました. てなわけでmac上のoFに対しワイヤレスで音声入力させてみたー.

つくりたいもの

iPhoneのマイクへの入力をUDPmacに送信,再生するよ.

参考にするサンプル

oFのサンプルを参考に実装してみました.参考にしたのは以下.

  • sound/audioInputExample
  • sound/audioOutputExample

音声入力をビジュアライズするinput,正弦波をジェネレートして再生するoutput. 音声取得と再生部分のコードを参考にします.

  • addons/networkUdpSenderExample
  • addons/networkUdpRecieverExample

UDPでの通信サンプル.iPhoneからmacへのデータ送信に使います. 音声通信なのでTCPでなくUDPにしました.

コード

iPhone

iPhone側.マイク入力をUDPで送信するところまでのコード.

//header

#pragma once

#include "ofMain.h"
#include "ofxiOS.h"
#include "ofxiOSExtras.h"

#include "ofxNetwork.h"

class testApp : public ofxiOSApp{
    
    public:
        void setup();
        void update();
        void draw();
        void exit();
    
        void touchDown(ofTouchEventArgs & touch);
        void touchMoved(ofTouchEventArgs & touch);
        void touchUp(ofTouchEventArgs & touch);
        void touchDoubleTap(ofTouchEventArgs & touch);
        void touchCancelled(ofTouchEventArgs & touch);

        void lostFocus();
        void gotFocus();
        void gotMemoryWarning();
        void deviceOrientationChanged(int newOrientation);
 
    //sound
    void audioIn(float * input, int bufferSize, int nChannels);

    int initialBufferSize;
    int sampleRate;
    float * buffer;
    int drawCounter;
    int bufferCounter;
    
    // network
    ofxUDPManager udpConnection;
};

続いて実装.重要なとこ抜粋.

//implement

void testApp::setup(){
    
    ofSetVerticalSync(true);
    ofSetFrameRate(60);
    
    //sound
    initialBufferSize = 512;
    sampleRate = 44100;
    buffer = new float[initialBufferSize];
    memset(buffer, 0, initialBufferSize * sizeof(float));
    ofSoundStreamSetup(0, 1, this, sampleRate, initialBufferSize, 1);
    
    //network
    int port = 12346;
    udpConnection.Create();
    udpConnection.Connect("192.168.1.87", port);
    udpConnection.SetNonBlocking(true);
    
    // draw
    drawCounter = 0;
    bufferCounter = 0;
    
}

void testApp::audioIn(float *input, int bufferSize, int nChannels){

        if(initialBufferSize < bufferSize){
        ofLog(OF_LOG_ERROR, "your buffer size was set to %i - but the stream needs a buffer size of %i", initialBufferSize, bufferSize);
    }   

    // udp messege
    string message = "";
    int minBufferSize = MIN(initialBufferSize, bufferSize);
    for(int i=0; i<minBufferSize; i++) {
        cout << input[i] << endl;
        buffer[i] = input[i];
        message += ofToString(input[i])+",";
    }
    
    bufferCounter++;
    udpConnection.Send(message.c_str(), message.length());
     
}

なんとこれだけです.注意すべきはaudioIn.ヘッダで定義してあると,デバイスが音声入力を感知するたびにaudioIn関数が呼ばれます.setup内のofSoundStreamSetupで設定したbuffersizeの音声データが利用できます.

そしてその受信したデータをカンマで区切ったstring化.UDPManagerのsendメソッドにより,setup内で指定したhost/portにデータを送れます.

mac

では次に受信データのパース・再生部分を見てみましょう.

// header

#pragma once

#include "ofMain.h"

#include "ofxNetwork.h"

class testApp : public ofBaseApp{

    public:
        void setup();
        void update();
        void draw();

        void keyPressed(int key);
        void keyReleased(int key);
        void mouseMoved(int x, int y );
        void mouseDragged(int x, int y, int button);
        void mousePressed(int x, int y, int button);
        void mouseReleased(int x, int y, int button);
        void windowResized(int w, int h);
        void dragEvent(ofDragInfo dragInfo);
        void gotMessage(ofMessage msg);
    
    
    // sound
    void audioOut(float *input, int bufferSize, int nChannels);
    
    ofSoundStream soundStream;
    float   pan;
    int     bufferSize;
    int     sampleRate;
    float   volume;
    
    vector<float> lAudio;
    vector<float> rAudio;
    
    // network
    ofxUDPManager udpConnection;
};

続いて実装ファイル.

void testApp::setup(){
    
    ofSetVerticalSync(true);
    ofSetFrameRate(60);
    
    //sound
    bufferSize      = 512;
    sampleRate      = 44100;
    volume          = 0.5f;
    pan             = 0.5;
    
    lAudio.assign(bufferSize, 0.0);
    rAudio.assign(bufferSize, 0.0);
    
    soundStream.setup(this, 2, 0, sampleRate, bufferSize, 4);
   
    // network
    
    udpConnection.Create();
    udpConnection.Bind(12345);
    udpConnection.SetNonBlocking(true);
    
}

void testApp::update(){
    
    char udpMessage[100000];
    udpConnection.Receive(udpMessage, 100000);
    string message = udpMessage;
    if (message != "") {
        vector<string> stringValue = ofSplitString(message, ",");
        cout << "recieve message. length:" << stringValue.size() << endl;
        for(unsigned int i=0;i<stringValue.size();i++){
            lAudio[i] = atof(stringValue[i].c_str());
            rAudio[i] = atof(stringValue[i].c_str());
        }
    }
}

void testApp::draw(){

    ofPushStyle();
    ofSetColor(0);
    ofSetLineWidth(2);
    
    float y1 = ofGetHeight() * 0.5;
    ofLine(0, y1, ofGetWidth(), y1);
    
    int bufferSize = 512;
    for(int i=0; i<bufferSize; i++){
        float p = i / (float)(bufferSize-1);
        float x = p * ofGetWidth();
        float y2 = y1 + lAudio[i] * 200;

        ofLine(x, y1, x, y2);
    }
    ofPopStyle();
}

void testApp::audioOut(float * output, int bufferSize, int nChannels){
    float leftScale = 1 - pan;
    float rightScale = pan;
    
    for (int i=0; i<bufferSize; i++) {
        output[i*nChannels    ] = lAudio[i] * leftScale  * volume;
        output[i*nChannels + 1] = rAudio[i] * rightScale * volume;
    }
}

ちょっとやること増えました.とはいえdrawはビジュアライズだけなのでなくても可(というかサンプルのコピペ).ポイントはupdateとaudioOutです.

updateではループ毎にUDPManagerがrecieveメソッドを呼んでいます.受信データがあるときのみカンマ区切りをパースし,atofでfloatに直してvectorに格納していきます.

audioOutはbuffersize分のデータを再生するごとに呼ばれます.最初よくわからなかったのですが,引数のoutputにデータを格納するだけで自動的に再生してくれるみたいです.今回はl/rチャンネルのデータをそのまま格納しています.

まとめ

通信に問題がなければ,これでVoIPっぽいものができるはずです. 動かしてみた感じ,ちょっと音質的にどーよ?って感じがします. updateでrecieveが呼ばれるタイミングとbuffersizeが合ってないのかな?と思うのですが,原因はよくわからないです. UDPのrecieveをイベントドリブンにしたコードにだとよりよいのかな.データの圧縮などもやってないので,そこも合わせるとよりよくなるのではないかと思います.

これ使ってなにしようかな笑