基于FFmpeg和HLS的大文件分片傳輸方案

1:功能介紹

????????在視頻這類大文件的傳輸過程中,經常會因為文件太大而受到網絡帶寬的限制。比如在實現視頻預覽功能時,常常會出現長時間加載、緩存卡頓的問題。我在項目中也遇到了類似的情況,于是采用了這個解決方案。

????????我們可以利用 FFmpeg 這個強大的工具,把體積較大的 MP4 視頻文件轉換成 HLS 格式。HLS 會將視頻切分成多個小片段:一個個 .ts 文件,同時生成一個 .m3u8 播放列表文件。

????????你可以把 .m3u8 文件理解成一個“目錄”,它告訴播放器一共有多少個視頻片段、按什么順序播放。而 .ts 文件就是按固定時長(比如每10秒一段)切出來的視頻小片段。

????????播放時,客戶端不再需要一次性加載整個視頻,而是根據 .m3u8 目錄,一個片段一個片段地按需加載。這樣即使網絡帶寬有限,也能快速開始播放,邊下邊播,大大減少了等待緩存的時間,顯著提升了用戶體驗。

????????這個方案特別適合用于在線視頻播放、課程平臺、監控回放等需要快速預覽大視頻的場景。

優點:

  1. 漸進式加載:客戶端按需加載小片段,無需等待整個文件下載

  2. 自適應碼率:支持不同網絡條件下的流暢播放

  3. 斷點續傳:客戶端可以從中斷處繼續播放

  4. CDN 友好:便于內容分發網絡緩存

2:使用FFmpeg實現格式轉換

將 MP4 轉換為 HLS 格式轉換指令:

ffmpeg -i input.mp4 \-c:v copy -c:a copy \          # 保持原始編碼-hls_time 10 \                 # 每個切片10秒-hls_list_size 0 \             # 播放列表包含所有分段-hls_segment_filename "output_%03d.ts" \ # 分段文件名output.m3u8                    # 播放列表

程序實現轉換功能:

int convert_to_hls(const char *mp4_path) {// 直接從完整路徑提取文件名(不含路徑和擴展名)const char *base_name = strrchr(mp4_path, '/');base_name = base_name ? base_name + 1 : mp4_path;char file_name[256];strncpy(file_name, base_name, sizeof(file_name)-1);file_name[sizeof(file_name)-1] = '\0';// 移除擴展名char *ext = strrchr(file_name, '.');if (ext) *ext = '\0';// 確保HLS目錄存在ensure_directory(HLS_DIR);char playlist_path[512];snprintf(playlist_path, sizeof(playlist_path), "%s/%s.m3u8", HLS_DIR, file_name);// 檢查是否已轉換struct stat st;if (stat(playlist_path, &st) == 0) {printf("HLS already exists: %s\n", playlist_path);return 0;}// 獲取文件大小if (stat(mp4_path, &st)) {perror("Failed to get file size");return -1;}off_t file_size = st.st_size;// 動態計算切片時間int segment_time = 10; // 默認10秒if (file_size > 100 * 1024 * 1024) { // >100MBsegment_time = 20;}if (file_size > 500 * 1024 * 1024) { // >500MBsegment_time = 30;}if (file_size > 1024 * 1024 * 1024) { // >1GBsegment_time = 60;}printf("File size: %.2f MB, using segment time: %d seconds\n", (double)file_size/(1024*1024), segment_time);char command[4096];snprintf(command, sizeof(command),"%s -i '%s' -c:v copy -c:a copy -hls_time %d -hls_list_size 0 ""-threads 4 "  // 使用4個線程加速轉換"-hls_segment_filename '%s/%s_%%03d.ts' ""'%s/%s.m3u8'", FFMPEG_PATH, mp4_path, segment_time, HLS_DIR, file_name, HLS_DIR, file_name);printf("Converting to HLS: %s\n", command);int ret = system(command);if (ret != 0) {fprintf(stderr, "FFmpeg conversion failed with code %d\n", ret);return -1;}return 0;
}

其中采用動態的切片操作根據要傳輸的文件大小來選擇執行對應的切片大小,這樣可以優化一點由于視頻文件過長而導致切片過多的現象。

3:構建嵌入式http服務器

http協議屬于應用層協議,其中使用的傳輸層是基于TCP協議進行傳輸,在c語言中創建TCP服務器采用的是socket編程。其中相關的協議就不過多介紹,附上源碼。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <poll.h>
#include <stdarg.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <sched.h>
#include "Function.h"
#include "record_management_app.h"#define OPEN_MAX 512
#define SERVER_PORT 1001typedef struct ClientInfo {struct pollfd client_fds[OPEN_MAX];  
} ClientInfo;       //定義客戶端總結構體ClientInfo client_info;volatile sig_atomic_t keep_running = 1;void signal_handler(int signal) {if (signal == SIGINT || signal == SIGTERM) {printf("Caught signal %d, shutting down gracefully...\n", signal);keep_running = 0;}
}void handle_connection(int num, struct sockaddr_in *client);int main(int argc, char const *argv[])
{int ret;                int socket_fd;   int client_fd;struct sockaddr_in server;struct sockaddr_in client;socklen_t client_len = sizeof(client);// 設置退出信號處理器struct sigaction term_sa;memset(&term_sa, 0, sizeof(term_sa));term_sa.sa_handler = signal_handler;sigemptyset(&term_sa.sa_mask);term_sa.sa_flags = 0;  // 關鍵:不自動重啟系統調用if (sigaction(SIGINT, &term_sa, NULL) == -1) {perror("sigaction(SIGINT) failed");exit(EXIT_FAILURE);}if (sigaction(SIGTERM, &term_sa, NULL) == -1) {perror("sigaction(SIGTERM) failed");exit(EXIT_FAILURE);}// 設置 SIGCHLD 信號處理器struct sigaction sa;memset(&sa, 0, sizeof(sa));sa.sa_handler = sigchld_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;sigaction(SIGTERM, &sa, NULL);  // kill 命令或系統關機信號if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction failed");exit(EXIT_FAILURE);}// 創建socket 對象socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket_fd < 0) {perror("socket");return -1;}printf("create socket success, socket = %d\n", socket_fd);//端口復用int optval = 1;if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {perror("setsockopt(SO_REUSEADDR) failed");exit(EXIT_FAILURE);}// 給服務器綁定 net_info.ipmemset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(SERVER_PORT);server.sin_addr.s_addr = INADDR_ANY;printf("Port: %d\n",  SERVER_PORT);ret = bind(socket_fd, (struct sockaddr *)&server, sizeof(server));if (ret < 0) {perror("bind");return -1;}printf("bind success\n");// 創建最大連接數量ret = listen(socket_fd, 10);if (ret < 0) {perror("listen");}//添加監聽描述符client_info.client_fds[0].fd = socket_fd;client_info.client_fds[0].events = POLLIN; // 監聽讀事件//初始化客戶連接描述符for (int i = 1; i < OPEN_MAX; i++) {client_info.client_fds[i].fd = -1;}int nready = 0;     // 可以描述符個數int i = 1;          // 存儲下一個要添加的描述符的下標// 主循環,監聽并處理客戶端的連接while (keep_running) {//獲取可用描述符的個數nready = poll(client_info.client_fds, OPEN_MAX, 1000);               if (nready == -1) {if (errno == EINTR) {// 如果是被信號中斷,則繼續循環continue;} else {perror("poll error:");continue;  // 繼續循環而不是退出}}//測試監聽描述符是否準備好if (client_info.client_fds[0].revents & POLLIN){client_fd = accept(socket_fd, (struct sockaddr *)&client, &client_len);if (client_fd == -1){perror("accept error:");exit(1);}   printf("one client coming,  net_info.ip = %s\n", inet_ntoa(client.sin_addr));//將新的連接描述符添加到數組中for (i = 0; i < OPEN_MAX; i++){if (client_info.client_fds[i].fd < 0){client_info.client_fds[i].fd = client_fd;break;}}if (i == OPEN_MAX){printf("too many clients\n");exit(1);}//將新的描述符添加到讀描述符集合中client_info.client_fds[i].events = POLLIN;// 主線程不再監聽新的連接if (--nready <= 0){continue;}}//處理客戶連接handle_connection(OPEN_MAX, &client);}return 0;
}
//接口處理函數
void handle_connection(int num, struct sockaddr_in *client)
{int i = 0;size_t cnt = 0;uint8_t rbuf[65535] = {0};   // 增大緩沖區大小為64kbfor (i = 0; i < num; i++){if (client_info.client_fds[i].fd < 0) continue;//測試客戶端描述符是否準備好if(client_info.client_fds[i].revents & POLLIN){cnt = read(client_info.client_fds[i].fd, rbuf, sizeof(rbuf));if (cnt == 0){close(client_info.client_fds[i].fd);printf("client %s disconnect\n", inet_ntoa(client->sin_addr));client_info.client_fds[i].fd = -1;continue;}if (cnt < 0){if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // 非阻塞模式下,沒有數據可讀perror("read error:");continue;}printf("rbuf: \r%s\n", rbuf);ApiPath api_path = {0};if (parse_api_path((char*)rbuf, &api_path) != 0) {printf("Invalid API path format\n");continue;}printf("Parsed Topic: %s, Method: %s\n", api_path.topic, api_path.method);if (strncmp((const char*)rbuf, "GET", 3) == 0) {   if (strncmp(api_path.topic, "record_management", 17) == 0) {printf("進入視頻預覽功能\n");// 清理 method,去掉空格之后的內容char *space = strchr(api_path.method, ' ');if (space) {*space = '\0';}char decoded_method[200];url_decode(api_path.method, decoded_method, sizeof(decoded_method));printf("Method: %s\n", decoded_method);// 處理HLS文件請求(.m3u8或.ts)if (strstr(decoded_method, ".m3u8") || strstr(decoded_method, ".ts")) {char file_path[512];// 直接定位到HLS目錄snprintf(file_path, sizeof(file_path), "%s/hls/%s", MOUNT_POINT, decoded_method);const char *content_type = strstr(decoded_method, ".m3u8") ? "application/x-mpegURL" : "video/MP2T";send_file(client_info.client_fds[i].fd, file_path, content_type);continue;}// 啟動HLS流char *video_name = strdup(decoded_method);if (!video_name) {perror("strdup failed");continue;}// 移除可能的文件擴展名char *ext = strrchr(video_name, '.');if (ext) *ext = '\0';// 準備線程參數size_t arg_size = sizeof(int) + strlen(video_name) + 1;void *thread_arg = malloc(arg_size);if (!thread_arg) {perror("malloc for thread_arg failed");free(video_name);continue;}// 復制客戶端文件描述符和視頻名int client_fd = client_info.client_fds[i].fd;memcpy(thread_arg, &client_fd, sizeof(int));memcpy(thread_arg + sizeof(int), video_name, strlen(video_name) + 1);pthread_t hls_thread;pthread_create(&hls_thread, NULL, send_hls_stream, thread_arg);pthread_detach(hls_thread);free(video_name);}}}}
}

4:編譯與運行

Makefile

# 設置SDK根目錄
SYSROOT := /home/qingwu007/aarch64-buildroot-linux-gnu_sdk-buildroot# 設置工具鏈前綴
BUILD_TOOL_DIR := $(SYSROOT)
BUILD_TOOL_PREFIX := $(BUILD_TOOL_DIR)/bin/aarch64-buildroot-linux-gnu-# 定義工具鏈
CC := $(BUILD_TOOL_PREFIX)gcc
AR := $(BUILD_TOOL_PREFIX)ar
LD := $(BUILD_TOOL_PREFIX)gcc# 編譯參數
CFLAGS := -g -Wall \--sysroot=$(SYSROOT) \-I$(SYSROOT)/include \-I$(SYSROOT)/usr/include \-I$(SYSROOT)/cjson \-I$(SYSROOT)/usr/include/aarch64-buildroot-linux-gnu \-I./include# 鏈接參數
LDFLAGS := --sysroot=$(SYSROOT) \-L$(SYSROOT)/lib64 \-L$(SYSROOT)/usr/lib64 \-Wl,-rpath-link,$(SYSROOT)/lib64 \-Wl,-rpath-link,$(SYSROOT)/usr/lib64 \-Wl,-rpath,/opt/app/bin       # 添加這一行,指定運行時庫路徑-Wl,--dynamic-linker=/lib64/ld-linux-aarch64.so.1 \-fPIC# 需要鏈接的庫
LIBS :=  -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lc -lcjson -lpthread# 目標設置
TARGET := hettp_save# 源文件處理 - 自動查找src目錄下的所有.c文件
SRC_DIR := src
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c,%.o,$(SRCS)).PHONY: all cleanall: $(TARGET)$(TARGET): $(OBJS)$(LD) -o $@ $^ $(LDFLAGS) $(LIBS)# 模式規則:編譯源文件
%.o: $(SRC_DIR)/%.c@echo "Compiling $<..."$(CC) $(CFLAGS) -c $< -o $@# 靜態庫目標示例
libexample.a: $(OBJS)$(AR) rcs $@ $^clean:rm -f $(TARGET) $(OBJS) libexample.a# 安裝目標
install: $(TARGET)cp $(TARGET) /usr/local/bin# 調試目標
debug: CFLAGS += -DDEBUG -O0
debug: clean all.PHONY: install debug

我在http服務器上面寫的接口是:

http://IP:port/_api/app/record_management/xxxxx

測試的接口根據自己的環境來確定。

我的視頻文件是放在開發板里面的,然后通過搭建的http服務器加上ffmpeg就可以實現本地視頻的預覽和播放了。

我使用VLC來進行測試:

可以看到上面的請求數據,就按照這個視頻的一個個切片請求這樣就可以實現大視頻文件的預覽傳輸。但是這樣也會有一個問題當要傳輸的文件過于大的話要進行切片的時間也就越長,但是相比較與直接進行視頻文件的傳輸還是較為好用的。完整的程序放在了我的資源中有需要自取,我是在rk3588上面跑的環境,根據自己的環境跟換Makefile即可。
【免費】在rk3588上面基于FFmpeg和HLS的大文件分片傳輸方案,以實現大視頻文件高效預覽效果資源-CSDN下載

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917885.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917885.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917885.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

體育場預定-下單-扣減庫存一致性

流程1:通過庫存服務緩存(緩存里面不僅有位圖存儲該時間點id的位置信息還有庫存信息)的Redis獲取令牌2:拿著令牌向訂單服務同步下單如果有令牌就執行下面的Redis&#xff0c;如果沒有就直接返回扣減Redis庫存緩存扣減成功:繼續扣減失敗:返回前端重試整套流程3:1鎖2查3更新生成訂…

【計算機網絡】王道考研筆記整理(3)數據鏈路層

目錄 第三章 數據鏈路層 3.1 數據鏈路層的功能 3.2 組幀 3.2.1 字符計數法 3.2.2 字節填充法 3.2.3 零比特填充法 3.2.4 違規編碼法 3.3 差錯控制 3.3.1 奇偶校驗碼 3.3.2 CRC 校驗碼 3.3.3 海明校驗碼 3.4 可靠傳輸與流量控制 3.4.1 滑動窗口機制 3.4.2 停止 - 等待…

【后端】java 抽象類和接口的介紹和區別

文章目錄一、抽象類&#xff08;Abstract Class&#xff09;二、接口&#xff08;Interface&#xff09;三、核心區別總結四、使用場景對比五、從設計思想理解最佳實踐在Java中&#xff0c;抽象類&#xff08;Abstract Class&#xff09;和接口&#xff08;Interface&#xff0…

Apache OFBiz Scrum 組件命令注入漏洞

【嚴重】Apache OFBiz Scrum 組件命令注入漏洞 漏洞描述 Apache OFBiz 是一款知名的開源企業資源規劃(ERP)解決方案&#xff0c;它提供了一整套開箱即用的企業級應用。Scrum 是 OFBiz 的一個插件&#xff0c;旨在為敏捷開發團隊提供項目管理功能&#xff0c;其中包括與 SVN 版…

FastAPI入門:多個文件、后臺任務、元數據和文檔 URL

更大的應用 - 多個文件 假設文件結構如下&#xff1a;. ├── app # 「app」是一個 Python 包 │ ├── __init__.py # 這個文件使「app」成為一個 Python 包 │ ├── main.py # 「main」模塊&#xff0c;例如 import app.main │ ├…

一個示例mcp agent功能的交互式框架

https://github.com/whym3/Deepseek_MCPDeepseek_MCP https://github.com/whym3/Deepseek_MCP Deepseek_MCP是一個演示mcp agent的框架&#xff0c;基于Flask開發&#xff0c;支持在瀏覽器采用交互方式與deepseek及agent對話。需要注冊外部Deepseek api&#xff0c;不支持本地…

nodejs 基礎知識-2

模塊的暴露和導入 編寫date.js module.exports.echo 導出的名稱 module.exports.echo function echo(){ return Date.now(); } 編寫 index.js const echoDate require(‘./date.js’) 在index引入 console.log(echoDate.echo()); //調用 開發一個自定義模塊 exports.forma…

遞歸推理樹(RR-Tree)系統:構建認知推理的骨架結構

探索基于三維評估的動態推理系統如何實現智能決策與知識演化引言 在復雜問題求解領域&#xff08;如戰略決策或科學探索&#xff09;&#xff0c;人類思維的遞歸本質為AI系統設計提供了重要啟發。我設計并實現的遞歸推理樹&#xff08;Recursive Reasoning Tree, RR-Tree&#…

《動手學深度學習》讀書筆記—9.5機器翻譯與數據集

本文記錄了自己在閱讀《動手學深度學習》時的一些思考&#xff0c;僅用來作為作者本人的學習筆記&#xff0c;不存在商業用途。 語言模型是自然語言處理的關鍵&#xff0c; 而機器翻譯是語言模型最成功的基準測試。 因為機器翻譯正是將輸入序列轉換成輸出序列的 序列轉換模型&a…

Mysql進行操作時鎖的具體行為

場景一&#xff1a;單個事務更新一條存在的數據 假設有表 user (id PK, name, age)&#xff0c;數據&#xff1a;[id1, nameAlice, age25] 你的 SQL&#xff1a; UPDATE user SET age 26 WHERE id 1; 底層動作&#xff1a; 事務 A (主動方) 發起更新請求。Lock Manager 介入&…

人工智能領域、圖歐科技、IMYAI智能助手2025年7月更新月報

IMYAI 平臺 2025 年 7 月重要功能更新與優化匯總 2025年07月31日更新 細節優化&#xff1a; 修復了移動端提交后自動彈出側邊欄的BUG。優化對話高級配置界面&#xff0c;增加滾動條并固定高度&#xff0c;避免內容超出屏幕。音樂生成界面的人聲選擇新增“合唱”選項&#xff…

HTTP 與 HTTPS 的區別深度解析:從原理到實踐

HTTP 和 HTTPS 是現代 Web 開發中不可或缺的協議&#xff0c;它們決定了瀏覽器與服務器之間數據傳輸的方式。HTTPS 作為 HTTP 的安全版本&#xff0c;在安全性、性能和用戶體驗上都有顯著提升。本文將通過萬字篇幅&#xff0c;結合圖表和代碼示例&#xff0c;詳細剖析 HTTP 與 …

STM32F407VET6學習筆記11:smallmodbus_(多從機)創建新的slave從機

今日記錄一些smallmodbus 創建新的slave 從機 的過程&#xff0c;以及使用的關鍵點. 目錄 創建新的從機對應操作函數與buffer 創建新的從機線程與操作代碼&#xff1a; slave使用的要點&#xff1a; 完整的slave代碼&#xff1a; 能正常通信&#xff1a; 創建新的從機對應操作函…

【論文閱讀】Transformer Feed-Forward Layers Are Key-Value Memories

Transformer Feed-Forward Layers Are Key-Value Memories 原文摘要 研究背景與問題&#xff1a; 前饋層占Transformer模型參數總量的2/3&#xff0c;但其功能機制尚未得到充分研究 核心發現&#xff1a;提出前饋層實質上是鍵值存儲系統 鍵&#xff1a;這里的鍵與訓練數據中出…

昇思+昇騰開發板:DeepSeek-R1-Distill-Qwen-1.5B 模型推理部署與 JIT 優化實踐

目錄 引言 模型推理部署 環境準備 安裝 MindSpore 查看當前 mindspore 版本 安裝 MindNLP 模型與分詞器加載 導入必要的庫 加載分詞器 加載模型 對話功能實現 設置系統提示詞 構建對話歷史輸入 推理函數實現 交互界面實現 推理JIT優化 基礎環境安裝 JIT 優化配置…

用phpstudy安裝php8.2后報錯:意思是找不到php_redis.dll拓展時

1.地址&#xff1a;https://pecl.php.net/package/redis/6.2.0/windows 2.下載3.解壓后復制php_redis.dll到phpstudy_pro\Extensions\php\php8.2.9nts\ext目錄 4.打開php.ini&#xff0c;加上 extension_dir “D:\software\phpstudy_pro\Extensions\php\php8.2.9nts\ext”

開源列式分布式數據庫clickhouse

這里寫自定義目錄標題開源列式OLAP數據庫clickhouseclickhouse使用 ClickHouse 的場景如何理解行式存儲和列式存儲clickhouse-go開源列式OLAP數據庫clickhouse OLAP (分析型)&#xff1a;專為快速掃描、聚合、分析海量數據設計。OLTP (事務型)&#xff1a;專為處理大量短事務&…

Java Stream API 詳解(Java 8+)

1. Stream 操作分類Stream 操作分為兩類&#xff1a;中間操作&#xff08;Intermediate Operations&#xff09;返回新的 Stream&#xff0c;可以鏈式調用&#xff08;如 filter, map, sorted, distinct&#xff09;。惰性求值&#xff1a;只有遇到終止操作時才會執行。終止操作…

「源力覺醒 創作者計劃」_文心大模型4.5系列開源模型, 從一行代碼到一個生態:聊聊開源戰略那些事兒,順便扯扯文心大模型 4.5 的使用心得

前言&#xff1a;哈嘍&#xff0c;大家好&#xff0c;今天給大家分享一篇文章&#xff01;并提供具體代碼幫助大家深入理解&#xff0c;徹底掌握&#xff01;創作不易&#xff0c;如果能幫助到大家或者給大家一些靈感和啟發&#xff0c;歡迎收藏關注哦 &#x1f495; 目錄從一行…

算法專題(二)回文鏈表

1、源代碼class Solution {public boolean isPalindrome(ListNode head) {ListNode fasthead,slowhead; //快慢指針都在頭結點//快指針走2步&#xff0c;慢指針走一步。//雙數快指針最后是null&#xff0c;單數快指針下一位是nullwhile(fast!null && fast.next!null){f…