83 lượt xem
Ẩn danh
Ngày 17 Tháng 05
Chào mọi người, em đang làm dự án bằng react có phần upload video, mà để giảm tải cho server thì em đang tìm cách để nén video (giảm chất lượng video xuống) trên web. Thường mọi người hay nén video hay nói chung là quản lý file video như thế nào vậy ạ? Em có thử dùng thư viện @ffmpeg/ffmpeg mà thấy tốc độ chậm quá.
Đánh giá câu hỏi ngay!
Hãy ấn ↑Up Vote với những câu hỏi cụ thể và chi tiết
Hãy ấn ↓Down Vote với những câu hỏi chưa rõ ràng Careerly sẽ nhắc người hỏi chỉnh sửa lại.
2 câu trả lời
BEST
Chào bạn, trước hết mình phải khẳng định: Nén video ở phía client là một ý tưởng khá tệ. Chí it cho tới thời điểm hiện tại. 🥑 Sơ lược về cách để nén video Video là một tập hợp các hình ảnh, đi kèm theo đó là âm thanh. Các codec nén mới hơn như h264, h265 hay có thể kể tới VP9 đều có khả năng nén rất tốt, tuy nhiên bù lại việc nén các video như vậy yêu cầu một sức mạnh phần cứng tương ứng. Kích thước của video thường sẽ tỉ lệ nghịch với lượng tài nguyên bỏ ra để nén. 🍃 Quay trở lại vấn đề. Thông thường các hệ thống stream video như Youtube, hoặc các bên thứ 3 như Brightcove đều xử lý video ở phía server. Đa phần là sẽ chạy render, compress với cơ chế bất đồng bộ, lúc nào xử lý xong thì gửi thông báo cho user (bằng webhook). Nếu giả định như user của hệ thống bạn đang triển khai chấp nhận việc chờ đợi để nén video ở trình duyệt, thì mình có vài lý do cho việc render chậm như sau. - Javascript không phải là một ngôn ngữ phù hợp để xử lý dữ liệu dạng này. - Có thể bạn tối ưu cấu hình ffmpeg chưa tốt. Điều này dẫn đến việc có khi dung lượng video bạn render lại cao hơn nhưng chất lượng lại thua video gốc. - Bạn có thể sử dụng ffmpeg bản web assembly để có hiệu năng tối ưu hơn: https://github.com/ffmpegwasm/ffmpeg.wasm 📺 Cách config ffmpeg tối ưu Không có một con số cụ thể nào cho mọi trường hợp, tuy nhiên nếu không có yêu cầu quá cao về mặt hình ảnh thì có thể theo gợi ý của mình ở bên dưới. Ở đây mình chọn codec h264, 1 codec phổ biến nhưng vẫn nén tốt và hỗ trợ nhiều thiết bị hơn cả. Bạn cần quan tâm 2 thuật ngữ chính: Constant Rate Factor (CRF) có giá trị từ 0 - 51, con số này càng thấp, thì video của bạn càng nét, và ngược lại. Mặc định cho CRF là 23. Các Preset thời gian, từ `ultrafast` tới `veryslow`, các Preset thời gian tỷ lệ nghịch với dung lượng file output, thời gian render càng nhanh thì dung lượng file càng lớn. Lưu ý: Bạn có thể cần phải thử nghiệm 2 yếu tố trên và tinh chỉnh lại cho phù hợp với nhu cầu. Suggest: Mình nhớ mình từng để CRF là 24-25 cho video quay màn hình 1080p, thì chất lượng vẫn tốt, chấp nhận được và theo đó dung lượng file vẫn ổn để upload lên youtube. Video đó đây: https://youtu.be/Vw2F_SRnrmg, 24 phút 1080p nhưng mình nhớ dung lượng khoảng 300mb đổ về. Tham khảo: - https://write.corbpie.com/ffmpeg-preset-comparison-x264-2019-encode-speed-and-file-size/ - https://handbrake.fr/docs/en/latest/workflow/adjust-quality.html - https://trac.ffmpeg.org/wiki/Encode/H.264
Hi bạn. Câu hỏi của bạn hay đấy xứng đáng có trong list câu hỏi phỏng vấn BE và FE kaka. Mình có một vài suggest, ý kiến riêng của mình và example code mẫu cho bạn tham khảo nhé 1. Mình khuyên bạn không nên dùng ffmpeg cho FE nhé bởi vì ffmpeg render chậm lắm. Nếu có dùng thì nên để BE dùng và xử lý. 2. Nếu bạn làm upload video tầm max 100mb thì mình nghĩ bạn cứ dùng api gọi BE xử lý bình thường cũng được không sao cả. Vì giờ các storage cũng xử lý khá nhanh file nặng rồi. 3. Bài toán này mình suggest bạn không nên nén file hay giảm chất lượng ảnh ở cả FE hay BE gì cả mà nên làm như sau - FE bạn nên chia nhỏ file ra thành từng phần nhỏ 10mb / 1 phần chẳng hạn. Ví dụ file 100mb => bạn chia 10 phần gọi async 10 lần API lên BE. - BE lúc nhận được thông tin bạn đẩy dữ liệu vào queue để xử lý => lưu lại các phần file nhỏ được FE gửi lên. Sau khi nhận được hết file nhỏ => BE tiến hành concatenate các phần nhỏ lại với nhau để tạo ra file gốc ban đầu. Bạn có thể F12 và gửi file trên Zalo để xem nhé. Hiện tại, mình thấy Zalo cũng đang làm kiểu vậy. Dưới đây là code sample JavaScript và Go để xử lý. Bạn tham khảo nhé. JavaScript cho FE const importInvoices = () => { document.getElementById("file-input").click(); // Example usage const fileInput = document.getElementById("file-input"); fileInput.addEventListener("change", async (event) => { const file = event.target.files[0]; const chunkSize = 10 * 1024 * 1024; // Set the desired chunk size (10MB in this example) const totalChunks = Math.ceil(file.size / chunkSize); // Iterate over the chunks and upload them sequentially for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const start = chunkIndex * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); // Make an API call to upload the chunk to the backend await uploadChunk(chunk, chunkIndex); } }); }; async function uploadChunk(chunk, chunkIndex) { const formData = new FormData(); formData.append("chunk", chunk); formData.append("chunkIndex", chunkIndex); try { const response = await fetch("http://localhost:3000/upload", { method: "POST", body: formData, }); if (!response.ok) { throw new Error("Error uploading chunk."); } } catch (error) { console.error(error); // Handle error } } document.getElementById("btn-import").addEventListener("click", (event) => { event.preventDefault(); importInvoices(); }); Golang cho BE => API xử lý lưu file chia nhỏ func uploadHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") index := r.FormValue("chunkIndex") file, _, err := r.FormFile("chunk") if err != nil { http.Error(w, "Failed to retrieve file", http.StatusBadRequest) return } defer file.Close() // Process and save the chunk to a file outputFile, err := os.Create(fmt.Sprintf("video%v", index)) if err != nil { http.Error(w, "Failed to create file", http.StatusInternalServerError) return } defer outputFile.Close() _, err = io.Copy(outputFile, file) if err != nil { http.Error(w, "Failed to write file", http.StatusInternalServerError) return } fmt.Fprintf(w, "Chunk uploaded successfully") } func main() { http.HandleFunc("/upload", uploadHandler) // Start the server log.Fatal(http.ListenAndServe(":3000", nil)) } Nối file nhỏ thành file hoàn chỉnh func main() { outputFile, err := os.Create("output.mp4") if err != nil { fmt.Println("Error:", err) return } defer outputFile.Close() chunkPaths := []string{ "video0", "video1", "video2", } // Concatenate the video chunks for _, chunkPath := range chunkPaths { chunkFile, err := os.Open(chunkPath) if err != nil { fmt.Println("Error:", err) return } defer chunkFile.Close() _, err = io.Copy(outputFile, chunkFile) if err != nil { fmt.Println("Error:", err) return } } }
Đăng ký ngay bây giờ để đọc toàn bộ câu trả lời!
Cộng đồng lập trình viên sẽ giải đáp tường tận cho bạn.
Bạn đã có tài khoản rồi?
Đăng ký ngay bây giờ để đọc toàn bộ câu trả lời!
Cộng đồng lập trình viên sẽ giải đáp tường tận cho bạn.