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…