前言
使用ffmpeg推流很简单,使用ffmpeg命令推流更简单。本篇以本文标题《ffmpeg推流flv到rtmp》为中心。只推流输入flv格式的媒体文件,只推流到rtmp。
原因很简单,简化一切复杂的流程,稍后再说原因。我们通过多篇慢慢的提升代码复杂度,例如:把mp4推流到rtmp需要使用ffmpeg代码做什么和flv推流到rtmp有什么不同,等问题都会讲解。
编写代码
为了方便我引入的头文件有点多:
#ifndef __FFMPEG_COMMON_H
#define __FFMPEG_COMMON_H
extern "C" {
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include "libavdevice/avdevice.h"
#include "libswresample/swresample.h"
#include <libavutil/dict.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
}
#endif
技巧:
你可以专门提出来一个.h头文件,引入一些公共的头文件,这样再写一个新.cpp文件的时候就不用每次都引入这么多了。
这样其他地方只很简单的引入即可:
整体函数模型:
/**
* 只能推流RTMP数据
* @param input_filename 输入文件
* @param output_filename 输出流地址
* @return
*/
int ff_push_flv_to_rtmp_stream(char *input_filename, char *output_filename){
...
}
创建输入和输出的ffmpeg上下文:
int ret = 0;
// in stream
AVFormatContext *ctx = NULL;
// out stream
AVFormatContext *octx = NULL;
打开输入文件,初始化输入上下文
// 1.打开输入文件,返回 AVFormatContext
ret = avformat_open_input(&ctx, input_filename, NULL, NULL);
if(ret < 0){
AV_PRT("open inputfile=%s error\n", input_filename);
return -1;
}
查找分析输入媒体流
// 2.查找流
ret = avformat_find_stream_info(ctx, NULL);
if(ret < 0) {
AV_PRT("avformat_find_stream_info=%s error\n", input_filename);
return -1;
}
创建输出流上下文
//创建输出流上下文
ret = avformat_alloc_output_context2(&octx, 0, "flv",output_filename);
if (!octx) {
AV_PRT("Couldn't avformat_alloc_output_context2 %d.\n", ret);
return 0;
}
创建输出流
因为输入的是flv,所以直接把输入的参数都拷贝过来了。
需要注意的是:如果是mp4什么的, 就需要其他的解码,编码处理了。
// 创建输出流
for (int i = 0; i < ctx->nb_streams; ++i) {
AVStream *s = ctx->streams[i];
const AVCodec * codec= avcodec_find_decoder(s->codecpar->codec_id);
AVStream *out = avformat_new_stream(octx, codec);
//拷贝相关编码器信息
avcodec_parameters_copy(out->codecpar, s->codecpar);
// out->codecpar->codec_tag = 0;
}
dump信息:
这里也需要注意,为什么两个av_dump_format之间需要增加休眠的sleep代码?这个可以自己做个实验把sleep去掉试试。欢迎留言实验结果。
AV_PRT("输入信息: \n");
av_dump_format(ctx, 0 , input_filename, 0);
av_usleep(1 * 1000 * 1000);
AV_PRT("输出信息: \n");
av_dump_format(octx, 0 , output_filename, 1);
打开输出流
//打开输出io流
ret = avio_open2(&octx->pb, output_filename, AVIO_FLAG_WRITE,&octx->interrupt_callback,NULL);
if (!octx->pb) {
AV_PRT("Couldn't avio_open outUrl: %s ,ret = %d.\n",output_filename, ret);
avformat_free_context(octx);
octx = NULL;
return 0;
}
av_opt_set(octx->priv_data, "pes_payload_size", "0", 0);
写入头数据
//写入头信息
ret = avformat_write_header(octx, 0);
if (ret < 0) {
AV_PRT("Couldn't avformat_write_header,ret = %d.\n", ret);
if (octx && !(octx->oformat->flags & AVFMT_NOFILE)) {
avio_close(octx->pb);
octx->pb = NULL;
}
avformat_free_context(octx);
octx = NULL;
return 0;
}
创建AVPacket,读输入数据时用到,因为ffmpeg会把读取到的数据,封装成一个一个的AVPacket
AVPacket *avPacket = NULL;
avPacket = av_packet_alloc();
获取当前时间
int64_t start_time = av_gettime();
循环读取数据
while (av_read_frame(ctx, avPacket) >= 0)
{
//计算转换pts dts
AVRational i_tb = ctx->streams[avPacket->stream_index]->time_base;
AVRational o_tb = octx->streams[avPacket->stream_index]->time_base;
avPacket->pts = av_rescale_q_rnd(avPacket->pts, i_tb, o_tb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
avPacket->dts = av_rescale_q_rnd(avPacket->pts, i_tb, o_tb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
avPacket->duration = av_rescale_q_rnd(avPacket->duration, i_tb, o_tb, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
avPacket->pos = -1;
//视频帧推送速度
if (ctx->streams[avPacket->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
AVRational tb = ctx->streams[avPacket->stream_index]->time_base;
//已经过去的时间
int64_t now = av_gettime() - start_time;
int64_t dts = avPacket->dts * (1000 * 1000 * r2d(tb));
if (dts > now)
av_usleep(dts - now);
}
//写入流数据
ret = av_interleaved_write_frame(octx, avPacket);
if (ret<0)
{
break;
}
//unref AVPacket
av_packet_unref(avPacket);
if(ret < 0)
break;
}
释放资源
//exit
av_write_trailer(octx);
if (octx && !(octx->oformat->flags & AVFMT_NOFILE)) {
avio_close(octx->pb);
octx->pb = NULL;
}
avformat_free_context(octx);
if(!ctx){
avformat_close_input(&ctx);
}
if(!avPacket){
av_packet_free(&avPacket);
}
main函数:
int main (int argc, char **argv)
{
// char *default_filename = "/home/zhenghui/视频/1080P.flv";
char *default_filename = "/home/zhenghui/视频/cctv1.flv";
char *output_filename = "rtmp://192.168.1.106/live/av?secret=3573a0c4b84e4d288524da305ab40b00";
char *input_filename = NULL;
int ret = 0;
if(argc == 2){
input_filename = argv[1];
}else{
input_filename = default_filename;
}
AV_PRT("input:%s \n", input_filename);
AV_PRT("output:%s \n", output_filename);
ff_push_flv_to_rtmp_stream(input_filename, output_filename);
return 0;
}
测试
运行程序:
ffplay播放:
我们输入一个mp4测试下:
[flv @ 0x5fe24c7bcc80] Tag avc1 incompatible with output codec id '27' ([7][0][0][0])
因为我们只实现了最最最简单的把flv推流到了rtmp,没有加入编码和解码的功能部分,所以不行。
下一篇,我们就来研究下,如何把mp4和flv等格式兼容起来。
总结
因为rtmp只能接收flv格式的视频容器,所以需要把数据封装成flv之后,rtmp才可以识别到。
粉丝福利,博主耗时2个月整理了一份详细的音视频开发学习路线,涵盖了音视频开发FFmmpeg、流媒体客户端、流媒体服务器、WebRTC、Android NDK开发、IOS音视频开发等等全栈技术栈,并提供了配套的免费领取C++音视频学习资料包、技术视频/代码,内容包括(FFmpeg ,WebRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs流媒体服务器,音视频通话等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓