一.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

有需要源码可以后台私信我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/17222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Pandas】深入解析`pd.read_pickle()`函数

【Pandas】深入解析pd.read_pickle()函数 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&…

Ora-32004错误处理

问题现象 处理方法 观察alert会发现报错的参数在加载spfile的阶段 按照mos的方法对其reset 重新启动无报错 观察日志无报错

ora-00392 ora-00312错误处理

检查当前日志组状态 对日志组进行clear操作 重新开库无报错

java的变量关系~使用和扩展

一、变量的概述 1、什么是变量 白话:变量就是一个装东西的盒子。 通俗:变量是用于存放数据的容器。我们通过变量名 获取数据&#xff0c;甚至数据可以修改。 2、变量在内存中的存储 本质:变量是程序在内存中申请的一块用来存放数据的空间&#xff0c;类似我们酒店的房间&a…

C++ (week5):Linux系统编程3:线程

文章目录 三、线程1.线程的基本概念①线程相关概念②我的理解 2.线程的基本操作 (API)(1)获取线程的标识&#xff1a;pthread_self(2)创建线程&#xff1a;pthread_create()(3)终止线程①pthread_exit()&#xff1a;当前线程终止&#xff0c;子线程主动退出②pthread_cancel()&…

OrangePi AIpro(8T) 基本介绍和资料汇总

OrangePi AIpro 基本介绍和资料汇总 1 介绍1.1 香橙派(Orange Pi) 公司概述1.2 OrangePi AIpro(8T) 介绍概述硬件规格参数引脚图产品详细图芯片优缺点优点 2 资料汇总2.1 官方资料概述资料下载 参考 1 介绍 1.1 香橙派(Orange Pi) 公司概述 香橙派&#xff08;Orange Pi&…

惊喜:我一晚上赚了10万!深扒挖瑞幸1.8亿起死回生的商业真相

■ ■ ■ ■ 瑞幸咖啡 2020年1月&#xff0c;浑水报告发出后&#xff0c;瑞幸股价一路颠簸&#xff0c;最终退市停牌。 然而&#xff0c;19个月后&#xff0c;在粉单市场股价再次增长781.16% 暴雷后的19个月&#xff0c;瑞幸经历了怎样的起死回生&#xff1f; 2020年4月2日…

【RuoYi】如何启动RuoYi项目

一、前言 最近&#xff0c;在做一个管理系统的项目&#xff0c;接触到了RuoYi这个前后端分离的框架&#xff0c;自己是第一次接触这个框架&#xff0c;所以刚开始有点好奇&#xff0c;在用该框架写了一些代码后。发现RuoYi这个框架做的真的好&#xff0c;它包含了权限管理和一些…

【稳定检索】2024年电子技术、传感器与信号处理国际会议(ETSS 2024)

2024年电子技术、传感器与信号处理国际会议 2024 International Conference on Electronic Technology, Sensors, and Signal Processing 【1】会议简介 2024年电子技术、传感器与信号处理国际会议&#xff0c;作为业内领先的学术交流平台&#xff0c;将汇聚全球顶尖的电子技术…

99%的人都不知道,微信才是真正的学习神器

微信&#xff0c;作为一款全球最受欢迎的社交应用之一&#xff0c;除了聊天、朋友圈、小程序等功能外&#xff0c;还有许多隐藏的学习功能&#xff0c;今天小编就给大家分享10个微信隐藏的学习功能&#xff0c;助您轻松成为学霸。 1、微信笔记 用过代办清单软件的朋友都知道&…

如何调用通义千问大模型API

目录 登录阿里云 大模型服务平台百炼 登录控制台 QWen Long QWen 通义千问开源系列 大语言模型 OpenAI接口兼容 登录阿里云 阿里云-计算&#xff0c;为了无法计算的价值 大模型服务平台百炼 降价信息&#xff1a; 登录控制台 右上角取得API key 创建Key QWen Long qw…

Linux网络编程:传输层协议|UDP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

【VTKExamples::Utilities】第九期 FrameRate

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例FrameRate,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. FrameRate 该样例介绍 如…

【Mongo】索引结构

结论 Mongo3.2版本开始&#xff0c;索引的结构默认是B树。 起因 面试的时候&#xff0c;面试官问为什么Mongo DB底层使用B树而不是B树&#xff1f; 面试完赶紧恶补&#xff0c;结果发现面试官好像给我埋了个坑。。。 MongoDB官方描述&#xff1a; 翻译一下就是&#xff1…

JVM 内存布局深度解析,你所不知道的一面

作为Java开发者&#xff0c;想要写出高质量的代码&#xff0c;理解JVM的内存结构是必修课。本文将为您深度解析 Java 虚拟机(JVM)中的内存布局及其细节分析&#xff0c;让你在内存管理的道路上行稳致远。希望通过本文能让你彻底理解其中的奥秘。 一、内存布局概览 在我们深入具…

Python爬虫实战(实战篇)—17获取【CSDN某一专栏】数据转为Markdown列表放入文章中

文章目录 专栏导读背景结果预览1、页面分析2、通过返回数据发现适合利用lxmlxpath3、进行Markdown语言拼接总结 专栏导读 在这里插入图片描述 &#x1f525;&#x1f525;本文已收录于《Python基础篇爬虫》 &#x1f251;&#x1f251;本专栏专门针对于有爬虫基础准备的一套基…

电脑可以录音吗?这里有你想要的答案!

在数字化时代&#xff0c;电脑已经成为我们日常生活中不可或缺的工具。除了办公、娱乐等基本功能外&#xff0c;电脑还具备许多实用的辅助功能&#xff0c;其中之一就是录音功能。可是电脑可以录音吗&#xff1f;本文将介绍两种在电脑上录音的方法&#xff0c;希望通过本文的介…

客服快捷回复话术分享:618议价话术和催发货话术

随着618活动大促的临近&#xff0c;客服小伙伴们将迎来一年中最繁忙的时刻。面对顾客的议价、催发货等需求&#xff0c;我们应该如何回复才能既满足顾客的需求&#xff0c;又能保持良好的服务形象呢&#xff1f;下面就为大家分享一些议价和催发货的快捷回复话术&#xff0c;希望…

申请免费通配符SSL证书教程

申请免费通配符SSL证书的步骤相对直接&#xff0c;但需要注意的是免费且支持通配符的证书提供商较为有限&#xff0c;JoySSL是一个被多次提及提供此类服务的机构。以下是一个基于汇总信息的简明教程&#xff0c;帮助你申请免费的通配符SSL证书&#xff1a; 1. 准备工作 确认兼…

Android studio 连接 adb传输文件到电脑

前提是已经连接到adb window R&#xff1a; 打开控制台adb devices&#xff1a;可以查看已经连接的设备adb pull /storage/emulated/0/Download/aa.png C:\Users\Administrator\Desktop&#xff1a;拉取连接设备的文件 aa.png 到电脑桌面上 (在电脑控制台进行拉取操作) 如果…