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;
//---------------------------------------------------------------------------
#endifUnit1.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しくんでも、瞬時値は得られないいやな予感が……。



コメント