Sơ đồ hoạt động của hệ thống:
Dưới đây là sơ đồ hoạt động của hệ thống digital human:
Nhìn vào kiến trúc tổng thể, ta thấy rằng để hiển thị Digital Humans một cách sống động, chúng ta cần đồng bộ hóa giữa hình ảnh video và âm thanh, và đẩy cả hai vào một luồng stream duy nhất. Nhưng đó mới chỉ là một nửa câu chuyện. Trải nghiệm sẽ chẳng thể trọn vẹn nếu người dùng không thể tương tác trực tiếp, trò chuyện một cách tự nhiên với Digital Human của mình trong thời gian thực.
Đây chính là lúc chúng ta cần nghĩ đến WebRTC. WebRTC được sinh ra để giải quyết bài toán giao tiếp thời gian thực, đảm bảo sự mượt mà và liền mạch cho trải nghiệm người dùng. Và thật may mắn, thư viện aiortc
cung cấp cho chúng ta những công cụ có sẵn để hiện thực hóa điều này.
Thư viện aiortc
aiortc
là gì ?
Hiểu đơn giản aiortc
thực chất là một thư viện mã nguồn mở, mang sức mạnh của WebRTC đến với cộng đồng lập trình viên Python giúp nhiều nhà phát triển dễ dàng tiếp cận và đơn giản hóa việc tích hợp khả năng thời gian thực vào các dự án khác nhau.
Điểm cốt lõi làm nên sự linh hoạt của aiortc
chính là việc nó được xây dựng trên asyncio
, framework I/O bất đồng bộ tiêu chuẩn của Python. Điều này cho phép xử lý hiệu quả nhiều kết nối và luồng dữ liệu cùng lúc mà không làm tắc nghẽn chương trình.
Thế WebRTC là gì?
Để dễ hình dung, WebRTC (Web Real-Time Communication) là một tập hợp các giao thức và API cho phép giao tiếp thời gian thực trực tiếp giữa các trình duyệt và thiết bị. Nó hỗ trợ truyền tải video, giọng nói và dữ liệu đa dạng giữa các "peer", giúp chúng ta xây dựng các ứng dụng giao tiếp hiệu quả mà không cần đến plugin độc quyền hay phần mềm bổ sung phức tạp.
Tại sao nên chọn aiortc
?
Trong khi các triển khai WebRTC và ORTC phổ biến thường được tích hợp sẵn vào trình duyệt web hoặc tồn tại dưới dạng mã nguồn gốc (native code). Mặc dù đã được thử nghiệm rộng rãi, cấu trúc bên trong của chúng khá phức tạp và quan trọng hơn là không cung cấp "Python bindings" (cầu nối để Python có thể tương tác). Hơn nữa, chúng thường gắn chặt với một "media stack" (bộ công cụ xử lý đa phương tiện) cụ thể, gây khó khăn khi chúng ta muốn tích hợp các thuật toán xử lý âm thanh hoặc video của riêng mình.
Ngược lại, aiortc
mang đến một cách tiếp cận rất khác biệt. Mã nguồn khá dễ đọc, dễ dùng, nhất là khi mình mới tìm hiểu về WebRTC (vốn đã nhiều khái niệm phức tạp). Hơn nữa do viết bằng python, điều này giúp ta dễ dàng tích hợp với các thư viện xử lý ảnh của python khác như OpenCV, NumPy,...
Nói tóm lại, vì chúng ta đang lập trình bằng python cho digital human, nên để dễ tích hợp, ta cần 1 thư viện python cho dễ code ^^
Tuy nhiên, nếu bạn muốn một giải pháp WebRTC linh hoạt, dễ tiếp cận và tích hợp sâu vào môi trường Python, aiortc
là một lựa chọn sáng giá nha.
Các tính năng chính aiortc
cung cấp:
- Giao tiếp Ngang hàng (P2P) trực tiếp: Giảm độ trễ, tăng hiệu suất truyền tải.
- Xử lý Luồng Media hiệu quả: Tối ưu cho hội nghị video và livestreaming.
- Dữ liệu đa năng: Cho phép trao đổi mọi loại dữ liệu, từ chia sẻ file thông qua kết nối.
Cài đặt:
pip install aiortc
Thực hành với code demo
Tạo một project demo chứa 3 file
demo/
├── index.html
├── client.js
└── main.py
main.py
là backend chính sử dụng thư viện aiohttp
để xử lý các request HTTP và aiortc
để xử lý logic WebRTC phía server:
import asyncio
import json
import os from aiohttp import web
from av import VideoFrame
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, VideoStreamTrack
from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder ROOT = os.path.dirname(__file__) class VideoTransformTrack(MediaStreamTrack): kind = "video" def __init__(self, track): super().__init__() self.track = track async def recv(self): frame = await self.track.recv() return frame pcs = set() async def index(request): content = open(os.path.join(ROOT, "index.html"), "r").read() return web.Response(content_type="text/html", text=content) async def javascript(request): content = open(os.path.join(ROOT, "client.js"), "r").read() return web.Response(content_type="application/javascript", text=content) async def offer(request): params = await request.json() offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) pc = RTCPeerConnection() pcs.add(pc) @pc.on("connectionstatechange") async def on_connectionstatechange(): print("Connection state is %s" % pc.connectionState) if pc.connectionState == "failed": await pc.close() pcs.discard(pc) @pc.on("track") def on_track(track): print("Track %s received" % track.kind) if track.kind == "video": local_video = VideoTransformTrack(track) pc.addTrack(local_video) @track.on("ended") async def on_ended(): print("Track %s ended" % track.kind) # handle offer await pc.setRemoteDescription(offer) # send answer answer = await pc.createAnswer() await pc.setLocalDescription(answer) return web.Response( content_type="application/json", text=json.dumps( {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} ), ) async def on_shutdown(app): # close peer connections coros = [pc.close() for pc in pcs] await asyncio.gather(*coros) pcs.clear() if __name__ == "__main__": app = web.Application() app.on_shutdown.append(on_shutdown) app.router.add_get("/", index) app.router.add_get("/client.js", javascript) app.router.add_post("/offer", offer) web.run_app(app, host="0.0.0.0", port=8080)
Giải thích 1 chút:
-
async def offer(request)
: Đây là hàm cốt lõi xử lý toàn bộ quá trình "bắt tay" (signaling) của WebRTC từ phía server.- Nhận "offer" từ client.
- Tạo một đối tượng
RTCPeerConnection
(pc
) cho client. - Thiết lập các trình xử lý sự kiện cho
pc
: @pc.on("track")
: Phát hiện khi server nhận được một luồng media (video) từ client. Tại đây, server sẽ lấy luồng video này, bọc nó bằngVideoTransformTrack
và thêm trở lại vàopc
để gửi ngược lại cho client.- Đặt "offer" của client làm mô tả từ xa (
setRemoteDescription
). - Tạo một "answer" (lời chấp nhận kết nối SDP) và đặt làm mô tả cục bộ (
setLocalDescription
). - Gửi "answer" này trở lại cho client.
-
class VideoTransformTrack(MediaStreamTrack)
: Đại diện cho luồng video mà server sẽ gửi đi. Tại đây ta có thể can thiệp và biến đổi các frame video trước khi gửi.
3. client.js
(Logic Client)
-
startButton.onclick = async () => { ... }
:- Vai trò chính: Đây là hàm xử lý chính khi người dùng muốn bắt đầu phiên WebRTC.
- Nhiệm vụ:
navigator.mediaDevices.getUserMedia(...)
: Yêu cầu quyền truy cập camera và microphone của người dùng.- Hiển thị luồng video cục bộ lên thẻ
localVideo
. pc = new RTCPeerConnection(configuration);
: Tạo đối tượngRTCPeerConnection
, là trung tâm của kết nối WebRTC phía client.- Thêm các luồng media (audio/video) cục bộ vào
pc
để gửi đi. pc.ontrack = (event) => { ... }
: Lắng nghe và xử lý khi nhận được luồng media từ server (hiển thị lênremoteVideo
).- Thực hiện Signaling (Offer/Answer Process):
const offer = await pc.createOffer();
: Tạo "offer" SDP.await pc.setLocalDescription(offer);
: Đặt "offer" làm mô tả cục bộ.fetch('/offer', { ... })
: Gửi "offer" này đến server.- Nhận "answer" từ server.
await pc.setRemoteDescription(answer);
: Đặt "answer" của server làm mô tả từ xa, hoàn tất quá trình "bắt tay".
-
stopButton.onclick = () => { ... }
:- Vai trò chính: Xử lý khi người dùng muốn dừng phiên WebRTC.
- Nhiệm vụ:
- Đóng kết nối
RTCPeerConnection
(pc.close()
). - Dừng các luồng media cục bộ (tắt camera/mic).
- Dọn dẹp giao diện người dùng.
- Đóng kết nối
index.html
chịu tránh nhiệm hiển thị webcam và video stream bằng WebRTC cùng với các nút điều khiển.
<!DOCTYPE html>
<html>
<head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>WebRTC Demo</title> <style> button { padding: 8px 16px; margin: 0 8px; cursor: pointer; background-color: #4CAF50; color: white; border: none; border-radius: 4px; } video { width: 640px; height: 480px; margin: 1em; background: #ddd; } .grid { display: grid; grid-template-columns: repeat(auto-fill, 640px); gap: 1em; } </style>
</head>
<body> <h1>WebRTC Demo</h1> <div class="grid"> <div> <h2>Local Video</h2> <video id="localVideo" autoplay playsinline></video> </div> <div> <h2>Remote Video</h2> <video id="remoteVideo" autoplay playsinline></video> </div> </div> <button id="start">Start</button> <button id="stop">Stop</button> <script src="client.js"></script>
</body>
</html>
<video id="localVideo" ...></video>
và<video id="remoteVideo" ...></video>
:- Vai trò chính: Hai thẻ này là nơi hiển thị trực quan luồng video.
localVideo
: Hiển thị video từ webcam của người dùng.remoteVideo
: Hiển thị video nhận được từ server (trong demo này là video của chính người dùng được server gửi lại).
<button id="start">Start</button>
và<button id="stop">Stop</button>
:- Vai trò chính: Cho phép người dùng bắt đầu và kết thúc phiên truyền video WebRTC. Các hành động của người dùng trên các nút này sẽ kích hoạt các hàm JavaScript tương ứng trong
client.js
.
- Vai trò chính: Cho phép người dùng bắt đầu và kết thúc phiên truyền video WebRTC. Các hành động của người dùng trên các nút này sẽ kích hoạt các hàm JavaScript tương ứng trong
<script src="client.js"></script>
:- Vai trò chính: Nhúng và thực thi file JavaScript
client.js
, nơi chứa toàn bộ logic WebRTC phía client.
- Vai trò chính: Nhúng và thực thi file JavaScript
client.js
chứa logic phía máy khách cho các kết nối WEBRTC, DOM và logic cho thao tác của người dùng.
// Get DOM elements
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop'); // WebRTC configuration
const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
}; let pc;
let localStream; // Start button click handler
startButton.onclick = async () => { try { // Get local media stream localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); localVideo.srcObject = localStream; // Create peer connection pc = new RTCPeerConnection(configuration); // Add local stream to peer connection localStream.getTracks().forEach(track => { pc.addTrack(track, localStream); }); // Listen for remote stream pc.ontrack = (event) => { if (remoteVideo.srcObject !== event.streams[0]) { remoteVideo.srcObject = event.streams[0]; } }; // Create and send offer const offer = await pc.createOffer(); await pc.setLocalDescription(offer); const response = await fetch('/offer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sdp: pc.localDescription.sdp, type: pc.localDescription.type }) }); const answer = await response.json(); await pc.setRemoteDescription(answer); startButton.disabled = true; stopButton.disabled = false; } catch (e) { console.error('Error starting WebRTC:', e); }
}; // Stop button click handler
stopButton.onclick = () => { if (pc) { pc.close(); pc = null; } if (localStream) { localStream.getTracks().forEach(track => track.stop()); localStream = null; } localVideo.srcObject = null; remoteVideo.srcObject = null; startButton.disabled = false; stopButton.disabled = true;
}; // Initialize
stopButton.disabled = true;
Chạy thử với main.py
Kết quả
Thực hành với ứng dụng lớn hơn
Tiếp theo, chúng ta sẽ cùng nhau tạo 1 ứng dụng lớn hơn nha. Ứng dụng của chúng ta sẽ là một phòng chat video đơn giản cho phép nhiều người có thể tham gia.
Mục tiêu ứng dụng
Mục tiêu là xây dựng một ứng dụng web cho phép nhiều người dùng tham gia vào một phiên chat video chung. Cụ thể, ứng dụng sẽ có các tính năng sau:
- Người dùng có thể nhập tên người dùng và tham gia vào phòng chat video.
- Sau khi tham gia, người dùng có thể truyền phát video và audio của mình cho những người khác trong phòng và ngược lại, nhận được video/audio từ họ.
- Điều khiển Media: Người dùng sẽ có các nút điều khiển cơ bản như:
- Tắt/Bật tiếng
- Dừng/Phát Video
- Hiển thị động người tham gia: Giao diện sẽ tự động cập nhật để hiển thị video của tất cả những người đang tham gia trong phòng chat.
Kiến trúc dự kiến
aiortc-test/
├── templates/
│ └── index.html # Main HTML page for the video chat interface
├── static/
│ ├── css/
│ │ └── style.css # Stylesheets for the application
│ └── js/
│ └── main.js # Client-side JavaScript for WebRTC and UI logic
├── server.py # (Assumed) Python backend server (e.g., using aiohttp and aiortc)
├── requirements.txt # (Assumed) Python dependencies
└── README.md # This file
Cài đặt các thư viện sau:
aiortc
fastapi
uvicorn
python-multipart
aiofiles
jinja2
Cách chạy dự án
Đầu tiên là chuẩn bị môi trường phát triển và hiểu cách khởi chạy ứng dụng.
Clone repo sau về máy:
git clone https://github.com/h-oneohone/aiortc-test.git
cd aiortc-test
Tạo và kích hoạt môi trường ảo:
python -m venv venv
Kích hoạt môi trường ảo:
Trên Windows:
venv\Scripts\activate
Trên macOS/Linux:
source venv/bin/activate
Cài đặt các thư viện:
pip install -r requirements.txt
Khởi chạy Backend Server
Sau khi đã cài đặt xong các thư viện, chúng ta cần chạy server Python (server.py).
python server.py
Sử dụng ứng dụng
Khi backend server đã hoạt động, bạn có thể trải nghiệm ứng dụng video chat:
- Mở trình duyệt web: Chrome hoặc Firefox.
- Truy cập URL ứng dụng: Mở trình duyệt và điều hướng đến địa chỉ mà server đang chạy, ví dụ: http://localhost:8080
- Bạn sẽ thấy màn hình "Join the Session"
- Nhập tên người dùng mong muốn vào ô nhập liệu.
- Nhấn nút "Join" Trình duyệt của bạn có thể sẽ yêu cầu quyền truy cập vào camera và microphone. Hãy cấp quyền để tiếp tục.
Sau khi tham gia thành công, video của bạn sẽ xuất hiện, và bạn sẽ ở trong giao diện chính với bảng điều khiển và khu vực hiển thị người tham gia, bạn có thể:
- Tắt/Bật tiếng audio của bạn bằng nút "Mute".
- Dừng/Phát video của bạn bằng nút "Stop Video".
Kết luận
Vậy là chúng ta đã cùng nhau tìm hiểu về aiortc
và cách ứng dụng nó vào việc giúp người dùng giao tiếp được với digital human. Hy vọng bạn đã hình dung được cách tạo ra một ứng dụng giao tiếp thời gian thực.
aiortc
thực sự là một người bạn đồng hành tuyệt vời, giúp chúng ta dễ dàng hiểu và ứng dụng dụng WebRTC hơn. Tuy nhiên vẫn còn điểm hạn chế khi số lượng người dùng quá lớn, hẹn gặp lại trong bài viết tiếp theo, nơi chúng ta sẽ cùng nhau giải quyết bài toán này ^^.