Mở đầu: nhạc "vỡ" nghe rất khác với nhiễu "rè"

Bài 2 mổ xẻ trục THỜI GIAN bị rời rạc hoá (lấy mẫu). Nhưng còn một trục thứ hai cũng bị rời rạc hoá: trục BIÊN ĐỘ. Mỗi mẫu $x[n]$ không thể lưu một số thực chính xác tuyệt đối — máy tính chỉ có hữu hạn bit, nên biên độ phải làm tròn về một trong số hữu hạn mức. Hạ đủ thấp số bit này, bạn sẽ nghe một thứ âm thanh rất đặc trưng: không phải "rè" như nhiễu radio, mà "vỡ", "gai góc", giống như nhạc bị bóp méo qua một chiếc loa rẻ tiền. Bài này giải thích chính xác vì sao âm thanh đó nghe như vậy — và một quy tắc kỹ thuật cực kỳ gọn: mỗi bit thêm vào cho bạn thêm đúng ~6dB chất lượng, không hơn không kém.

Đây cũng là bài nối trực tiếp Series Nhúng Bài 10 — một ADC thật CHÍNH LÀ một bộ lượng tử hoá, và mọi công thức trong bài này áp dụng y hệt cho phần cứng thật bạn cầm trên tay.


📚 Điều kiện tiên quyết
Bắt buộc: Bài 1Bài 2 (tín hiệu, lấy mẫu). Nên đọc thêm: Series Nhúng Bài 10 (ADC 10-bit — cùng công thức lượng tử hoá, khác đơn vị bit).

1. Trục thứ hai bị rời rạc

Với $B$ bit, biên độ tín hiệu bị làm tròn về một trong $2^B$ mức rời rạc (hoặc $2^{B-1}$ mức dương tối đa nếu tính kiểu có dấu, như PCM audio thật). Sai số lượng tử tại mỗi mẫu:

$$e[n] \in \left[-\tfrac{1}{2}LSB,\ +\tfrac{1}{2}LSB\right]$$

trong đó LSB (Least Significant Bit) là kích thước một bậc lượng tử — đúng khái niệm đã gặp ở Series Nhúng Bài 10, giờ tổng quát hoá cho bất kỳ số bit nào. Đây chính xác là những gì một ADC thật làm với MỌI mẫu nó đọc được — không có ngoại lệ.

2. Nhiễu lượng tử & quy tắc 6dB/bit

Coi sai số lượng tử như một loại "nhiễu trắng" phân bố đều trong khoảng $\pm\tfrac{1}{2}LSB$ (mô hình gần đúng nhưng cực kỳ hữu ích), tỷ số tín hiệu trên nhiễu lượng tử (SQNR — Signal-to-Quantization-Noise Ratio) có công thức nổi tiếng:

$$SQNR \approx 6.02B + 1.76 \text{ dB}$$

Giải nghĩa từng hằng số: 6,02dB là mức tăng SQNR ứng với MỖI BIT thêm vào — vì thêm 1 bit nghĩa là số mức tăng gấp đôi, LSB giảm còn một nửa, sai số lượng tử giảm một nửa biên độ, tương đương $20\log_{10}(2) \approx 6,02$dB ít nhiễu hơn. Hằng số 1,76dB là một hệ số hiệu chỉnh từ việc mô hình hoá sai số là phân bố đều (uniform), xuất phát từ chính phép tính tích phân phương sai của phân bố đó.

Verify bằng số thật — 3 mức bit phổ biến:

Bit depth SQNR lý thuyết SQNR đo thật (sine 440Hz)
16-bit (chuẩn CD) ≈ 98,08dB ≈ 97,43dB
8-bit ≈ 49,92dB ≈ 49,15dB
4-bit ≈ 25,84dB ≈ 25,51dB

Số đo THẬT (tính trực tiếp từ tỷ số công suất tín hiệu/công suất sai số lượng tử trên một sine 440Hz thật) khớp công thức lý thuyết trong sai số dưới 1dB ở cả 3 mức — công thức "6dB/bit" không phải một câu nói cửa miệng, nó là một định luật vật lý-toán học đo được. 16-bit CD cho khoảng 98dB — đủ để tai người trong phòng yên tĩnh không nghe ra sàn nhiễu lượng tử.

3. Dải động & headroom

Thang đo âm lượng chuẩn của audio số là dBFS (decibel Full Scale): biên độ tối đa hệ thống có thể biểu diễn (fullScale, thường chuẩn hoá về 1.0) LUÔN ứng với đúng 0dBFS — mọi giá trị nhỏ hơn cho ra số âm. Verify: biên độ 0,5 (một nửa full scale) cho -6,02dBFS — đúng con số 6dB đã gặp ở Mục 2, giờ áp dụng cho biên độ tín hiệu thay vì sai số lượng tử.

⚠️ Pitfall: clipping cứng TỆ HƠN NHIỀU so với sàn nhiễu lượng tử
Khi tín hiệu vượt quá 0dBFS, nó bị clipping (cắt phẳng) — verify: hardClip(1.5) = 1, mọi thông tin về hình dạng sóng thật phía trên ngưỡng bị MẤT HOÀN TOÀN, không phải làm tròn nhẹ như lượng tử hoá bình thường. Tai người nghe méo clipping (một dạng méo hài rất gắt, chứa nhiều tần số cao mới hoàn toàn không có trong tín hiệu gốc) khó chịu hơn HẲN so với việc chấp nhận một sàn nhiễu lượng tử thấp hơn vài dB. Nguyên tắc vàng của gain staging (thiết lập mức tín hiệu qua từng tầng xử lý): luôn ưu tiên chừa headroom (khoảng đệm dưới 0dBFS) thay vì cố "đẩy" tín hiệu sát ngưỡng để tối đa hoá SQNR — thà mất vài dB SQNR còn hơn dính clipping dù chỉ một lần.

4. Dithering — cố tình thêm nhiễu để bớt méo

Ở mức tín hiệu nhỏ (gần với kích thước LSB), sai số lượng tử KHÔNG còn giống nhiễu trắng ngẫu nhiên nữa — nó trở nên tương quan với chính tín hiệu, tạo ra méo hài (harmonic distortion) nghe khó chịu, có quy luật, thay vì một sàn nhiễu "trung tính". Dithering là kỹ thuật cố tình cộng thêm một lượng nhiễu nhỏ (thường ±½LSB) TRƯỚC khi làm tròn, phá vỡ sự tương quan đó — đổi lấy méo hài lấy một sàn nhiễu đều, dễ chịu hơn với tai người.

⚠️ Verify: dither giúp "trung bình hoá" ra thông tin dưới-LSB
Trường hợp tệ nhất cho lượng tử hoá thường: một giá trị DC nằm ĐÚNG GIỮA hai mức lượng tử liền kề (ở 4-bit, verify với $x=0,1875$ — đúng 1,5 lần kích thước một bậc). KHÔNG dither: verify — quantize() luôn cho ra CÙNG MỘT mức cố định, sai số CỐ ĐỊNH đúng bằng nửa LSB (0,0625) mọi lúc — không có cách nào cải thiện chỉ bằng lấy trung bình nhiều lần đọc, vì luôn ra cùng 1 con số. CÓ dither: verify — kết quả DAO ĐỘNG giữa 2 mức lân cận theo từng mẫu; lấy trung bình 200 mẫu, sai số giảm xuống CÒN CHƯA ĐẦY 1/10 so với không dither — dither cho phép "trung bình hoá" ra một phần thông tin nằm DƯỚI độ phân giải danh nghĩa của bộ lượng tử hoá.

Khi nào dùng: dithering CHỈ có ý nghĩa khi giảm bit depth (vd master một bản nhạc từ 24-bit studio xuống 16-bit để phát hành CD) — không dùng khi bit depth đã đủ cao cho nhu cầu, vì dithering luôn đánh đổi lấy một chút nhiễu nền để đổi lấy việc phá tương quan méo hài.

5. Thực hành: kéo bit depth nghe nhạc vỡ dần

Demo dưới đây chạy đúng công thức lượng tử hoá và SQNR vừa học trên một sine thật — kéo bit depth để NGHE sự khác biệt, xem histogram sai số, và so sánh SQNR đo thật với lý thuyết:

🎚️ Bit Depth Explorer — DSPJS thật
Đang tải…

Đồ thị trên: dạng sóng gốc (xanh dương) chồng lên dạng sóng đã lượng tử hoá (cam) — chú ý bậc thang rõ dần khi giảm bit. Đồ thị dưới: histogram sai số lượng tử — kéo xuống 1-2 bit và bật/tắt dither để thấy hình dạng histogram đổi khác hẳn.

quantize_demo.js (đúng logic đang chạy ở tab Xem trước)
import { sine, quantize, quantizeWithDither, sqnrDbTheoretical, measuredSqnrDb } from './dsp-core.js';

const SAMPLE_RATE = 44100;
const numSamples = Math.round(SAMPLE_RATE * 0.5);
const original = Array.from({length: numSamples}, (_, n) => sine(n, 440, SAMPLE_RATE, 0.9));

const bits = 4; // keo tu 16 xuong con 4
const quantized = original.map((x, n) => quantize(x, bits));       // hoac quantizeWithDither(x, bits, 1, n)

console.log('SQNR ly thuyet:', sqnrDbTheoretical(bits).toFixed(2) + 'dB');   // 25.84dB
console.log('SQNR do that:', measuredSqnrDb(original, bits).toFixed(2) + 'dB'); // ~25.51dB

// Phat am thanh da luong tu hoa qua Web Audio (AudioContext SAU cu chi nguoi dung):
const ctx = new AudioContext();
const buffer = ctx.createBuffer(1, quantized.length, SAMPLE_RATE);
buffer.copyToChannel(new Float32Array(quantized), 0);
const src = ctx.createBufferSource();
src.buffer = buffer;
src.connect(ctx.destination);
src.start();

Tóm lược

  • ✅ Lượng tử hoá làm tròn biên độ về $2^B$ mức, sai số $e[n] \in [-\tfrac{1}{2}LSB, +\tfrac{1}{2}LSB]$ — cùng cơ chế một ADC thật dùng (Series Nhúng Bài 10).
  • ✅ Quy tắc 6dB/bit verified: SQNR lý thuyết 16/8/4-bit ≈ 98,08/49,92/25,84dB, đo THẬT khớp trong sai số dưới 1dB ở cả 3 mức.
  • ✅ dBFS verified: -6,02dBFS mỗi lần giảm biên độ một nửa. Clipping cứng (verify: hardClip(1.5)=1) tệ hơn nhiều so với sàn nhiễu lượng tử — ưu tiên headroom.
  • ✅ Dithering verified: không dither cho sai số CỐ ĐỊNH 0,0625 (nửa LSB); có dither, trung bình 200 mẫu giảm sai số xuống dưới 1/10 — chỉ nên dùng khi GIẢM bit depth.

Trắc nghiệm ôn tập

Câu 1

Verified: SQNR lý thuyết tăng đúng ~6,02dB mỗi khi thêm 1 bit. Vì sao đúng 6,02dB, không phải một con số khác?

Câu 2

Vì sao clipping cứng (hard clipping) khi vượt quá 0dBFS được coi là TỆ HƠN NHIỀU so với chấp nhận một sàn nhiễu lượng tử cao hơn (ít bit hơn)?

Câu 3

Verified: một giá trị DC nằm giữa 2 mức lượng tử — không dither luôn cho sai số cố định 0,0625; có dither, trung bình 200 mẫu giảm sai số xuống dưới 1/10. Cơ chế nào giải thích cải thiện này?

Câu 4

Khi nào NÊN sử dụng dithering theo bài học?

Tải file code thực hành minh họa bài học

File JavaScript DSPJS — thư viện DSP mini dùng xuyên suốt cả 15 bài, Bài 3 vừa thêm quantize, sqnrDbTheoretical, measuredSqnrDb, quantizeWithDither, amplitudeToDbfs, hardClip, kèm self-test đối chiếu đúng mọi hành vi trong bài (chạy node dsp-core.js, không cần cài thêm gì):

Tải về dsp-core.js

📖 Tài liệu tham khảo

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

Bài 2: Lấy mẫu, Nyquist & Aliasing Bài 4: Hệ LTI, tích chập & đáp ứng xung Quay lại Lộ trình Series Xử Lý Tín Hiệu Số

Bình luận