一.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.rhkb.cn/news/332877.html

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

相关文章

06.深入学习Java 线程

1 线程的状态/生命周期 Java 的 Thread 类对线程状态进行了枚举&#xff1a; public class Thread implements Runnable {public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;} } 初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用…

panic: concurrent write to websocket connection【golang、websocket】

文章目录 异常信息原由代码错误点 解决办法 异常信息 panic: concurrent write to websocket connection原由 golang 编写 websocket go版本&#xff1a;1.19 使用了第三方框架&#xff1a; https://github.com/gorilla/websocket/tree/main 代码 server.go // Copyright …

Linux - crond任务调度、at定时任务

1 crontab 进行-定时任务的设置 1&#xff09;概述&#xff1a; 任务调度&#xff1a;是指系统在某个时间执行的特定的命令或程序。 任务调度分类&#xff1a; 系统工作&#xff1a;有些重要的工作必须周而复始地执行。如病毒扫描等个别用户工作&#xff1a;个别用户可能希…

Vue3实战笔记(38)—粒子特效终章

文章目录 前言一、怎样使用官方提供的特效二、海葵特效总结 前言 官方还有很多漂亮的特效&#xff0c;但是vue3只有一个demo&#xff0c;例如我前面实现的两个页面就耗费了一些时间&#xff0c;今天记录一下tsparticles官方内置的几个特效的使用方法&#xff0c;一般这几个就足…

SSM志愿服务管理小程序-计算机毕业设计源码97923

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;志愿服务管理小程序被用户普遍使用&#xff0c;方便用户能…

Vue3:动态路由+子页面(新增、详情页)动态路由配置(代码全注释)

文章目录 实现思路调用后端接口获取用户权限获取页面权限动态绑定到路由对象中动态添加子页面路由 实现思路 emm&#xff0c;项目中使用动态路由实现根据后端返回的用户详情信息&#xff0c;动态将该用户能够访问的页面信息&#xff0c;动态生成并且绑定到路由对象中。但是后…

【C语言】实现贪吃蛇--项目实践(超详细)

前言&#xff1a; 贪吃蛇游戏大家都玩过吧&#xff1f;这次我们要用C语言来亲手制作一个&#xff01;这个项目不仅能让我们复习C语言的知识&#xff0c;还能了解游戏是怎么一步步做出来的。我们会一起完成蛇的移动、食物的生成&#xff0c;还有碰撞检测等有趣的部分。准备好了…

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

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

55页PDF|人工智能通用大模型(ChatGPT)的进展、风险与应对(附下载)

&#x1f449;获取方式&#xff1a; &#x1f61d;有需要的小伙伴&#xff0c;可以保存图片到wx扫描二v码免费领取【保证100%免费】&#x1f193;

WIFI国家码设置的影响

记录下工作中关于国家码设置对WIFI的影响&#xff0c;以SKYLAB的SKW99和SDZ202模组为例进行说明。对应到日常&#xff0c;就是我们经常提及手机是“美版”“港版”等&#xff0c;它们的wifi国家码是不同的&#xff0c;各版本在wifi使用中遇到的各种情况与下面所述是吻合的。 现…

从alpine构建预装vcpkg的docker image用于gitea actions CI

动机 想要构建一个基于vcpkg的交叉编译容器平台用于cpp项目的CI(自动集成),此处仅提供最基础的image,amd64的机子上构建完成后大小为533兆(着实不小😓),各位看官可以在此基础上自行构建需要的版本。 hello world效果展示 corss_compiler.dockerfile FROM alpine:la…

【Ubuntu常用命令】终端个人常用命令总结

【Ubuntu常用命令】终端常用命令总结 查看硬盘挂载情况查看内存占用情况移动或重命名文件和目录复制文件或目录 查看硬盘挂载情况 mount 命令会列出当前系统上所有已挂载的文件系统。它会显示挂载点、文件系统类型、挂载选项等信息 mount df 命令用于显示文件系统的磁盘空间使…

springboot 集成 es--未完结

基于es7.10.x版本 一、前提知识 常见的两种方式&#xff1a;spring boot提供的API 和 ES 官方提供的API ES官方&#xff1a; RestHighLevelClient&#xff1a; 适用于复杂、更细粒度控制的Elasticsearch 操作 spring boot&#xff1a; ElasticsearchRestTemplate&#xff1a…

linux查看是否被入侵(一)

1、查看当前系统状态 [rootbastion-IDC ~]#top #一般挖矿等病毒点用CPU比较大 2、查看当前登录用户(w\who) 3、检查系统日志 检查系统错误登陆日志&#xff0c;统计IP重试次数 [rootbastion-IDC ~]# lastb 4、查看近期用户登录情况 [rootkvm01 ~]# last -n 5 #-n 5 表示…

软件3班20240527

JDK 版本与 Tomcat 的 兼容性

nginx流量监控:goAccess安装与使用

关于goAccess GoAccess 是一款实时、快速的日志分析工具&#xff0c;专门设计用于分析Web服务器日志&#xff0c;特别是Nginx日志。 安装 &#xff08;1&#xff09;准备相关依赖 # Missing development libraries for ncursesw # centOS yum install -y ncurses-devel # U…

【EI会议】2024年互联网技术与环境工程国际会议(IACITEE 2024)

【EI会议】2024年互联网技术与环境工程国际会议&#xff08;IACITEE 2024&#xff09; 2024 International Conference on Internet Technology and Environmental Engineering 互联网技术与环境工程国际会议&#xff08;IACITEE 2024&#xff09;将在重庆举行&#xff0c;主…

怎么把记事本钉在桌面上 桌面记事本固定不动的方法

我的生活&#xff0c;总是被密密麻麻的待办事项和灵感想法填得满满当当。记事本&#xff0c;就是我随身的记忆银行&#xff0c;帮我存储那些稍纵即逝的思维火花和不能错过的琐事提醒。每当翻开那一页页工整的笔记&#xff0c;心中的焦虑和压力似乎都找到了释放的出口。 但一直…

锐捷网络与您相约第七届数字中国建设峰会 共话数字未来

第七届数字中国建设峰会将于5月24日至25日在福建福州举办,本届峰会是国家数据工作体系优化调整后首次举办的数字中国建设峰会,主题是“释放数据要素价值,发展新质生产力”。作为行业领先的ICT基础设施及解决方案提供商,锐捷网络与福建省电子信息集团、星网锐捷,围绕“发展新质生…

mvc的常见注解

问文心一言的&#xff0c;记录一下。 PathVariable 路径变量注解 PathVariable 是 Spring MVC 提供的一个注解&#xff0c;它用于从 URI 模板变量中绑定值到控制器方法的参数上。当你在 RequestMapping、GetMapping、PostMapping、PutMapping、DeleteMapping 等注解的 URL 路…