【FFmpeg】调用ffmpeg库进行RTMP推流和拉流

2024-05-10 17:12

本文主要是介绍【FFmpeg】调用ffmpeg库进行RTMP推流和拉流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【FFmpeg】调用ffmpeg库实现RTMP推流

  • 1.FFmpeg编译
  • 2.RTMP服务器搭建
  • 3.调用FFmpeg库实现RTMP推流和拉流
    • 3.1 基本框架
    • 3.2 实现代码
    • 3.3 测试
      • 3.3.1 推流
      • 3.3.2 拉流

参考:雷霄骅博士, 调用ffmpeg库进行RTMP推流
====== 示例工程 ======
【FFmpeg】调用FFmpeg库实现264软编
【FFmpeg】调用FFmpeg库实现264软解

1.FFmpeg编译

参考: FFmpeg在Windows下的编译
本文使用FFmpeg-7.0版本

2.RTMP服务器搭建

将本机配置成为服务器,实现本地的推流和拉流操作。RTMP服务器的搭建参考:RTMP服务器的搭建

RTMP是Adobe提出的一种应用层的协议,用于解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题传输,传输媒体的格式为FLV,因此本文推流的格式是flv格式。flv文件可以使用ffmpeg命令行,从yuv文件转换而来。

3.调用FFmpeg库实现RTMP推流和拉流

3.1 基本框架

在实现时,对参考博客中的部分函数进行了修改

  1. 不再使用av_register_all()函数对编解码器进行初始化
  2. 增加av_log_set_level(AV_LOG_TRACE)增加日志输出信息
  3. 使用本机IP127.0.0.1
  4. 修改部分参数的调用,因为部分变量存储的位置发生了变化
  5. 修改部分函数的调用,因为使用的FFmpeg版本不同
  6. 将推流和拉流的部分合并,用一套代码实现

在编码过程当中,主要使用了如下的函数

函数名作用
av_log_set_level配置输出日志级别 (AV_LOG_TRACE最详细)
avformat_network_init初始化网络模块
avformat_open_input打开输入文件,并且将文件信息赋值给AVFormatContext保存
avformat_find_stream_info根据AVFormatContext查找流信息
av_dump_format将AVFormatContext中的媒体文件的信息进行格式化输出
avformat_alloc_output_context2根据format_name(或filename或oformat)创建输出文件的AVFormatContext信息
avformat_new_stream根据AVFormatContext和AVCodecContext创建新的流
avcodec_parameters_copy拷贝AVCodecParameters
avio_open根据url进行AVIOContext的创建与初始化(这个url在推流时就是服务器地址)
avformat_write_header为流分配priv_data并且将流的头信息写入到输出媒体文件
av_read_frame根据AVFormatContext所提供的的信息读取一帧,存入AVPacket
av_interleaved_write_frame以交错的方式将帧送入到媒体文件中
av_packet_unref释放AVPacket
av_write_trailer将流的尾部写入到输出的媒体文件中,并且释放文件中的priv_data
avformat_close_input释放AVFormatContext

从使用的函数来看,主要的操作流程和数据流走向大约为:

  1. 初始化网络模块,为RTMP传输进行准备(avformat_network_init)
  2. 打开输入文件,创建输入文件结构体并且读取输入文件信息(avformat_open_input),此时也会创建输入的流信息结构体
  3. 根据输入文件查找流信息,赋值给流信息结构体(avformat_find_stream_info)
  4. 打印输入文件信息(av_dump_format)
  5. 根据输出文件信息来创建输出文件结构体(avformat_alloc_output_context2)
  6. 创建输出流(avformat_new_stream)
  7. 将输入流的参数拷贝给输出给输出流(avcodec_parameters_copy)
  8. 打印输出文件信息(av_dump_format)
  9. 打开输出口,准备推流(avio_open)
  10. 写入流的头部信息(avformat_write_header)
  11. 读取一帧信息,存储到AVPacket中(av_read_frame)
  12. 处理时间戳;PTS是播放时间戳,告诉播放器播放这一帧的时间;DTS是解码时间戳,告诉播放器解码这一帧的时间;PTS通常是按照递增顺序排列的。这里雷博士认为延时很重要,如果不对前后帧推流的时间进行控制,帧会瞬时推送到服务器端,会出现服务器无法正常接收帧的情况
  13. 将帧推流(av_interleaved_write_frame)
  14. 写入流的尾部信息(av_write_trailer)
  15. 释放结构体信息(av_packet_unref、av_write_trailer和avformat_close_input)

3.2 实现代码

在调试时,发现如果AVPacket这里定义如果是指针的话,会出现av_read_frame第二帧读取失败的情况,这里有待进一步学习。

在代码中,利用bool is_sender来控制是发送还是接收,发送和接收都使用同一套代码,只是在时间戳部分有所区别,即发送端需要计算而接收端不需要使用。不过这里的in_url和out_url还是固定的,实际使用时得重新配置。

#pragma warning(disable : 4996)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "streamer.h"#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/timestamp.h"
#include "libavutil/mathematics.h"
#include "libavutil/log.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#ifdef __cplusplus
};
#endif
#endifint streamer_internal(const char* in_url, const char* out_url, bool is_sender)
{// set log level// av_log_set_level(AV_LOG_TRACE);AVOutputFormat* av_out_fmt = NULL;AVFormatContext* av_in_fmt_ctx = NULL;AVFormatContext* av_out_fmt_ctx = NULL;AVPacket av_pkt;const char* in_filename = in_url;const char* out_filename = out_url;int ret = 0;int i = 0;int video_idx = -1;int frame_idx = 0;int64_t start_time = 0;// bool b_sender = 0;//in_filename = "enc_in_all.flv"; // input flv file//out_filename = "rtmp://127.0.0.1:1935/live/stream"; // output url//in_filename = "rtmp://127.0.0.1:1935/live/stream"; // input flv file//out_filename = "receive.flv"; // output url// av_register_all(); // 新版本ffmpeg不再使用// init networkavformat_network_init();if ((ret = avformat_open_input(&av_in_fmt_ctx, in_filename, 0, 0)) < 0) {fprintf(stderr, "Could not open input file.");goto end;}if ((ret = avformat_find_stream_info(av_in_fmt_ctx, 0)) < 0) {fprintf(stderr, "Failed to retrive input stream information");goto end;}for (i = 0; i < av_in_fmt_ctx->nb_streams; i++) {if (av_in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_idx = i;break;}}// 将AVFormatContext结构体中媒体文件的信息进行格式化输出av_dump_format(av_in_fmt_ctx, 0, in_filename, 0);// Output// av_out_fmt_ctx是函数执行成功之后的上下文信息结构体// "flv"是输出格式// out_filename是输出文件ret = avformat_alloc_output_context2(&av_out_fmt_ctx, NULL, NULL, out_filename); // RTMP// ret = avformat_alloc_output_context2(&av_out_fmt_ctx, NULL, "flv", out_filename); // RTMP// avformat_alloc_output_context2(&av_out_fmt_ctx, NULL, "mpegts", out_filename); // UDPif (ret < 0) {fprintf(stderr, "Could not create output context, error code:%d\n", ret);//ret = AVERROR_UNKNOWN;goto end;}// av_out_fmt_ctx->oformat;for (i = 0; i < av_in_fmt_ctx->nb_streams; i++) {AVStream* in_stream = av_in_fmt_ctx->streams[i];// 为av_out_fmt_ctx创建一个新的流,第二个参数video_codec没有被使用AVStream* out_stream = avformat_new_stream(av_out_fmt_ctx, av_in_fmt_ctx->video_codec);//AVStream* out_stream = avformat_new_stream(av_out_fmt_ctx, in_stream->codec->codec);if (!out_stream) {fprintf(stderr, "Failed to allocating output stream\n");ret = AVERROR_UNKNOWN;goto end;}// Copy the setting of AVCodecContext// ret = avcodec_copy_context(out_stream->codecpar, in_stream->codecpar);ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);if (ret < 0) {fprintf(stderr, "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codecpar->codec_tag = 0;if (av_out_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {// out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;// out_stream->event_flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}}// Dump format// 将AVFormatContext结构体中媒体文件的信息进行格式化输出av_dump_format(av_out_fmt_ctx, 0, out_filename, 1);// Open output URL if (!(av_out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {// 打开文件ret = avio_open(&av_out_fmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);if (ret < 0) {fprintf(stderr, "Could not open output URL '%s'", out_filename);goto end;}}// Write file headerret = avformat_write_header(av_out_fmt_ctx, NULL);if (ret < 0) {fprintf(stderr, "Error occured when opening output URL\n");goto end;}if (is_sender) {start_time = av_gettime();}while(1) {AVStream* in_stream;AVStream* out_stream;// get an AVPacket// 这里如果使用av_pkt指针的话,第二帧时就会出错ret = av_read_frame(av_in_fmt_ctx, &av_pkt);if (ret < 0) {break;}// write ptsif (av_pkt.pts == AV_NOPTS_VALUE && is_sender) {// write ptsAVRational time_base1 = av_in_fmt_ctx->streams[video_idx]->time_base;// Duration between 2 frames (us)int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(av_in_fmt_ctx->streams[video_idx]->r_frame_rate);// parameters// pts是播放时间戳,告诉播放器什么时候播放这一帧视频,PTS通常是按照递增顺序排列的,以保证正确的时间顺序和播放同步// dts是解码时间戳,告诉播放器什么时候解码这一帧视频av_pkt.pts = (double)(frame_idx * calc_duration) / (double)(av_q2d(time_base1) * AV_TIME_BASE);av_pkt.dts = av_pkt.pts;av_pkt.duration = (double)calc_duration / (double)(av_q2d(time_base1) * AV_TIME_BASE);}// important: delayif (av_pkt.stream_index == video_idx && is_sender) {AVRational time_base = av_in_fmt_ctx->streams[video_idx]->time_base;AVRational time_base_q = { 1, AV_TIME_BASE };int64_t pts_time = av_rescale_q(av_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);}// av_usleep(50);}in_stream = av_in_fmt_ctx->streams[av_pkt.stream_index];out_stream = av_out_fmt_ctx->streams[av_pkt.stream_index];// copy packet// convert PTS/DTSav_pkt.pts = av_rescale_q_rnd(av_pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));av_pkt.dts = av_rescale_q_rnd(av_pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));av_pkt.duration = av_rescale_q(av_pkt.duration, in_stream->time_base, out_stream->time_base);av_pkt.pos = -1;// print to screenif (av_pkt.stream_index == video_idx) {if (is_sender) {fprintf(stdout, "Send %8d video frames to output URL\n", frame_idx);}else {fprintf(stdout, "Receive %8d video frames from input URL\n", frame_idx);}frame_idx++;}ret = av_interleaved_write_frame(av_out_fmt_ctx, &av_pkt);// ret = av_write_frame(av_out_fmt_ctx, av_pkt);if (ret < 0) {fprintf(stderr, "Error muxing packet, error code:%d\n", ret);break;}// av_packet_free(&av_pkt);av_packet_unref(&av_pkt);} // write file trailerav_write_trailer(av_out_fmt_ctx);end:avformat_close_input(&av_in_fmt_ctx);// close output/*if (av_out_fmt_ctx && !(av_out_fmt->flags & AVFMT_NOFILE)) {avio_close(av_out_fmt_ctx->pb);}*//*avformat_free_context(av_out_fmt_ctx);if (ret < 0 && ret != AVERROR_EOF) {fprintf(stderr, "Error occured\n");return -1;}*/return 0;
}int streamer()
{const char* in_url = "rtmp://127.0.0.1:1935/live/stream"; // input flv fileconst char* out_url = "receive.flv"; // output urlbool is_sender = 0;streamer_internal(in_url, out_url, is_sender);return 0;
}

3.3 测试

3.3.1 推流

使用代码进行推流,可以访问http://localhost/stat地址查看推流的状态。

...
...Metadata:encoder         : Lavf61.3.100Duration: 00:00:40.00, start: 0.000000, bitrate: 18849 kb/sStream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1200, 25 fps, 25 tbr, 1k tbn
Output #0, flv, to 'rtmp://127.0.0.1:1935/live/stream':Stream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1200, q=2-31
Send        0 video frames to output URL
Send        1 video frames to output URL
Send        2 video frames to output URL
Send        3 video frames to output URL
Send        4 video frames to output URL
Send        5 video frames to output URL
Send        6 video frames to output URL
Send        7 video frames to output URL
Send        8 video frames to output URL
Send        9 video frames to output URL
Send       10 video frames to output URL
Send       11 video frames to output URL
Send       12 video frames to output URL
Send       13 video frames to output URL
Send       14 video frames to output URL
Send       15 video frames to output URL
Send       16 video frames to output URL
Send       17 video frames to output URL
Send       18 video frames to output URL
...
...

3.3.2 拉流

拉流时,需要对齐推流和拉流时的RTMP地址。如果不对齐,拉流将会一直处于idel状态。

Input #0, flv, from 'rtmp://127.0.0.1:1935/live/stream':Metadata:|RtmpSampleAccess: trueServer          : NGINX RTMP (github.com/arut/nginx-rtmp-module)displayWidth    : 1920displayHeight   : 1200fps             : 25profile         :level           :Duration: 00:00:00.00, start: 59.120000, bitrate: N/AStream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1200, 25 fps, 25 tbr, 1k tbn
Output #0, flv, to 'receive.flv':Stream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1200, q=2-31
Receive        0 video frames from input URL
Receive        1 video frames from input URL
Receive        2 video frames from input URL
Receive        3 video frames from input URL
Receive        4 video frames from input URL
Receive        5 video frames from input URL
Receive        6 video frames from input URL
Receive        7 video frames from input URL
Receive        8 video frames from input URL
Receive        9 video frames from input URL
Receive       10 video frames from input URL
Receive       11 video frames from input URL
Receive       12 video frames from input URL

另外,推流和拉流也可以使用其他已有工具,例如推流直接使用ffmpeg.exe,拉流使用ffplay.exe(或VLC Media Player)

CSDN: https://blog.csdn.net/weixin_42877471
Github: https://github.com/DoFulangChen/

这篇关于【FFmpeg】调用ffmpeg库进行RTMP推流和拉流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977057

相关文章

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述。以下是从不同角度对气象站的种类和应用范围的介绍: 一、气象站的种类 根据用途和安装环境分类: 农业气象站:专为农业生产服务,监测土壤温度、湿度等参数,为农业生产提供科学依据。交通气象站:用于公路、铁路、机场等交通场所的气象监测,提供实时气象数据以支持交通运营和调度。林业气象站:监测林区风速、湿度、温度等气象要素,为林区保护和

企业如何进行员工的网络安全意识培训?

企业网络安全意识培训的重要性         企业网络安全意识培训是提升员工网络安全素质的关键环节。随着网络技术的快速发展,企业面临的网络安全威胁日益增多,员工的网络安全意识和技能水平直接关系到企业的信息安全和业务连续性。因此,企业需要通过系统的网络安全意识培训,提高员工对网络安全的认识和防范能力,从而降低企业在面对潜在安全风险时的损失和影响。 企业网络安全意识培训的方法         企

使用JWT进行安全通信

在现代Web应用中,安全通信是至关重要的。JSON Web Token(JWT)是一种流行的安全通信方式,它允许用户和服务器之间安全地传输信息。JWT是一种紧凑的、URL安全的表示方法,用于在两方之间传输信息。本文将详细介绍JWT的工作原理,并提供代码示例帮助新人理解和实现JWT。 什么是JWT? JWT是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSO

ccp之间是不可以直接进行+,-的,要用ccpSub和ccpAdd。

1.  http://www.cnblogs.com/buaashine/archive/2012/11/12/2765691.html  上面有好多的关于数学的方面的知识,cocos2dx可能会用到的 2.学到了   根据tilemap坐标得到层上物体的id int oneTiled=flagLayer->tileGIDt(tilePos);

ScrollView 非手动调用的方法

1. /**  *  非人为的时候调用这个方法  *  *  @param scrollView <#scrollView description#>  */ - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {           } 2.判断控制器的view是否加载过 [willShowVC

使用 GoPhish 和 DigitalOcean 进行网络钓鱼

配置环境 数字海洋VPS 我创建的丢弃物被分配了一个 IP 地址68.183.113.176 让我们登录VPS并安装邮件传递代理: ssh root@68.183.113.176apt-get install postfix 后缀配置中的点变量到我们在 DigitalOcean 中分配的 IP:mynetworks nano /etc/postfix/main.cf

基于ZYNQ7000的交叉编译工具链Qt+OpenCV+ffmpeg等库支持总结

最近刚刚接触XILINX的ZYNQ板,刚接触没有十天。XILINX定位它为SOC,我也很认同,起码比TI定位MPU为SOC强很多。据说今年TI的最新产品也加入了ZYNQ板。 之前的MIPS处理器设计与实现的项目就算做告一段落,搞了将近7个月,成果显著,收获颇多,最近打算搞搞ZYNQ。 之前MIPS也有一套交叉编译工具,不过是老师提供的,自己也尝试搞了搞,太辛苦了,而且也没什么成果,因为我

自动驾驶规划中使用 OSQP 进行二次规划 代码原理详细解读

目录 1 问题描述 什么是稀疏矩阵 CSC 形式 QP Path Planning 问题 1. Cost function 1.1 The first term: 1.2 The second term: 1.3 The thrid term: 1.4 The forth term: 对 Qx''' 矩阵公式的验证 整体 Q 矩阵(就是 P 矩阵,二次项的权重矩阵)