Problem
このレシピは HARK が標準でサポートしている ALSA(Advanced Linux Sound Architecture) 準拠のデバイス, System In Fronteir, Inc. RASP シリーズ, 東京エレクトロンデバイス製 TD-BD-16ADUSB 以外のデバイスを 使って音響信号をキャプチャしたいときに読む.
Solution
上記の3種以外のデバイスを使ってキャプチャするためには,デバイスに 対応したモジュールを自作する必要がある. もちろん,AudioStreamFromMic と全く異なる方法でも実装は可能である. しかし,ここでは AudioStreamFromMic を拡張する方法を述べる.
作成手順は大きく以下のようになる.
デバイスに対応したクラス “NewDeviceRecorder” を作成し,そのソースファイル とヘッダーファイルをHARK ディレクトリ内の librecorder ディレクトリに置く.
作成したクラスを使えるよう AudioStreamFromMic を書き変える.
コンパイルするため,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 = NewDeviceRecorderGetInstance();
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 = NewDeviceRecorderGetInstance();
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->BufferedNodeinitialize();
}
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);
RecorderBUFFER_STATE state;
do {
usleep(5000);
state = recorder->ReadBuffer(0, length, buffer.begin());
} while (state != RecorderOK); // 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);
RecorderBUFFER_STATE state;
for (;;) {
state = recorder->ReadBuffer((count - 1) * advance + length,
advance, buffer.begin());
if (state == RecorderOK) {
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* NewDeviceRecorderinstance = 0;
// This function is executed in another thread, and
// records acoustic signals into circular buffer.
void NewDeviceRecorderoperator()()
{
for(;;){
// wait during less than (read_buf_size/sampling_rate) [ms]
usleep(sleep_time);
mutexscoped_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;
mutexscoped_lock lk(mutex_buffer);
buffer.insert(buffer.end(), read_buf.begin(), read_buf.begin() + buff_len);
lk.unlock();
}
}
}
int NewDeviceRecorderInitialize(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 クラス等の ソースコードを参考にすると良い.