常用 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 URL
if (!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) {
// 移动文件指针跳过数据和PreviousTagSize
fseek(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;
}