4.6 別のA/D変換器を使いたい。

Problem

このレシピは HARK が標準でサポートしている ALSA(Advanced Linux Sound Architecture) 準拠のデバイス, System In Frontier, Inc. RASP シリーズ, 東京エレクトロンデバイス製 TD-BD-16ADUSB 以外のデバイスを 使って音響信号をキャプチャしたいときに読む.

Solution

上記の3種以外のデバイスを使ってキャプチャするためには,デバイスに 対応したモジュールを自作する必要がある. もちろん,AudioStreamFromMic と全く異なる方法でも実装は可能である. しかし,ここでは AudioStreamFromMic を拡張する方法を述べる.

作成手順は大きく以下のようになる.

  1. デバイスに対応したクラス “NewDeviceRecorder” を作成し,そのソースファイル とヘッダーファイルをHARK ディレクトリ内の librecorder ディレクトリに置く.

  2. 作成したクラスを使えるよう AudioStreamFromMic を書き変える.

  3. コンパイルするため,Makefile.am, configure.in を書き変える.

(a)では,デバイスからデータを取り込みバッファに送るクラスを作成する. このクラスは Recorder クラスを継承して実装する.デバイスを使用するため の準備等を行う Initialize とデバイスからデータを取り出す operator() メソッドを実装する.

(b)では,オプションに “NewDevice” が指定された時の処理を記述する. 具体的には,コンストラクタ内に NewDevice の宣言,初期化,および シグナルの設定を行う.

(c)では,新しく追加したファイルに対応し,Makefile.am, configure.inなどを変更 する.

以下に示すのは新しいデバイス(NewDevice)をサポートする AudioStreamFromMic 2のサンプルである.


#include "BufferedNode.h"
#include "Buffer.h"
#include "Vector.h"
#include <climits>
#include <csignal>


#include <NewDeviceRecorder.hpp> // Point1: 必要なヘッダファイルを読み込む

using namespace std;
using namespace FD;

class AudioStreamFromMic2;

DECLARE_NODE( AudioStreamFromMic2);
/*Node
 *
 * @name AudioStreamFromMic2
 * @category MyHARK
 * @description This node captures an audio stream using microphones and outputs frames.
 *
 * @output_name AUDIO
 * @output_type Matrix<float>
 * @output_description Windowed wave form. A row index is a channel, and a column index is time.
 *
 * @output_name NOT_EOF
 * @output_type bool
 * @output_description True if we haven't reach the end of file yet.
 *
 * @parameter_name LENGTH
 * @parameter_type int
 * @parameter_value 512
 * @parameter_description The length of a frame in one channel (in samples).
 *
 * @parameter_name ADVANCE
 * @parameter_type int
 * @parameter_value 160
 * @parameter_description The shift length beween adjacent frames (in samples).
 *
 * @parameter_name CHANNEL_COUNT
 * @parameter_type int
 * @parameter_value 16
 * @parameter_description The number of channels.
 *
 * @parameter_name SAMPLING_RATE
 * @parameter_type int
 * @parameter_value 16000
 * @parameter_description Sampling rate (Hz).
 *
 * @parameter_name DEVICETYPE // Point2-1: 使うデバイスのタイプを加える
 * @parameter_type string
 * @parameter_value NewDevice
 * @parameter_description Device type.
 *
 * @parameter_name DEVICE // Point2-2: 使うデバイスの名前を加える
 * @parameter_type string
 * @parameter_value /dev/newdevice
 * @parameter_description The name of device.
 END*/

// Point3: 録音を途中で停止させるための処理を記述する
void sigint_handler_newdevice(int s)
{
  Recorder* recorder = NewDeviceRecorder::GetInstance();
  recorder->Stop();
  exit(0);
}

class AudioStreamFromMic2: public BufferedNode {
  int audioID;
  int eofID;
  int length;
  int advance;
  int channel_count;
  int sampling_rate;
  string device_type;
  string device;
  Recorder* recorder;
  vector<short> buffer;

public:
  AudioStreamFromMic2(string nodeName, ParameterSet params) :
    BufferedNode(nodeName, params), recorder(0) {
    audioID = addOutput("AUDIO");
    eofID = addOutput("NOT_EOF");

    length = dereference_cast<int> (parameters.get("LENGTH"));
    advance = dereference_cast<int> (parameters.get("ADVANCE"));
    channel_count = dereference_cast<int> (parameters.get("CHANNEL_COUNT"));
    sampling_rate = dereference_cast<int> (parameters.get("SAMPLING_RATE"));
    device_type = object_cast<String> (parameters.get("DEVICETYPE"));
    device = object_cast<String> (parameters.get("DEVICE"));

// Point4: デバイスタイプに対応したレコーダクラスを作成する
    if (device_type == "NewDevice") {
      recorder = NewDeviceRecorder::GetInstance();
      recorder->Initialize(device.c_str(), channel_count, sampling_rate, length * 1024);
    } else {
      throw new NodeException(NULL, string("Device type " + device_type + " is not supported."), 
                              __FILE__, __LINE__);
    }
    inOrder = true;
  }

  virtual void initialize() {
    outputs[audioID].lookAhead 
      = outputs[eofID].lookAhead
      = 1 + max(outputs[audioID].lookAhead, outputs[eofID].lookAhead);
    this->BufferedNode::initialize();
  }

  virtual void stop() {
    recorder->Stop();
  }

  void calculate(int output_id, int count, Buffer &out) {
    Buffer &audioBuffer = *(outputs[audioID].buffer);
    Buffer &eofBuffer = *(outputs[eofID].buffer);
    eofBuffer[count] = TrueObject;
    RCPtr<Matrix<float> > outputp(new Matrix<float> (channel_count, length));
    audioBuffer[count] = outputp;
    Matrix<float>& output = *outputp;

    if (count == 0) { //Done only the first time
      recorder->Start();
      buffer.resize(length * channel_count);

      Recorder::BUFFER_STATE state;
      do {
        usleep(5000);
        state = recorder->ReadBuffer(0, length, buffer.begin());
      } while (state != Recorder::OK); // original

      convertVectorToMatrix(buffer, output, 0);

    } else { // Normal case (not at start of file)

      if (advance < length) {

        Matrix<float>& previous = object_cast<Matrix<float> > (

          for (int c = 0; c < length - advance; c++) {
            for (int r = 0; r < output.nrows(); r++) {
              output(r, c) = previous(r, c + advance);
            }
          }
      } else {

        for (int c = 0; c < length - advance; c++) {
          for (int r = 0; r < output.nrows(); r++) {
            output(r, c) = 0;
          }
        }
      }

      buffer.resize(advance * channel_count);
      Recorder::BUFFER_STATE state;

      for (;;) {

        state = recorder->ReadBuffer((count - 1) * advance + length,
				     advance, buffer.begin());
        if (state == Recorder::OK) {
          break;
        } else {
          usleep(5000);
        }
      }

      int first_output = length - advance;
      convertVectorToMatrix(buffer, output, first_output);
    }

    bool is_clipping = false;
    for (int i = 0; i < buffer.size(); i++) {
      if (!is_clipping && checkClipping(buffer[i])) {
        is_clipping = true;
      }
    }
    if (is_clipping) {
      cerr << "[" << count << "][" << getName() << "] clipping" << endl;
    }
  }

protected:

  void convertVectorToMatrix(const vector<short>& in, Matrix<float>& out,
			     int first_col) {
    for (int i = 0; i < out.nrows(); i++) {
      for (int j = first_col; j < out.ncols(); j++) {
        out(i, j) = (float) in[i + (j - first_col) * out.nrows()];
      }
    }
  }

  bool checkClipping(short x) {
    if (x >= SHRT_MAX || x <= SHRT_MIN) {
      return true;
    } else {
      return false;
    }
  }

};

次に示すのは,Recorder クラスを NewDevice が扱えるように 派生させたクラスのソースコードの概略である.initialize 関数 でデバイスとの接続を行い,() オペレータにデバイスからデータを 読み込む処理を記述する.このソースコード(NewDeviceRecorder.cpp) は librecorder フォルダ内に作成する.

#include "NewDeviceRecorder.hpp"

using namespace boost;

NewDeviceRecorder* NewDeviceRecorder::instance = 0;

// This function is executed in another thread, and
// records acoustic signals into circular buffer.
void NewDeviceRecorder::operator()()
{
  
  for(;;){

    // wait during less than (read_buf_size/sampling_rate) [ms]
    usleep(sleep_time);
    
    mutex::scoped_lock lk(mutex_buffer);

    if (state == PAUSE) {
      continue;
    }
    else if (state == STOP) {
      break;
    }
    else if (state == RECORDING) {
      lk.unlock();

      // Point5: ここにデバイスからデータを読み込む処理を記述する
      read_buf = receive_data;
      // Point6: これまでに読み込んだデータに対応する時間だけ cur_time を進める
      cur_time += timelength_of_read_data;


      mutex::scoped_lock lk(mutex_buffer);
      buffer.insert(buffer.end(), read_buf.begin(), read_buf.begin() + buff_len);

      lk.unlock();
    }
  }

}

int NewDeviceRecorder::Initialize(const string& device_name, int chan_count, int samp_rate, size_t buf_size)
{

// Point7: 使用する変数やデバイスを初期化する処理を記述する
  new_device_open_function

}

Discussion

AudioStreamFromMic モジュールは Recorder クラス が持つバッファの内容を読み込む処理だけをしており,デバイス とのデータのやり取りは Recorder クラスから派生した各クラス (ALSARecorder, ASIORecorder, WSRecorder) が行っている.そのため,これらの派生クラスに変わるクラス (NewDeviceRecorder)を実装することで,新たなデバイスを サポートすることができる.

See Also

ノードの作り方の詳細は13.1節「ノードを作りたい」に記述されている. NewDeviceRecorder クラスの作り方は,例えば ALSARecorder クラス等の ソースコードを参考にすると良い.