一.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)。区别在于,基于事件的架构只是把这个请求发出,并不急于得到结果,而是等合适的子系统处理完这个请求ÿ…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...