Quay lại blog
19 tháng 10, 2026Sergei Solod30 phút đọc

Cách thêm tính năng chuyển giọng nói thành văn bản bảo vệ quyền riêng tư cho trang web bằng Whisper và WebAssembly

Xây dựng tính năng nhập liệu bằng giọng nói chạy hoàn toàn trên trình duyệt với Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet và WebAssembly. Tìm hiểu cách tối ưu hiệu năng trên thiết bị di động và khắc phục lỗi runtime, worker, tokenizer khiến tính năng nhận dạng giọng nói không hoạt động.

Chuyển giọng nói thành văn bảnWhisperWebAssemblyAudioWorkletJavaScript

Thêm nút micro vào một trang web nghe có vẻ đơn giản: thu giọng nói của người dùng, nhận dạng lời nói rồi chèn văn bản vào ô nhập liệu. Trên thực tế, để tính năng hoạt động ổn định trên cả trình duyệt máy tính và thiết bị di động khó hơn nhiều, nhất là khi toàn bộ quá trình nhận dạng giọng nói phải diễn ra tại thiết bị, không tải âm thanh từ micro lên máy chủ.

Thách thức không chỉ nằm ở việc chọn mô hình nhận dạng giọng nói. Một hệ thống hoạt động đúng phải xử lý quyền truy cập micro, thu âm, lấy mẫu lại, worker của trình duyệt, WebAssembly, khởi tạo mô hình ONNX, tách token, bộ nhớ đệm và việc tích hợp với ô nhập văn bản của ứng dụng.

Bài viết giải thích cách xây dựng tính năng chuyển giọng nói thành văn bản thiết thực, bảo vệ quyền riêng tư bằng OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet và một Web Worker chuyên dụng. Bài viết cũng phân tích một số lỗi tích hợp có thật khiến nút micro mắc kẹt ở trạng thái đang tải dù các tệp mô hình đã được tải xuống thành công.

Mục tiêu rất rõ ràng: khách truy cập nhấn nút micro, nói tự nhiên và thấy văn bản được nhận dạng xuất hiện ngay trong ô nhập liệu sẵn có. Họ có thể xem lại hoặc chỉnh sửa kết quả trước khi gửi. Bản ghi âm từ micro hoàn toàn không cần rời khỏi trình duyệt.

Nhận dạng giọng nói hoàn toàn ở phía máy khách là gì?

Có hai cách tiếp cận khác nhau về bản chất để bổ sung khả năng nhập liệu bằng giọng nói cho trang web.

Cách thứ nhất sử dụng dịch vụ nhận dạng giọng nói. Trình duyệt thu âm, gửi âm thanh đến dịch vụ từ xa và nhận về văn bản đã nhận dạng. Cách này có thể giúp triển khai đơn giản hơn nhưng kéo theo sự phụ thuộc vào mạng, các vấn đề về quyền riêng tư, tình trạng sẵn có của nhà cung cấp và khả năng phát sinh chi phí theo mức sử dụng.

Cách thứ hai chạy suy luận trực tiếp trong trình duyệt. Trang web tải mô hình nhận dạng giọng nói cùng môi trường thực thi cần thiết. Trình duyệt thu âm từ micro, chuyển âm thanh thành các mẫu số, chạy mô hình ngay trên thiết bị và tạo bản chép lời mà không cần máy chủ nhận dạng giọng nói.

Bài viết này chỉ tập trung vào cách thứ hai.

Không nên nhầm nhận dạng giọng nói thông thường trên trình duyệt với nhận dạng chắc chắn được xử lý tại thiết bị. Web Speech API có thể sử dụng bộ máy nhận dạng trên máy chủ. Một số cách triển khai mới hơn cung cấp tùy chọn processLocally, nhưng khả năng hỗ trợ khác nhau giữa các trình duyệt và hệ điều hành. Nếu bắt buộc xử lý hoàn toàn tại thiết bị, không được tự động dùng SpeechRecognition thông thường làm phương án dự phòng.

Thay vào đó, ứng dụng kiểm soát toàn bộ quy trình suy luận.

Micro
    |
    v
AudioWorklet
    |
    v
Âm thanh PCM Float32
    |
    v
Web Worker chuyên dụng
    |
    v
Lấy mẫu lại ở 16 kHz
    |
    v
Whisper + ONNX Runtime Web
    |
    v
Tách token và giải mã văn bản
    |
    v
State ô nhập React

Trang web vẫn phân phối JavaScript, WebAssembly, tệp cấu hình và trọng số mô hình. Đây đều là các tệp tĩnh được tải xuống theo cách thông thường. Điểm khác biệt là âm thanh micro được xử lý tại thiết bị thay vì tải lên để nhận dạng. Nếu sau đó người dùng gửi văn bản thu được qua một biểu mẫu thông thường, đó là một thao tác ứng dụng riêng biệt.

Chọn mô hình phù hợp trong thực tế: Whisper Tiny hay Base

Whisper là họ mô hình nhận dạng giọng nói đa ngôn ngữ. Khả năng nhận dạng nhiều ngôn ngữ bằng một mô hình khiến nó đặc biệt hữu ích với các trang web đa ngôn ngữ.

Đối với tin nhắn thoại ngắn, hai mô hình mang lại sự cân bằng hợp lý giữa chất lượng nhận dạng và mức sử dụng tài nguyên trình duyệt là Whisper Tiny và Whisper Base.

Mô hìnhDung lượng gần đúng của trọng số ONNX Q8 được chọnVai trò đề xuất
Whisper Tiny41 MBLựa chọn thận trọng cho thiết bị di động và thiết bị ít tài nguyên
Whisper Base77 MBLựa chọn chất lượng cao hơn cho thiết bị đủ mạnh đã được kiểm thử

Các dung lượng này chỉ tính những tệp ONNX encoder và decoder đã chọn trong các bản xuất mô hình được xem xét. Chúng không bao gồm mọi tệp hỗ trợ và không được nhầm với mức sử dụng bộ nhớ cao nhất trong trình duyệt.

Whisper Tiny đáng cân nhắc vì tải xuống nhanh hơn, thường đòi hỏi ít tài nguyên tính toán hơn và phù hợp hơn với môi trường di động chưa rõ khả năng. Whisper Base nhìn chung cho chất lượng nhận dạng tốt hơn, đặc biệt ở một số ngôn ngữ mà mô hình nhỏ thường mắc nhiều lỗi hơn.

Không nên chọn mô hình chỉ dựa vào dung lượng RAM vật lý. Trình duyệt có thể được cấp ít bộ nhớ hơn đáng kể so với thông số thiết bị. Bộ nhớ mà trang đang dùng, các vùng nhớ do WebAssembly cấp phát, tensor tạm thời và áp lực bộ nhớ của hệ điều hành đều có ảnh hưởng.

Cách tiếp cận thận trọng là bắt đầu với Tiny trên thiết bị di động chưa rõ năng lực, chỉ dùng Base cho những nhóm thiết bị đã đáp ứng tiêu chí, và chuyển từ Base sang Tiny nếu quá trình khởi tạo mô hình thất bại.

Phải giải phóng phiên bản Base bị lỗi trước khi tải Tiny. Giữ cả hai mô hình trong bộ nhớ sẽ làm mất ý nghĩa của cơ chế dự phòng.

Vì sao WebAssembly là lựa chọn mặc định hợp lý cho môi trường sản xuất?

ONNX Runtime Web hỗ trợ thực thi mạng nơ-ron trong trình duyệt. Với loại tác vụ này, WebAssembly cung cấp cách chạy suy luận trên CPU thiết thực ở các trình duyệt máy tính và di động phổ biến.

WebGPU có thể tăng tốc các mô hình được hỗ trợ trên phần cứng phù hợp, nhưng sự hiện diện của nó không đồng nghĩa với một quy trình nhận dạng giọng nói hoạt động được. Trình duyệt có navigator.gpu không đảm bảo ONNX Runtime hỗ trợ nền tảng đó, đồ thị Whisper cụ thể khởi tạo đúng hay các lần suy luận lặp lại không gặp vấn đề tài nguyên.

Vì vậy, một cách triển khai thận trọng có thể sử dụng WASM làm backend trong môi trường sản xuất và đánh giá WebGPU riêng sau khi kiểm thử khả năng tương thích, độ chính xác, bộ nhớ và các phiên chạy lặp lại.

Ở giai đoạn triển khai ban đầu, WASM đơn luồng còn giúp tránh việc cả trang web phải phụ thuộc vào cơ chế cô lập giữa các nguồn gốc và SharedArrayBuffer.

env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;

Chạy suy luận trong worker chuyên dụng giúp tách phần tính toán nặng khỏi luồng JavaScript chính. Ô nhập liệu, bàn phím ảo, thao tác cuộn và giao diện ứng dụng vẫn phản hồi tốt trong lúc mô hình xử lý âm thanh.

Không tải hệ thống nhận dạng giọng nói cùng lần tải trang đầu tiên

Tính năng micro không nên làm tăng đáng kể lượng dữ liệu tải qua mạng và bộ nhớ đối với những khách truy cập không bao giờ sử dụng nó.

Không nhập Transformers.js ngay vào gói mã ban đầu của ứng dụng. Không khởi tạo ONNX Runtime khi trang vừa mở và không tự động tải trọng số mô hình trong lúc ứng dụng nhàn rỗi.

Thay vào đó, chỉ tải hệ thống nhận dạng sau khi người dùng chủ động nhấn nút micro.

Trình tự hợp lý cho lần sử dụng đầu tiên là:

  1. Nhận thao tác nhấn nút micro.
  2. Yêu cầu quyền truy cập micro thông qua getUserMedia().
  3. Nếu quyền bị từ chối, hiển thị lỗi mà không tải mô hình.
  4. Nếu được cấp quyền, giải phóng luồng micro tạm thời trong khi chuẩn bị mô hình.
  5. Khởi tạo động worker chuyên dụng và tải mô hình cục bộ đã chọn.
  6. Chờ worker xác nhận toàn bộ quy trình nhận dạng đã sẵn sàng.
  7. Tự động bắt đầu ghi âm nếu người dùng chưa hủy thao tác.
  8. Hiển thị trạng thái đang nghe rõ ràng, dễ hiểu.

Yêu cầu cấp quyền phải gắn với tương tác của người dùng. Ứng dụng cũng cần xử lý các thao tác đã bị hủy và những hạn chế thu âm riêng của từng trình duyệt, thay vì giả định rằng luôn có thể âm thầm bắt đầu ghi âm lại.

Quá trình chuẩn bị mô hình cần có trạng thái hiển thị, chẳng hạn Đang chuẩn bị nhập liệu bằng giọng nói. Một biểu tượng xoay mãi mà không chuyển trạng thái rõ ràng có thể khiến việc tải mô hình thành công trông giống như micro bị hỏng.

Sau khi khởi tạo, worker có thể tiếp tục sẵn sàng trong thời gian ngắn để phục vụ lần đọc chính tả tiếp theo. Thời gian chờ khi không hoạt động khoảng một phút là điểm khởi đầu hợp lý. Sau đó, hãy dừng worker và giải phóng mô hình khỏi bộ nhớ của nó. Các tệp mô hình đã được trình duyệt lưu vào bộ nhớ đệm vẫn có thể dùng ở những lần truy cập sau.

Thu âm thanh thô bằng AudioWorklet

Whisper cần các mẫu âm thanh dạng số. Dù MediaRecorder rất phù hợp để ghi âm thành tệp, việc dùng nó để nhận dạng giọng nói tại thiết bị có thể thêm một vòng mã hóa rồi giải mã không cần thiết.

Luồng xử lý đơn giản hơn là:

getUserMedia()
    |
    v
AudioContext
    |
    v
AudioWorklet
    |
    v
Bộ đệm PCM Float32
    |
    v
Worker chuyên dụng

AudioWorklet chạy mã xử lý âm thanh bên ngoài luồng JavaScript chính của ứng dụng. Nó có thể thu các mẫu PCM, chuyển âm thanh nhiều kênh thành mono, tính các chỉ số âm lượng đơn giản và chuyển những bộ đệm đã gom đến worker.

Ví dụ khởi tạo micro rút gọn như sau:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    channelCount: { ideal: 1 },
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
});

const context = new AudioContext();

await context.audioWorklet.addModule(
  '/assets/speech/pcm-worklet.js'
);

const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
  context,
  'pcm-capture'
);

source.connect(processor);

// Connect the worklet through an inaudible output path
// if required to keep the audio processing graph active.

Các ràng buộc micro ở đây chỉ thể hiện mong muốn, không phải bảo đảm. Trình duyệt có thể áp dụng các thuật toán khử tiếng vọng, giảm tiếng ồn và tự động điều chỉnh độ khuếch đại khác nhau.

Một chi tiết quan trọng là tần số lấy mẫu của micro không nhất thiết bằng 16 kHz. Nhiều ngữ cảnh âm thanh dùng 44.1 kHz hoặc 48 kHz. Whisper yêu cầu đầu vào 16 kHz, vì vậy các mẫu thu được cần được lấy mẫu lại đúng cách.

Đơn thuần bỏ qua mỗi mẫu thứ hai hoặc thứ ba thường không phải giải pháp đúng. Bộ lấy mẫu lại có bộ lọc thông thấp phù hợp, chẳng hạn cách triển khai windowed-sinc, giúp giảm hiện tượng chồng phổ và giữ lại những tần số quan trọng của tiếng nói khi hạ tần số lấy mẫu.

Hãy thực hiện việc lấy mẫu lại trong worker chuyên dụng, không phải trong callback AudioWorklet chạy theo thời gian thực.

Truyền âm thanh hiệu quả giữa các luồng của trình duyệt

AudioWorklet có thể tạo ra nhiều đoạn âm thanh rất nhỏ. Chuyển mỗi lượng mẫu âm thanh được xử lý thành một thông điệp riêng có thể gây chi phí xử lý không cần thiết.

Một cách làm thực tế là gom các đoạn PCM thành những nhóm ngắn, rồi truyền từng nhóm dưới dạng ArrayBuffer.

worker.postMessage(
  {
    type: 'audio-chunk',
    buffer,
  },
  [buffer]
);

Danh sách chuyển giao rất quan trọng: nó chuyển quyền sở hữu ArrayBuffer thay vì tạo một bản sao đầy đủ không cần thiết.

Worker có thể nối các đoạn này vào bộ tích lũy âm thanh rồi ghép chúng khi quá trình ghi âm kết thúc.

Cũng nên theo dõi tần số lấy mẫu thực tế lúc thu, số mẫu đã thu thập và thời lượng ghi âm. Những siêu dữ liệu này giúp chẩn đoán bản ghi không có tiếng mà không cần ghi lại dữ liệu âm thanh nhạy cảm.

Phát hiện khoảng dừng mà không cần thêm mạng nơ-ron

Đọc chính tả để nhập tin nhắn ngắn không nhất thiết phải dùng bộ phát hiện hoạt động giọng nói dựa trên mạng nơ-ron.

Bộ phát hiện RMS thích ứng có thể ước lượng hoạt động giọng nói với chi phí thấp. RMS đo biên độ tín hiệu, nhờ đó ứng dụng có thể ước tính thời điểm một người đang nói chuyển sang im lặng.

Giao diện phải luôn cho phép dừng thủ công bằng lần nhấn nút micro thứ hai. Tự động phát hiện im lặng chỉ là tính năng tiện ích, không phải điều kiện bắt buộc để nhận dạng thành công.

Các giá trị khởi đầu hợp lý cho đoạn đọc chính tả ngắn gồm:

Tham sốGiá trị khởi đầu
Khoảng phân tích20 ms
Hiệu chuẩn tiếng ồn300 ms
Xác nhận có tiếng nói140 ms
Khoảng im lặng trước khi tự dừng1.4 giây
Thời gian chờ khi không có tiếng nói8 giây
Thời lượng ghi âm tối đa30 giây

Đây là những giá trị khởi đầu để cấu hình, không phải ngưỡng tối ưu trong mọi trường hợp. Nói nhỏ, nhạc nền và cách micro xử lý tiếng ồn đều có thể ảnh hưởng đến phép đo RMS.

Nên ưu tiên cho người dùng tự dừng bản ghi thay vì tự động cắt mất những từ cuối cùng trong câu.

Với ô nhập văn bản ngắn thông thường, chép lời một lần thường đơn giản hơn việc chạy Whisper liên tục để lấy kết quả tạm thời. Hãy ghi âm câu nói, dừng lại, chạy mô hình một lần và chèn văn bản cuối cùng.

Quy trình Whisper hoàn chỉnh đòi hỏi nhiều hơn một mô hình ONNX

Đây là một trong những chi tiết triển khai quan trọng nhất.

Suy luận bằng Whisper không đơn giản chỉ là gọi encoder ONNX rồi đến decoder ONNX. Một quy trình nhận dạng giọng nói tự động hoàn chỉnh còn cần trích xuất đặc trưng âm thanh, tách token, cấu hình sinh kết quả và giải mã các token được dự đoán thành văn bản dễ đọc.

Chuỗi xử lý đầy đủ như sau:

PCM thô từ micro
        |
        v
Lấy mẫu lại về 16 kHz
        |
        v
Bộ trích xuất đặc trưng Whisper
        |
        v
Encoder ONNX
        |
        v
Decoder ONNX và sinh token
        |
        v
Tokenizer Whisper
        |
        v
Văn bản được nhận dạng

Khởi tạo thành công một phiên ONNX chỉ chứng minh rằng đồ thị mô hình có thể được tải. Điều đó không chứng minh cả quy trình nhận dạng đã hoạt động.

Trong một lần tích hợp Transformers.js đã được kiểm thử, các phiên mô hình có thể khởi tạo nhưng quá trình chép lời vẫn thất bại vì quy trình được tự lắp ghép tại thiết bị không có processor và tokenizer cần thiết được cấu hình đúng.

Cách triển khai tương thích đã chủ động khởi tạo các thành phần đó bằng siêu dữ liệu mô hình cục bộ.

Đoạn sau chỉ minh họa rút gọn cách tiếp cận dành cho phiên bản cụ thể đó, không phải một worker độc lập hoàn chỉnh:

import {
  WhisperTokenizer,
  WhisperFeatureExtractor,
  WhisperProcessor,
} from '@huggingface/transformers';

// These values come from local model configuration files.
const tokenizer = new WhisperTokenizer(
  tokenizerJson,
  tokenizerConfig
);

const featureExtractor = new WhisperFeatureExtractor(
  preprocessorConfig
);

const processor = new WhisperProcessor(
  {},
  {
    tokenizer,
    feature_extractor: featureExtractor,
  },
  ''
);

// In the affected version-specific integration:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;

Chữ ký constructor và phương pháp tích hợp chính xác phải được đối chiếu với phiên bản Transformers.js đã cài đặt. Một số phiên bản và bản xuất mô hình có thể tự tạo các thành phần này; không nên xem việc khởi tạo thủ công là yêu cầu chung cho mọi trường hợp.

Khi đã có quy trình hoàn chỉnh, ta có thể gọi chức năng chép lời ngắn với ngôn ngữ và tác vụ được chỉ định rõ:

const result = await recognizer(audio16k, {
  language: 'en',
  task: 'transcribe',
  return_timestamps: false,
});

Nếu ứng dụng đã biết ngôn ngữ của người dùng, truyền ngôn ngữ đó một cách tường minh sẽ tránh bước phát hiện ngôn ngữ không cần thiết và có thể giúp việc xử lý các bản ghi ngắn dễ dự đoán hơn.

Cố định các phiên bản Transformers.js và ONNX Runtime tương thích

Một dạng lỗi quan trọng khác liên quan đến mối quan hệ giữa Transformers.js và ONNX Runtime Web.

Transformers.js cung cấp quy trình nhận dạng ở mức cao hơn, còn ONNX Runtime Web thực thi các đồ thị mạng nơ-ron. Hai gói phải tương thích với nhau cũng như với chính những bản xuất mô hình ONNX được sử dụng.

Trong cách triển khai được khảo sát, một bản dựng phát triển của ONNX Runtime Web khởi tạo được encoder Whisper nhưng thất bại khi tạo phiên decoder đã lượng tử hóa. Lỗi liên quan đến việc thiếu hệ số tỉ lệ lượng tử hóa trong phép toán DequantizeLinear.

Đó không phải lỗi quyền truy cập micro, lỗi tải xuống hay lỗi định dạng âm thanh. Trình duyệt nhận được các tệp mô hình nhưng môi trường thực thi không thể khởi tạo một trong những đồ thị mô hình cần thiết.

Kiểm thử trực tiếp cả encoder và decoder với một bản phát hành ONNX Runtime Web ổn định, được cố định phiên bản, đã giải quyết sự không tương thích cụ thể đó.

Việc kiểm tra tương thích đặc biệt hữu ích vì giúp phân biệt hai khẳng định:

  • Tệp mô hình đã được tải xuống thành công.
  • Môi trường thực thi đã tạo được phiên suy luận có thể sử dụng từ tệp mô hình đó.

Hai điều này không tương đương.

Với những bản xuất mô hình được kiểm thử, cặp phiên bản tương thích là Transformers.js 4.2.0 và ONNX Runtime Web 1.24.3. Bản môi trường thực thi phát triển mới hơn thất bại với đồ thị decoder đã lượng tử hóa. Đây là quan sát chỉ đúng trong bối cảnh phiên bản cụ thể, không phải bằng chứng rằng môi trường thực thi cũ luôn tốt hơn.

Khi ghi đè phiên bản của một phụ thuộc runtime gián tiếp, hãy cố định phiên bản chính xác trong tệp khóa gói và ghi rõ lý do ghi đè. Việc nâng cấp runtime phải trải qua kiểm thử hồi quy đầy đủ cho suy luận trong trình duyệt, chứ không chỉ kiểm tra xem gói đã cài đặt thành công hay chưa.

Kiểm tra định dạng gói worker trước khi tìm lỗi trong Whisper

JavaScript Web Worker có thể được tạo bằng các định dạng mô-đun khác nhau.

Một lỗi tích hợp xuất hiện vì ứng dụng tạo worker dưới dạng mô-đun ES, trong khi gói worker sinh ra lại sử dụng cơ chế worker cổ điển importScripts().

Hai mô hình thực thi này không thể tùy ý thay thế cho nhau. Worker dạng mô-đun không thể đơn giản chạy mã chỉ dành cho worker cổ điển.

Bài học thực tế là phải chọn constructor Worker phù hợp với đầu ra gói mã thực tế, thay vì sao chép tùy chọn constructor từ một hướng dẫn chung.

Chẳng hạn, hai cách sau không tự động tương đương:

// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });

// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);

Lựa chọn đúng phụ thuộc vào công cụ đóng gói và JavaScript mà nó tạo ra.

Khi chẩn đoán lỗi khởi tạo worker, hãy kiểm tra Console của trình duyệt, tệp worker được tạo ra và bảng Network. Cần xác định lỗi có xảy ra trước cả khi ONNX Runtime bắt đầu khởi tạo hay không.

Một số thư viện trình duyệt sau khi được đóng gói còn tính URL tương đối theo vị trí mô-đun của chính chúng. Khi đóng gói các thư viện này bên trong worker, hãy kiểm thử đường dẫn tài nguyên runtime cuối cùng thay vì mặc định rằng các URL tương đối của gói ban đầu vẫn hoạt động.

Phục vụ mô-đun WASM và JavaScript với loại MIME chính xác

Ngay cả các tệp JavaScript và mô hình ONNX hoàn toàn tương thích vẫn có thể lỗi nếu máy chủ web trả về các header phản hồi không chính xác.

Một lỗi cụ thể xảy ra khi mô-đun JavaScript có phần mở rộng .mjs được phục vụ với kiểu application/octet-stream. Trình duyệt từ chối mô-đun vì kiểu nội dung phản hồi không phù hợp để thực thi mô-đun JavaScript.

Các kiểu phản hồi thường được mong đợi là:

Phần mở rộng tệpContent-Type
.mjsapplication/javascript
.wasmapplication/wasm

Hãy kiểm tra phản hồi thực tế trên hệ thống đang chạy, không chỉ xem tệp cấu hình máy chủ.

curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasm

Các URL ở trên chỉ mang tính minh họa. Hãy sử dụng tên tệp tài nguyên có phiên bản thực tế từ bản dựng runtime đã chọn.

Mã trạng thái HTTP đúng vẫn chưa đủ. Trình duyệt có thể từ chối tệp vì loại MIME, Content Security Policy, chế độ thực thi mô-đun hoặc URL tài nguyên không đúng.

Lưu ý rằng cùng một yêu cầu có thể thành công khi mở trực tiếp trong một thẻ trình duyệt nhưng lại thất bại khi được tải như mô-đun thực thi hoặc phụ thuộc WebAssembly.

Dùng URL tài nguyên bất biến mà không khiến người dùng mắc kẹt với runtime lỗi

Các tệp mô hình Whisper đủ lớn để việc lưu vào bộ nhớ đệm của trình duyệt trở nên quan trọng.

Đường dẫn tài nguyên có đánh phiên bản và nội dung bất biến cho phép trình duyệt tái sử dụng các tệp mô hình và runtime không thay đổi mà không phải tải lại mỗi lần người dùng nói.

Một chính sách bộ nhớ đệm điển hình là:

Cache-Control: public, max-age=31536000, immutable

Tuy nhiên, điều này tạo ra một yêu cầu khi triển khai: một khi URL đã được khai báo là bất biến thì không nên âm thầm thay thế nội dung tại URL đó.

Nếu tệp nhị phân của runtime phải thay đổi, hãy công bố nó tại URL mới mang phiên bản khác.

/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/

Đổi đường dẫn runtime không chỉ là thay đổi tên cho đẹp. Trình duyệt có thể tiếp tục dùng tệp WebAssembly không tương thích đã được lưu từ trước nếu ứng dụng tái sử dụng URL bất biến sau khi đổi nội dung của nó.

Cũng nên giữ các tệp mã JavaScript kết nối và tệp WebAssembly thuộc cùng một bản phân phối runtime. Trộn tệp runtime từ nhiều phiên bản có thể gây lỗi khởi tạo khó chẩn đoán.

Một bản kê khai tạo ở bước build, ghi rõ các tệp dự kiến, kích thước và giá trị kiểm tra mật mã, có thể phát hiện tài nguyên bị thiếu hoặc không khớp trước khi triển khai.

Cấu hình thư viện nhận dạng để dùng tài nguyên do chính trang web cung cấp và tắt chức năng tải mô hình từ xa. Nhờ đó, khi thiếu mô hình cục bộ, hệ thống sẽ báo lỗi rõ ràng thay vì tự phát sinh lượt tải ngoài dự kiến từ máy chủ mô hình bên thứ ba.

Vì sao biểu tượng micro cứ quay dù không có gì hoạt động?

Một triệu chứng thường gặp khi phát triển là nút micro yêu cầu quyền, hiển thị hiệu ứng đang tải rồi cuối cùng trở về trạng thái lỗi mà không chèn được văn bản.

Trình duyệt thậm chí có thể báo micro đang được sử dụng.

Điều đó không chứng minh bộ nhận dạng giọng nói hoạt động. Nó chỉ cho thấy việc thu âm bằng micro đã được cho phép hoặc từng diễn ra.

Một số lỗi độc lập có thể gây ra những triệu chứng gần như giống nhau:

  1. Loại MIME của mô-đun JavaScript không đúng. Trình duyệt tải mô-đun runtime xuống nhưng từ chối thực thi.
  2. Định dạng thực thi worker không khớp. Worker đã biên dịch dùng API của worker cổ điển nhưng lại được chạy như worker dạng mô-đun, hoặc ngược lại.
  3. Decoder ONNX không tương thích. Runtime tạo được phiên encoder nhưng thất bại với đồ thị decoder đã lượng tử hóa.
  4. Quy trình nhận dạng chưa đầy đủ. Các phiên ONNX đã tồn tại nhưng tokenizer hoặc bộ xử lý âm thanh chưa được khởi tạo đúng.

Cần chẩn đoán riêng từng vấn đề. Sửa được lỗi này có thể làm lộ ra lỗi tiếp theo.

Nguyên tắc quan trọng nhất khi gỡ lỗi là HTTP 200 không có nghĩa mô hình đã khởi tạo thành công, và mô hình khởi tạo thành công không có nghĩa việc chép lời đã thành công.

Ngay cả khi chép lời thành công, vẫn chưa chắc kết quả đã đến đúng ô nhập văn bản của ứng dụng.

Mỗi điểm chuyển tiếp giữa các thành phần cần có tín hiệu chẩn đoán rõ ràng.

Bổ sung khả năng quan sát mà không thu thập lời nói riêng tư

Việc ghi nhật ký chẩn đoán hữu ích không đòi hỏi phải ghi lại những gì người dùng nói.

Ứng dụng có thể theo dõi những trạng thái kỹ thuật như:

permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-inserted

Khi xảy ra lỗi, hãy ghi lại giai đoạn, mã lỗi kỹ thuật ổn định và siêu dữ liệu lỗi đã được loại bỏ thông tin nhạy cảm.

Phải phân biệt được lỗi khởi tạo mô hình với trường hợp bị từ chối quyền micro hoặc bản ghi trống. Lỗi worker cần hiển thị trong quá trình phát triển, thay vì bị bộ xử lý lỗi chung âm thầm bỏ qua.

Không ghi nhật ký mẫu âm thanh từ micro, câu đã nhận dạng, chuỗi token hay bất kỳ nội dung lời nói nào khác. Dữ liệu đo hiệu năng có thể gồm các nhóm khoảng thời lượng, cấp mô hình đã chọn, thời gian khởi tạo, thời gian suy luận và nhóm lỗi.

Nhờ vậy, việc chẩn đoán micro gặp lỗi trở nên dễ dàng hơn nhiều mà không làm suy yếu yêu cầu bảo vệ quyền riêng tư.

Chèn văn bản đã nhận dạng thông qua state của React

Nhận dạng giọng nói chỉ hữu ích khi bản chép lời cuối cùng đến được ô nhập liệu sẵn có trong biểu mẫu.

Trong ứng dụng React, ô nhập liệu đó thường do state của thành phần quản lý. Gán trực tiếp vào input.value có thể xung đột với cơ chế hiển thị lại giao diện của framework.

Thay vào đó, văn bản nhận dạng được phải đi qua cùng đường cập nhật state như khi người dùng gõ chữ thông thường.

Phần tích hợp cũng phải tính đến tính bất đồng bộ. Quá trình nhận dạng có thể kết thúc sau khi người dùng đã gõ thêm ký tự hoặc di chuyển con trỏ.

Dùng giá trị ô nhập đã cũ được lưu lúc bắt đầu ghi âm có thể vô tình ghi đè những chỉnh sửa mới.

Một mẫu tích hợp rút gọn như sau:

const insertion = insertTranscript({
  currentValue: latestValue,
  transcript: recognizedText,
  selection: currentSelection,
  maxLength: inputLimit,
});

setMessage(insertion.value);
restoreCaret(insertion.caret);

Hàm hỗ trợ chèn văn bản phải giữ lại nội dung hiện tại, có thể thay thế vùng đang được chọn, thêm khoảng trắng phù hợp và tuân thủ giới hạn độ dài của ô nhập.

Cần chú ý đến Unicode vì độ dài chuỗi JavaScript được tính theo đơn vị mã UTF-16. Cắt chuỗi tại vị trí tùy ý có thể chia đôi một cặp surrogate.

Quan trọng nhất, nhận dạng giọng nói không được tự động gửi biểu mẫu. Người dùng phải có thể xem lại và sửa văn bản trước khi gửi.

Xử lý lỗi mà không ảnh hưởng đến việc nhập văn bản thông thường

Hệ thống con xử lý giọng nói phải là tính năng tùy chọn.

Nếu quyền micro bị từ chối, trình duyệt không cấp phát được bộ nhớ cho mô hình, worker gặp sự cố hoặc bản ghi không có tiếng nói, ô nhập văn bản thông thường vẫn phải hoạt động.

Biểu diễn vòng đời micro bằng những trạng thái rõ ràng như:

idle
preparing
listening
recognizing
error

Sử dụng nhãn hỗ trợ khả năng tiếp cận và phản hồi trạng thái dễ quan sát. Một biểu tượng micro chỉ đổi sang màu đỏ hoặc hồng sau khi tải gần như không cho người dùng biết điều gì.

Khi hủy, phải dọn dẹp các luồng âm thanh, ngữ cảnh âm thanh, các bộ đệm đang chờ và phiên ghi âm còn hoạt động. Giao diện cũng phải ngăn kết quả cũ từ lượt ghi âm đã hủy ghi đè state nhập liệu mới hơn.

Nếu mô hình lớn thất bại do giới hạn bộ nhớ hoặc lỗi khởi tạo, hãy giải phóng hoàn toàn trước khi thử mô hình nhỏ hơn. Giới hạn số lần thử lại để trình duyệt không liên tục tải hoặc khởi tạo những mô hình mà nó không thể chạy.

Không âm thầm chuyển sang dịch vụ nhận dạng giọng nói trên đám mây khi nhận dạng tại thiết bị thất bại. Nếu chỉ được phép xử lý tại thiết bị thì khi bộ nhận dạng cục bộ gặp lỗi, hệ thống phải báo lỗi ngay tại thiết bị.

Kiểm thử toàn bộ quy trình trong trình duyệt

Kiểm tra TypeScript hoặc tạo bản dựng sản xuất thành công chưa đủ để chứng minh chức năng nhập liệu bằng giọng nói hoạt động.

Tương tự, xác nhận trình duyệt tải được các tệp ONNX không chứng minh cả hai phiên mô hình đã khởi tạo thành công.

Bài kiểm thử hồi quy hữu ích nhất cần chạy đúng worker, các tệp runtime, mô hình, processor và tokenizer mà môi trường sản xuất sử dụng.

Đưa một bản ghi PCM ngắn đã biết vào đường suy luận thực tế trong trình duyệt và xác minh rằng kết quả chép lời không rỗng.

Sau đó, kiểm thử toàn bộ thao tác với micro trên một trình duyệt thực.

  • Tải trang lần đầu: xác nhận không có mô hình giọng nói hoặc tài nguyên runtime nào được tải trước khi sử dụng micro.
  • Khởi tạo khi chưa có bộ nhớ đệm: kiểm tra quyền micro, yêu cầu tải mô hình, loại MIME của JavaScript và WASM, cùng phản hồi thực sự xác nhận worker đã sẵn sàng.
  • Khởi tạo ONNX: xác nhận có thể tạo cả phiên encoder và decoder.
  • Thu âm: xác nhận AudioWorklet tạo ra các bộ đệm PCM không rỗng.
  • Lấy mẫu lại: xác nhận worker nhận đúng tần số lấy mẫu thực tế và tạo ra âm thanh 16 kHz hợp lệ.
  • Dừng thủ công: xác nhận nhấn nút micro lần thứ hai sẽ dừng ghi âm đáng tin cậy.
  • Dừng tự động: xác nhận những khoảng ngừng tự nhiên kết thúc bản ghi mà không cắt mất tiếng nói nhỏ.
  • Chép lời: xác nhận processor, mô hình và tokenizer đầy đủ trả về văn bản có thể nhận biết.
  • Tích hợp ô nhập: xác nhận văn bản nhận dạng xuất hiện trong ô nhập thực sự được state điều khiển mà không ghi đè chỉnh sửa của người dùng.
  • Phục hồi khi lỗi: kiểm thử trường hợp bị từ chối quyền, thiếu tệp, lỗi worker, khởi tạo mô hình thất bại, hủy thao tác và chuyển sang mô hình nhỏ hơn.
  • Quyền riêng tư: kiểm tra các yêu cầu mạng và xác nhận âm thanh micro không bị tải lên máy chủ hay dịch vụ phân tích.
  • Khả năng tương thích trên di động: lặp lại kiểm thử trên điện thoại thật, gồm lần khởi tạo đầu, các lần dùng lại và các phiên đọc chính tả liên tiếp.

Các bài kiểm thử dùng PCM đã ghi sẵn rất tốt để xác thực hệ thống suy luận, nhưng không chứng minh việc thu âm từ micro hoạt động. Bài kiểm thử nghiệm thu cuối cùng phải bao gồm micro thật và giao diện người dùng thực tế.

Kiến trúc thực tế cho tính năng nhập liệu bằng giọng nói đáng tin cậy trên trình duyệt

Với trang web cần nhập giọng nói ngắn, hỗ trợ nhiều ngôn ngữ và bảo vệ quyền riêng tư, kiến trúc thiết thực nhất được chủ ý giữ đơn giản:

Nút micro React
        |
        v
Yêu cầu quyền truy cập
        |
        v
Khởi tạo mô hình khi cần
        |
        v
Thu PCM bằng AudioWorklet
        |
        v
RMS thích ứng / dừng thủ công
        |
        v
Worker chuyên dụng
        |
        v
Lấy mẫu lại âm thanh ở 16 kHz
        |
        v
Whisper Tiny hoặc Base Q8
        |
        v
ONNX Runtime WASM tương thích
        |
        v
Processor + tokenizer Whisper
        |
        v
Bản chép lời cuối cùng
        |
        v
Ô nhập React được state điều khiển

Ứng dụng nên tự lưu trữ các tệp mô hình và runtime có đánh phiên bản, chạy suy luận ngoài luồng chính, tránh tải mô hình không cần thiết, giải phóng tài nguyên sau khi dùng và tuyệt đối không tải âm thanh micro lên để nhận dạng giọng nói.

Các lựa chọn phức tạp hơn như tăng tốc WebGPU, phát hiện hoạt động giọng nói bằng mạng nơ-ron, chép lời từng phần liên tục hoặc thêm các cấp mô hình có thể được cân nhắc sau khi số liệu đo đạc cho thấy sự phức tạp đó là hợp lý.

Bài học có giá trị nhất không nằm ở một phiên bản thư viện hay kích thước mô hình cụ thể, mà ở việc cần xác thực mọi điểm chuyển tiếp trong quy trình nhận dạng giọng nói trên trình duyệt.

Hộp thoại xin quyền micro hoạt động không có nghĩa âm thanh đã được chép lời. Tải được mô hình không có nghĩa decoder đã khởi tạo. Phiên ONNX hoạt động không có nghĩa tokenizer đã sẵn sàng. Và chép lời thành công cũng chưa có nghĩa ứng dụng đã chèn đúng văn bản.

Tính năng chỉ thật sự hoàn chỉnh khi khách truy cập có thể nhấn micro, nói tự nhiên, dừng ghi âm và thấy những từ được nhận dạng xuất hiện trong ô văn bản có thể chỉnh sửa—mà không gửi bản ghi lên máy chủ nhận dạng giọng nói.