一.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;
    //缓存buffersize
    size_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;
    }
    //创建数据缓存Buffer
    avio_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_packet
    ost->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);
    ...
    //初始化有音频packet
    ost->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);
    //读一帧H264
    ret = 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 PTS
		AVRational 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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

es问题汇总--待完善

1. 查询某个索引库中数据总量 方式一&#xff1a; CountRequest 鄙人喜欢这种方式 public long getTotalNum(String indexName) throws IOException {CountRequest countRequest new CountRequest(indexName);// 如果需要&#xff0c;你可以在这里添加查询条件// countReques…

内脏油脂是什么?如何减掉?

真想减的人&#xff0c;减胖是很容易的&#xff0c;但想要形体美又健康&#xff0c;还是得从减内脏油脂开始&#xff0c;那么&#xff0c;问题来了&#xff0c;什么是内脏油脂&#xff1f; 油脂它分部于身体的各个角落&#xff0c;四肢、腹部、腰、臀部、脸、脖子...等&#xf…

暴雨信息液冷计算解决方案亮相CCIG 2024

5月24日&#xff0c;2024中国图象图形大会&#xff08;CCIG&#xff09;在陕西西安正式开幕。作为涵盖图像图形各专业领域的综合性的全国性学术会议&#xff0c;CCIG面向开放创新、交叉融合的发展趋势&#xff0c;为图像图形相关领域的专家学者和产业界的同仁&#xff0c;搭建了…

深入用户内心:设计师如何通过可用性测试洞察用户需求

可用性测试是指让用户体验产品的原型或成品。设计师通过观察和分析用户的使用行为和感受&#xff0c;进一步合理地改进产品的设计方法。你可能会想知道我们可以用什么方法来测试可用性&#xff1f;随着互联网行业的快速迭代更新&#xff0c;可用性测试衍生出了许多类型和方法。…

【C++初阶】—— 类和对象 (下)

&#x1f4dd;个人主页&#x1f339;&#xff1a;EterNity_TiMe_ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 类和对象 1. 运算符重载运算符重载赋值运算符重载前置和后置重载 2. 成员函数的补充3. 初始化列…

报错:找不到或无法加载主类 com.example.SpringbootApplication(idea)

OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 错误: 找不到或无法加载主类 com.example.SpringbootApplication 原因: java.lang.NoClassDefFoundError: com/example/SpringBootAppli…

C++---运算符重载

运算符重载介绍 在类中重新定义运算符&#xff0c;赋予运算符新的功能以适应类的运算&#xff0c;就称为运算符重载。 运算符重载是一种形式的C多态,它使得对象操作更直观,本质上也是属于函数重载。 实际上&#xff0c;我们已经在不知不觉之中使用了运算符重载。例如&#xff…

【JAVA |再谈接口、Object、内部类】Object类中子类重写,Cloneable 接口、比较器、内部类

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; &#x1f388;丠丠64-CSDN博客&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起…

python写页面自动截图

from selenium import webdriver def take_screenshot(url, file_path):driver webdriver.Chrome()driver.get(url)driver.save_screenshot(file_path)driver.quit() if __name__ __main__:take_screenshot(http://baidu.com, D:\桌面\wang.png)要安装selenium还要安装google…

React类组件生命周期详解

在React的类组件中&#xff0c;从组件创建到组件被挂载到页面中&#xff0c;这个过程react存在一系列的生命周期函数&#xff0c;最主要的生命周期函数是componentDidMount、componentDidUpdate、componentWillUnmount 生命周期图例如下 1. componentDidMount组件挂载 如果你…

网络安全资源和参考指南

由美国国防部&#xff08;DoD&#xff09;发布的《网络安全资源和参考指南》&#xff0c;旨在为美国政府、商业部门以及美国盟友和伙伴之间的安全合作提供有用的、现成的参考资料。文档涵盖了网络安全规范、最佳实践、政策和标准&#xff0c;这些都是由美国联邦政府、国防部以及…

KubeSphere 社区双周报|2024.05.09-05.23

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2024.05.09-05.23…

CCF20230301——田地丈量

CCF20230301——田地丈量 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,a,b;cin>>n>>a>>b;int x1,x2,y1,y2,x,y,sum0;for(int i0;i<n;i){cin>>x1>>y1>>x2>>y2;xmin(x2,a)-max(x1,…

【科普知识】伺服电机中的内置制动器

在工业自动化和机器人技术快速发展的今天&#xff0c;伺服电机作为核心驱动元件&#xff0c;其性能与功能直接影响整个系统的运行效率与稳定性。 近年来&#xff0c;一体化伺服电机技术不断融合创新&#xff0c;并逐步加入了许多新的硬件和软件的功能&#xff0c;为工业自动化领…

SpringBoot+Vue开发记录(五)-- 数据库设计

我去&#xff0c;时隔这么久又开始了QAQ。主要是还是自己太懒了。 本篇文章的主要内容是数据库设计。 先简单创建个数据库&#xff1a; 这是创建好了的&#xff1a; 一、数据库设计 先就做一个很简单的设计&#xff0c;里面就只有用户和题。 大概就这样&#xff1a; 二、创…

GQL 来了!ISO/IEC 正式发布 GQL 数据库国际标准!

历时四年筹备&#xff0c;超过20个国家的标准和技术专家参与制定&#xff0c;ISO/IEC GQL &#xff08;图查询语言&#xff09;标准于2024年4月12日正式发布&#xff01; 作为国际标准化组织&#xff08;ISO&#xff09;继 1987年 发布SQL后&#xff0c;唯一发布的数据库查询语…

express路由的介绍与使用

一、什么是路由&#xff1f; 官方&#xff1a;路由确定了应用程序如何响应客户端对特定端点的请求 通俗来说&#xff1a;在Web开发中&#xff0c;路由是指根据不同的请求路径和请求方法&#xff0c;将请求分发到相应的处理函数、模块或中间件。简单来说&#xff0c;就是URL到…

【运维心得】双WAN配置的一个误区

目录 双WAN配置及优势 实际案例 解决之道 最后总结 双WAN配置及优势 什么是双WAN配置&#xff0c;这里就不多赘述&#xff0c;简单的说&#xff0c;首先你要有一台支持双WAN口的路由器&#xff0c;目前大多数企业级路由器都具备了这个功能。甚至有些家用路由器也有此类功能…

揭秘:水滴式粉碎机为何如此受欢迎

在粉碎机市场中&#xff0c;水滴式粉碎机以其D特的设计和G效的性能脱颖而出&#xff0c;成为众多用户的选择产品。那么&#xff0c;水滴式粉碎机究竟有何魅力&#xff0c;能够赢得如此广泛的赞誉呢&#xff1f; 首先&#xff0c;水滴式粉碎机的G效性能是其受欢迎的关键因素之一…

【软件设计师】下午题总结-数据流图、数据库、统一建模语言

下午题总结 1 试题一1.1 结构化语言 2 试题二弱实体增加权限增加实体间联系和联系的类型 3 试题三3.1 UML关系例子 3.2 例子&#xff08;2016上半年&#xff09;3.3 设计类分类3.3.1 接口类3.3.2 控制类3.3.3 实体类 3.4 简答题3.4.1 简要说明选择候选类的原则3.4.2 某个类必须…