[直播推流] rtmpdump 库学习
常用 api 学习
参考文档
直播推流需要用到
#初始化:
RTMP_Alloc() :用于创建一个RTMP会话的句柄。
RTMP_Init():初始化句柄。
RTMP_SetupURL():设置会话的参数。
RTMP_EnableWrite(): 启用发布模式
RTMP_SetBufferMS():设置缓存区时间
#连接服务器
RTMP_Connect():建立RTMP链接中的网络连接(NetConnection)。
RTMP_ConnectStream():建立RTMP链接中的网络流(NetStream)。
#发送数据
RTMP_Write():发送数据
#销毁
RTMP_Close():关闭连接
RTMP_Free():用于清理会话。
#include "RtmpStream.h"
// 平台特定头文件
// 如果不是Windows平台
#ifndef _WIN32
// 定义毫秒级睡眠函数
#define SLEEP_MS(ms) usleep((ms)*1000)
// 如果是Windows平台
#else
// 定义毫秒级睡眠函数
#define SLEEP_MS(ms) Sleep(ms)
#endif
// 定义连接超时时间,单位为秒
const int CONNECT_TIMEOUT = 5; // seconds
// 定义缓冲区持续时间,单位为秒(1小时)
const int BUFFER_DURATION = 3600; // seconds (1 hour)
// 定义目标帧率,单位为帧每秒
const int TARGET_FRAME_RATE = 30; // fps
// 计算每帧之间的延迟时间,单位为毫秒
const int FRAME_DELAY_MS = 1000 / TARGET_FRAME_RATE;// 构造函数,初始化RTMP URL
RtmpStream::RtmpStream(const char* rtmpUrl): m_rtmpUrl(rtmpUrl), m_rtmp(nullptr)
{
}// 析构函数,清理资源
RtmpStream::~RtmpStream()
{cleanup();
}// 初始化RTMP连接
bool RtmpStream::initialize()
{// 分配RTMP对象内存m_rtmp = RTMP_Alloc();if (!m_rtmp) {// 记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP_Alloc failed");return false;}// 初始化RTMP对象RTMP_Init(m_rtmp);// 设置连接超时时间m_rtmp->Link.timeout = CONNECT_TIMEOUT;// 设置RTMP URLif (!RTMP_SetupURL(m_rtmp, const_cast<char*>(m_rtmpUrl))) {// 记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP_SetupURL failed");return false;}// 启用发布模式RTMP_EnableWrite(m_rtmp);// 设置缓冲区持续时间RTMP_SetBufferMS(m_rtmp, BUFFER_DURATION * 1000);return true;
}// 连接到RTMP服务器
bool RtmpStream::connect()
{// 建立RTMP连接if (!RTMP_Connect(m_rtmp, nullptr)) {// 记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP_Connect failed");return false;}// 连接到RTMP流if (!RTMP_ConnectStream(m_rtmp, 0)) {// 记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP_ConnectStream failed");return false;}return true;
}// 发布FLV文件到RTMP服务器
bool RtmpStream::publish(char* packet, uint32_t packetSize, uint32_t timestamp)
{// 记录开始发布日志RTMP_LogPrintf("Starting to publish stream...");// 记录开始时间uint32_t startTime = RTMP_GetTime();// 记录上一帧时间戳uint32_t lastTimestamp = 0;// 记录已发送帧数uint32_t framesSent = 0;// 检查RTMP连接是否仍然有效if (!RTMP_IsConnected(m_rtmp)) {// 若连接丢失,记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP connection lost");return false;}// 尝试将数据包写入RTMP连接if (!RTMP_Write(m_rtmp, (char*)packet, packetSize)) {// 若写入失败,记录错误日志RTMP_Log(RTMP_LOGERROR, "RTMP_Write failed");// 释放已分配的内存return false;}// 增加已发送的帧数计数framesSent++;// 帧率控制逻辑// 计算从开始发布到现在经过的时间uint32_t elapsed = RTMP_GetTime() - startTime;// 如果当前标签的时间戳大于已过去的时间,进行适当延迟if (timestamp > elapsed) {// 调用平台特定的睡眠函数进行延迟SLEEP_MS(FRAME_DELAY_MS);}// 定期记录发布进度if (framesSent % 100 == 0) {// 每发送100帧,记录已发送帧数和当前时间戳RTMP_LogPrintf("Sent %u frames, timestamp: %ums",framesSent, timestamp);}RTMP_LogPrintf("Publishing completed. Total frames sent: %u", framesSent);return true;
}void RtmpStream::cleanup()
{if (m_rtmp) {if (RTMP_IsConnected(m_rtmp)) {RTMP_Close(m_rtmp);}RTMP_Free(m_rtmp);m_rtmp = nullptr;}
}
rtmp url 格式学习
url 格式
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
格式解析
参考文档
rtmp://localhost/vod/mp4:sample1_1500kbps.f4v
“: //”之前的是使用的协议类型,可以是rtmp,rtmpt,rtmps等
之后是服务器地址;再之后是端口号(可以没有,默认1935);在之后是application的名字,在这里是“vod”;最后是流媒体文件路径。
RTMP_Write 需要的数据
flv 格式
flv header + flv body
flv body = PreviousTagSize + n* Tag
Tag = Type+DataSize+Timestamp+TimestampExtended+StreamID+Data+PreviousTagSize
只需要将 Tag 传入RTMP_Write,他就会解析了,底层使用的是 RTMP_Publish。
解析参考代码:
- 一开始就跳过 13个字节,分别是FLV文件头(9字节)和第一个PreviousTagSize(4字节)
- 之后每次先读取 tag 的 header,获取后面数据的长度,
然后读取数据。
bool FlvParse::open()
{// 打开FLV文件m_file = fopen(m_flvPath, "rb");if (!m_file) {// 记录错误日志cerr << "Failed to open FLV file: " << m_flvPath << endl;return false;}// 跳过FLV文件头(9字节)和第一个PreviousTagSize(4字节)if (fseek(m_file, 13, SEEK_SET) != 0) {// 记录错误日志cerr << "Failed to seek FLV file" << endl;return false;}return true;
}bool FlvParse::readTag(char** packet, uint32_t* packetSize, uint32_t* timestamp)
{bool bRes = false;do{// 读取标签头(11字节)uint8_t header[11];if (fread(header, 1, 11, m_file) != 11) break;// 解析标签信息// 标签类型uint8_t tagType = header[0];// 数据大小uint32_t dataSize = (header[1] << 16) | (header[2] << 8) | header[3];// 时间戳m_timestamp = (header[4] << 16) | (header[5] << 8) | header[6];// 扩展时间戳(如果需要)m_timestamp |= (header[7] << 24);// 跳过非音频/视频标签if (tagType != 0x08 && tagType != 0x09) {// 移动文件指针跳过数据和PreviousTagSizefseek(m_file, dataSize + 4, SEEK_CUR);continue;}// 分配缓冲区用于完整标签(头 + 数据 + 前一个标签大小)uint32_t packetSize = 11 + dataSize + 4;cout << "packetSize: " << packetSize << endl;// 动态分配内存以存储完整的数据包m_packet.resize(packetSize);// 复制标签头到数据包缓冲区memcpy(m_packet.data(), header, 11);// 从文件中读取数据部分到数据包缓冲区if (fread(m_packet.data() + 11, 1, dataSize + 4, m_file) != dataSize + 4) {// 若读取失败,释放已分配的内存m_packet.clear();break;}bRes = true;break;} while (1);if (packet && packetSize && timestamp){*packet = (char*)m_packet.data();*packetSize = m_packet.size();*timestamp = m_timestamp;}return bRes;
}