ffmpeg推流时与服务器断开后的自动重连功能的实现

当我们使用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;
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值