最简单的基于 FFmpeg 的推流器(以推送 RTMP 为例)

最简单的基于 FFmpeg 的推流器(以推送 RTMP 为例)

  • 最简单的基于 FFmpeg 的推流器(以推送 RTMP 为例)

最简单的基于 FFmpeg 的推流器(以推送 RTMP 为例)

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的推流器(以推送RTMP为例)

简介

本文记录一个最简单的基于 FFmpeg的 推流器(simplest ffmpeg streamer)。

推流器的作用就是将本地的视频数据推送至流媒体服务器。本文记录的推流器,可以将本地的 MOV / AVI / MKV / MP4 / FLV 等格式的媒体文件,通过流媒体协议(例如 RTMP,HTTP,UDP,TCP,RTP 等等)以直播流的形式推送出去。由于流媒体协议种类繁多,不一一记录。在这里记录将本地文件以 RTMP 直播流的形式推送至 RTMP 流媒体服务器(例如 Flash Media Server,Red5,Wowza 等等)的方法。

在这个推流器的基础上可以进行多种方式的修改,实现各式各样的推流器。例如:

  • 将输入文件改为网络流 URL,可以实现转流器。
  • 将输入的文件改为回调函数(内存读取)的形式,可以推送内存中的视频数据。
  • 将输入文件改为系统设备(通过 libavdevice),同时加上编码的功能,可以实现实时推流器(现场直播)。

PS:本程序并不包含视频转码的功能。

RTMP 推流器(Streamer)的在流媒体系统中的作用可以用下图表示。首先将视频数据以 RTMP 的形式发送到流媒体服务器端(Server,比如 FMS,Red5,Wowza 等),然后客户端(一般为 Flash Player)通过访问流媒体服务器就可以收看实时流了。

在这里插入图片描述

运行本程序之前需要先运行 RTMP 流媒体服务器,并在流媒体服务器上建立相应的 Application。有关流媒体服务器的操作不在本文的论述范围内,在此不再详述,可以参考链接:《Windows搭建RTMP视频流服务(Nginx服务器版)》。

本程序运行后,即可通过 RTMP 客户端(例如 Flash Player, FFplay 等等)收看推送的直播流。

需要注意的地方

封装格式

RTMP 采用的封装格式是 FLV。因此在指定输出流媒体的时候需要指定其封装格式为“flv”。

同理,其他流媒体协议也需要指定其封装格式。例如采用 UDP 推送流媒体的时候,可以指定其封装格式为“mpegts”。

延时

发送流媒体的数据的时候需要延时。不然的话,FFmpeg 处理数据速度很快,瞬间就能把所有的数据发送出去,流媒体服务器是接受不了的,因此需要按照视频实际的帧率发送数据。

本文记录的推流器在视频帧与帧之间采用了 av_usleep() 函数休眠的方式来延迟发送。这样就可以按照视频的帧率发送数据了。

参考代码如下:

//…
int64_t start_time=av_gettime();
while (1) {
//…
	//Important:Delay
	if(pkt.stream_index==videoindex){
		AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
		AVRational time_base_q={1,AV_TIME_BASE};
		int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
		int64_t now_time = av_gettime() - start_time;
		if (pts_time > now_time)
			av_usleep(pts_time - now_time);
	}
//…
}
//…

PTS/DTS问题

没有封装格式的裸流(例如 H.264 裸流)是不包含 PTS、DTS 这些参数的。在发送这种数据的时候,需要自己计算并写入 AVPacket 的 PTS,DTS,duration 等参数。这里还没有深入研究,简单写了一点代码,如下所示:

//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
	//Write PTS
	AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
	//Duration between 2 frames (us)
	int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->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);
}

程序流程图

程序的流程图如下图所示:

请添加图片描述

可以看出和《 最简单的基于 FFmpeg 的封装格式转换器(无编解码)》中的封装格式转换器比较类似。它们之间比较明显的区别在于:

  1. Streamer 输出为 URL。

  2. Streamer 包含了延时部分。

源程序

// Simplest FFmpeg Streamer.cpp : 定义控制台应用程序的入口点。
//

/**
* 最简单的基于 FFmpeg 的推流器(推送 RTMP)
* Simplest FFmpeg Streamer (Send RTMP)
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本例子实现了推送本地视频至流媒体服务器(以 RTMP 为例)。
* 是使用 FFmpeg 进行流媒体推送最简单的教程。
*
* This example stream local media files to streaming media
* server (Use RTMP as example).
* It's the simplest FFmpeg streamer.
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>

// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)

// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
	// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
	FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}

#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 <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif
#endif

int main(int argc, char* argv[])
{
	AVOutputFormat *ofmt = NULL;
	// Input AVFormatContext and Output AVFormatContext
	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;
	// Input file
	const char in_filename[] = "cuc_ieschool.flv";
	// Output RTMP URL
	const char out_filename[] = "rtmp://127.0.0.1:1935/live/test";
	// Output UDP URL
	// const char*out_filename[] =  "rtp://233.233.233.233:6666"; 

	int ret;
	int videoindex = -1;
	int frame_index = 0;
	int64_t start_time = 0;

	av_register_all();
	// Init network
	avformat_network_init();

	// 输入
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		printf("Could not open input file.\n");
		goto end;
	}
	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0)
	{
		printf("Failed to retrieve input stream information.\n");
		goto end;
	}

	for (unsigned int i = 0; i < ifmt_ctx->nb_streams; i++)
		if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoindex = i;
			break;
		}

	// Print some input information
	av_dump_format(ifmt_ctx, 0, in_filename, 0);
	// RTMP
	avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename);
	// UDP
	// avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename); 

	if (ofmt_ctx == NULL)
	{
		printf("Could not create output context.\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt = ofmt_ctx->oformat;
	for (unsigned int i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		// 根据输入流创建输出流(Create output AVStream according to input AVStream)
		AVStream *in_stream = ifmt_ctx->streams[i];
		AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
		if (out_stream == NULL)
		{
			printf("Failed allocating output stream.\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}

		// 复制 AVCodecContext 的设置(Copy the settings of AVCodecContext)
		ret = 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 = 0;
		if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		{
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
		}
	}

	// Print some output information
	av_dump_format(ofmt_ctx, 0, out_filename, 1);

	// Open output URL
	if (!(ofmt->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("Could not open output URL '%s'.\n", out_filename);
			goto end;
		}
	}

	// Write file header
	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0)
	{
		printf("Error occurred when opening output URL.\n");
		goto end;
	}

	start_time = av_gettime();

	while (1)
	{
		AVStream *in_stream, *out_stream;

		// 获取一个 AVPacket
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
		{
			break;
		}
		// FIX:No PTS (Example: Raw H.264)
		// Simple Write PTS
		if (pkt.pts == AV_NOPTS_VALUE)
		{
			// Write PTS
			AVRational time_base1 = ifmt_ctx->streams[videoindex]->time_base;
			// Duration between 2 frames (us)
			int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ifmt_ctx->streams[videoindex]->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);
		}
		// Delay
		if (pkt.stream_index == videoindex)
		{
			AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
			AVRational time_base_q = { 1, AV_TIME_BASE };
			int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
			int64_t now_time = av_gettime() - start_time;
			if (pts_time > now_time)
			{
				av_usleep(pts_time - now_time);
			}
		}
		in_stream = ifmt_ctx->streams[pkt.stream_index];
		out_stream = ofmt_ctx->streams[pkt.stream_index];
		// copy packet
		// 转换 PTS/DTS(Convert PTS/DTS)
		pkt.pts = 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 = 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 = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;
		// Print to Screen
		if (pkt.stream_index == videoindex)
		{
			printf("Send %8d video frames to output URL.\n", frame_index);
			frame_index++;
		}
		// ret = av_write_frame(ofmt_ctx, &pkt);
		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
		if (ret < 0)
		{
			printf("Error muxing packet.\n");
			break;
		}

		av_free_packet(&pkt);
	}

	// Write file trailer
	av_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 != AVERROR_EOF)
	{
		printf("Error occurred.\n");
		return -1;
	}

	system("pause");
	return 0;
}

结果

按《Windows搭建RTMP视频流服务(Nginx服务器版)》 流程的配置 RTMP 服务器。

播放地址根据 nginx.conf 文件一一对应:

在这里插入图片描述

本文实现推流地址为:rtmp://127.0.0.1:1935/live/test

打开 CMD,进入 nginx 1.7.11.3 Gryphon 目录下,执行如下指令,检查 nginx 的配置文件是否正确。

在这里插入图片描述

启动 nginx 服务器:start nginx,载入缺省 ./conf/nginx.conf 配置文件,启动 nginx。

若要采用别的配置去开启 nginx,命令如下:nginx.exe -c conf\nginx-win.conf

在这里插入图片描述

运行程序,输出如下:

在这里插入图片描述

程序运行的同时,使用 VLC media player 打开网络串流,输入之前的地址。

在这里插入图片描述

播放直播流的截图如下图所示:

在这里插入图片描述

此外,也可以通过 PotPlayer/FFplay 这样的客户端播放直播流。

停止 nginx 服务器,命令如下:taskkill /f /im nginx.exe

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Streamer

CSDN:Simplest FFmpeg Streamer.zip

参考链接

  1. 最简单的基于 FFmpeg 的封装格式转换器(无编解码)
  2. Windows搭建RTMP视频流服务(Nginx服务器版)

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

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

相关文章

HTML5:七天学会基础动画网页4

backgorund-size 值与说明 length(单位像素):设置背景图片高度和宽度&#xff0c;第一个值设置宽度&#xff0c;第二个值设置高度&#xff0c;如果只给出一个值&#xff0c;第二个是设置为auto。 percentage(百分比):以父元素的百分比来设置背景图像的宽度和高度&#xff0c…

ChatGPT4.0 的优势、升级 4.0 为什么这么难以及如何进行升级?

前言 “ChatGPT4.0一个月多少人民币&#xff1f;” ”chatgpt4账号“ ”chatgpt4 价格“ “chatgpt4多少钱” 最近发现很多小伙伴很想知道关于ChatGPT4.0的事情&#xff0c;于是写了这篇帖子&#xff0c;帮大家分析一下。 一、ChatGPT4.0 的优势 &#xff08;PS&#xff1a;…

SpringBoot接收参数的几种形式

SpringBoot接收参数的几种形式 在SpringBoot中获取参数基本方式有5种,需要都掌握. 这里需要记住一个技术术语或概念 API接口: 你写好的那个URL地址,就被称为API接口 1. 接收常规参数 给/param/demo1这个URL接口发送id, name两个参数 以上是以GET请求类型进行发送,实际发送…

深度学习500问——Chapter02:机器学习基础(1)

文章目录 前言 2.1 基本概念 2.1.1 大话理解机器学习本质 2.1.2 什么是神经网络 2.1.3 各种常见算法图示 2.1.4 计算图的导数计算 2.1.5 理解局部最优与全局最优 2.1.5 大数据与深度学习之间的关系 2.2 机器学习学习方式 2.2.1 监督学习 2.2.2 非监督式学习 2.2.3 …

【iOS ARKit】协作 Session 实例

协作 Session 使用注意事项 协作 Session 是在 ARWorldMap 基础上发展起来的技术&#xff0c;ARWorldMap 包含了一系列的地标、ARAnchor 及在观察这些地标和 ARAnchor 时摄像机的视场&#xff08;View&#xff09;。如果用户在某一个位置新创建了一个 ARAnchor&#xff0c;这时…

指针的传递使用场景

C语言函数调用时为值传递&#xff0c;实参赋值给形参&#xff0c;形参值改变不会影响实参&#xff08;原理&#xff1a;两个参数地址不同&#xff09;&#xff0c;若要函数改变实参值&#xff0c;应当传递实参的地址&#xff0c;参考以下实例。 代码展示&#xff1a; #includ…

WiFi模块引领智能家居革命:连接未来的生活

随着科技的快速发展&#xff0c;智能家居正成为现代生活的一部分&#xff0c;极大地改变了我们与家庭环境互动的方式。其中&#xff0c;WiFi模块作为关键的连接技术&#xff0c;在推动智能家居革命中发挥着不可忽视的作用。本文将深入探讨WiFi模块如何驱动智能家居革命。 设备互…

OD(13)之Mermaid饼图和象限图

OD(13)之Mermaid饼图和象限图使用详解 Author: Once Day Date: 2024年2月29日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Mermaid使用指南_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool‍‌⁡…

layui中,父页面与子页面,函数方法的相互调用、传参

<%--父页面--%> <script type"text/javascript">var KaoHaoType 0; // 考号类型 自定义参数1// 选取考号类型function SelectKaoHaoType(callBack) {KaoHaoType 0; // 默认选择填涂考号layer.open({type: 2, title: 请选择 考号区类型, ar…

Linux信号【保存-处理】

目录 前言&#xff1a; 1、再次认识信号 1.1、概念 1.2、感性理解 1.3、在内核中的表示 1.4、sigset_t 信号集 2、信号集操作函数 2.1、增删改查 2.2、sigprocmask 2.3、sigpending 3.信号的处理机制 3.1处理情况 3.2合适时机 4用户态与内核态 4.1、概念 4.2、…

Python:练习:编写一个程序,写入一个美金数量,然后显示出如何用最少的20美元、10美元、5美元和1美元来付款

案例&#xff1a; python编写一个程序&#xff0c;写入一个美金数量&#xff0c;然后显示出如何用最少的20美元、10美元、5美元和1美元来付款&#xff1a; Enter a dollar amout:93 $20 bills: 4 $10 bills: 1 $5 bills:0 $1 bills:3 思考&#xff1a; 写入一个美金数量&…

Android NDK底层BUG,记录:connect、socket(AF_INET, SOCK_STREAM, 0) 等系统套接字接口函数崩溃问题。

在 Android NDK 之中&#xff0c;看上去调用 connect、socket 函数是不会崩溃的&#xff0c;但这是否定的&#xff0c;它在特定的情况下存在必定的崩溃的问题。 但是这种情况放到MACOS、LINUX、WINDOWS都不会崩溃&#xff0c;而它崩溃的点出现在操作系统底层。 人们需要参考这…

设计推特(Leetcode355)

例题&#xff1a; https://leetcode.cn/problems/design-twitter/ 分析&#xff1a; 推特其实类似于微博&#xff0c;在微博中可以发送文章。 求解这类题目&#xff0c;我们需要根据题目需求&#xff0c;利用面向对象的思想&#xff0c;先对需求做一个抽象&#xff0c;看看能…

TDengine 研发分享:利用 Windbg 解决内存泄漏问题的实践和经验

内存泄漏是一种常见的问题&#xff0c;它会导致程序的内存占用逐渐增加&#xff0c;最终导致系统资源耗尽或程序崩溃。AddressSanitizer (ASan) 和 Valgrind 是很好的内存检测工具&#xff0c;TDengine 的 CI 过程就使用了 ASan 。不过这次内存泄漏问题发生在 Windows 下&#…

MYSQL01高级_Linux版安装、各级别字符集、字符集与比较规则、SQL大小写规范

文章目录 ①. MySQL - linux版安装②. 字符集的相关操作③. 各级别的字符集④. 字符集与比较规则(了解)⑤. SQL大小写规范⑥. sql_mode的合理设置 ①. MySQL - linux版安装 ①. 进入mysql官网,找到安装文件 ②. 将抽取出来的文件放在linux下的opt下 MySQL Community Serv…

视频在线压缩

video2edit 一款免费的在线视频编辑软件&#xff0c;可以进行视频合并、视频剪辑、视频压缩以及转换视频格式等。 链接地址&#xff1a;在线视频编辑器和转换器 - 编辑&#xff0c;转换和压缩视频文件 打开视频压缩页面&#xff0c;上传想要压缩视频&#xff0c;支持MP4&…

ffmpeg单张图片生成固定时长的视频

ffmpeg -r 25 -f image2 -loop 1 -i fps_1.jpg -vcodec libx264 -pix_fmt yuv420p -s 1080*1920 -r 25 -t 30 -y fps.mp4这个命令将 fps_1.jpg 图片转换为一个 30 秒长的视频&#xff0c;分辨率为 1920x1080&#xff0c;帧率为 25 帧/秒&#xff0c;并使用 libx264 编码器进行压…

1.3 vue ui框架-element-ui框架

1 前言 ElementUI是一套基于VUE2.0的桌面端组件库&#xff0c;ElementUI提供了丰富的组件帮助开发人员快速构建功能强大、风格统一的页面。 ElementUI官网 https://element.eleme.io 2 安装 运行命令 cnpm i element-ui -S -S表示只在该项目下安装&#xff0c;不是全局安…

C++指针(二)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 文章目录 1.数组指针 1.1数组指针的概念 1.2数组指针的用处 1.3数组指针的操作 1.4二维数组如何访问 1.5数组指针访问流程 1.6数组指针的练习题 2.指针数组 2.1指针数组的概念 2.2指针数组的用处 2…

使用Python,networkx绘制有向层级结构图

使用Python&#xff0c;networkx绘制有向层级结构图 1. 效果图2. 源码2.1 tree.txt2.2 pyNetworkx.py参考 上一篇介绍了&#xff1a;1. 使用Python&#xff0c;networkx对卡勒德胡赛尼三部曲之《群山回唱》人物关系图谱绘制 当前博客介绍&#xff1a; 2. 使用Python&#xff0c…