任天堂おしゃべりフラワーのしゃべりを網羅する?

C++ Builder CE サウンドプログラミング post-triggerによる非同期Mic入力録音の試み その1

前記事

で触れた。”ランダムにしゃべるので、すべてのおしゃべりを動画に記録するのはすこし工夫がいるかな。”の件ですが。高速度ビデオ記録とかで行うpost-triggerの技法でなんとかランダム現象を捉えようというものです。第1回は、普通の録音です。動画の記録は意味がないので、音声のみとします。まずは検索ですね。で、Edge経由にBing君にうかがうと、例えば

windows mic 録音 c++ builder

等の検索ワード入れると、しばらく間があって、何か言ってきました。冒頭は、

なるほど、Windows環境でC++ Builderを使ってマイク録音を行いたいのですね。
C++ Builderでは、MMSystem API や Media Foundation、あるいは DirectSound を使って録音できます。
ここでは、比較的シンプルな WinMM API(waveIn系関数) を使った例を紹介します。
________________________________________
🎯 基本の流れ
1.	waveInOpen でマイクデバイスを開く
2.	waveInPrepareHeader で録音バッファを準備
3.	waveInStart で録音開始
4.	コールバックでデータを受け取り、ファイルに保存
5.	waveInClose で終了処理
________________________________________
💻 サンプルコード(C++ Builder / VCL)
Cpp
#include <vcl.h>
#include <mmsystem.h>
#pragma hdrstop
#pragma comment(lib, "winmm.lib")

HWAVEIN hWaveIn;
WAVEHDR WaveInHdr;
short buffer[44100]; // 1秒分の16bitモノラル
以下略

ま、とりあえず試してみましょう。珍しくも動きましたよ。ぼぼまんまのソースでね。だんだんCopilot君もお利口になってきたのかな?(笑)読者の環境で同じ結果が得られるかは不明なので、一応ソースを掲載します。断っておきますが、今回に限って筆者はほぼ1行もコードを書いていません。(ま、プロトタイプ宣言を1行だけ足しましたけどね。)まず、フォームは、

StartとStopというボタン2個だけ置きます。Unit1.hは、

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TButton *Start;
	TButton *Stop;
	void __fastcall StartClick(TObject *Sender);
	void __fastcall StopClick(TObject *Sender);
    void __fastcall SaveToWav(const UnicodeString &filename, short *data, DWORD dataSize);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cppは、

//---------------------------------------------------------------------------

#include <vcl.h>
#include <mmsystem.h>
#include <fstream>
#pragma hdrstop
#pragma comment(lib, "winmm.lib")

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

HWAVEIN hWaveIn;
WAVEHDR WaveInHdr;
short buffer[44100 * 5]; // 5秒分の16bitモノラル
DWORD recordedBytes = 0;


//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StartClick(TObject *Sender)
{
    WAVEFORMATEX wfx;
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = 1;
    wfx.nSamplesPerSec = 44100;
    wfx.wBitsPerSample = 16;
    wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
    wfx.cbSize = 0;

    if (waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) {
        ShowMessage(L"マイクを開けません");
        return;
    }

    WaveInHdr.lpData = (LPSTR)buffer;
    WaveInHdr.dwBufferLength = sizeof(buffer);
    WaveInHdr.dwBytesRecorded = 0;
    WaveInHdr.dwUser = 0;
    WaveInHdr.dwFlags = 0;
    WaveInHdr.dwLoops = 0;

    waveInPrepareHeader(hWaveIn, &WaveInHdr, sizeof(WAVEHDR));
    waveInAddBuffer(hWaveIn, &WaveInHdr, sizeof(WAVEHDR));

    recordedBytes = 0;
    waveInStart(hWaveIn);

    ShowMessage(L"録音開始");

}
//---------------------------------------------------------------------------
void __fastcall TForm1::StopClick(TObject *Sender)
{
    waveInStop(hWaveIn);
    waveInUnprepareHeader(hWaveIn, &WaveInHdr, sizeof(WAVEHDR));
    waveInClose(hWaveIn);

    recordedBytes = WaveInHdr.dwBytesRecorded;

    SaveToWav(L"output.wav", buffer, recordedBytes);

    ShowMessage(L"録音終了: output.wav に保存しました");

}

void __fastcall TForm1::SaveToWav(const UnicodeString &filename, short *data, DWORD dataSize)
{
    std::ofstream out(filename.c_str(), std::ios::binary);

    // WAVヘッダ作成
    DWORD chunkSize = 36 + dataSize;
    DWORD subchunk1Size = 16;
    WORD audioFormat = 1;
    WORD numChannels = 1;
    DWORD sampleRate = 44100;
    WORD bitsPerSample = 16;
    WORD blockAlign = numChannels * bitsPerSample / 8;
    DWORD byteRate = sampleRate * blockAlign;
    DWORD subchunk2Size = dataSize;

    // RIFFヘッダ
    out.write("RIFF", 4);
    out.write((char*)&chunkSize, 4);
    out.write("WAVE", 4);

    // fmtチャンク
    out.write("fmt ", 4);
    out.write((char*)&subchunk1Size, 4);
    out.write((char*)&audioFormat, 2);
    out.write((char*)&numChannels, 2);
    out.write((char*)&sampleRate, 4);
    out.write((char*)&byteRate, 4);
    out.write((char*)&blockAlign, 2);
    out.write((char*)&bitsPerSample, 2);

    // dataチャンク
    out.write("data", 4);
    out.write((char*)&subchunk2Size, 4);
    out.write((char*)data, dataSize);

    out.close();
}


//---------------------------------------------------------------------------

最終的にはwavファイルを保存するので、バッファサイズは5秒に増えていますが、その割にStopは自動じゃありません。が、これを使って、本番に近い録音をしました。手動トリガー版なので、Start->おしゃべりフラワーのボタン->Stopという流れで、

を得ました。プログラムの外観は、

こんな風でした。スタートしたらサイクリックにバッファーに録音しつつ、入力データが一定の閾値を超えたら、録音を止めて、バッファー一杯分をファイルに落とすということを目指していくわけですが、入力レベル(瞬時値)を捉えていくことをまずやらないとダメですね。以降は次記事以降ということで….。不定期連載つまり非連続の強い予感。(笑)実際の試行錯誤の記録だと思っていただいて結構です。そういう記事少ないですよね。右往左往しているのにその経緯を書かないで、最初からできたようなフリをするという…..。自戒を込めて。ちなみにLibvlcのwrapperを使うことも一瞬考えたのですが、音声のみで、比較的単純な内容なので採用しませんでした。

さて次では瞬時値が捉えられるのかどうか?callbackしくんでも、瞬時値は得られないいやな予感が……。

コメント