Software decoding — quá trình giải nén dữ liệu đa phương tiện bằng CPU thông qua các thư viện phần mềm, mà không sử dụng các khối phần cứng của SoC. Bộ giải mã phần mềm được triển khai dưới dạng thư viện đa nền tảng: FFmpeg với libavcodec và dav1d cho AV1. Theo tài liệu FFmpeg (2026), libavcodec hỗ trợ hơn 200 codec, khiến software decoding trở thành cách duy nhất để phát các định dạng hiếm.
Những điểm chính
Software decoding là phương pháp giải nén dữ liệu đa phương tiện trong đó tất cả các hoạt động tính toán được thực hiện trên các lõi CPU đa năng. Không giống như giải mã phần cứng, nơi mỗi codec có một khối vật lý chuyên dụng, bộ giải mã phần mềm là mã thông thường thực thi các thuật toán tương tự bằng lệnh của bộ xử lý.
Bộ giải mã phần mềm được viết bằng C/C++ với các tối ưu hóa cho kiến trúc CPU cụ thể: lệnh SIMD ARM NEON cho thiết bị di động, Intel SSE/AVX cho máy tính để bàn. Thư viện libavcodec từ FFmpeg chứa hàng chục nghìn dòng mã assembly được tối ưu hóa cho các nền tảng khác nhau, cho phép giải mã phần mềm đạt được hiệu suất tốt ngay cả đối với các định dạng nặng như AV1 trên CPU mạnh.
Ưu điểm chính của giải mã phần mềm là tính linh hoạt. Nếu bộ giải mã phần cứng chỉ hỗ trợ 4–5 định dạng chính (H.264, H.265, VP9, AV1), FFmpeg có thể giải mã hơn 200 codec: từ AV1 và H.265 hiện đại đến Sorenson Spark, RealVideo và Motion JPEG cổ điển. Điều này làm cho giải mã phần mềm trở thành công cụ không thể thiếu cho các ứng dụng làm việc với dữ liệu đa phương tiện không chuẩn — như trình chỉnh sửa video chuyên nghiệp, hệ thống giám sát video và trình phát chuyên dụng.
Giải mã phần mềm tuân theo các giai đoạn giống như giải mã phần cứng, nhưng trên CPU đa năng. Mỗi giai đoạn được triển khai dưới dạng các hàm được gọi tuần tự cho mỗi macroblock hoặc khung hình. Sự khác biệt chính là tính linh hoạt: nhà phát triển có thể sửa đổi pipeline, thêm bộ lọc và xử lý hậu kỳ giữa các giai đoạn giải mã.
Một bộ giải mã phần mềm điển hình bao gồm các mô-đun thực hiện các giai đoạn thuật toán riêng lẻ. Mô-đun giải mã entropy đọc luồng bit và tái tạo các hệ số DCT đã lượng tử hóa. Đối với H.264, mô-đun này triển khai CABAC (Mã hóa số học nhị phân thích ứng ngữ cảnh) — một thuật toán phức tạp với các nhánh có điều kiện khó tăng tốc bằng phần cứng nhưng chạy hiệu quả trên CPU có bộ dự đoán nhánh tốt.
Mô-đun lượng tử hóa nghịch đảo nhân các hệ số với bước lượng tử, và mô-đun DCT nghịch đảo áp dụng biến đổi cosin rời rạc. Triển khai phần mềm của DCT nghịch đảo sử dụng thuật toán Chen nhanh hoặc thuật toán Loeffler, giúp giảm số lượng phép toán nhân-tích lũy từ 4096 xuống 256 cho khối 8x8. Các lệnh SIMD NEON (ARM) hoặc SSE (x86) cho phép xử lý 4–8 hệ số mỗi lệnh, mang lại tốc độ nhanh hơn 4–8 lần so với mã vô hướng.
Mô-đun bù chuyển động là mô-đun tốn nhiều bộ nhớ nhất. Nó trích xuất các vùng từ khung tham chiếu theo vector chuyển động và áp dụng nội suy dưới điểm ảnh. Đối với H.265, độ chính xác nội suy đạt 1/8 pixel, yêu cầu bộ lọc FIR 8 tap cho độ chói và 4 tap cho độ màu. Triển khai phần mềm phải tải một lượng lớn dữ liệu khung tham chiếu từ bộ nhớ đệm, khiến bù chuyển động trở thành nút thắt cổ chai khi giải mã độ phân giải cao trên CPU.
Các bộ xử lý di động hiện đại, như Apple A17 hoặc Qualcomm Snapdragon 8 Gen 2, có 6–8 lõi với hiệu suất đủ để giải mã phần mềm 1080p H.264 mà không bỏ khung hình. Tuy nhiên, đối với nội dung 4K, đặc biệt ở định dạng H.265 và AV1, giải mã phần mềm trên CPU có thể gặp khó khăn: tải điển hình trên tất cả các lõi đạt 70–90%, rất quan trọng đối với đa nhiệm. Các lõi lớn (Apple Performance, Qualcomm Kryo Prime) cung cấp hiệu suất gấp ~4–5 lần so với các lõi tiết kiệm năng lượng nhỏ, nhưng tiêu thụ nhiều năng lượng hơn tương ứng.
Thị trường bộ giải mã phần mềm có một số thư viện chính, mỗi thư viện được tối ưu hóa cho lĩnh vực riêng. Việc chọn bộ giải mã phụ thuộc vào định dạng yêu cầu, nền tảng và các ràng buộc về giấy phép.
FFmpeg là tiêu chuẩn thực tế cho giải mã phần mềm trong ngành. Thư viện libavcodec bao gồm các bộ giải mã cho tất cả codec chính và hầu hết codec hiếm, hỗ trợ tất cả các container (MP4, MKV, AVI, MOV, WebM) và chạy trên mọi nền tảng. FFmpeg được cấp phép theo LGPL/GPL, yêu cầu tuân thủ các điều khoản cấp phép khi sử dụng thương mại. Trên thiết bị di động, FFmpeg được sử dụng qua các wrapper: ffmpeg-kit cho iOS và Android, mobile-ffmpeg cho React Native.
Dav1d là bộ giải mã phần mềm AV1 từ VideoLAN (nhà phát triển VLC), được viết bằng C với tối ưu hóa SIMD. Mục tiêu chính của nó là giải mã phần mềm AV1 nhanh nhất có thể trên CPU không có hỗ trợ phần cứng. Dav1d nhanh hơn 30–50% so với bộ giải mã tham chiếu libaom từ Alliance for Open Media nhờ các tối ưu hóa mạnh mẽ: quản lý bộ nhớ đệm thủ công, biên dịch JIT cho bộ lọc xử lý hậu kỳ và vector hóa các hàm quan trọng.
Trên thiết bị di động, dav1d có thể giải mã 1080p AV1 theo thời gian thực trên các SoC hàng đầu (Apple A16+, Snapdragon 8 Gen 2+), nhưng 4K yêu cầu CPU mạnh. Ví dụ, trên Apple M1, dav1d phần mềm đạt ~60 FPS cho 4K AV1, trong khi trên Snapdragon 8 Gen 2 đạt ~35 FPS. Để phát 4K AV1 ổn định trên thiết bị di động, hỗ trợ phần cứng vẫn được khuyến nghị.
| Bộ giải mã | Định dạng | Nền tảng | Giấy phép |
|---|---|---|---|
| libavcodec | 200+ codec | Tất cả | LGPL/GPL |
| dav1d | AV1 | Tất cả | BSD 2-Clause |
| libaom | AV1 | Tất cả | BSD 2-Clause |
| MediaFoundation | H.264, H.265 | Windows | Độc quyền |
Sự lựa chọn giữa giải mã phần mềm và phần cứng là sự đánh đổi giữa tương thích và hiệu quả. Bảng dưới đây so sánh chi tiết các đặc điểm chính.
| Tham số | Software Decoding | Hardware Decoding |
|---|---|---|
| Định dạng hỗ trợ | 200+ codec | 4–6 codec |
| Tiêu thụ năng lượng | 1,5–5 W | 0,2–0,8 W |
| Tùy chỉnh | Kiểm soát hoàn toàn pipeline | Chỉ qua API |
| Độ trễ | 30–80 ms | 5–15 ms |
| Tản nhiệt | Cao (45–50 C) | Thấp (35–40 C) |
| Cập nhật codec | Qua cập nhật thư viện | Chỉ với SoC mới |
Giải mã phần mềm mang lại sự linh hoạt tối đa: nhà phát triển có thể sửa đổi thuật toán, thêm bộ lọc tùy chỉnh và triển khai pipeline xử lý riêng. Ví dụ, trong ứng dụng chỉnh sửa video, mỗi giai đoạn giải mã có thể được chuyển hướng đến GPU để hiệu chỉnh màu sắc hoặc chồng hiệu ứng — điều này chỉ khả thi khi có kiểm soát phần mềm đối với giải mã.
Tuy nhiên, cái giá của sự linh hoạt là tiêu thụ năng lượng. Đối với thiết bị di động có pin 3000–5000 mAh, giải mã phần mềm liên tục làm giảm thời gian xem từ 10–15 giờ (phần cứng) xuống 2–4 giờ. CPU nóng lên đến 45–50 độ cũng có thể gây ra hiện tượng throttling — giảm tần số bộ xử lý để tránh quá nhiệt, dẫn đến bỏ khung hình và suy giảm trải nghiệm người dùng.
Hãy xem xét triển khai thực tế của giải mã phần mềm trên cả hai nền tảng di động. Trên iOS, giải mã phần mềm được sử dụng qua FFmpeg, và trên Android — qua cùng thư viện với wrapper Java/Kotlin.
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
class SoftwareDecoder {
AVCodecContext* codecCtx;
public:
bool init(const char* filename) {
AVFormatContext* fmtCtx = nullptr;
avformat_open_input(&fmtCtx, filename, nullptr, nullptr);
avformat_find_stream_info(fmtCtx, nullptr);
int videoStream = av_find_best_stream(
fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0
);
AVCodec* decoder = avcodec_find_decoder(
fmtCtx->streams[videoStream]->codecpar->codec_id
);
codecCtx = avcodec_alloc_context3(decoder);
avcodec_parameters_to_context(codecCtx,
fmtCtx->streams[videoStream]->codecpar);
avcodec_open2(codecCtx, decoder, nullptr);
return true;
}
AVFrame* decodePacket(AVPacket* packet) {
avcodec_send_packet(codecCtx, packet);
AVFrame* frame = av_frame_alloc();
int ret = avcodec_receive_frame(codecCtx, frame);
return (ret >= 0) ? frame : nullptr;
}
};
Mã này minh họa một pipeline FFmpeg tối thiểu cho giải mã phần mềm. avformat_open_input mở tệp và xác định định dạng container, avcodec_find_decoder tự động tìm bộ giải mã phù hợp cho bất kỳ codec nào. Phương thức decodePacket sử dụng API mới (avcodec_send_packet / avcodec_receive_frame), hỗ trợ giải mã đa luồng với cờ AV_CODEC_FLAG_LOW_DELAY được bật cho ứng dụng thời gian thực.
class SoftwareDecoder(private val context: Context) {
fun decodeVideo(inputPath: String, outputFolder: String) {
val cmd = "-i $inputPath -vf fps=1 $outputFolder/frame_%04d.jpg"
FFmpegExecutor(context).executeCommand(cmd) { rc ->
Log.d("Decoder", "Finished with rc: $rc")
}
}
fun getFrameCount(filePath: String): Int {
val probe = MediaMetadataRetriever()
probe.setDataSource(filePath)
val duration = probe.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION
)?.toIntOrNull() ?: 0
val fps = probe.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT
)?.toIntOrNull() ?: 0
probe.release()
return fps
}
}
Ví dụ Kotlin sử dụng FFmpegExecutor để trích xuất một khung hình mỗi giây từ video. Tham số -vf fps=1 tạo bộ lọc bỏ qua 59 khung hình trong số 60 khung hình, giảm tải CPU. Cách tiếp cận này hữu ích để tạo bản xem trước và trình giữ chỗ trong ứng dụng di động. Để giải mã phần mềm thời gian thực, nên sử dụng API cấp thấp libavcodec trực tiếp qua JNI.
#include <dav1d/dav1d.h>
int decode_av1_frame(const uint8_t* data, size_t size) {
Dav1dContext* ctx = nullptr;
Dav1dSettings settings = { 0 };
dav1d_default_settings(&settings);
settings.n_threads = 4;
dav1d_open(&ctx, &settings);
Dav1dData dav1d_data = { 0 };
dav1d_data_wrap(&dav1d_data, data, size, nullptr, nullptr);
Dav1dPicture pic = { 0 };
if (dav1d_send_data(ctx, &dav1d_data) == 0) {
dav1d_get_picture(ctx, &pic);
}
dav1d_close(&ctx);
return pic.p.w;
}
Dav1d cung cấp API tối giản: dav1d_open tạo ngữ cảnh bộ giải mã với số luồng được chỉ định, dav1d_send_data chấp nhận luồng bit đã nén, dav1d_get_picture trả về khung hình đã giải mã ở định dạng YUV420. Đối với thiết bị di động, số luồng tối ưu (n_threads) là số lõi CPU hiệu suất trừ một, để dành tài nguyên cho luồng giao diện người dùng. Dav1d cũng hỗ trợ Dav1dPicAllocator để quản lý bộ nhớ và tránh sao chép không cần thiết khi truyền khung hình đến GPU.
Mặc dù tiêu thụ năng lượng cao hơn, giải mã phần mềm không thể thiếu trong một số tình huống mà giải mã phần cứng không thể cung cấp chức năng cần thiết. Hiểu các tình huống này giúp nhà phát triển đưa ra quyết định kiến trúc.
Bộ giải mã phần cứng chỉ hỗ trợ các định dạng hiện đại. Nếu ứng dụng làm việc với bản ghi lưu trữ, giám sát video (MJPEG, H.263), codec chuyên nghiệp (ProRes, DNxHD, CineForm) hoặc nội dung từ nguồn bên thứ ba — giải mã phần mềm qua FFmpeg sẽ là lựa chọn duy nhất. ProRes được giải mã chỉ bằng phần mềm trên tất cả các thiết bị ngoại trừ chip Apple A13+ có hỗ trợ phần cứng. Đối với H.263, không có hỗ trợ phần cứng trên bất kỳ SoC hiện đại nào — chỉ giải mã phần mềm.
Giải mã phần mềm cung cấp quyền truy cập đầy đủ vào từng giai đoạn xử lý khung hình. Điều này rất quan trọng đối với các ứng dụng cần áp dụng bộ lọc (làm mờ, giảm nhiễu, làm sắc nét) trực tiếp trên dữ liệu đã giải mã trước khi xuất. Bộ lọc FFmpeg cho phép xây dựng các chuỗi phức tạp: giải mã -> hiệu chỉnh màu -> thay đổi kích thước -> chồng phụ đề -> mã hóa — tất cả trong một thư viện duy nhất mà không cần truyền dữ liệu giữa các API khác nhau.
Kiến trúc trình phát đa phương tiện được khuyến nghị là lai ghép: giải mã phần cứng làm chính, giải mã phần mềm làm dự phòng. Trước khi phát, ứng dụng kiểm tra sự sẵn có của bộ giải mã phần cứng cho codec được chỉ định. Nếu không tìm thấy bộ giải mã, giải mã phần mềm được khởi chạy qua FFmpeg. Chiến lược này đảm bảo khả năng tương thích tối đa mà không hy sinh hiệu suất cho các định dạng chính. Việc kiểm tra khả năng sẵn có nên được thực hiện mỗi lần khởi chạy, vì hỗ trợ phần cứng có thể khác nhau ngay cả trên các thiết bị cùng model do các phiên bản SoC khác nhau.
Câu hỏi thường gặp
CPU là bộ xử lý đa năng thực hiện nhiều tác vụ khác nhau. Để giải mã, nó sử dụng các đơn vị tính toán dùng chung và bộ nhớ đệm, tiêu thụ năng lượng ngay cả khi thực hiện một tác vụ duy nhất. Bộ giải mã phần cứng là một mạch chuyên dụng cao với pipeline cố định, nơi mọi bóng bán dẫn chỉ dành riêng cho giải mã, giúp giảm triệt để mức tiêu thụ năng lượng.
Đối với H.264/H.265 — libavcodec từ FFmpeg với tối ưu hóa SIMD được bật. Đối với AV1 — dav1d, nhanh hơn 30–50% so với libaom tham chiếu. Trên thiết bị di động, hiệu suất của dav1d cho phép giải mã 1080p AV1 thời gian thực trên các SoC hàng đầu (A16+, Dimensity 9200+).
Có, FFmpeg đã được chuyển sang cả hai nền tảng. Đối với iOS, hãy sử dụng ffmpeg-kit — bản dựng sẵn có hỗ trợ tất cả codec và định dạng. Đối với Android — mobile-ffmpeg hoặc tự xây dựng FFmpeg qua NDK. Lưu ý các hạn chế về giấy phép GPL/LGPL khi phân phối thương mại.
Giải mã thời gian thực có nghĩa là CPU có thể giải mã khung hình nhanh hơn tốc độ hiển thị trên màn hình (thường là 30 hoặc 60 FPS). Đối với 1080p H.264, CPU di động hiện đại xử lý dễ dàng, sử dụng khoảng 30–50% của một lõi hiệu suất. Đối với 4K H.265, thời gian thực trên CPU chỉ khả thi trên các SoC hàng đầu với tải 70–90% trên tất cả các lõi.
Sử dụng giải mã đa luồng (song song cấp khung hình) qua FFmpeg với cờ thread_count, đặt skip_frame thành B-frames (nếu chấp nhận được cho tình huống), giảm độ phân giải qua bộ lọc scale trước khi giải mã. Đối với AV1 với dav1d, sử dụng n_threads = số lõi CPU trừ một.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm