JavaScriptで音声録音時に無音を検知して自動停止する方法
音声入力機能を持つWebアプリケーションを開発する際、「ユーザーが話し終わったら自動的に録音を停止したい」という要件はよくあります。今回は、JavaScriptのWeb Audio APIを使って、マイク入力の音量を監視し、一定時間(今回は5秒)無音が続いたら録音を終了する実装方法を紹介します。
実装のポイント
MediaRecorder: 音声の録音に使用します。AudioContext と AnalyserNode: 音声データの解析(音量取得)に使用します。requestAnimationFrame: リアルタイムに音量をチェックするために使用します。
サンプルコード
以下は、録音開始ボタンを押すと録音が始まり、無音が5秒続くと自動的に停止するサンプルのHTMLとJavaScriptです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
| <!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>無音検知録音サンプル</title>
</head>
<body>
<h1>無音検知録音サンプル</h1>
<button id="startBtn">録音開始</button>
<button id="stopBtn" disabled>録音停止</button>
<p id="status">待機中</p>
<p>無音継続時間: <span id="silenceTime">0.00</span> 秒</p>
<script>
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const statusEl = document.getElementById('status');
const silenceTimeEl = document.getElementById('silenceTime');
let mediaRecorder;
let audioContext;
let analyser;
let microphone;
let silenceStart = null;
let isRecording = false;
let animationId;
// 無音とみなす閾値(0.0〜1.0)。環境に合わせて調整してください。
const SILENCE_THRESHOLD = 0.02;
// 録音を停止するまでの無音時間(ミリ秒)
const SILENCE_DURATION = 5000;
startBtn.addEventListener('click', startRecording);
stopBtn.addEventListener('click', stopRecording);
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// AudioContextのセットアップ
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
// FFTサイズの設定(データ取得の解像度)
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// MediaRecorderのセットアップ
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.onstart = () => {
statusEl.textContent = '録音中...';
startBtn.disabled = true;
stopBtn.disabled = false;
isRecording = true;
silenceStart = performance.now();
detectSilence(dataArray, bufferLength);
};
mediaRecorder.onstop = () => {
statusEl.textContent = '録音終了';
startBtn.disabled = false;
stopBtn.disabled = true;
isRecording = false;
cancelAnimationFrame(animationId);
// ストリームのトラックを停止
stream.getTracks().forEach(track => track.stop());
if (audioContext && audioContext.state !== 'closed') {
audioContext.close();
}
};
mediaRecorder.ondataavailable = (e) => {
// ここで録音データを処理(保存や送信など)
console.log('データ取得:', e.data);
};
mediaRecorder.start();
} catch (err) {
console.error('マイクへのアクセスに失敗しました:', err);
statusEl.textContent = 'エラー: マイクにアクセスできません';
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
}
}
function detectSilence(dataArray, bufferLength) {
if (!isRecording) return;
analyser.getByteTimeDomainData(dataArray);
let sum = 0;
for (let i = 0; i < bufferLength; i++) {
// 128が中心(無音)なので、振幅を計算
const x = (dataArray[i] - 128) / 128.0;
sum += x * x;
}
// RMS(二乗平均平方根)を計算して音量とする
const rms = Math.sqrt(sum / bufferLength);
const now = performance.now();
if (rms < SILENCE_THRESHOLD) {
// 無音の場合
if (!silenceStart) {
silenceStart = now;
}
const silenceDuration = now - silenceStart;
silenceTimeEl.textContent = (silenceDuration / 1000).toFixed(2);
if (silenceDuration > SILENCE_DURATION) {
console.log('無音が続いたため自動停止しました');
stopRecording();
return;
}
} else {
// 音がある場合、タイマーリセット
silenceStart = now;
silenceTimeEl.textContent = "0.00";
}
animationId = requestAnimationFrame(() => detectSilence(dataArray, bufferLength));
}
</script>
</body>
</html>
|
解説
音量の取得方法
AnalyserNode.getByteTimeDomainData() を使用して、波形データを取得しています。このデータは 0〜255 の範囲で、128 が無音(振幅0)の状態です。
各データ点から 128 を引いて正規化し、二乗平均平方根(RMS)を計算することで、現在の音量レベルを算出しています。
無音判定ロジック
算出された RMS 値が SILENCE_THRESHOLD(閾値)を下回っている場合を「無音」と判定します。
requestAnimationFrame ループ内で毎回チェックを行い、無音が続いている時間を計測します。閾値を超えた(音が鳴った)場合は、計測開始時間 silenceStart を現在時刻にリセットします。
注意点
- 閾値の調整: マイクの感度や環境ノイズによって適切な
SILENCE_THRESHOLD は異なります。実際の環境でテストして調整してください。 - ブラウザの制限:
AudioContext はユーザー操作(クリックなど)がないと開始できない場合があります。このサンプルではボタンクリックをトリガーにしているため問題ありません。
デモ
待機中
無音継続時間: 0.00 秒
まとめ
Web Audio API を組み合わせることで、単純な録音だけでなく、入力レベルに応じた制御が可能になります。音声入力フォームやボイスメモアプリなどで活用してみてください。