当我们使用ffmpeg进行视频推流的时候,流媒体服务器与推流终端一直连接的时候,推流是成功的,但是如果服务器重启,就会出现推流一直失败的问题,av_interleaved_write_frame返回值-32,根据ffmpeg对返回值的解释:
-32:管道阻塞:这个一般是socket错误,推流的服务器断开了socket链接,导致发送失败。
推流程序如果没有断开重连功能的话,就只能关掉程序,重新启动来重新连接服务器解决问题,但这显然不是解决问题的办法,我们期望的办法是程序能够在推流失败后能够自动重连服务器,其实现逻辑如下:
1.启动Init成功,开始推流
2.推流失败,调用stop,清理调用的FFmpeg的环境。
3.重新启动Init,成功后开始推流
ffmpeg关于rtmp推流的代码,网上有很多,我把这些代码修改了下,封装成一个类,名字叫PushRtmp, 其有三个函数:
1.Init,初始化连接服务器
2.Push,推流
3.Stop, 停止推流,清理环境
头文件
#pragma once
#include <opencv2/opencv.hpp>
#include <string>
#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>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
#endif
class PushRtmp {
public:
static PushRtmp* getInst();
bool Init( std::string url, int width, int height, int fps );
void Push( cv::Mat& image );
void Stop();
private:
static PushRtmp* instance;
// rtmp服务地址
std::string rtmp_url_;
int width_;
int height_;
int fps_;
// 输出的数据结构
AVFrame* yuv_ = NULL;
// 像素格式转换上下文
SwsContext* vsc_ = NULL;
int vpts = 0;
// 编码器上下文
AVCodecContext* vc_ = NULL;
// rtmp flv 封装器
AVFormatContext* ic_ = NULL;
AVPacket pack_;
AVStream* vs_ = NULL;
};
cpp文件
#include "push_rtmp.hpp"
#include <chrono>
#include <exception>
#include <thread>
PushRtmp* PushRtmp::instance = nullptr;
PushRtmp* PushRtmp::getInst()
{
if ( instance == nullptr )
{
instance = new PushRtmp();
}
return instance;
}
// 初始化函数
// url--推流的地址
// width --帧的宽度
// height --帧的高度
bool PushRtmp::Init( std::string url, int width, int height, int fps )
{
rtmp_url_ = url;
width_ = width;
height_ = height;
fps_ = fps;
//注册所有的编解码器
avcodec_register_all();
//注册所有的封装器
av_register_all();
//注册所有网络协议
avformat_network_init();
try
{
int inWidth = width;
int inHeight = height;
std::cout <<"+++++++++"<< inWidth << inHeight<<std::endl;
/// 2 初始化格式转换上下文
vsc_ = sws_getCachedContext( vsc_, inWidth, inHeight, AV_PIX_FMT_BGR24, //源宽、高、像素格式
inWidth, inHeight, AV_PIX_FMT_YUV420P, //目标宽、高、像素格式
SWS_BICUBIC, // 尺寸变化使用算法
0, 0, 0 );
if ( !vsc_ )
{
printf( "sws_getCachedContext failed!" );
return false;
}
/// 3 初始化输出的数据结构
yuv_ = av_frame_alloc();
yuv_->format = AV_PIX_FMT_YUV420P;
yuv_->width = inWidth;
yuv_->height = inHeight;
yuv_->pts = 0;
//分配yuv空间
int ret = av_frame_get_buffer( yuv_, 32 );
if ( ret != 0 )
{
char buf[ 1024 ] = { 0 };
av_strerror( ret, buf, sizeof( buf ) - 1 );
printf( buf );
return false;
}
/// 4 初始化编码上下文
// a 找到编码器
AVCodec* codec = avcodec_find_encoder( AV_CODEC_ID_H264 );
if ( !codec )
{
printf( "Can`t find h264 encoder!" );
return false;
}
// b 创建编码器上下文
vc_ = avcodec_alloc_context3( codec );
if ( !vc_ )
{
printf( "avcodec_alloc_context3 failed!" );
return false;
}
// c 配置编码器参数
vc_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数
vc_->codec_id = codec->id;
vc_->thread_count = 8;
vc_->bit_rate = 50 * 1024 * 8; //压缩后每秒视频的bit位大小 50kB
vc_->width = inWidth;
vc_->height = inHeight;
vc_->time_base = { 1, fps };
vc_->framerate = { fps, 1 };
//画面组的大小,多少帧一个关键帧
vc_->gop_size = 50;
vc_->max_b_frames = 0;
vc_->pix_fmt = AV_PIX_FMT_YUV420P;
// d 打开编码器上下文
ret = avcodec_open2( vc_, 0, 0 );
if ( ret != 0 )
{
char buf[ 1024 ] = { 0 };
av_strerror( ret, buf, sizeof( buf ) - 1 );
printf( buf );
return false;
}
std::cout << "avcodec_open2 success!" << std::endl;
/// 5 输出封装器和视频流配置
// a 创建输出封装器上下文
ret = avformat_alloc_output_context2( &ic_, 0, "flv", url.c_str() );
if ( ret != 0 )
{
char buf[ 1024 ] = { 0 };
av_strerror( ret, buf, sizeof( buf ) - 1 );
printf( buf );
return false;
}
// b 添加视频流
vs_ = avformat_new_stream( ic_, NULL );
if ( !vs_ )
{
printf( "avformat_new_stream failed" );
return false;
}
vs_->codecpar->codec_tag = 0;
//从编码器复制参数
avcodec_parameters_from_context( vs_->codecpar, vc_ );
av_dump_format( ic_, 0, url.c_str(), 1 );
///打开rtmp 的网络输出IO
ret = avio_open( &ic_->pb, url.c_str(), AVIO_FLAG_WRITE );
if ( ret != 0 )
{
char buf[ 1024 ] = { 0 };
av_strerror( ret, buf, sizeof( buf ) - 1 );
printf( buf );
return false;
}
//写入封装头
ret = avformat_write_header( ic_, NULL );
if ( ret != 0 )
{
char buf[ 1024 ] = { 0 };
av_strerror( ret, buf, sizeof( buf ) - 1 );
printf( buf );
return false;
}
}
catch ( std::exception& ex )
{
// if (cam.isOpened())
// cam.release();
if ( vsc_ )
{
sws_freeContext( vsc_ );
vsc_ = NULL;
}
if ( vc_ )
{
avio_closep( &ic_->pb );
avcodec_free_context( &vc_ );
}
return false;
// std::cerr << ex.what() << endl;
}
return true;
}
void PushRtmp::Push( cv::Mat& image )
{
//输入的数据结构
uint8_t* indata[ AV_NUM_DATA_POINTERS ] = { 0 };
indata[ 0 ] = image.data;
int insize[ AV_NUM_DATA_POINTERS ] = { 0 };
//一行(宽)数据的字节数
insize[ 0 ] = image.cols * image.elemSize();
int h = sws_scale( vsc_, indata, insize, 0, image.rows, //源数据
yuv_->data, yuv_->linesize );
if ( h <= 0 )
{
return;
}
/// h264编码
yuv_->pts = vpts;
vpts++;
int ret = avcodec_send_frame( vc_, yuv_ );
if ( ret != 0 )
return;
ret = avcodec_receive_packet( vc_, &pack_ );
if ( ret != 0 || pack_.size > 0 )
{
// cout << "*" << pack.size << flush;
}
else
{
return;
}
//推流
pack_.pts = av_rescale_q( pack_.pts, vc_->time_base, vs_->time_base );
pack_.dts = av_rescale_q( pack_.dts, vc_->time_base, vs_->time_base );
pack_.duration = av_rescale_q( pack_.duration, vc_->time_base, vs_->time_base );
ret = av_interleaved_write_frame( ic_, &pack_ );
if ( ret == 0 )
{
// std::cout << "#" << flush;
}
else
{
std::cout << "push rtmp failed error code:"<< ret;
if ( ret == -32 )
{
std::cout <<"Server disconnected, start reconnect..." ;
Stop();
Init( rtmp_url_, width_, height_, fps_ );
while ( true )
{
if ( Init( rtmp_url_, width_, height_, fps_ ) )
break;
std::this_thread::sleep_for( std::chrono::milliseconds( 5000 ) );
}
}
}
}
//停止推流
void PushRtmp::Stop()
{
if ( vsc_ )
{
sws_freeContext( vsc_ );
vsc_ = NULL;
}
if ( vc_ )
{
avio_closep( &ic_->pb );
avcodec_free_context( &vc_ );
}
avformat_free_context( ic_ );
// avformat_close_input(&ifmt_ctx);
}
示范代码
#include "push_rtmp.hpp"
#include <chrono>
#include <iostream>
#include <opencv2/highgui.hpp>
#include <thread>
using namespace cv;
int main( int argc, char* argv[] )
{
VideoCapture cam;
Mat frame;
cam.open( 0 );
if ( !cam.isOpened() )
{
throw exception("cam open failed!");
}
namedWindow( "video" );
int inWidth = cam.get( CAP_PROP_FRAME_WIDTH );
int inHeight = cam.get( CAP_PROP_FRAME_HEIGHT );
int fps = cam.get( CAP_PROP_FPS );
fps = 25;
while ( true )
{
if ( PushRtmp::getInst()->Init( "rtmp://192.168.123.32/live/24", inWidth, inHeight, fps ) )
break;
std::this_thread::sleep_for( std::chrono::milliseconds( 1000 ) );
}
for ( ;; )
{
///读取rtsp视频帧,解码视频帧
if ( !cam.grab() )
{
continue;
}
/// yuv转换为rgb
if ( !cam.retrieve( frame ) )
{
continue;
}
PushRtmp::getInst()->Push( frame );
}
return 0;
}