一.ffmpeg 将内存中的H264跟PCM 数据流合成多媒体文件

news2024/11/19 15:14:25

在有一些嵌入式平台中,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

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

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

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

相关文章

什么是经典蓝牙模块?

什么是经典蓝牙模块&#xff1f;   前面我们已经就蓝牙模块的概念做了了解&#xff0c;随着时间的推移&#xff0c;产品越来越智能&#xff0c;需要的蓝牙模块也就越来越广泛&#xff0c;本篇文章我们就一起了解下什么是经典蓝牙模块。   经典蓝牙模块(BT)泛指支持蓝牙协议…

ClickHouse配置与使用

静态IP配置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.128 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

AI办公自动化-kimi批量在多个Excel工作表中绘制柱状图

工作任务和目标&#xff1a;批量在多个Excel工作表中生成一个柱状图 第一步&#xff0c;在kimi中输入如下提示词&#xff1a; 你是一个Python编程专家&#xff0c;完成下面任务的Python脚本&#xff1a; 打开文件夹&#xff1a;F:\aivideo 读取里面所有的xlsx文件&#xff1…

【云原生之】K8s 管理工具 kubectl 详解(二)

一、项目的生命周期 创建–>发布–>更新–>回滚–>删除 1.1、创建kubectl create命令 创建并运行一个或多个容器镜像。创建一个deployment 或job 来管理容器。 kubectl create --help kubectl -n 命名空间 create deployment 资源名称 --image镜像 --port容器的端…

useTransition:开启React并发模式

写在前面&#xff1a;并发 并发模式&#xff08;Concurrent Mode&#xff09;1的一个关键特性是渲染可中断。 React 18 之前&#xff0c;更新内容渲染的方式是通过一个单一的且不可中断的同步事务进行处理。同步渲染意味着&#xff0c;一旦开始渲染就无法中断&#xff0c;直到…

将某一个 DIV 块全屏展示

文章目录 需求分析 需求 上节我们研究了如何将页面中的指定 div 下载为图片&#xff1a;跳转查看 本节演技一下如何将 DIV 全屏展示 全屏展示某一个 DIV 分析 其实就是模拟键盘动作 F11 var element document.getElementById(pic) var requestMethod element.requestFullS…

Alinx xc7z020 原理图

时钟引脚 CLK&#xff1a;U18 复位 RST&#xff1a;N15 扩展接口 J10 J11 PL LED

实战:生成个性化词云的Python实践【7个案例】

文本挖掘与可视化&#xff1a;生成个性化词云的Python实践【7个案例】 词云&#xff08;Word Cloud&#xff09;&#xff0c;又称为文字云或标签云&#xff0c;是一种用于文本数据可视化的技术&#xff0c;通过不同大小、颜色和字体展示文本中单词的出现频率或重要性。在词云中…

CVPR2022医疗图像-GBCNet网络:胆囊癌(GBC)超声(USG)图像检测模型

Surpassing the Human Accuracy:Detecting Gallbladder Cancer from USG Images with Curriculum Learning&#xff1a;超越人类的准确性:基于课程学习的USG图像检测胆囊癌 目录 一、背景与意义 二、介绍 三、网络框架 3.1 区域选择网络 3.2 MS-SoP分类器 3.3 多尺度块 …

前端加载excel文件数据 XLSX插件的使用

npm i xlsx import axios from axios; axios //这里用自己封装的http是不行的&#xff0c;踩过坑.get(url,{ responseType: "arraybuffer" }).then((re) > {console.log(re)let res re.datavar XLSX require("xlsx");let wb XLSX.read(r…

20240523每日运维--------聊聊docker简介(一)

dotCloud 说Docker&#xff0c;必不可免不得不说dotCloud&#xff0c;Docker本来只是dotCloud公司的内部项目&#xff0c;其公司创始人 Solomon Hykes 发了一个内部项目&#xff0c;而这个项目就是Docker&#xff0c;自从2013年docker开源以后&#xff0c;在世界范围引起相当轰…

【设计模式】JAVA Design Patterns——Bridge(桥接模式)

&#x1f50d;目的 将抽象与其实现分离&#xff0c;以便二者可以独立变化。 &#x1f50d;解释 真实世界例子 考虑一下你拥有一种具有不同附魔的武器&#xff0c;并且应该允许将具有不同附魔的不同武器混合使用。 你会怎么做&#xff1f; 为每个附魔创建每种武器的多个副本&…

Android 性能为王时代SparseArray和HashMap一争高下

文章目录 一、SparseArray 源码分析1. **类定义和构造函数**2. **基本方法**2.1 put(int key, E value)2.2 get(int key)2.3 delete(int key)2.4 removeAt(int index)2.5 gc()2.6 size()2.7 keyAt(int index) 和 valueAt(int index) 3. **辅助方法**3.1 binarySearch() 二、使用…

Axure软件安装教程

链接&#xff1a;https://pan.baidu.com/s/1fHrSrZ7PIeDZZpn6QyJ6jQ?pwdb4mv 提取码&#xff1a;b4mv 安装完后点击Finish 名字随便起 关闭Axure 复制到安装目录下 最后成果

SQL学习小记(一)

SQL学习小记&#xff08;一&#xff09; 1. 存储过程&存储函数1.1. 存储过程1.2. 存储函数 2. DEFINER3. INSERT INTO&#xff08;插入新记录&#xff09;4. REPLACE()…AS…5. SUM()函数6. CASE WHEN7. STR_TO_DATE日期时间处理函数8. SUBSTRING函数9. dateFormat函数10. …

Python数据可视化(六)

实现事件处理效果 我们借助 matplotlib 可以实现事件处理效果&#xff0c;例如&#xff0c;单击关闭画布会出现画布被关闭的文本提 示&#xff0c;在画布上的图形界面任意位置单击可以获得放大后的此处图形界面等。下面&#xff0c;我们就挑选一些 典型的事件处理案例来讲解实现…

7 Series FPGAs Integrated Block for PCI Express IP核 Advanced模式配置详解(三)

1 TL Settings Transaction Layer (TL)设置只在Advanced模式下有效。 Endpoint: Unlock and PME_Turn_Off Messages: 与端点的电源管理相关&#xff0c;允许发送解锁和电源管理事件关闭消息。 Root Port: Error Messages: Error Correctable&#xff08;错误可纠正&#xff09…

探数API统计分享-1949年-2021年中国历年夏粮产量统计报告

​​​​​​​​中国历年夏粮产量​&#xff0c;为1949年到2021年我国每年的夏粮产量数据。2021年&#xff0c;我国夏粮产量为14596万吨&#xff0c;比上年增长2.2%。 数据统计单位为&#xff1a;万吨 。 我国夏粮产量有多少&#xff1f; 2021年&#xff0c;我国夏粮产量为1…

【小白向】MAC端VSCode C++环境配置(超干货、超详细)

提示&#xff1a;使用环境为 MAC&#xff08;M2&#xff09; 其实 VSCode 很早就下载好了&#xff0c;但是因为在配置过程中总是遇到很多坑&#xff0c;搁置了很久&#xff0c;回头捡起遇到报 Error 还是两眼抓瞎&#xff0c;到处翻 blog。为了减少以后的遇坑可能性&#xff0c…

uniapp开发安卓app高德地图

uniapp开发安卓app高德地图 一、高德创建key二、uniapp配置三、uniapp打包证书获取 一、高德创建key 优先去高德地图开发者平台去创建关联项目 高德开发者平台 打开我的应用&#xff0c;添加key 进行高德key的配置 其中key建议设置成app的名称&#xff0c;便于区分SHA1获取可以…