使用srs_librtmp实现RTMP推流

发布时间:2026/9/4 13:59:46
使用srs_librtmp实现RTMP推流 1、背景由于项目有需求在一个现有的产品上增加RTMP推流的功能目前只推视频流。2、方案选择由于是在现有的产品上新增功能那么为了减少总的成本故选择只动应用软件的来实现需求。现有的产品中的第三方库比较有限连个ffmpeg都没所以要选择可以直接集成代码进来的第三方库最后选中了srs_librtmp。虽然它已经停止维护了但是主要功能没问题使用简单且可以直接集成代码。3、实现代码step1去github上面先把源码下下来。GitHub - ossrs/srs-librtmp at masterstep2把对应的代码文件集成到项目里。这里只需要src/srs目录下的srs_librtmp.h和srs_librtmp.cpp就行了如下图step3封装成工具类封装过程中使用了另一个第三方库POCO这个库只要用来实现线程不想要的话可以直接改掉。RTMPPusher.h// // Created by zhengqiuxu on 2023/8/5. // #ifndef VIS_G3_SOFTWARE_RTMPPUSHER_H #define VIS_G3_SOFTWARE_RTMPPUSHER_H #include Poco/Runnable.h #include Poco/Thread.h #include mutex #include srs_librtmp.h class RTMPPusher : public Poco::Runnable{ public: // h264 nalu struct NaluHead { unsigned type : 5; unsigned nal_ref_idc : 2; unsigned forbidden_zero_bit : 1; }; RTMPPusher(); /** * 初始化 * * param url 推流地址 * return 0成功 其他失败 */ int init(const std::string url); /** * 启动线程 */ void start(); /** * 设置一帧H264数据帧 * * param h264Data 一帧H264数据的指针 * param dataLen 一帧H264数据的指针的长度 */ void setH264Data(uint8_t *h264Data, const int dataLen); /** * 停止推流 */ void stop(); void run() override; int getCameraId() const; void setCameraId(int cameraId); const std::string getRtmpUrl() const; void setRtmpUrl(const std::string rtmpUrl); bool isInited() const; void setInited(bool inited); private: /** * 推送一帧H264数据帧(真实推送到RTMP) * * param h264Data 一帧H264数据的指针 * param dataLen 一帧H264数据的指针的长度 */ void pushH264Data(char *h264Data, const int dataLen); /* 对应的相机ID */ int cameraId -1; /* RTMP的推送地址 */ std::string rtmpUrl; /* 是不是需要停止推送 */ bool isNeedStop true; /* 是否初始化了 */ bool inited false; /* 是否可以发送了需要第一帧是sps才行 */ bool canSen false; Poco::Thread pushThread; srs_rtmp_t rtmp; uint64_t pts 0; uint64_t dts 0; const int MAX_H264CACHE_SIZE 1024*1024*4; /* 缓冲起来的h264数据 */ char *h264DataCache; /* 缓冲起来的h264数据的长度 */ int curH264DataLen 0; /* 读写H264数据的互斥锁 */ std::mutex h264DataLock; }; #endif //VIS_G3_SOFTWARE_RTMPPUSHER_HRTMPPusher.cpp// // Created by zhengqiuxu on 2023/8/5. // #include RTMPPusher.h #include cstring #include unistd.h RTMPPusher::RTMPPusher() { } /** * 初始化 * * param url 推流地址 * return 0成功 其他失败 */ int RTMPPusher::init(const std::string url) { int ret -1; rtmpUrl url; inited true; h264DataCache (char *)malloc(MAX_H264CACHE_SIZE); ret 0; return ret; } /** * 推送一帧H264数据帧(真实推送到RTMP) * * param h264Data 一帧H264数据的指针 * param dataLen 一帧H264数据的指针的长度 */ void RTMPPusher::pushH264Data(char *h264Data, const int dataLen) { try { printf(RTMPPusher::pushH264Data size%d \n,dataLen); /* 推流到RTMP */ pts 40; /* 如果是B帧的话PTS应该等于离它最近的P帧或者I帧的的PTS 一般都是选择填上一帧数据的PTS */ dts pts; int ret srs_h264_write_raw_frames(rtmp, h264Data, dataLen, dts, pts); if (ret ! 0) { if (srs_h264_is_dvbsp_error(ret)) { srs_human_trace(ignore drop video error, code%d, ret); } else if (srs_h264_is_duplicated_sps_error(ret)) { srs_human_trace(ignore duplicated sps, code%d, ret); } else if (srs_h264_is_duplicated_pps_error(ret)) { srs_human_trace(ignore duplicated pps, code%d, ret); } else { srs_human_trace(send h264 raw data failed. ret%d, ret); } } } catch (...) { printf(push H264Data to %s failed! %s \n,rtmpUrl.c_str(),strerror(errno)); } } /** * 停止推流 */ void RTMPPusher::stop() { isNeedStop true; srs_human_trace(h264 raw data completed); srs_rtmp_destroy(rtmp); free(h264DataCache); inited false; } /** * 启动线程 */ void RTMPPusher::start() { pushThread.start(*this); } void RTMPPusher::run() { std::string pthreadName RTMPPusher_; pthreadName.append(rtmpUrl); pthread_setname_np(pthread_self(), pthreadName.c_str()); isNeedStop false; /* 创建一个RTMP客户端对象 */ rtmp srs_rtmp_create(rtmpUrl.c_str()); /* 开始跟RTMP服务器握手 */ if (srs_rtmp_handshake(rtmp) ! 0) { srs_human_trace(simple handshake failed.); }else{ srs_human_trace(simple handshake success); /* 连接RTMP流 */ if (srs_rtmp_connect_app(rtmp) ! 0) { srs_human_trace(connect vhost/app failed.); }else{ srs_human_trace(connect vhost/app success); /* 看看RTMP流是否可以推流 */ if (srs_rtmp_publish_stream(rtmp) ! 0) { srs_human_trace(publish stream failed.); }else{ srs_human_trace(publish stream success); canSen false; /* 循环从内存里读出H264并推到RTMP服务器 */ while (!isNeedStop){ h264DataLock.lock(); if(curH264DataLen 0){ if(canSen){ pushH264Data(h264DataCache,curH264DataLen); curH264DataLen 0; }else{ /* 拿出NALU头用来后面判断NALU类型 */ struct NaluHead curNaluHead *(struct NaluHead *)(h264DataCache4); /* 从SPSPPS开始推有些服务器做的不好不是从SPSPPS开始推的话会报错 */ if(curNaluHead.type 7){ canSen true; pushH264Data(h264DataCache,curH264DataLen); curH264DataLen 0; } } } h264DataLock.unlock(); usleep(10000); } } } } } /** * 设置一帧H264数据帧 * * param h264Data 一帧H264数据的指针 * param dataLen 一帧H264数据的指针的长度 */ void RTMPPusher::setH264Data(uint8_t *h264Data, const int dataLen) { if(dataLen 0){ h264DataLock.lock(); memcpy(h264DataCache,h264Data,dataLen); curH264DataLen dataLen; h264DataLock.unlock(); } } int RTMPPusher::getCameraId() const { return cameraId; } void RTMPPusher::setCameraId(int cameraId) { RTMPPusher::cameraId cameraId; } const std::string RTMPPusher::getRtmpUrl() const { return rtmpUrl; } void RTMPPusher::setRtmpUrl(const std::string rtmpUrl) { RTMPPusher::rtmpUrl rtmpUrl; } bool RTMPPusher::isInited() const { return inited; } void RTMPPusher::setInited(bool inited) { RTMPPusher::inited inited; }到这里我们就已经实现完成且封装成一个方便调用的工具类了。调用的时候只要需要先调用init()函数初始化再调用start()函数让发送线程跑起来一有H264数据就通过setH264Data()函数设置给工具类的就行了。这样工具类就会循环读取设置过来的H264数据并推送到RTMP服务器了。4、其他1、由于这个库的pts和dts是需要自己赋值的所以有时候推送上去的数据要么是播放速度变快要么是卡顿都很有可能是pts和dts的问题。由于我这里是固定25帧的所有我直接pts固定每帧都比上一帧40ms。pts和dts还有很多研究空间实际使用的时候具体情况具体分析。