4 bài trước dùng OscillatorNode làm nguồn — tiện cho học, nhưng thực tế thường cần phát file nhạc thật hoặc bắt âm thanh từ micro. Bài này thêm 2 nguồn mới cùng cách đo độ to (loudness) chuẩn hơn biên độ đỉnh đơn thuần.

1. decodeAudioData: Giải Mã File Thành AudioBuffer

Đọc file âm thanh (upload hoặc fetch) dưới dạng ArrayBuffer, rồi audioContext.decodeAudioData(arrayBuffer) giải mã toàn bộ (MP3, WAV, OGG...) thành 1 AudioBuffer — PCM thô nằm sẵn trong bộ nhớ, sẵn sàng phát qua AudioBufferSourceNode. Giống OscillatorNode, source node này cũng chỉ start() được đúng 1 lần — phát lại phải tạo node mới (buffer gốc thì tái sử dụng thoải mái).

ℹ️ decodeAudioData giải mã TOÀN BỘ file vào bộ nhớ cùng lúc
Phù hợp cho hiệu ứng ngắn (tiếng click, hit game) — nhưng với 1 bản nhạc dài vài phút, giải mã toàn bộ trước khi phát được gì cả vừa tốn thời gian chờ vừa tốn RAM đáng kể. Với file dài, mục 2 (MediaElementSourceNode) là lựa chọn hợp lý hơn nhiều.

2. MediaElementSourceNode: Bọc Thẻ <audio> HTML5

audioContext.createMediaElementSource(audioElement) biến 1 thẻ <audio>/<video> bình thường thành 1 node trong đồ thị Web Audio — tận dụng luôn cơ chế streaming/buffer tăng dần sẵn có của trình duyệt (không cần chờ tải/giải mã xong toàn bộ), trong khi vẫn áp được filter/analyser/hiệu ứng như mọi node khác đã học.

3. getUserMedia: Đầu Vào Micro

navigator.mediaDevices.getUserMedia({ audio: true }) xin quyền micro, trả về 1 MediaStream. audioContext.createMediaStreamSource(stream) biến stream đó thành 1 AudioNode — nối tiếp vào AnalyserNode y hệt mọi nguồn khác đã học.

🕳️ Cạm bẫy thường gặp: nối mic thẳng tới destination gây hú rít (feedback loop)
Nếu vô tình nối micSource.connect(audioContext.destination), âm thanh từ loa sẽ bị chính micro thu lại, khuếch đại, phát ra loa, thu lại tiếp... tạo vòng lặp hú rít (audio feedback) giống hệt micro để gần loa ngoài đời thật. Với ứng dụng chỉ cần phân tích mic (VU meter, spectrum), chỉ nối micSource → analyser — dừng lại ở đó, không nối tiếp ra destination.
💡 Mẹo: luôn dừng track mic khi dùng xong
stream.getTracks().forEach(track => track.stop()) giải phóng thiết bị mic hoàn toàn — nếu quên, icon/đèn báo mic trên tab trình duyệt vẫn sáng, mic vẫn bị đọc dữ liệu liên tục dù ứng dụng không còn cần nữa, vừa tốn tài nguyên vừa là mối lo về quyền riêng tư.

4. Đo Âm Lượng Bằng RMS (Root Mean Square)

Biên độ đỉnh (giá trị lớn nhất tức thời) không phản ánh đúng độ to cảm nhận — 1 tín hiệu có thể đạt đỉnh cao trong tích tắc mà nghe không hề to. RMS lấy trung bình bình phương biên độ trên cả cửa sổ mẫu rồi khai căn, phản ánh sát hơn nhiều năng lượng liên tục mà tai người cảm nhận là "to" (với x_i là biên độ mẫu thứ i, N là số mẫu trong cửa sổ đang xét):

$$\text{RMS} = \sqrt{\dfrac{1}{N}\sum_{i=1}^{N} x_i^2}$$

🔬 Đào sâu: vì sao RMS chính xác hơn peak cho VU meter
Peak chỉ là 1 con số — dễ bị "đánh lừa" bởi 1 xung nhiễu ngắn không đại diện cho cảm nhận tổng thể. RMS bình phương từng mẫu trước khi lấy trung bình, khiến các giá trị lớn đóng góp nhiều hơn tỉ lệ thuận (bình phương phóng đại chênh lệch), rồi khai căn để đưa về đúng đơn vị biên độ ban đầu — kết quả là 1 con số ổn định phản ánh năng lượng trung bình, đúng là cách hầu hết đồng hồ VU chuyên nghiệp đo độ to.

Sân chơi tương tác: Visualizer File Upload + Mic

Chọn nguồn: tải lên 1 file âm thanh để phát qua decodeAudioData, hoặc bật micro qua getUserMedia. Cả 2 dùng chung 1 đường ống phân tích: waveform + thanh đo RMS bên dưới.

🎙️ Sân chơi tương tác: File Upload & Mic Visualizer
Nguồn: File
Nguồn: Micro

Nhật ký

audio-file-mic-input-live.js

Trắc nghiệm ôn tập

Câu 1: Vì sao decodeAudioData không phù hợp để phát ngay 1 bản nhạc dài vài phút?

Trắc nghiệm ôn tập

Câu 2: Vì sao phải luôn gọi stream.getTracks().forEach(t => t.stop()) sau khi dùng xong micro?

Trắc nghiệm ôn tập

Câu 3: Vì sao RMS phản ánh độ to cảm nhận (loudness) chính xác hơn biên độ đỉnh (peak) đơn thuần?

📖 Tài liệu tham khảo / References

Bài viết liên quan trong series

Bài 6: Spatial & Stereo Audio Bài 4: AnalyserNode & FFT Quay lại Lộ trình Series Web Audio API