Files
SonicForgeStudio/md/6_NEW_PLAN.md

1146 lines
44 KiB
Markdown

Trò chuyện với Gemini
Hãy chuyển nội dung sau sang dạng markdown:
Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
Chức năng ở Frontend (JS)
Cơ chế hoạt động của Web Audio
Thư viện & Module Python thay thế
Giải mã âm thanh
AudioContext.decodeAudioData()
librosa.load() hoặc soundfile.read()
Tìm Zero-Crossing
Duyệt mảng Float32Array thủ công
Phép toán mảng numpy.diff() & numpy.sign()
Cắt & Tạo Loop
AudioBuffer.slice() & AudioBufferSourceNode.loop
pydub.AudioSegment hoặc Slicing trên numpy.ndarray
Fade-in / Fade-out
GainNode.gain.linearRampToValueAtTime()
pydub.fade() hoặc Nhân mảng với numpy.linspace()
Thay đổi Volume (Gain)
GainNode.gain.value
Phép nhân số thực trên mảng numpy hoặc pydub + dB
Trộn âm đa kênh
OfflineAudioContext (Mixdown)
pydub.AudioSegment.overlay() hoặc Phép cộng mảng Numpy
Xuất tệp WAV/MP3/OGG
Ghi dữ liệu nhị phân RIFF thủ công
soundfile.write() (WAV) và pydub/FFmpeg (MP3, OGG)
2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
Trong JavaScript, ta quét một cửa sổ $\pm 40\text{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
Mã nguồn Python nguyên mẫu (dsp_utils.py):
import numpy as np
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
"""
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
y: Mảng biên độ âm thanh (1D numpy array, Mono)
sr: Tần số lấy mẫu (Sample Rate)
target_time: Vị trí mong muốn cắt (giây)
window_seconds: Cửa sổ quét (mặc định 40ms)
"""
target_sample = int(target_time * sr)
window_samples = int(window_seconds * sr)
# Xác định giới hạn vùng quét an toàn
start_idx = max(0, target_sample - window_samples)
end_idx = min(len(y) - 2, target_sample + window_samples)
# Lấy phân khúc sóng âm trong cửa sổ quét
y_window = y[start_idx:end_idx]
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
signs = np.sign(y_window)
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
zero_crossings = np.where(np.diff(signs) != 0)[0]
if len(zero_crossings) == 0:
return target_time # Không tìm thấy, trả về vị trí gốc
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
absolute_crossings = zero_crossings + start_idx
# Tìm điểm gần với target_sample nhất
distances = np.abs(absolute_crossings - target_sample)
closest_sample_idx = absolute_crossings[np.argmin(distances)]
# Trả về thời gian tương ứng (giây)
return float(closest_sample_idx / sr)
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
Mã nguồn Python nguyên mẫu (audio_editor.py):
from pydub import AudioSegment
import os
def cut_and_loop_segment(
file_path: str,
start_sec: float,
end_sec: float,
loop_count: int = 1,
fade_in_ms: int = 50,
fade_out_ms: int = 50,
volume_db_change: float = 0.0
) -> AudioSegment:
"""
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
"""
# Tải tệp tin âm thanh gốc
sound = AudioSegment.from_file(file_path)
# Chuyển đổi giây sang mili-giây
start_ms = int(start_sec * 1000)
end_ms = int(end_sec * 1000)
# Trích xuất phân đoạn (Slicing)
clip = sound[start_ms:end_ms]
# Thay đổi âm lượng nếu có yêu cầu
if volume_db_change != 0.0:
clip = clip + volume_db_change
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
if fade_in_ms > 0:
clip = clip.fade_in(fade_in_ms)
if fade_out_ms > 0:
clip = clip.fade_out(fade_out_ms)
# Tạo chuỗi lặp (Looping)
looped_clip = clip * loop_count
return looped_clip
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
"""
tracks_meta: Danh sách cấu hình của từng track:
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
"""
master_mix = None
for track in tracks_meta:
if track.get("muted", False):
continue
# Đọc tệp âm thanh của track
sound = AudioSegment.from_file(track["file_path"])
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
gain_raw = track.get("volume", 1.0)
if gain_raw <= 0.001:
continue # Xem như tắt tiếng hoàn toàn
gain_db = 20 * np.log10(gain_raw)
sound = sound + gain_db
# Gộp vào Master Mix
if master_mix is None:
master_mix = sound
else:
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
master_mix = master_mix.overlay(sound, position=0)
if master_mix is not None:
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
# Chuyển đổi định dạng xuất bản theo yêu cầu
temp_wav = "temp_mix.wav"
master_mix.export(temp_wav, format="wav")
import soundfile as sf
y, sr = sf.read(temp_wav)
# Xác định subtype mã hóa bit-depth
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
# Ghi tệp nén/không nén hoàn chỉnh
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
# Dọn dẹp tệp tạm
if os.path.exists(temp_wav):
os.remove(temp_wav)
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
Khi người dùng thao tác trên giao diện SonicForge Studio và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
3.1. Thiết kế JSON Payload gửi từ Client lên Server
{
"session_id": "session_pro_2026_07_18",
"export_settings": {
"sample_rate": 44100,
"bit_depth": 24,
"format": "wav"
},
"tracks": [
{
"track_id": "1",
"file_id": "source_audio_drum_loop.wav",
"volume": 0.85,
"muted": false,
"clips": [
{
"clip_id": "clip_01",
"start_time_seconds": 2.450,
"end_time_seconds": 12.800,
"loop_count": 4,
"apply_zero_crossing": true,
"fade_in_ms": 150,
"fade_out_ms": 150
}
]
}
]
}
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
Chi tiết các bước thực thi:
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg và libsndfile1.
Xây dựng các Endpoint cơ bản phục vụ tải lên (/upload) và tải xuống (/download) tệp tin âm nhạc.
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức $0$ (tuyệt đối đồng bộ).
Tuần 3: Biên tập và Hòa âm Đa kênh
Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) và sóng âm.
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (bars).
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
Python
import os
markdown_content = """# Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
---
## 1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
| Chức năng ở Frontend (JS) | Cơ chế hoạt động của Web Audio | Thư viện & Module Python thay thế |
| :--- | :--- | :--- |
| **Giải mã âm thanh** | `AudioContext.decodeAudioData()` | `librosa.load()` hoặc `soundfile.read()` |
| **Tìm Zero-Crossing** | Duyệt mảng `Float32Array` thủ công | Phép toán mảng `numpy.diff()` & `numpy.sign()` |
| **Cắt & Tạo Loop** | `AudioBuffer.slice()` & `AudioBufferSourceNode.loop` | `pydub.AudioSegment` hoặc Slicing trên `numpy.ndarray` |
| **Fade-in / Fade-out** | `GainNode.gain.linearRampToValueAtTime()` | `pydub.fade()` hoặc Nhân mảng với `numpy.linspace()` |
| **Thay đổi Volume (Gain)** | `GainNode.gain.value` | Phép nhân số thực trên mảng `numpy` hoặc `pydub` + dB |
| **Trộn âm đa kênh** | `OfflineAudioContext` (Mixdown) | `pydub.AudioSegment.overlay()` hoặc Phép cộng mảng `Numpy` |
| **Xuất tệp WAV/MP3/OGG** | Ghi dữ liệu nhị phân RIFF thủ công | `soundfile.write()` (WAV) và `pydub`/`FFmpeg` (MP3, OGG) |
---
## 2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
### 2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
Trong JavaScript, ta quét một cửa sổ $\pm 40\text{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
**Mã nguồn Python nguyên mẫu (`dsp_utils.py`):**
```python
import numpy as np
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
\"\"\"
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
y: Mảng biên độ âm thanh (1D numpy array, Mono)
sr: Tần số lấy mẫu (Sample Rate)
target_time: Vị trí mong muốn cắt (giây)
window_seconds: Cửa sổ quét (mặc định 40ms)
\"\"\"
target_sample = int(target_time * sr)
window_samples = int(window_seconds * sr)
# Xác định giới hạn vùng quét an toàn
start_idx = max(0, target_sample - window_samples)
end_idx = min(len(y) - 2, target_sample + window_samples)
# Lấy phân khúc sóng âm trong cửa sổ quét
y_window = y[start_idx:end_idx]
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
signs = np.sign(y_window)
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
zero_crossings = np.where(np.diff(signs) != 0)[0]
if len(zero_crossings) == 0:
return target_time # Không tìm thấy, trả về vị trí gốc
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
absolute_crossings = zero_crossings + start_idx
# Tìm điểm gần với target_sample nhất
distances = np.abs(absolute_crossings - target_sample)
closest_sample_idx = absolute_crossings[np.argmin(distances)]
# Trả về thời gian tương ứng (giây)
return float(closest_sample_idx / sr)
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) hai đầu phân đoạn bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
nguồn Python nguyên mẫu (audio_editor.py):
Python
from pydub import AudioSegment
import os
def cut_and_loop_segment(
file_path: str,
start_sec: float,
end_sec: float,
loop_count: int = 1,
fade_in_ms: int = 50,
fade_out_ms: int = 50,
volume_db_change: float = 0.0
) -> AudioSegment:
\"\"\"
Cắt đoạn âm thanh, áp dụng gain, fade-in/out lặp lại đoạn đó.
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
\"\"\"
# Tải tệp tin âm thanh gốc
sound = AudioSegment.from_file(file_path)
# Chuyển đổi giây sang mili-giây
start_ms = int(start_sec * 1000)
end_ms = int(end_sec * 1000)
# Trích xuất phân đoạn (Slicing)
clip = sound[start_ms:end_ms]
# Thay đổi âm lượng nếu có yêu cầu
if volume_db_change != 0.0:
clip = clip + volume_db_change
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
if fade_in_ms > 0:
clip = clip.fade_in(fade_in_ms)
if fade_out_ms > 0:
clip = clip.fade_out(fade_out_ms)
# Tạo chuỗi lặp (Looping)
looped_clip = clip * loop_count
return looped_clip
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, gộp chúng vào một tệp âm thanh Master duy nhất.
Python
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
\"\"\"
tracks_meta: Danh sách cấu hình của từng track:
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
\"\"\"
master_mix = None
for track in tracks_meta:
if track.get("muted", False):
continue
# Đọc tệp âm thanh của track
sound = AudioSegment.from_file(track["file_path"])
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
gain_raw = track.get("volume", 1.0)
if gain_raw <= 0.001:
continue # Xem như tắt tiếng hoàn toàn
gain_db = 20 * np.log10(gain_raw)
sound = sound + gain_db
# Gộp vào Master Mix
if master_mix is None:
master_mix = sound
else:
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
master_mix = master_mix.overlay(sound, position=0)
if master_mix is not None:
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
# Chuyển đổi định dạng xuất bản theo yêu cầu
temp_wav = "temp_mix.wav"
master_mix.export(temp_wav, format="wav")
import soundfile as sf
y, sr = sf.read(temp_wav)
# Xác định subtype mã hóa bit-depth
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
# Ghi tệp nén/không nén hoàn chỉnh
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
# Dọn dẹp tệp tạm
if os.path.exists(temp_wav):
os.remove(temp_wav)
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
Khi người dùng thao tác trên giao diện SonicForge Studio thực hiện một lệnh xử (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
3.1. Thiết kế JSON Payload gửi từ Client lên Server
JSON
{
"session_id": "session_pro_2026_07_18",
"export_settings": {
"sample_rate": 44100,
"bit_depth": 24,
"format": "wav"
},
"tracks": [
{
"track_id": "1",
"file_id": "source_audio_drum_loop.wav",
"volume": 0.85,
"muted": false,
"clips": [
{
"clip_id": "clip_01",
"start_time_seconds": 2.450,
"end_time_seconds": 12.800,
"loop_count": 4,
"apply_zero_crossing": true,
"fade_in_ms": 150,
"fade_out_ms": 150
}
]
}
]
}
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
Để quá trình tích hợp nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
Plaintext
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
[ TUẦN 3 ] ──► Viết bộ xử biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
Chi tiết các bước thực thi:
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg libsndfile1.
Xây dựng các Endpoint bản phục vụ tải lên (/upload) tải xuống (/download) tệp tin âm nhạc.
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) bằng Numpy (trên Server).
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức 0 (tuyệt đối đồng bộ).
Tuần 3: Biên tập Hòa âm Đa kênh
Triển khai bộ xử hàng đợi Celery kết nối Redis để thực thi các tác vụ xử âm thanh nặng dưới nền.
Đảm bảo tính năng tăng giảm âm lượng tuyến tính logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
Tuần 4: Phân tích Âm học Tích hợp Trí tuệ Nhân tạo (AI)
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) sóng âm.
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) vị trí phách của khuôn nhạc (bars).
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
Kiểm tra hiệu năng khi nhiều người dùng đồng thời tải lên thao tác cắt ghép trên dòng thời gian.
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
"""
with open("Porting_DSP_Engine_Plan.md", "w", encoding="utf-8") as f:
f.write(markdown_content)
print("File written successfully.")
```text?code_stdout&code_event_index=1
File written successfully.
Your Markdown file is ready
Biểu tượng MD
Porting_DSP_Engine_Plan
MD
Đang mở
Dưới đây là toàn bộ nội dung đã được chuyển đổi sang định dạng Markdown chuẩn, căn chỉnh bảng biểu, tối ưu hóa code block syntax highlighting (python, json, text), cũng như giữ nguyên các công thức dạng toán học LaTeX:
Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
Chức năng ở Frontend (JS) Cơ chế hoạt động của Web Audio Thư viện & Module Python thay thế
Giải mã âm thanh AudioContext.decodeAudioData() librosa.load() hoặc soundfile.read()
Tìm Zero-Crossing Duyệt mảng Float32Array thủ công Phép toán mảng numpy.diff() & numpy.sign()
Cắt & Tạo Loop AudioBuffer.slice() & AudioBufferSourceNode.loop pydub.AudioSegment hoặc Slicing trên numpy.ndarray
Fade-in / Fade-out GainNode.gain.linearRampToValueAtTime() pydub.fade() hoặc Nhân mảng với numpy.linspace()
Thay đổi Volume (Gain) GainNode.gain.value Phép nhân số thực trên mảng numpy hoặc pydub + dB
Trộn âm đa kênh OfflineAudioContext (Mixdown) pydub.AudioSegment.overlay() hoặc Phép cộng mảng Numpy
Xuất tệp WAV/MP3/OGG Ghi dữ liệu nhị phân RIFF thủ công soundfile.write() (WAV) và pydub/FFmpeg (MP3, OGG)
2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
Trong JavaScript, ta quét một cửa sổ ±40ms quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
Mã nguồn Python nguyên mẫu (dsp_utils.py):
Python
import numpy as np
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
"""
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
y: Mảng biên độ âm thanh (1D numpy array, Mono)
sr: Tần số lấy mẫu (Sample Rate)
target_time: Vị trí mong muốn cắt (giây)
window_seconds: Cửa sổ quét (mặc định 40ms)
"""
target_sample = int(target_time * sr)
window_samples = int(window_seconds * sr)
# Xác định giới hạn vùng quét an toàn
start_idx = max(0, target_sample - window_samples)
end_idx = min(len(y) - 2, target_sample + window_samples)
# Lấy phân khúc sóng âm trong cửa sổ quét
y_window = y[start_idx:end_idx]
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
signs = np.sign(y_window)
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
zero_crossings = np.where(np.diff(signs) != 0)[0]
if len(zero_crossings) == 0:
return target_time # Không tìm thấy, trả về vị trí gốc
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
absolute_crossings = zero_crossings + start_idx
# Tìm điểm gần với target_sample nhất
distances = np.abs(absolute_crossings - target_sample)
closest_sample_idx = absolute_crossings[np.argmin(distances)]
# Trả về thời gian tương ứng (giây)
return float(closest_sample_idx / sr)
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
Mã nguồn Python nguyên mẫu (audio_editor.py):
Python
from pydub import AudioSegment
import os
def cut_and_loop_segment(
file_path: str,
start_sec: float,
end_sec: float,
loop_count: int = 1,
fade_in_ms: int = 50,
fade_out_ms: int = 50,
volume_db_change: float = 0.0
) -> AudioSegment:
"""
Cắt đoạn âm thanh, áp dụng gain, fade-in/out lặp lại đoạn đó.
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
"""
# Tải tệp tin âm thanh gốc
sound = AudioSegment.from_file(file_path)
# Chuyển đổi giây sang mili-giây
start_ms = int(start_sec * 1000)
end_ms = int(end_sec * 1000)
# Trích xuất phân đoạn (Slicing)
clip = sound[start_ms:end_ms]
# Thay đổi âm lượng nếu có yêu cầu
if volume_db_change != 0.0:
clip = clip + volume_db_change
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
if fade_in_ms > 0:
clip = clip.fade_in(fade_in_ms)
if fade_out_ms > 0:
clip = clip.fade_out(fade_out_ms)
# Tạo chuỗi lặp (Looping)
looped_clip = clip * loop_count
return looped_clip
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
Python
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
"""
tracks_meta: Danh sách cấu hình của từng track:
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
"""
master_mix = None
for track in tracks_meta:
if track.get("muted", False):
continue
# Đọc tệp âm thanh của track
sound = AudioSegment.from_file(track["file_path"])
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
gain_raw = track.get("volume", 1.0)
if gain_raw <= 0.001:
continue # Xem như tắt tiếng hoàn toàn
gain_db = 20 * np.log10(gain_raw)
sound = sound + gain_db
# Gộp vào Master Mix
if master_mix is None:
master_mix = sound
else:
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
master_mix = master_mix.overlay(sound, position=0)
if master_mix is not None:
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
# Chuyển đổi định dạng xuất bản theo yêu cầu
temp_wav = "temp_mix.wav"
master_mix.export(temp_wav, format="wav")
import soundfile as sf
y, sr = sf.read(temp_wav)
# Xác định subtype mã hóa bit-depth
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
# Ghi tệp nén/không nén hoàn chỉnh
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
# Dọn dẹp tệp tạm
if os.path.exists(temp_wav):
os.remove(temp_wav)
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
Khi người dùng thao tác trên giao diện SonicForge Studio và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
3.1. Thiết kế JSON Payload gửi từ Client lên Server
JSON
{
"session_id": "session_pro_2026_07_18",
"export_settings": {
"sample_rate": 44100,
"bit_depth": 24,
"format": "wav"
},
"tracks": [
{
"track_id": "1",
"file_id": "source_audio_drum_loop.wav",
"volume": 0.85,
"muted": false,
"clips": [
{
"clip_id": "clip_01",
"start_time_seconds": 2.450,
"end_time_seconds": 12.800,
"loop_count": 4,
"apply_zero_crossing": true,
"fade_in_ms": 150,
"fade_out_ms": 150
}
]
}
]
}
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
Plaintext
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
Chi tiết các bước thực thi:
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg và libsndfile1.
Xây dựng các Endpoint cơ bản phục vụ tải lên (/upload) và tải xuống (/download) tệp tin âm nhạc.
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức 0 (tuyệt đối đồng bộ).
Tuần 3: Biên tập và Hòa âm Đa kênh
Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) và sóng âm.
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (bars).
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
Gemini là AI và có thể mắc sai sót.
# Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
---
## 1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
| Chức năng ở Frontend (JS) | Cơ chế hoạt động của Web Audio | Thư viện & Module Python thay thế |
| :--- | :--- | :--- |
| **Giải mã âm thanh** | `AudioContext.decodeAudioData()` | `librosa.load()` hoặc `soundfile.read()` |
| **Tìm Zero-Crossing** | Duyệt mảng `Float32Array` thủ công | Phép toán mảng `numpy.diff()` & `numpy.sign()` |
| **Cắt & Tạo Loop** | `AudioBuffer.slice()` & `AudioBufferSourceNode.loop` | `pydub.AudioSegment` hoặc Slicing trên `numpy.ndarray` |
| **Fade-in / Fade-out** | `GainNode.gain.linearRampToValueAtTime()` | `pydub.fade()` hoặc Nhân mảng với `numpy.linspace()` |
| **Thay đổi Volume (Gain)** | `GainNode.gain.value` | Phép nhân số thực trên mảng `numpy` hoặc `pydub` + dB |
| **Trộn âm đa kênh** | `OfflineAudioContext` (Mixdown) | `pydub.AudioSegment.overlay()` hoặc Phép cộng mảng `Numpy` |
| **Xuất tệp WAV/MP3/OGG** | Ghi dữ liệu nhị phân RIFF thủ công | `soundfile.write()` (WAV) và `pydub`/`FFmpeg` (MP3, OGG) |
---
## 2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
### 2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
Trong JavaScript, ta quét một cửa sổ $\pm 40 ext{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
**Mã nguồn Python nguyên mẫu (`dsp_utils.py`):**
```python
import numpy as np
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
"""
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
y: Mảng biên độ âm thanh (1D numpy array, Mono)
sr: Tần số lấy mẫu (Sample Rate)
target_time: Vị trí mong muốn cắt (giây)
window_seconds: Cửa sổ quét (mặc định 40ms)
"""
target_sample = int(target_time * sr)
window_samples = int(window_seconds * sr)
# Xác định giới hạn vùng quét an toàn
start_idx = max(0, target_sample - window_samples)
end_idx = min(len(y) - 2, target_sample + window_samples)
# Lấy phân khúc sóng âm trong cửa sổ quét
y_window = y[start_idx:end_idx]
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
signs = np.sign(y_window)
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
zero_crossings = np.where(np.diff(signs) != 0)[0]
if len(zero_crossings) == 0:
return target_time # Không tìm thấy, trả về vị trí gốc
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
absolute_crossings = zero_crossings + start_idx
# Tìm điểm gần với target_sample nhất
distances = np.abs(absolute_crossings - target_sample)
closest_sample_idx = absolute_crossings[np.argmin(distances)]
# Trả về thời gian tương ứng (giây)
return float(closest_sample_idx / sr)
```
### 2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
Khi thực hiện cắt một phân đoạn từ `start_time` đến `end_time`, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
**Mã nguồn Python nguyên mẫu (`audio_editor.py`):**
```python
from pydub import AudioSegment
import os
def cut_and_loop_segment(
file_path: str,
start_sec: float,
end_sec: float,
loop_count: int = 1,
fade_in_ms: int = 50,
fade_out_ms: int = 50,
volume_db_change: float = 0.0
) -> AudioSegment:
"""
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
"""
# Tải tệp tin âm thanh gốc
sound = AudioSegment.from_file(file_path)
# Chuyển đổi giây sang mili-giây
start_ms = int(start_sec * 1000)
end_ms = int(end_sec * 1000)
# Trích xuất phân đoạn (Slicing)
clip = sound[start_ms:end_ms]
# Thay đổi âm lượng nếu có yêu cầu
if volume_db_change != 0.0:
clip = clip + volume_db_change
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
if fade_in_ms > 0:
clip = clip.fade_in(fade_in_ms)
if fade_out_ms > 0:
clip = clip.fade_out(fade_out_ms)
# Tạo chuỗi lặp (Looping)
looped_clip = clip * loop_count
return looped_clip
```
### 2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
Tương tự như `OfflineAudioContext` gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
```python
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
"""
tracks_meta: Danh sách cấu hình của từng track:
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
"""
master_mix = None
for track in tracks_meta:
if track.get("muted", False):
continue
# Đọc tệp âm thanh của track
sound = AudioSegment.from_file(track["file_path"])
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
gain_raw = track.get("volume", 1.0)
if gain_raw <= 0.001:
continue # Xem như tắt tiếng hoàn toàn
gain_db = 20 * np.log10(gain_raw)
sound = sound + gain_db
# Gộp vào Master Mix
if master_mix is None:
master_mix = sound
else:
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
master_mix = master_mix.overlay(sound, position=0)
if master_mix is not None:
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
# Chuyển đổi định dạng xuất bản theo yêu cầu
temp_wav = "temp_mix.wav"
master_mix.export(temp_wav, format="wav")
import soundfile as sf
y, sr = sf.read(temp_wav)
# Xác định subtype mã hóa bit-depth
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
# Ghi tệp nén/không nén hoàn chỉnh
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
# Dọn dẹp tệp tạm
if os.path.exists(temp_wav):
os.remove(temp_wav)
```
---
## 3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
Khi người dùng thao tác trên giao diện *SonicForge Studio* và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
### 3.1. Thiết kế JSON Payload gửi từ Client lên Server
```json
{
"session_id": "session_pro_2026_07_18",
"export_settings": {
"sample_rate": 44100,
"bit_depth": 24,
"format": "wav"
},
"tracks": [
{
"track_id": "1",
"file_id": "source_audio_drum_loop.wav",
"volume": 0.85,
"muted": false,
"clips": [
{
"clip_id": "clip_01",
"start_time_seconds": 2.450,
"end_time_seconds": 12.800,
"loop_count": 4,
"apply_zero_crossing": true,
"fade_in_ms": 150,
"fade_out_ms": 150
}
]
}
]
}
```
---
## 4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (*Phase cancellation*) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
```text
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
```
### Chi tiết các bước thực thi:
#### **Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway**
* Cài đặt Docker image tích hợp `python:3.11-slim`, biên dịch bổ sung gói phần mềm `ffmpeg``libsndfile1`.
* Xây dựng các Endpoint cơ bản phục vụ tải lên (`/upload`) và tải xuống (`/download`) tệp tin âm nhạc.
#### **Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing**
* Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
* Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức $0$ (tuyệt đối đồng bộ).
#### **Tuần 3: Biên tập và Hòa âm Đa kênh**
* Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
* Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (*Clipping distortion*).
#### **Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)**
* Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (*metadata*) và sóng âm.
* Sử dụng `librosa.beat.beat_track` để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (*bars*).
#### **Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống**
* Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
* Tối ưu hóa dung lượng bộ nhớ tạm (*Temporary storage*) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
Porting_DSP_Engine_Plan.md
Đang hiển thị Porting_DSP_Engine_Plan.md.