一.ffmpeg 将内存中的H264跟PCM 数据流合成多媒体文件
在有一些嵌入式平台中,H264数据流一般来自芯片内部的硬编码器, AAC音频数据则是通过采集PCM进行软编码,但是如何对它实时进行封装多媒体文件 ,参考ffmpeg example,花了一些时间终于实现了该功能。
流程图如下:
本文只展示DEMO
一.视频输入流 创建
//内存数据回调部分
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{char * input_filename = (char *)opaque;static FILE *fl = NULL;if(fl == NULL){fl = fopen(input_filename,"r");}static unsigned long long read_len=0;static unsigned long long fps_count=0;int len=0;int i =0;if(!feof(fl))len = fread(buf,1,buf_size,fl);else return AVERROR_EOF;read_len+= len;printf("%s len:%d read_len:%d\n",__FUNCTION__, len ,read_len);for(i=0;i<4091;i++){if(buf[i+0] == 0 &&buf[i+1] == 0 &&buf[i+2] == 0 &&buf[i+3] == 1){// int data = buf[i+4] &=31;printf("0 0 0 1 %x %d\n",buf[i+4],fps_count);fps_count++;}}return len;
}static AVFormatContext * getInputVideoCtx(const char *fileName) {uint8_t *avio_ctx_buffer = NULL;AVIOContext *avio_ctx = NULL;//缓存buffersizesize_t buffer_size, avio_ctx_buffer_size = 4096;AVFormatContext * video_fmt_ctx = NULL;int ret = 0;if (!(video_fmt_ctx = avformat_alloc_context())) {ret = AVERROR(ENOMEM);return NULL;}//创建数据缓存Bufferavio_ctx_buffer = av_malloc(avio_ctx_buffer_size);if (!avio_ctx_buffer) {ret = AVERROR(ENOMEM);return NULL;}avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,0, fileName, &read_packet, NULL, NULL);if (!avio_ctx) {ret = AVERROR(ENOMEM);return NULL;}video_fmt_ctx->pb = avio_ctx;//打开数据ret = avformat_open_input(&video_fmt_ctx, NULL, NULL, NULL);if (ret < 0) {fprintf(stderr, "Could not open input\n");return NULL;}//获取数据格式ret = avformat_find_stream_info(video_fmt_ctx, NULL);if (ret < 0) {fprintf(stderr, "Could not find stream information\n");return NULL;}//打印数据参数av_dump_format(video_fmt_ctx, 0, fileName, 0);return video_fmt_ctx;
}
1.注册内存回调read_packet,avformat_find_stream_info会从回调里读取大概2S的h264视频数据并解析。首先会读取SPS PPS,然后是帧数据,读取2S的数据结束,如果给的数据不对,解析不正常会一直读,所以要确保刚开始给的数据是否正常。av_dump_format打印出数据格式
执行如下:
二.创建多媒体输出,添加视频输出流音频输出流
avformat_alloc_output_context2(&oc, NULL, NULL, filename);...//fmt = oc->oformat;if (fmt->video_codec != AV_CODEC_ID_NONE) {add_video_stream(&video_st, oc, video_fmt_ctx, fmt->video_codec);...}/* Add the audio and video streams using the default format codecs* and initialize the codecs. */if (fmt->audio_codec != AV_CODEC_ID_NONE) {add_audio_stream(&audio_st, oc, &audio_codec, fmt->audio_codec);...}
1.添加视频流和初始化
/* media file output */
static void add_video_stream(OutputStream *ost, AVFormatContext *oc,const AVFormatContext *video_fmt_ctx,enum AVCodecID codec_id)
{...//创建一个输出流ost->st = avformat_new_stream(oc, NULL);...ost->st->id = oc->nb_streams-1;c = avcodec_alloc_context3(NULL);...//流的time_base初始化for (i = 0; i < video_fmt_ctx->nb_streams; i++) {if(video_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){avcodec_parameters_to_context(c, video_fmt_ctx->streams[i]->codecpar);video_fmt_ctx->streams[i]->time_base.den = video_fmt_ctx->streams[i]->avg_frame_rate.num;}}//初始化av_packetost->tmp_pkt = av_packet_alloc();...ost->enc = c;
}
2.添加音频流 初始化编解码器
/* Add an output stream. */
static void add_audio_stream(OutputStream *ost, AVFormatContext *oc,const AVCodec **codec,enum AVCodecID codec_id)
{*codec = avcodec_find_encoder(codec_id);...//初始化有音频packetost->tmp_pkt = av_packet_alloc();...//初始化流ost->st = avformat_new_stream(oc, NULL);...switch ((*codec)->type) {case AVMEDIA_TYPE_AUDIO:c->sample_fmt = (*codec)->sample_fmts ?(*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;c->bit_rate = 64000;c->sample_rate = 44100;//采样率if ((*codec)->supported_samplerates) {c->sample_rate = (*codec)->supported_samplerates[0];for (i = 0; (*codec)->supported_samplerates[i]; i++) {if ((*codec)->supported_samplerates[i] == 44100)c->sample_rate = 44100;}}av_channel_layout_copy(&c->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO);//输出audio流的time_base初始化ost->st->time_base = (AVRational){ 1, c->sample_rate };break;default:break;}}
3.初始化输出流音频和视频codecpar
static int open_video(AVFormatContext *oc, const AVCodec *codec,AVFormatContext *vedio_fmt_ctx,OutputStream *ost)
{...ret = avcodec_parameters_copy(ost->st->codecpar, vedio_fmt_ctx->streams[index]->codecpar);...
}
static void open_audio(AVFormatContext *oc, const AVCodec *codec,OutputStream *ost, AVDictionary *opt_arg)
{.../* copy the stream parameters to the muxer */ret = avcodec_parameters_from_context(ost->st->codecpar, c);if (ret < 0) {fprintf(stderr, "Could not copy the stream parameters\n");exit(1);}...
}
三.开始写入多媒体文件
1.比较写入音视频的时间戳,判断下一次要写入音频还是视频
while (encode_video) {/* select the stream to encode */if (encode_video &&( !encode_audio || av_compare_ts(video_st.next_pts, video_fmt_ctx->streams[v_ctx_index]->time_base,audio_st.next_pts, audio_st.enc->time_base) <= 0)) {encode_video = !write_video_frame(oc, video_fmt_ctx, &video_st, video_st.tmp_pkt);} else {encode_audio = !write_audio_frame(oc, &audio_st);}}
av_compare_ts 通过对比当前Audio Video帧的写入量判断当前要写入Audio 还是Video
(例如: Video= 写入10帧* 1/25 > Audio 写入 10240*1/44100 则写入audio)
2.写入一帧Video
static int write_video_frame(AVFormatContext *oc,AVFormatContext *vic, OutputStream *ost, AVPacket *pkt)
{int ret,i; static int frame_index = 0;AVStream *in_stream, *out_stream;int stream_index;stream_index = av_find_best_stream(vic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);//读一帧H264ret = av_read_frame(vic, pkt);if(ret == AVERROR_EOF)return ret == AVERROR_EOF ? 1 : 0;av_packet_rescale_ts(pkt, ost->enc->time_base, ost->st->time_base);if(pkt->pts==AV_NOPTS_VALUE){in_stream = vic->streams[stream_index];out_stream = ost->st;//Write PTSAVRational time_base1=in_stream->time_base;int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->avg_frame_rate);//计算出包的解码时间pkt->pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);pkt->dts=pkt->pts;pkt->duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);//帧的计数累加frame_index++;//pkt的pts dts是输入流的时间戳 要转换成 输出流的时间戳av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);pkt->pos = -1;pkt->stream_index=ost->st->index;}//写入到多媒体文件ret = av_interleaved_write_frame(oc, pkt);if (ret < 0) {fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret));exit(1);}return ret == AVERROR_EOF ? 1 : 0;
}
av_read_frame会回调read_packet 获取一帧H264数据,再通过计算时间戳 pts dts 再转换对应的输出流时间戳才能写入多媒体文件
3.写入一帧Audio
//获取一帧原始的Audio PCM 数据
/* Prepare a 16 bit dummy audio frame of 'frame_size' samples and* 'nb_channels' channels. */
static AVFrame *get_audio_frame(OutputStream *ost)
{...c = ost->enc;for (j = 0; j <frame->nb_samples; j++) {v = (int)(sin(ost->t) * 10000);for (i = 0; i < ost->enc->ch_layout.nb_channels; i++)*q++ = v;ost->t += ost->tincr;ost->tincr += ost->tincr2;}...frame->pts = ost->next_pts;ost->next_pts += frame->nb_samples;count++;return frame;
}
static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
{....//获取一帧原始的Audio PCM 数据 frame = get_audio_frame(ost);if (frame) {dst_nb_samples = av_rescale_rnd(swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples,c->sample_rate, c->sample_rate, AV_ROUND_UP);ret = av_frame_make_writable(ost->frame);/* convert to destination format */ret = swr_convert(ost->swr_ctx,ost->frame->data, dst_nb_samples,(const uint8_t **)frame->data, frame->nb_samples);frame = ost->frame;frame->pts = av_rescale_q(ost->samples_count, (AVRational){1, c->sample_rate}, c->time_base);ost->samples_count += dst_nb_samples;}//先送去编码再写入多媒体文件return write_frame(oc, c, ost, frame, ost->tmp_pkt);
}static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,OutputStream *ost, AVFrame *frame, AVPacket *pkt)
{...ret = avcodec_send_frame(c, frame);...while (ret >= 0) {ret = avcodec_receive_packet(c, pkt);.../* rescale output packet timestamp values from codec to stream timebase */av_packet_rescale_ts(pkt, c->time_base, st->time_base);printf("%d %d\n", c->time_base.den, st->time_base.den);pkt->stream_index = st->index;ret = av_interleaved_write_frame(fmt_ctx, pkt);...count++;}return ret == AVERROR_EOF ? 1 : 0;
}
四.写入多媒体尾部结束:
av_write_trailer(oc);
一些BUG:
控制写入时间,可以在写入循环里添加break。写入数据过长会出现音视频不同步的情况,建议写入时间不超过30分钟
DEMO
有需要源码可以后台私信我
相关文章:

一.ffmpeg 将内存中的H264跟PCM 数据流合成多媒体文件
在有一些嵌入式平台中,H264数据流一般来自芯片内部的硬编码器, AAC音频数据则是通过采集PCM进行软编码,但是如何对它实时进行封装多媒体文件 ,参考ffmpeg example,花了一些时间终于实现了该功能。 流程图如下…...

C++ (week5):Linux系统编程3:线程
文章目录 三、线程1.线程的基本概念①线程相关概念②我的理解 2.线程的基本操作 (API)(1)获取线程的标识:pthread_self(2)创建线程:pthread_create()(3)终止线程①pthread_exit():当前线程终止,子线程主动退出②pthread_cancel()&…...

二叉树习题精讲-相同的树
相同的树 100. 相同的树 - 力扣(LeetCode)https://leetcode.cn/problems/same-tree/description/ /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool i…...

「架构」模型驱动架构设计方法及其运用
本文通过一个实际的软件项目案例,深入探讨了模型驱动架构(MDA)在软件开发全过程中的应用。MDA是一种以模型为中心的设计方法,它通过分离计算、数据和业务逻辑,提高了软件的可维护性、可扩展性和可移植性。文章将从需求分析、架构设计、实现与测试三个阶段出发,分析MDA的应…...

基于 React + Nest 全栈开发的后台系统
Xmw Admin 基于 React Nest 全栈开发的后台系统 🪴 项目简介 🎯 前端技术栈: React、Ant Design、Umi、TypeScript🎯 后端技术栈: Nest.js、Sequelize、Redis、Mysql😝 线上预览: https://r…...

jQuery值操作例子 (代码)
直接上代码 <!DOCTYPE html> <html><head></head><body><div id"x1">例子</div><script src"js/jquery-3.7.1.min.js"></script><script>console.log($("#x1").text()) // 在浏览…...

Next-Admin,一款基于Nextjs开发的开箱即用的中后台管理系统(全剧终)
hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的 Next-Admin 项目的最新更新。 这次更新是1.0版本最后一次更新,也根据用户反馈的问题做了一些优化&am…...

HTML5 文件处理及应用
HTML5 文件处理及应用 目录 核心对象文件信息读取文件拖放FileReaderSyncFileWriter APIBlob URL与 revokeObjectURL()跨源资源共享 (CORS)HTML5文件File API为浏览器提供了与用户计算机上的文件进行交互的能力,使得Web应用程序能够在客户端直接处理文件数据,而无需依赖服务…...

逻辑分析仪的调试使用
调试软件下载:点击跳转 一、接线 逻辑分析仪 设备 GND --- GND CHX(数据通道) --- 通信引脚 二、数据采集 图中两个可以选择数字大小的地方分别表示 采样深度 (10M Samples&a…...

AI学习指南数学工具篇-Python中的凸优化库
AI学习指南数学工具篇-Python中的凸优化库 在人工智能和机器学习领域,凸优化是一个非常重要的数学工具。它可以帮助我们解决各种问题,包括线性规划、二次规划、半定规划等。而在Python中,有一个非常优秀的凸优化库,即CVXPY。本文…...

数据库mysql
一、mysql常用语句 登录MySQLmysql -u root -p列出所有数据库SHOW DATABASES;创建一个新数据库CREATE DATABASE test;删除一个数据库DROP DATABASE test;对一个数据库进行操作时USE test;列出当前数据库的所有表SHOW TABLES;要查看一个表的结构DESC students;创建表CREATE TAB…...

AWS联网和内容分发之Transit Gateway
将Amazon VPC、AWS账户和本地网络连接到一个网关中。AWS Transit Gateway通过中央枢纽连接Amazon虚拟私有云(VPC)和本地网络。此连接简化了您的网络,并且结束了复杂的对等关系。Transit Gateway充当高度可扩展的云路由器,每个新的…...

牛客NC236 最大差值【simple 动态规划 Java/Go/PHP】
题目 题目链接: https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204 思路 不难看出该题可以使用动态规划的方式解题。 在循环数组的过程中,记录截止到当前位置-1的最小值, 然后用当前的值去计算最大的差值。Java代码 im…...

ORACLE 6节点组成的ACFS文件系统异常的分析思路
近期遇到多次6节点集群的ACFS文件系统环境异常问题;如24日中午12点附近出现ACFS文件系统访问异常,通过查看集群ALERT日志、CSSD进程日志及OSW监控软件的日志,可以发现OSW监控软件在11:55-12:40分时没有收集到虚拟机LINUX主机的监控数据&#…...

vscode当前分支有未提交的修改,但是暂时不想提交,想要切换到另一个分支该怎么办
当前分支有未提交的修改,但是暂时不想提交,想要切换到另一个分支该怎么办? 首先,可以将当前修改暂存起来,以便之后恢复 git stash 然后切换到目标分支,例如需求A所在分支 git checkout feat-a-jie 修改完A需求后,需要先切换回之前的分支,例如需求B所在分支 git checkout feat…...

前端API: IntersectionObserver的那一二三件事
IntersectionObserver 基础 IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。比如可以用来处理图片的懒加载等等 首先我们来看下基本的格式: const observer new IntersectionObserver(c…...

C++迈向精通:vector复现与sort复现
vector复现 思考过程 对于vector考虑如下几点: 底层数据结构算法实现方式对外表现形式 这里底层的数据结构采用了顺序表,当然,原版STL中的vector也是采用的顺序表。 算法实现的方式放在代码中去设计 对外表现形式是数组,因此需…...

【头歌】计算机网络DHCP服务器配置第二关access口配置答案
头歌计算机网络DHCP服务器配置第二关access口配置操作步骤 任务描述 本关任务:创建 vlan ,并且将与 pc 机相连接口划分 vlan 。 操作要求 在第一关的拓扑图的基础上,配置交换机,具体要求如下: 1、在特权模式下进入 vla…...

Python机器学习 Tensorflow + keras 实现CNN
一、实验目的 1. 了解SkLearn Tensorlow使用方法 2. 了解SkLearn keras使用方法 二、实验工具: 1. SkLearn 三、实验内容 (贴上源码及结果) 使用Tensorflow对半环形数据集分 #encoding:utf-8import numpy as npfrom sklearn.datasets i…...

基于事件的架构工作机制和相关产品
基于事件的架构 基于事件的架构可否这样理解,每个事件相当于传统API的一次函数调用请求,比如Add(123,456)。区别在于,基于事件的架构只是把这个请求发出,并不急于得到结果,而是等合适的子系统处理完这个请求ÿ…...

OSINT 与心理学:通过开源情报进行剖析和行为分析
在不断发展的心理学领域,人们越来越认识到通过应用开源情报 (OSINT) 方法取得进步的潜力。OSINT 主要以其在安全和情报领域的应用而闻名,并且越来越多地展示其在心理分析和行为分析方面的潜力。本文探讨了 OSINT 和心理学的迷人交叉点,研究如…...

yarn 设置淘宝镜像配置
为了提升在中国大陆地区的下载速度,你可以将Yarn的包仓库配置为淘宝镜像。最新的推荐做法是使用npmmirror.com作为镜像源,替代旧的npm.taobao.org。以下是设置Yarn使用淘宝镜像(npmmirror.com)的步骤: 查询当前镜像配置…...

debian 常用命令
Debian 是一个广泛使用的 Linux 发行版,这里列出了一些常用的 Debian 命令,适用于系统管理和日常使用: ### 文件与目录操作 1. **ls** - 列出目录内容: bash ls ls -l # 长格式显示 ls -a # 显示所有文件ÿ…...

流水账(CPU设计实战)——lab3
Lab3 Rewrite V1.0 版本控制 版本描述V0V1.0相对V0变化: 修改了文件名,各阶段以_stage结尾(因为if是关键词,所以module名不能叫if,遂改为if_stage,为了统一命名,将所有module后缀加上_stage&a…...

k8s集群配置普通用户权限
集群管理员:负责管理 Kubernetes 集群的用户,拥有最高权限,可以对集群中的资源进行任何操作。 开发者:在 Kubernetes 集群中部署和管理自己的应用,可能有限制的权限,仅能管理特定的命名空间或资源。 第三…...

clickhouse——clickhouse单节点部署及基础命令介绍
clickhouse支持运行在主流的64位CPU架构的linux操作系统之上,可以通过源码编译,预编译压缩包,docker镜像和rpm等多种方式进行安装。 一、单节点部署 1、安装curl工具 yum install -y curl 2、添加clickhouse的yum镜像 curl -s https://pack…...

MATLAB基础应用精讲-【数模应用】价格敏感度PSM分析(附python代码实现)
目录 前言 算法原理 什么是价格敏感度分析? 原理 示例 PSM用途...

数据驱动的UI艺术:智能设计的视觉盛宴
数据驱动的UI艺术:智能设计的视觉盛宴 引言 在当今这个数据泛滥的时代,大数据不仅仅是一种技术手段,它更是一种艺术形式。当大数据遇上UI设计,两者的结合便催生了一种全新的艺术形式——数据驱动的UI艺术。本文将探讨如何将数据…...

栈的特性及代码实现(C语言)
目录 栈的定义 栈的结构选取 链式储存结构和顺序栈储存结构的差异 栈的代码实现 "stack.h" "stack.c" 总结 栈的定义 栈:栈是限定仅在表尾进行插入和删除操作的线性表。 我们把运行插入的和删除的一段叫做栈顶(TOPÿ…...

防火墙如何端口映射?
防火墙端口映射(Firewall Port Mapping)是一种网络技术,通过对防火墙配置进行调整,允许外部网络用户访问内部网络中的指定端口。该技术使得外部用户可以通过公共网络访问内部网络中的特定服务或应用程序,从而实现远程访…...