首页 网站首页 商业信息 推流 查看内容

FFmpeg流媒体处理-收流与推流

杭州共生网络 2022-12-18 19:14 10257人围观 推流

1. 简介

流媒体是利用了流式传输的多媒体利用技术。以下是维基百科关于流媒体概念的界说:

流媒体 (streaming media) 是指将连续串的媒体数据紧缩后,经过收集分段发送数据,在收集上立即传输影音以供观赏的一种技术与进程,此技术使得数据包得以像流水一样发送;假如不利用此技术,就必须在利用前下载全部媒体文件。

1.1 FFmpeg 影音处置的条理

FFmpeg 中对影音数据的处置,可以分别为协议层、容器层、编码层与原始数据层四个条理:

协议层:供给收集协议收发功用,可以接收或推送含封装格式的媒体流。协议层由 libavformat 库及第三方库(如 librtmp)供给支持。

容器层:处置各类封装格式。容器层由 libavformat 库供给支持。

编码层:处置音视频编码及解码。编码层由各类丰富的编解码器(libavcodec 库及第三方编解码库(如 libx264))供给支持。

原始数据层:处置未编码的原始音视频帧。原始数据层由各类丰富的音视频滤镜(libavfilter 库)供给支持。

本文说起的收流与推流的功用,属于协议层的处置。

FFmpeg 中 libavformat 库供给了丰富的协议处置及封装格式处置功用,在翻开输入/输出时,FFmpeg 会按照 输入 URL / 输出 URL 探测输入/输出格式,挑选合适的协议和封装格式。例如,假如输出 URL 是 "rtmp://192.168.0.104/live",那末 FFmpeg 翻开输出时,会肯定利用 rtmp 协议,封装格式为 flv。

FFmpeg 中翻开输入/输出的内部处置细节用户不必关注,是以本文流处置的例程和前面转封装的例程很是类似,分歧之处首要在于输入/输出 URL 形式分歧,若 URL 照顾 "rtmp://"、"rpt://"、"udp://"等前缀,则暗示触及流处置;否则,处置的是当地文件。

1.2 流媒系统统中的脚色

流媒系统统是一个比力复杂的系统,简单来说触及三个脚色:流媒体办事器、推流客户端和收流客户端。推流客户端是内容生产者,收流客户端是内容消耗者。表示图以下:



1.3 收流与推流

假如输入是收集流,输出是当地文件,则实现的是收流功用,将收集流存储为当地文件,以下:



假如输入是当地文件,输出是收集流,则实现的是推流功用,将当地文件推送到收集,以下:



假如输入是收集流,输出也是收集流,则实现的是转流功用,将一个流媒体办事器上的流推送到另一个流媒体办事器,以下:



相关视频保举:

LinuxC++音视频开辟视频:免费】FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开辟

【文章福利】:小编整理了一些相关的音视频开辟进修材料(材料包括C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等),qun994289133免费分享,有需要的可以加群支付哦!~点击裙994289133加入支付材料




企鹅群994289133支付材料



企鹅群994289133支付材料

2. 源码

源码和转封装例程大部分不异,可以以为是转封装例程的增强版:

#include <stdbool.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

// ffmpeg -re -i tnhaoxc.flv -c copy -f flv rtmp://192.168.0.104/live
// ffmpeg -i rtmp://192.168.0.104/live -c copy tnlinyrx.flv
// ./streamer tnhaoxc.flv rtmp://192.168.0.104/live
// ./streamer rtmp://192.168.0.104/live tnhaoxc.flv
int main(int argc, char **argv)
{
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *stream_mAPPing = NULL;
int stream_mapping_size = 0;

if (argc < 3) {
printf("usage: %s input output\n"
"API example program to remux a media file with libavformat and libavcodec.\n"
"The output format is guessed according to the file extension.\n"
"\n", argv[0]);
return 1;
}

in_filename = argv[1];
out_filename = argv[2];

// 1. 翻开输入
// 1.1 读取文件头,获得封装格式相关信息
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf("Could not open input file '%s'", in_filename);
goto end;
}

// 1.2 解码一段数据,获得流相关信息
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf("Failed to retrieve input stream information");
goto end;
}

av_dump_format(ifmt_ctx, 0, in_filename, 0);

// 2. 翻开输出
// 2.1 分派输出ctx
bool push_stream = false;
char *ofmt_name = NULL;
if (strstr(out_filename, "rtmp://") != NULL) {
push_stream = true;
ofmt_name = "flv";
}
else if (strstr(out_filename, "udp://") != NULL) {
push_stream = true;
ofmt_name = "mpegts";
}
else {
push_stream = false;
ofmt_name = NULL;
}
avformat_alloc_output_context2(&ofmt_ctx, NULL, ofmt_name, out_filename);
if (!ofmt_ctx) {
printf("Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}

stream_mapping_size = ifmt_ctx->nb_streams;
stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}

ofmt = ofmt_ctx->oformat;

AVRational frame_rate;
double duration;

for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
AVCodecParameters *in_codecpar = in_stream->codecpar;

if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}

if (push_stream && (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) {
frame_rate = av_guess_frame_rate(ifmt_ctx, in_stream, NULL);
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
}

stream_mapping[i] = stream_index++;

// 2.2 将一个新流(out_stream)增加到输出文件(ofmt_ctx)
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
printf("Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}

// 2.3 将当前输入流中的参数拷贝到输出流中
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
printf("Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);

if (!(ofmt->flags & AVFMT_NOFILE)) { // TODO: 研讨AVFMT_NOFILE标志
// 2.4 建立并初始化一个AVIOContext,用以拜候URL(out_filename)指定的资本
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
printf("Could not open output file '%s'", out_filename);
goto end;
}
}

// 3. 数据处置
// 3.1 写输出文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
printf("Error occurred when opening output file\n");
goto end;
}

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

// 3.2 从输出流读取一个packet
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0) {
break;
}

in_stream = ifmt_ctx->streams[pkt.stream_index];
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping[pkt.stream_index] < 0) {
av_packet_unref(&pkt);
continue;
}

int codec_type = in_stream->codecpar->codec_type;
if (push_stream && (codec_type == AVMEDIA_TYPE_VIDEO)) {
av_usleep((int64_t)(duration*AV_TIME_BASE));
}

pkt.stream_index = stream_mapping[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];

/* copy packet */
// 3.3 更新packet中的pts和dts
// 关于AVStream.time_base(容器中的time_base)的说明:
// 输入:输入流中含有time_base,在avformat_find_stream_info()中可取到每个流中的time_base
// 输出:avformat_write_header()会按照输出的封装格式肯定每个流的time_base并写入文件中
// AVPacket.pts和AVPacket.dts的单元是AVStream.time_base,分歧的封装格式AVStream.time_base分歧
// 所以输出文件中,每个packet需要按照输出封装格式重新计较pts和dts
av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;

// 3.4 将packet写入输出
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
printf("Error muxing packet\n");
break;
}
av_packet_unref(&pkt);
}

// 3.5 写输出文件尾
av_write_trailer(ofmt_ctx);

end:
avformat_close_input(&ifmt_ctx);

/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
}
avformat_free_context(ofmt_ctx);

av_freep(&stream_mapping);

if (ret < 0 && ret != AVERROR_EOF) {
printf("Error occurred: %s\n", av_err2str(ret));
return 1;
}

return 0;
}

2.1 收流

收流的代码与翻开普通文件的代码没有区分,翻开输入时,FFmpeg 能识别流协议及封装格式,按照响应的协议层代码来接收流,收到流数据去掉协议层后获得的数据和普通文件内容是一样的,后续的处置流程也就一样了。

2.2 推流

推流有两个需要留意的地方。

一是需要按照输出流协议显式指定输出 URL 的封装格式:

bool push_stream = false;
char *ofmt_name = NULL;
if (strstr(out_filename, "rtmp://") != NULL) {
push_stream = true;
ofmt_name = "flv";
}
else if (strstr(out_filename, "udp://") != NULL) {
push_stream = true;
ofmt_name = "mpegts";
}
else {
push_stream = false;
ofmt_name = NULL;
}
avformat_alloc_output_context2(&ofmt_ctx, NULL, ofmt_name, out_filename);

这里只写了两种。rtmp 推流必须推送 flv 封装格式,udp 推流必须推送 mpegts 封装格式,其他情况就看成是输出普通文件。这里利用 push_stream 变量来标志能否利用推流功用,这个标志前面会用到。

二是要留意推流的速度,不能一股脑将收到的数据全推进来,这样流媒体办事器承受不住。可以按视频播放速度(帧率)来推流。是以每推送一个视频帧,要延时一个视频帧的时长。音频流的数据量很小,可以不必关心此题目。

在翻开输入 URL 时,获得视频帧的延续时长:

AVRational frame_rate;
double duration;
if (push_stream && (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) {
frame_rate = av_guess_frame_rate(ifmt_ctx, in_stream, NULL);
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
}

在 av_read_frame() 以后,av_interleaved_write_frame() 之前增加延时,延不时长就是一个视频帧的延续时长:

int codec_type = in_stream->codecpar->codec_type;
if (push_stream && (codec_type == AVMEDIA_TYPE_VIDEO)) {
av_usleep((int64_t)(duration*AV_TIME_BASE));
}

3. 考证

3.1 编译第三方库 librtmp

FFmpeg 默许并不支持 rtmp 协议。需要先编译安装第三方库 librtmp,然后开启 --enable-librtmp 选项重新编译安装 FFmpeg。

3.2 搭建流媒体办事器

测试收流与推流功用需要搭建流媒体办事器。我们选用 nginx-rtmp 作为流媒体办事器用于测试。nginx-rtmp 办事器运转于虚拟机上,推流客户端与收流客户端和 nginx-rtmp 办事器处于同一局域网即可。我的虚拟机是 OPENSUSE LEAP 42.3,IP 是 192.168.0.104(就是 nginx-rtmp 办事器的地址)。

为避免搭建办事器的烦琐进程,我们间接利用 docker 拉取一个 nginx-rtmp 镜像。步调以下:

[1] 安装与设置docker办事

安装 docker:

sudo zypper install docker

将当前用户增加到 docker 组(若 docker 组不存在则先建立),从而可免得 sudo 利用 docker 号令:

sudo gpasswd -a ${USER} docker

[2] 设置镜像加速

docker 镜像源位于美国,摘取镜像很是缓慢。可设置国内镜像源,加速镜像拉取速度。

点窜 /etc/docker/daemon.json 文件并增加上 registry-mirrors 键值:

{
"registry-mirrors":
[
"https://registry.docker-cn.com",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.ccs.tencentyun.com"
]
}

上述设置文件增加了四个国内镜像源:docker 中国、清华、163 和腾讯。

点窜设置文件后重启 docker 办事:

systemctl restart docker

[3] 拉取 nginx-rtmp 镜像

docker pull tiangolo/nginx-rtmp

[4] 翻开容器

docker run -d -p 1935:1935 --name nginx-rtmp tiangolo/nginx-rtmp

[5] 防火墙增加破例端口

假如没法推流,应在防火墙中将 1935 端口增加破例

openSUSE 系统:点窜 /etc/sysconfig/SuSEfirewall2 文件,在 FW_SERVICES_EXT_TCP 项中增加 1935 端口,以下:

FW_SERVICES_EXT_TCP="ssh 1935"

然后重启防火墙:

systemctl restart SuSEfirewall2

CentOS 8 系统:运转以下号令将 1935 端口增加到防火墙破例端口中:

firewall-cmd --permanent --zone=public --add-port=1935/tcp

[6] 考证办事器

测试文件下载(右键另存为):tnhaoxc.flv

ffmpeg 推流测试:

ffmpeg -re -i tnhaoxc.flv -c copy -f flv rtmp://192.168.0.104/live

"-re":按视频帧率的速度读取输入
"-c copy":输出流利用和输入流不异的编解码器
"-f flv":指定输出流封装格式为flv

ffplay 收流播放测试:

ffplay rtmp://192.168.0.104/live

ffplay 播放一般,说明 nginx-rtmp 流媒体办事器搭建成功,可一般利用。

3.3 编译

在 shell 中运转以下号令下载例程源码:

svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_stream

在源码目录履行 ./compile.sh 号令,天生 streamer 可履行文件。

3.4 考证

测试文件下载(右键另存为):shifu.mkv,将测试文件保存在和源码同一目录。

推流测试:

./streamer shifu.mkv rtmp://192.168.0.104/live

利用 vlc 播放器翻开收集串流,输入流地址 "rtmp://192.168.0.104/live",播放一般。上述测试号令等价于:

ffmpeg -re -i shifu.mkv -c copy -f flv rtmp://192.168.0.104/live

收流测试:先依照上一步号令启动推流,然后运转以下号令收流

./streamer rtmp://192.168.0.104/live shifu.ts

以上测试号令等价于:

ffmpeg -i rtmp://192.168.0.104/live -c copy shifu.ts

接收竣事后检查一下天生的当地文件 shifu.ts 能否一般播放。

4. 遗留题目

推流的题目:非论是用 ffmpeg 号令,还是用本测试法式,推流竣事时会打印以下信息

[flv @ 0x22ab9c0] 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
Larger timestamp than 24-bit: 0xffffffc2
[flv @ 0x22ab9c0] Failed to update header with correct duration.
[flv @ 0x22ab9c0] Failed to update header with correct filesize.

收流的题目:推流竣事后,收流超时未收以数据,会打印以下信息后法式退出运转

RTMP_ReadPacket, failed to read RTMP packet header

高端人脉微信群

高端人脉微信群

人脉=钱脉,我们相信天下没有聚不拢的人脉,扫码进群找到你所需的人脉,对接你所需的资源。

商业合作微信

商业合作微信

本站创始人微信,13年互联网营销经验,擅长引流裂变、商业模式、私域流量,高端人脉资源丰富。

精彩点评

相关推荐

详解RTSP推流实战(1)

详解RTSP推流实战(1)

0.引言本篇文章主要讲解RTSP推流实战,整体推流流程与RTMP推流流程类似。如果对于RTSP

视频号推流直播是什么?怎么玩?(附最全操作流程!)

视频号推流直播是什么?怎么玩?(附最全操作流程!)

随着视频号直播的不断完善,美颜、抽奖、推流直播等功能纷纷上线,【附近的直播和人】

抖音直播间推流机制是什么?怎么做能让直播间一直有流量 ... ...

抖音直播间推流机制是什么?怎么做能让直播间一直有流量 ... ...

很多人直播间没有人,就是因为搞不懂抖音直播间推流机制。不明白推流机制就找不到正确

python利用ffmpeg进行rtmp推流直播

python利用ffmpeg进行rtmp推流直播

思路:opencv读取视频 — 将视频分割为帧 — 将每一帧进行需求加工后 — 将此帧写入pi

OBS推流如何实现多平台推流

OBS推流如何实现多平台推流

500强直播策划,策划过多起直播。经常会遇到需要全平台推流的情况,但是OBS的原生软件

这篇微头条是3个月前写的,最近几天被再次推流,它有何特别之处

这篇微头条是3个月前写的,最近几天被再次推流,它有何特别之处

#头条文章养成计划#根据我400多天创作的亲身体验,文章推荐的时间比较长,而微头条的

头条的推流机制,原来是这样的

头条的推流机制,原来是这样的

#头条创作挑战赛#头条推流的机制是怎样的?在头条写作了31天,小编一直被这个问题困扰

在电脑上使用OBS在各大平台 直播 推流的方法

在电脑上使用OBS在各大平台 直播 推流的方法

Open Broadcaster Software(简称 OBS)是一款好用的第三方开源程序直播流媒体内容制

视频和视频帧:ffmpeg的RTMP推流

视频和视频帧:ffmpeg的RTMP推流

写在前面本文将介绍以下内容:什么是推流?将介绍推流常见的协议RTMP,HLS等。怎么用f

西瓜媒体直播教程:30秒学会推流直播

西瓜媒体直播教程:30秒学会推流直播

头条直播换到西瓜后台啦! 推流还是一样方便快捷! 功能更多更

视频号如何推流直播?推流直播详细教程

视频号如何推流直播?推流直播详细教程

​最近有很多同学问我视频号推流直播怎么做,这种三两句话回答不清楚,今天特意写了详

推流与拉流简概

推流与拉流简概

推流:将直播内容推送至服务器的过程拉流:为服务器已有直播内容,用指定地址进行拉取

一篇文章突然停止推流,我只做了2件事,结果收益200元

一篇文章突然停止推流,我只做了2件事,结果收益200元

我是依伊,一个全职写作的创作人,点击右上角关注,为你分享【新媒体写作变现】和【个

OBS直播多平台同时推流解决方法,简单粗暴

OBS直播多平台同时推流解决方法,简单粗暴

用OBS作为电脑直播推流,是很多人使用的一款开源软件。我们使用OBS时,它默认的是只能

3分钟带你了解抖音推流机制!

3分钟带你了解抖音推流机制!

你是否也遇到过辛辛苦苦拍了视频,但结果却还不如跳舞的小姐姐?为什么别人随便拍的视

千万级直播运营必须掌握的OBS推流直播技能

千万级直播运营必须掌握的OBS推流直播技能

专业直播操盘手必须掌握的OBS推流直播技能私域直播母東東,业绩增长分分钟Hello,各位

如何获取抖音直播的推流地址?

如何获取抖音直播的推流地址?

想在直播间中直播游戏,那就需要用到2个东西,分别是推流码和obs软件。那抖音直播推流

视频号问题系列(一):视频号如何开通推流直播

视频号问题系列(一):视频号如何开通推流直播

缘起: 最近工作比较忙、文章也没怎么更新,不过最近 一段时间过来问我视频号问题的朋

玩转直播,直播推流软件你选对了吗?

玩转直播,直播推流软件你选对了吗?

随着近几年互联网技术高速发展,人们对社交形式多样化的需求不断增加,从一开始的文字

直播推流和拉流方法-VLC 播放器专题

直播推流和拉流方法-VLC 播放器专题

直播目前处于一个风口期,很多直播开始跨平台跨地域直播,如何实现异地直播,跨平台直

商业洽谈 文章投递 寻求报道
电话咨询: 15924191378
关注微信