热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

QT使用ffmpeg学习5ffmpegAPI推流Demo

本文使用FfmpegAPI实现推流。一、说明1.ffmpeg中的时间单位AV_TIME_BASEffmpeg中的内部计时单位(时间基)࿰

本文使用Ffmpeg API实现推流。

一、说明

1. ffmpeg中的时间单位

AV_TIME_BASE
ffmpeg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,比如AVStream中的duration即以为着这个流的长度为duration个AV_TIME_BASE。AV_TIME_BASE定义为:

#define AV_TIME_BASE 1000000

2. 关于时间戳的几个函数


i. av_rescale_rnd

函数声明
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)
直接看代码, 它的作用是计算 “a * b / c” 的值并分五种方式来取整.
但是在FFmpeg中,则是将以 “时钟基c” 表示的 数值a 转换成以 “时钟基b” 来表示。

看AVRounding结构体,就是这五种方式

enum AVRounding {AV_ROUND_ZERO = 0, /// 2** av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);* // Rescaling AV_NOPTS_VALUE:* // AV_NOPTS_VALUE == INT64_MIN* // AV_NOPTS_VALUE is passed through* // => AV_NOPTS_VALUE* @endcode*/AV_ROUND_PASS_MINMAX = 8192,
};

ii. av_rescale_q_rnd

将以时钟基为c 的时间戳a 转换成以b为时钟基并且以rnd 这种方式进行运算的值
函数定义:

int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,enum AVRounding rnd)
{int64_t b = bq.num * (int64_t)cq.den;int64_t c = cq.num * (int64_t)bq.den;return av_rescale_rnd(a, b, c, rnd);
}

iii. av_rescale_q

函数定义

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
{return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);
}

使用示例:
将以"1MHz时钟基" 表示的 “PTS/DTS值a” 转换成以 “90kHz时钟基” 表示。


//调用转换
int64_t av_rescale_q(pkt->pts=-10949117256, src_tb={num=1, den=1000000}, dst_tb{num=1, den=90000))
{return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);
}
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,enum AVRounding rnd)
{int64_t b = bq.num * (int64_t)cq.den;// = 1 * 90000 = 90000; int64_t c = cq.num * (int64_t)bq.den; // = 1 * 1000000 = 1000000 return av_rescale_rnd(a, b, c, 5);
}int64_t av_rescale_rnd(a=10949117256, b=90000, c=1000000, rnd=5)
{ if (rnd&#61;&#61;5) r &#61; c / 2; // r &#61;500000; if (b<&#61;INT_MAX && c<&#61;INT_MAX) { if (a<&#61;INT_MAX) return (a * b &#43; r)/c; else return a/c*b &#43; (a%c*b &#43; r)/c; // &#61; 10949117256 / 1000000 * 90000 &#43; // (10949117256 % 1000000 * 90000 &#43; 500000) / 1000000 // &#61; 985420553 } else { ... }
}

3. 关于pts计算


i、视频pts

视频比较好理解&#xff0c;就是每帧递增&#xff0c;假如fps是25帧的&#xff0c;时间基为fps的倒数1/25&#xff0c;那么pts递增即可。
如下&#xff1a;

  1. 第一帧&#xff1a;pts&#61;0
  2. 第二帧&#xff1a;pts&#61;1
  3. 第三帧&#xff1a;pts&#61;3
    第n帧&#xff1a;pts &#61; n - 1;

计算公式为&#xff1a;第n帧的pts&#61;n∗(&#xff08;1/timbase&#xff09;/fps);第n帧的pts &#61; n * (&#xff08;1 / timbase&#xff09;/ fps);npts&#61;n(&#xff08;1/timbase&#xff09;/fps);

ii、音频pts

音频相对来说更难理解一些&#xff0c;因为音频的一个packet不止一帧&#xff0c;所以一秒到底有多少个packet就不知道&#xff0c;就别说如何计算pts了。
假设音频一秒有num_pkt个packet&#xff0c;那么这个num_pkt到底是多少&#xff1f;
这的从音频基础开始说起&#xff0c;我们知道音频有个采样率&#xff0c;就是一秒钟采用多少次&#xff0c;很多音频都是44100的采样率&#xff0c;也有8k的&#xff0c;那么这个采样率和num_pkt有什么关系呢&#xff1f;
我们发现在AVFrame中有一个比较重要的字段叫做nb_samples&#xff0c;这个字段名为采样数&#xff0c;此字段可以结合音频数据格式计算这个frame->data有多大&#xff0c;其实这个字段联合采样率还可以计算音频一秒有多少个packet。
计算公式如下&#xff1a;
numpkt&#61;采样率/nbsamples;num_pkt &#61; 采样率 / nb_samples;numpkt&#61;/nbsamples;
这样我们就知道了音频每秒的包数目&#xff08;可以见到理解为帧&#xff09;&#xff0c;有了此数据计算pts就和视频一模一样了&#xff0c;
计算公式如下&#xff1a;
第n个包的pts &#61; n * (&#xff08;1 / timbase&#xff09;/ num_pkt);
很多音频时间基和采样率成倒数&#xff0c;那么根据公式我们的音频pts就可以很简单的以nb_samples递增了&#xff0c;如下&#xff1a;
第一个包&#xff1a;pts &#61; 0 * nb_samples;
第二个包&#xff1a;pts &#61; 1 * nb_samples;
第三个包&#xff1a;pts &#61; 2 * nb_samples;
.
.
.
第n个包&#xff1a;pts &#61; (n - 1) * nb_samples;

二、创建项目

使用QT创建新项目&#xff0c;添加ffmpeg包引用。

设置 pro文件

#-------------------------------------------------
#
# Project created by QtCreator 2020-05-13T09:08:20
#
#-------------------------------------------------QT &#43;&#61; core guigreaterThan(QT_MAJOR_VERSION, 4): QT &#43;&#61; widgetsTARGET &#61; OutputSample
TEMPLATE &#61; appSOURCES &#43;&#61; main.cpp\mainwindow.cppHEADERS &#43;&#61; mainwindow.hFORMS &#43;&#61; mainwindow.ui
INCLUDEPATH &#43;&#61;"D:\\tools\\ffmpeg\\win32\\dev\\include"LIBS &#43;&#61; -LD:\tools\ffmpeg\win32\dev\lib -lavutil -lavformat -lavcodec -lavdevice -lavfilter -lpostproc -lswresample -lswscale

主程序

#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
}
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#ifdef __cplusplus
};
#endif
#endifint main(int argc, char* argv[])
{AVOutputFormat *ofmt &#61; NULL;AVFormatContext *ifmt_ctx &#61; NULL, *ofmt_ctx &#61; NULL;AVPacket pkt;const char *in_filename, *out_filename;int ret, i;int videoindex&#61;-1;int frame_index&#61;0;int64_t start_time&#61;0;in_filename &#61; "D:/1.mp4";//输入URL&#xff08;Input file URL&#xff09;out_filename &#61; "rtmp://rtmp服务器地址:端口/flv/test_1_1";//输出 URL&#xff08;Output URL&#xff09;[RTMP]//out_filename &#61; "rtp://233.233.233.233:6666";//输出 URL&#xff08;Output URL&#xff09;[UDP]// 初始化ffmpegav_register_all();// 初始化网络库avformat_network_init();// 初始化输入if ((ret &#61; avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) <0) {printf( "Could not open input file.");goto end;}if ((ret &#61; avformat_find_stream_info(ifmt_ctx, 0)) <0) {printf( "Failed to retrieve input stream information");goto end;}for(i&#61;0; inb_streams; i&#43;&#43;)if(ifmt_ctx->streams[i]->codec->codec_type&#61;&#61;AVMEDIA_TYPE_VIDEO){videoindex&#61;i;break;}// 打印流媒体信息av_dump_format(ifmt_ctx, 0, in_filename, 0);// 输出流avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP//avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDPif (!ofmt_ctx) {printf( "Could not create output context\n");ret &#61; AVERROR_UNKNOWN;goto end;}ofmt &#61; ofmt_ctx->oformat;for (i &#61; 0; i nb_streams; i&#43;&#43;) {//Create output AVStream according to input AVStreamAVStream *in_stream &#61; ifmt_ctx->streams[i];AVStream *out_stream &#61; avformat_new_stream(ofmt_ctx, in_stream->codec->codec);if (!out_stream) {printf( "Failed allocating output stream\n");ret &#61; AVERROR_UNKNOWN;goto end;}//Copy the settings of AVCodecContextret &#61; avcodec_copy_context(out_stream->codec, in_stream->codec);if (ret <0) {printf( "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codec->codec_tag &#61; 0;if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |&#61; AV_CODEC_FLAG_GLOBAL_HEADER;}// 打印输出流信息av_dump_format(ofmt_ctx, 0, out_filename, 1);// 使用avio_open 打开输出if (!(ofmt->flags & AVFMT_NOFILE)) {ret &#61; avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);if (ret <0) {printf( "Could not open output URL &#39;%s&#39;", out_filename);goto end;}}// 输出写文件头ret &#61; avformat_write_header(ofmt_ctx, NULL);if (ret <0) {printf( "Error occurred when opening output URL\n");goto end;}start_time&#61;av_gettime();while (1) {AVStream *in_stream, *out_stream;//获取一帧ret &#61; av_read_frame(ifmt_ctx, &pkt);if (ret <0)break;// 如果没有PTS &#xff0c; 例如H.264裸流//更简单的处理就用 PTSpkt.pts&#61;&#61;AV_NOPTS_VALUEif(pkt.pts&#61;&#61;AV_NOPTS_VALUE){//Write PTSAVRational time_base1&#61;ifmt_ctx->streams[videoindex]->time_base;//Duration between 2 frames (us)int64_t calc_duration&#61;(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);//计算 pts的公式 pkt.pts&#61;(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);pkt.dts&#61;pkt.pts;pkt.duration&#61;(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);}//Important:Delayif(pkt.stream_index&#61;&#61;videoindex){AVRational time_base&#61;ifmt_ctx->streams[videoindex]->time_base;AVRational time_base_q&#61;{1,AV_TIME_BASE};int64_t pts_time &#61; av_rescale_q(pkt.dts, time_base, time_base_q);int64_t now_time &#61; av_gettime() - start_time;if (pts_time > now_time)av_usleep(pts_time - now_time);}in_stream &#61; ifmt_ctx->streams[pkt.stream_index];out_stream &#61; ofmt_ctx->streams[pkt.stream_index];// 复制包//Convert PTS/DTSpkt.pts &#61; av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt.dts &#61; av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));pkt.duration &#61; av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);pkt.pos &#61; -1;// 打印logif(pkt.stream_index&#61;&#61;videoindex){printf("Send %8d video frames to output URL\n",frame_index);frame_index&#43;&#43;;}//ret &#61; av_write_frame(ofmt_ctx, &pkt);ret &#61; av_interleaved_write_frame(ofmt_ctx, &pkt);if (ret <0) {printf( "Error muxing packet\n");break;}av_free_packet(&pkt);}//Write file trailerav_write_trailer(ofmt_ctx);
end:avformat_close_input(&ifmt_ctx);/* close output */if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))avio_close(ofmt_ctx->pb);avformat_free_context(ofmt_ctx);if (ret <0 && ret !&#61; AVERROR_EOF) {printf( "Error occurred.\n");return -1;}return 0;
}

使用VLC打开 rtmp://rtmp服务器地址:端口/flv/test_1_1 即可查看视频。

参考&#xff1a;https://blog.csdn.net/dancing_night/article/details/45972361
https://www.jianshu.com/p/5634712cfe1b


推荐阅读
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
author-avatar
mobiledu2502920327
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有