在 ffmpeg学习(3)编码、解码的流程介绍 和 ffmpeg学习(9)音视频文件demuxer中介绍了媒体文件的解封装。本文记录Ffmpeg封装格式另一种处理与与demuxer相反方式–视音频复用器(混流器)Muxer,视频压缩数据(例如H264)和音频压缩数据(例如AAC)合并到将封装格式数据(如MP4)中。

本文的音视频也不涉及到编解码,根据输入、输出结果不同,提供以下示例

(1) 输入一种音视频封装文件,输出为另一种音视频封装文件: ffmpeg学习(10)音视频文件muxer(1)封装格式转换
(2) 输入两个不同来源的音频、视频码流,保存到MP4封装数据文件(本文内容)

Muxer两种码流到封装文件

流程图如下。一共初始化3个AVFormatContext,其中两个用于输入,一个用于输出。当所有AVFormatContext初始化成功后,通过avcodec_parameters_copy将输入的视频流、音频流的参数拷贝到输出音频流、视频流的AVCodecParameters参数结构体中。之后分别调用输入视频流、输入音频流的av_read_frame(),读取输入流的AVPacket,并按照先后顺序写入到输出文件中即可。音视频同步需要考虑音频、视频数据写入的先后顺序,通过函数av_compare_ts()比较决定。

完整代码

/*两个音视频文件复用(混流)为一个封装封装文件,无编解码
*/
#include <stdio.h>#ifdef __cplusplus
extern "C" {#endif  #include "libavformat/avformat.h"#ifdef __cplusplus
}
#endif int main()
{int ret;const char* input_video_file_name = "video.mp4"; //const char* input_video_file_name = "video.h264"; //const char* input_video_file_name = "Titanic.mp4";//const char* input_video_file_name = "Titanic.mp4";//const char* input_audio_file_name = "audio.mp3";    //const char* input_audio_file_name = "audio.aac";  const char* input_audio_file_name = "Titanic.mp4";//const char* input_audio_file_name = "Titanic.ts";   // 音频有滞后const char* output_file_name = "out.mp4";AVFormatContext *ifmt_ctx_v = NULL, *ifmt_ctx_a = NULL;AVFormatContext *ofmt_ctx = NULL;int in_stream_index_v = -1;int in_stream_index_a = -1;int out_stream_index_v = -1;int out_stream_index_a = -1;/**   输入信息*/printf("<--------------- input file info --------------->\n");// 输入文件,查找视频流if((ret = avformat_open_input(&ifmt_ctx_v, input_video_file_name, NULL, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");return ret;}if((ret = avformat_find_stream_info(ifmt_ctx_v, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");return ret;}ret = av_find_best_stream(ifmt_ctx_v, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find video stream\n");return ret;}in_stream_index_v = ret;av_dump_format(ifmt_ctx_v, 0, input_video_file_name, 0);// 输入文件,查找音频流if((ret = avformat_open_input(&ifmt_ctx_a, input_audio_file_name, NULL, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");return ret;}if((ret = avformat_find_stream_info(ifmt_ctx_a, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");return ret;}ret = av_find_best_stream(ifmt_ctx_a, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if(ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find audio stream\n");return ret;}in_stream_index_a = ret;av_dump_format(ifmt_ctx_a, 0, input_audio_file_name, 0);/**   输出信息*/printf("\n<--------------- output file info --------------->\n");// 输出ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output_file_name);if(ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot open output file\n");return ret;}// 为输出添加视频流if(in_stream_index_v >= 0) {AVStream *in_stream = ifmt_ctx_v->streams[in_stream_index_v];AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);out_stream->codecpar->codec_tag = 0;out_stream_index_v = out_stream->index;}// 为输出添加音频流if(in_stream_index_a >= 0) {AVStream *in_stream = ifmt_ctx_a->streams[in_stream_index_a];AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);out_stream->codecpar->codec_tag = 0;out_stream_index_a = out_stream->index;}if(!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {if(avio_open(&ofmt_ctx->pb, output_file_name, AVIO_FLAG_WRITE) < 0) {av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", output_file_name);return ret;}}// dump output formatav_dump_format(ofmt_ctx, 0, output_file_name, 1);// 写文件头
avformat_write_header(ofmt_ctx, NULL);AVPacket *pkt = av_packet_alloc();int64_t cur_pts_v = 0, cur_pts_a = 0;int frame_index = 0;while(1) {int stream_index = 0;AVStream *in_stream = NULL, *out_stream = NULL;if(av_compare_ts(cur_pts_v, ifmt_ctx_v->streams[in_stream_index_v]->time_base,cur_pts_a, ifmt_ctx_a->streams[in_stream_index_a]->time_base) <= 0){stream_index = out_stream_index_v;if(av_read_frame(ifmt_ctx_v, pkt) >= 0) {do {in_stream = ifmt_ctx_v->streams[pkt->stream_index];out_stream = ofmt_ctx->streams[out_stream_index_v];if(pkt->stream_index == in_stream_index_v) {//FIX:No PTS (Example: Raw H.264)if(pkt->pts == AV_NOPTS_VALUE) {//Write PTSAVRational time_base1 = in_stream->time_base;//Duration between 2 frames (us)int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);//Parameterspkt->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++;}cur_pts_v = pkt->pts;break;}}while(av_read_frame(ifmt_ctx_v, pkt) >= 0);}else {break;}}else {stream_index = out_stream_index_a;if(av_read_frame(ifmt_ctx_a, pkt) >= 0) {do {in_stream = ifmt_ctx_a->streams[pkt->stream_index];out_stream = ofmt_ctx->streams[out_stream_index_a];if(pkt->stream_index == in_stream_index_a) {FIX:No PTS //if(pkt->pts == AV_NOPTS_VALUE) {//    //Write PTS//    AVRational time_base1 = in_stream->time_base;//    //Duration between 2 frames (us)//    int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);//    //Parameters//    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++;//}cur_pts_a = pkt->pts;break;}} while(av_read_frame(ifmt_ctx_a, pkt) >= 0);}else {break;}}av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);pkt->stream_index = stream_index;av_interleaved_write_frame(ofmt_ctx, pkt);av_packet_unref(pkt);}// 写文件尾av_write_trailer(ofmt_ctx);// 关闭释放avformat_free_context(ifmt_ctx_v);avformat_free_context(ifmt_ctx_a);if(ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))avio_close(ofmt_ctx->pb);//关闭视频文件avformat_free_context(ofmt_ctx);#ifdef NEED_MP4TOANNEXB_FILTERav_bsf_free(&bsf_ctx);
#endif av_packet_free(&pkt);
}

(1)输入视频为裸流H264,输出文件失败

错误信息如下

[mp4 @ 000001f856926200] Timestamps are unset in a packet for stream 0. This is deprecated
and will stop working in the future. Fix your code to set the timestamps properly

根源是H264裸流的视频帧没有PTS,需根据帧率为每一个帧设置PTS等数据。

//FIX:No PTS (Example: Raw H.264)
if(pkt->pts == AV_NOPTS_VALUE) {//Write PTSAVRational time_base1 = in_stream->time_base;//Duration between 2 frames (us)int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);//Parameterspkt->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++;
}

(2)音频帧、视频帧同步

多媒体文件播放时,视频帧、音频帧按照各自的节奏进行播放,就是同步播放。这里简单起见,针对来源于两个文件的数据帧,需要确定该写入哪一种类型的数据帧?

先介绍函数av_compare_ts(),定义如下

/*** Compare two timestamps each in its own time base.** @return One of the following values:*         - -1 if `ts_a` is before `ts_b`*         - 1 if `ts_a` is after `ts_b`*         - 0 if they represent the same position** @warning* The result of the function is undefined if one of the timestamps is outside* the `int64_t` range when represented in the other's timebase.*/
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);

作用为比较两个时基下的时间戳大小,前者时间戳早于后者时间戳返回-1,相同返回0,滞后返回1。

我们以转换到同一时基下的时间戳为例,假设上一时刻音、视频帧的保存时间戳都是0。

当前任意保存一种视频帧,例如保存视频的时间戳为video_t1。接着比较时间戳,发现音频时间戳为0 < video_t1,保存一帧音频,时间戳为audio_t1

继续比较时间戳,发现audio_t1 < video_t1,选择保存一帧音频,时间戳为audio_t2

再一次比较时间戳video_t1 < audio_t2,选择保存一帧视频,时间戳为video_t2

之后,继续比较两者时间戳,谁小就保存其对应的数据帧。

if(av_compare_ts(cur_pts_v, ifmt_ctx_v->streams[in_stream_index_v]->time_base,cur_pts_a, ifmt_ctx_a->streams[in_stream_index_a]->time_base) <= 0)
{// 保存视频帧, 更新时间戳//…cur_pts_v = pkt->pts;
}
else{// 保存音频帧, 更新时间戳// …cur_pts_a = pkt->pts;
}

(3)调整对齐时间

当输入的视频、音频文件流存在时间差,复用后的流媒体文件播放时,会发现有一种流有延时,例如画面一个人说话嘴型有变化,但是声音过一会才播放。

如上图,音频流来自于ts文件,其播放实际开始于1.418689。实际混流后播放文件,音频播放明显感觉滞后。

我们将视频时间戳提前,将每一帧的视频pts时间减去延时的时间,注意要时基转换。调整代码如下

if(pkt->stream_index == in_stream_index_v) {//FIX:No PTS (Example: Raw H.264)if(pkt->pts == AV_NOPTS_VALUE) {//Write PTSAVRational time_base1 = in_stream->time_base;//Duration between 2 frames (us)int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);//Parameterspkt->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++;}if(ifmt_ctx_a->start_time > 0) {pkt->pts += av_rescale_q(ifmt_ctx_a->start_time, AVRational{1, AV_TIME_BASE}, out_stream->time_base);}cur_pts_v = pkt->pts;break;
}

ffmpeg学习(11)音视频文件muxer(2)多输入混流相关推荐

  1. FFMPEG学习(4)-使用ffmpeg读取基本音视频文件信息,熟释AVFormatContext结构

    前段时间把环境整了下,闲时学习下ffmpeg. 最近在看雷神的创作,边看,边学,感谢雷神! 头文件: // // ffmpeg_read_av_info.hpp // ffmpegDemo // // ...

  2. FFmpeg学习(音视频理论知识)

    文章目录 1. 音视频理论知识 1.1 基本概念 1.1.1 音视频必备的基本概念 常用的视频封装格式 常用的视频编码器 常用的音频编程器: 视频流 裸数据YUV 1.1.2 音视频常见处理 采集 处 ...

  3. ffmpeg学习1 音视频基本概念

    https://blog.csdn.net/caofengtao1314/article/details/107220572

  4. Win10 使用python和ffmpeg批量合并音视频

    将m4a文件和mp4文件合并为MP4 具体代码如下 import os,sys import io import subprocess #在vscode运行时输出内容含中文不乱码,其他软件运行未知 s ...

  5. android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...

    ## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...

  6. ffmpeg音视频文件音视频流抽取,初步尝试人声分离

    文章目录 ffmpeg抽取音视频文件中的音频流 音频流类型 AAC与m4a的区别 AAC与mp3的区别 用ffmpeg查看视频的信息 用ffmpeg抽取AAC音频流 从AAC文件中获取音轨 音轨是什么 ...

  7. 音视频编解码流程与如何使用 FFMPEG 命令进行音视频处理

    一.前言 FFMPEG 是特别强大的专门用于处理音视频的开源库.你既可以使用它的 API 对音视频进行处理,也可以使用它提供的工具,如 ffmpeg, ffplay, ffprobe,来编辑你的音视频 ...

  8. FFmpeg 工具:音视频开发都用它,快@你兄弟来看丨音视频工具

    (本文基本逻辑:ffmpeg 常用命令介绍 → ffplay 常用命令介绍 → ffprobe 常用命令介绍) 从事音视频开发的程序员几乎都应该知道或使用过 FFmpeg.FFmpeg 是一个开源软件 ...

  9. Excel催化剂开源第37波-音视频文件元数据提取(分辨率,时长,采样率等)

    上一篇提到图片元信息Exif的提取,当然还有一类音视频文件,也同样存储着许多宝贵的元数据,那就开源到底呗,虽然自己找寻过程也是蛮艰辛坎坷的,大家看后有收获,只求多多传播下,让前人的工作可以更有价值. ...

最新文章

  1. java 导出文件,导出多个文件方案~
  2. ubuntu 安装docker_Docker: 教程04 - (初始化安装之在 Ubuntu 安装Docker CE)
  3. Android 从一个Activity跳转到另一个Activity获取第二个Activity的返回值
  4. java+什么时候才需要deploy_细思极恐 - 什么才是真正的会写 Java ?
  5. python contextlib closing
  6. SVN 冲突文件详解
  7. mysql是应用软件还是系统软件_数据库管理系统属于应用软件吗?
  8. 海创软件组-20200614-用户自定义工程认证调查模板-大创项目申报书
  9. javascript判断一个数是否是素数(质数)
  10. no.8 python 和 Linux (笔记)
  11. 实验五 CA的安装和使用
  12. 情感计算机具体应用领域,人工智能-情感计算
  13. 通过python scrapy shell 获取对应的网页元素值
  14. 安卓逆向007之安卓系统架构
  15. u盘电视测试软件,智能电视无法识别U盘里的APK文件?当贝市场教你搞定
  16. 统计学,机器学习,数据挖掘,深度学习
  17. 概论_第5章_切比雪夫不等式
  18. 计蒜客 17115 Coin(2017 ACM-ICPC 亚洲区(西安赛区)网络赛 B)
  19. 又是一年毕业季,这百道前端面试题你都会了嘛(基础题+2套简历模板)
  20. micropython入门(二)硬件入门及步进电机驱动(102+A4988板)

热门文章

  1. Vue项目文件代码介绍
  2. (实验39)单片机,STM32F4学习笔记,代码讲解【FATFS实验】【正点原子】【原创】
  3. 2018华为校招机试题目练习
  4. 宝清县职业技术学校计算机,宝清县职业技术学校开展 “3+2”贯通培养模式 为广大学子铺就全新成才之路...
  5. 2021年危险化学品经营单位主要负责人考试及危险化学品经营单位主要负责人考试题
  6. 简单的golang游戏服务器框架《railgun》的文档目录索引
  7. 阅读文献Evaluation of dynamic route planning impact on vehicular communications with SUMO
  8. 每日Scrum站会实践推荐
  9. 黑猫带你学UFS协议第11篇:两万字详解UFS协议信息单元(UPIU)
  10. C语言程序实现道格拉斯—普克算法(Douglas--Peucker)