热门标签 | 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++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 详解 Qt 串口通信程序全程图文 (4)
    Qt串口通信程序全程图文是本文介绍的内容,本文一开始先讲解对程序的改进,在文章最后将要讲解一些重要问题。1、在窗口中加入一些组合框ComboBox&# ... [详细]
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 在使用 Qt 进行 YUV420 图像渲染时,由于 Qt 本身不支持直接绘制 YUV 数据,因此需要借助 QOpenGLWidget 和 OpenGL 技术来实现。通过继承 QOpenGLWidget 类并重写其绘图方法,可以利用 GPU 的高效渲染能力,实现高质量的 YUV420 图像显示。此外,这种方法还能显著提高图像处理的性能和流畅性。 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 本文提出了一种基于栈结构的高效四则运算表达式求值方法。该方法能够处理包含加、减、乘、除运算符以及十进制整数和小括号的算术表达式。通过定义和实现栈的基本操作,如入栈、出栈和判空等,算法能够准确地解析并计算输入的表达式,最终输出其计算结果。此方法不仅提高了计算效率,还增强了对复杂表达式的处理能力。 ... [详细]
  • 题目要求维护一个数列,并支持两种操作:一是查询操作,语法为QL,用于查询数列末尾L个数中的最大值;二是更新操作,用于修改数列中的某个元素。本文通过ST表(Sparse Table)优化查询效率,确保在O(1)时间内完成查询,同时保持较低的预处理时间复杂度。 ... [详细]
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社区 版权所有