使用librtmp库进行推流与拉流

目前比较主流的直播技术有RTMP、HLS,其中RTMP主要基于TCP协议,HLS主要基于HTTP协议,二者在实施成本、延迟性等方面有较大差异。本文主要讲解RTMP的推流与拉流技术的应用。

理论知识:

原则上说,开发RTMP的推拉流应用,除具备基本的语言及工具链外,还需要掌握以下知识:
a) RTMP协议原理
b) 基本的音视频编解码知识
c) FLV等流媒体封装格式技巧

RTMP协议原理:

与大多的协议格式一样,RTMP也遵循TLV范式,以及其远程过程调用的指令构造格式AMF。

RTMP基于TCP,在TCP三次握手完成后,RTMP也定义了自己的六次握手,主要用于版本适配、RTT计算,解析这块需要较大篇幅,感兴趣的朋友请百度搜索“RTMP协议详解”。

完成握手之后,传输的报文都基于RTMP分块格式,如下:

+---------------------+-----------------------------------+----------------------+----------------------+|  1~3字节基本块头    |        0~11字节的消息块头            | 可选4字节扩展时间戳 |      变长消息负载        |
+---------------------+-----------------------------------+----------------------+----------------------+

采用分块技术原于分时复用的思想,不同的上层消息交错地在一条TCP链路中传输,通常一个块不会太长,避免因为某一个应用消息过多的占用网络资源而造成其它应用饿死。

基本块头介绍:
基本块头定义了两个关键要素:消息块头格式、块流ID。

先看基本块头的第一字节表示:

+-位7-+-位6-+-位5-+-位4-+-位3-+-位2-+-位1-+-位0-+|   块头格式   |             块流ID                                      |
+-------------+------------------------------------------+

块头格式占2个位,有4种表示:
00 —— 11字节完整的消息块头。
01 —— 7字节中等消息块头。
10 —— 3字节小型消息块头。
11 —— 0字节无消息块头。

首字节块流ID占6个位,有0~63种表示,每种表示一个块流通道。为了扩展更多的字节表示块流通道,首字节的块流ID被划出0和1两个值用于表示扩展1或2个字节。
扩展1字节的表示法:

+-------------+--------------------+----------------------+|   块头格式   |           0                 |           扩展1字节       |
+-------------+--------------------+----------------------+

扩展2字节的表示法:

+-------------+--------------------+----------------------+----------------------+|   块头格式   |           1                 |                          扩展2字节                          |
+-------------+--------------------+----------------------+----------------------+

扩展1字节可以表示64+255=319个块流通道。
扩展1字节可以表示64+65525=65599个块流通道。

消息块头介绍:

11字节完整的消息块头格式:

+------------------+------------------+------------------+------------------+|                             3字节绝对时间戳                            |    负载消息长度   |
+------------------+------------------+------------------+------------------+
|                3字节负载消息长度           |    1字节消息类型  |        块流ID         |
+------------------+------------------+------------------+------------------+
|                               4字节块流ID                                 |
+------------------+------------------+------------------+

7字节中等消息块头:

+------------------+------------------+------------------+------------------+|                             3字节相对时间戳                            |    负载消息长度   |
+------------------+------------------+------------------+------------------+
|                3字节负载消息长度           |    1字节消息类型  |
+------------------+------------------+------------------+

3字节小型消息块头:

+------------------+------------------+------------------+|                             3字节相对时间戳                            |
+------------------+------------------+------------------+

对于不完整的消息块头,省略的字段表示与上次的相同,这可以起到很好的压缩传输效果。

可选扩展时间戳:
只有当块消息头中的普通时间戳设置为0x00ffffff时,本字段才被传送。

变长消息负载:

在RTMP协议规范中在块协议基础上又进一步定义了消息协议,格式如下:

+------------------+------------------+------------------+------------------+|  1字节消息类型   |                          3字节负载消息长度                             |
+------------------+------------------+------------------+------------------+
|                                              4字节时间戳                                              |
+------------------+------------------+------------------+------------------+
|                               3字节消息流ID                              |
+------------------+------------------+------------------+

消息协议仍然遵循TLV范式,这种套娃式的结构在协议设计模式中也是主流,然而令疑惑的是,消息头的字段设计,与块头过于重合,并且还定义了一个消息流ID。官方对此的解释是,消息头旨在规范开发者应用层消息的设计,与块头属于两个不同的维度,各字段既使同名也互不影响。事实上,如果我们深入分析librtmp的实现,或者抓包分析RTMP的拉流过程,这个消息头规范是没有被采纳的。

基本的音视频编解码知识:

流媒体传输的音视频格式一般采用主流的压缩协议,如音频采用AAC、MP3,视频采用H264、MPEG4。对直播推流来说,我们需要从摄像头抓取YUV或者Bayer格式的视频,从声卡抓取PCM格式的音频,这些数据都是没有经过压缩的,数据量非常大,不便于网络传输。使用压缩协议执行压缩与解压缩的过程称为编解码。编解码可以使用当下流行的FFMpeg工具,开发者遵循其接口调用规范,将原始音视频数据打包,交给指定的编码模块,生成压缩后的编码帧。压缩后的数据大小通常不到原始数据的1/10,对视频来说,由于采用了P帧和B帧前后向预测技术,压缩效率更高。编码帧通过网络传输到目的端如播放器,交给解码器解码还原,当然,通常采用的编码算法都是有损的,解码后的音视频数据既使不能还原到100%的程度,由于人脑视觉和听觉系统的特殊性,大多数情况下仍然不会影响我们对消息的解读。

FLV等流媒体封装格式技巧:

编码器输出的裸音视频数据是不能直接用于推拉流的,RTMP直播通常采用FLV格式,FLV格式要求:
对于音频数据,在裸数据之前,必须增加1个字节的元格式,这个格式定义了采样率、采样精度、通道布局,对于AAC格式,还需要增加ADTS头。
对于视频数据,在裸数据之前,也必须增加1个字节的元格式,这个格式定义了编码器、帧格式,对于H264,还需要增加SPS和PPS。

librtmp库接口介绍:

官网下载:http://rtmpdump.mplayerhq.hu/download

结构定义:

与开发者最直接相关结构包括:

RTMP报文格式:

  typedef struct RTMPPacket{uint8_t m_headerType;   // 块头类型uint8_t m_packetType;   // 负载格式uint8_t m_hasAbsTimestamp;   // 是否绝对时间戳int m_nChannel;   // 块流IDuint32_t m_nTimeStamp;    // 时间戳int32_t m_nInfoField2;    // 块流IDuint32_t m_nBodySize;   // 负载大小uint32_t m_nBytesRead;   // 读入负载大小RTMPChunk *m_chunk;  // 在RTMP_ReadPacket()调用时,若该字段非NULL,表示关心原始块的信息,通常设为NULLchar *m_body;   // 负载指针} RTMPPacket;

RTMP上下文格式:

  typedef struct RTMP{int m_inChunkSize;    // 最大接收块大小int m_outChunkSize;    // 最大发送块大小int m_nBWCheckCounter;    // 带宽检测计数器int m_nBytesIn;    // 接收数据计数器int m_nBytesInSent;    // 当前数据已回应计数器int m_nBufferMS;    // 当前缓冲的时间长度,以MS为单位int m_stream_id;    // 当前连接的流IDint m_mediaChannel;    // 当前连接媒体使用的块流IDuint32_t m_mediaStamp;    // 当前连接媒体最新的时间戳uint32_t m_pauseStamp;    // 当前连接媒体暂停时的时间戳int m_pausing;    // 是否暂停状态int m_nServerBW;    // 服务器带宽int m_nClientBW;    // 客户端带宽uint8_t m_nClientBW2;    // 客户端带宽调节方式uint8_t m_bPlaying;    // 当前是否推流或连接中uint8_t m_bSendEncoding;    // 连接服务器时发送编码uint8_t m_bSendCounter;    // 设置是否向服务器发送接收字节应答int m_numInvokes;    // 0x14命令远程过程调用计数int m_numCalls;    // 0x14命令远程过程请求队列数量RTMP_METHOD *m_methodCalls;    // 远程过程调用请求队列RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS];    // 对应块流ID上一次接收的报文RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS];    // 对应块流ID上一次发送的报文int m_channelTimestamp[RTMP_CHANNELS];    // 对应块流ID媒体的最新时间戳double m_fAudioCodecs;    // 音频编码器代码double m_fVideoCodecs;    // 视频编码器代码double m_fEncoding;         /* AMF0 or AMF3 */double m_fDuration;    // 当前媒体的时长int m_msgCounter;    // 使用HTTP协议发送请求的计数器int m_polling;    // 使用HTTP协议接收消息主体时的位置int m_resplen;    // 使用HTTP协议接收消息主体时的未读消息计数int m_unackd;    // 使用HTTP协议处理时无响应的计数AVal m_clientID;    // 使用HTTP协议处理时的身份IDRTMP_READ m_read;    // RTMP_Read()操作的上下文RTMPPacket m_write;    // RTMP_Write()操作使用的可复用报文对象RTMPSockBuf m_sb;    // RTMP_ReadPacket()读包操作的上下文RTMP_LNK Link;    // RTMP连接上下文} RTMP;

函数定义:

关于返回值为int类型的函数,大多数其实是bool语义,即1表示成功,0表示失败,但是仍然也有少部分函数可以返回负值。

RTMP报文操作:

// 重置报文
void RTMPPacket_Reset(RTMPPacket *p);
// 为报文分配负载空间
int RTMPPacket_Alloc(RTMPPacket *p, int nSize);
// 释放负载空间
void RTMPPacket_Free(RTMPPacket *p);// 检查报文是否可读,当报文被分块,且接收未完成时不可读
#define RTMPPacket_IsReady(a)   ((a)->m_nBytesRead == (a)->m_nBodySize)

地址解析操作:

// 解析流地址
int RTMP_ParseURL(const char *url, int *protocol, AVal *host, unsigned int *port, AVal *playpath, AVal *app);

媒体缓存时长设置操作:

// 连接前,设置服务器发送给客户端的媒体缓存时长
void RTMP_SetBufferMS(RTMP *r, int size);
// 连接后,更新服务器发送给客户端的媒体缓存时长
void RTMP_UpdateBufferMS(RTMP *r);

RTMP播放地址及上下文选项操作:

// 更新RTMP上下文中的相应选项
int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);
// 设置流地址
int RTMP_SetupURL(RTMP *r, char *url);
// 设置RTMP上下文播放地址和相应选项,不关心的可以设为NULL
void RTMP_SetupStream(RTMP *r, int protocol,AVal *hostname,unsigned int port,AVal *sockshost,AVal *playpath,AVal *tcUrl,AVal *swfUrl,AVal *pageUrl,AVal *app,AVal *auth,AVal *swfSHA256Hash,uint32_t swfSize,AVal *flashVer,AVal *subscribepath,int dStart,int dStop, int bLiveStream, long int timeout);

RTMP连接及握手操作:

// 客户端连接及握手
int RTMP_Connect(RTMP *r, RTMPPacket *cp);
// 服务端握手
int RTMP_Serve(RTMP *r);

收发报文操作:

// 接收一个报文
int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet);
// 发送一个报文,queue为1表示当包类型为0x14时,将加入队列等待响应
int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue);
// 直接发送块
int RTMP_SendChunk(RTMP *r, RTMPChunk *chunk);

其他操作,不分类了,解释如下:

// 检查网络是否连接
int RTMP_IsConnected(RTMP *r);
// 返回套接字
int RTMP_Socket(RTMP *r);
// 检查连接是否超时
int RTMP_IsTimedout(RTMP *r);// 获取当前媒体的时长
double RTMP_GetDuration(RTMP *r);// 暂停与播放切换控制
int RTMP_ToggleStream(RTMP *r);// 连接流,并指定开始播放的位置
int RTMP_ConnectStream(RTMP *r, int seekTime);
// 重新创建流
int RTMP_ReconnectStream(RTMP *r, int seekTime);
// 删除当前流
void RTMP_DeleteStream(RTMP *r);// 获取第一个媒体包
int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet);// 处理客户端的报文交互,即处理报文分派逻辑
int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet);// 分配RTMP上下文
RTMP *RTMP_Alloc(void);
// 初使化RTMP上下文,设默认值
void RTMP_Init(RTMP *r);
// 关闭RTMP上下文
void RTMP_Close(RTMP *r);
// 释放RTMP上下文
void RTMP_Free(RTMP *r)// 开启客户端的RTMP写开关,用于推流
void RTMP_EnableWrite(RTMP *r);// 返回RTMP的版本
int RTMP_LibVersion(void);// 开启RTMP工作中断
void RTMP_UserInterrupt(void);// 发送0x04号命令的控制消息
int RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime);// 发送0x14号远程调用控制暂停
int RTMP_SendPause(RTMP *r, int DoPause, int dTime);
int RTMP_Pause(RTMP *r, int DoPause);// 递归在一个对象中搜索指定的属性
int RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, AMFObjectProperty * p);// 底层套接口的网络读取、发送、关闭连接操作
int RTMPSockBuf_Fill(RTMPSockBuf *sb);
int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len);
int RTMPSockBuf_Close(RTMPSockBuf *sb);// 发送建流操作
int RTMP_SendCreateStream(RTMP *r);// 发送媒体时间定位操作
int RTMP_SendSeek(RTMP *r, int dTime);// 发送设置服务器应答窗口大小操作
int RTMP_SendServerBW(RTMP *r);
// 发送设置服务器输出带宽操作
int RTMP_SendClientBW(RTMP *r);// 删除0x14命令远程调用队列中的请求
void RTMP_DropRequest(RTMP *r, int i, int freeit);// 读取FLV格式数据
int RTMP_Read(RTMP *r, char *buf, int size);
// 发送FLV格式数据
int RTMP_Write(RTMP *r, const char *buf, int size);

推流用法:

推流流程:

步骤:

  1. 初使化RTMP上下文
  2. 设置推流地址
  3. 开启推流标志
  4. 连接服务器
  5. 连接流地址
  6. 若从文件推流,循环读TAG,组织报文发送
  7. 若发送太快,适当做延迟
  8. 推流完毕,释放资源

简单例子:

下面这个例子演示了使用librtmp库将本地flv文件推流到服务器,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>#include <librtmp/rtmp.h>// 大小端字节序转换
#define HTON16(x) ( (x >> 8 & 0x00FF) | (x << 8 & 0xFF00) )
#define HTON24(x) ( (x >> 16 & 0x0000FF) | (x & 0x00FF00) | (x << 16 & 0xFF0000) )
#define HTON32(x) ( (x >> 24 & 0x000000FF) | (x >> 8 & 0x0000FF00) | (x << 8 & 0x00FF0000) | (x << 24 & 0xFF000000) )
#define HTONTIME(x) ( (x >> 16 & 0x000000FF) | (x & 0x0000FF00) | (x << 16 & 0x00FF0000) | (x & 0xFF000000) )// 从文件读取指定字节
bool ReadFP(char* pBuf, int nSize, FILE* pFile)
{return (fread(pBuf, 1, nSize, pFile) == nSize);
}// 从文件读取1个字节整数
bool ReadU8(uint8_t* u8, FILE* fp)
{return ReadFP((char*)u8, 1, fp);
}// 从文件读取2个字节整数
bool ReadU16(uint16_t* u16, FILE* fp)
{if (!ReadFP((char*)u16, 2, fp))return false;*u16 = HTON16(*u16);return true;
}// 从文件读取3个字节整数
bool ReadU24(uint32_t* u24, FILE* fp)
{if (!ReadFP((char*)u24, 3, fp))return false;*u24 = HTON24(*u24);return true;
}// 从文件读取4个字节整数
bool ReadU32(uint32_t* u32, FILE* fp)
{if (!ReadFP((char*)u32, 4, fp))return false;*u32 = HTON32(*u32);return true;
}// 从文件读取4个字节时间戳
bool ReadTime(uint32_t* utime, FILE* fp)
{if (!ReadFP((char*)utime, 4, fp))return false;*utime = HTONTIME(*utime);return true;
}// 从文件预读1个字节整数
bool PeekU8(uint8_t* u8, FILE* fp)
{if (!ReadFP((char*)u8, 1, fp))return false;fseek(fp, -1, SEEK_CUR);return true;
}int main(int argc, char* argv[])
{FILE* pFile = fopen("1.flv", "rb");// 初使化RTMP上下文RTMP* pRTMP = RTMP_Alloc();RTMP_Init(pRTMP);// 设置推流地址pRTMP->Link.timeout = 10;RTMP_SetupURL(pRTMP, (char*)"rtmp://127.0.0.1:1935/live/a");// 开启推流标志RTMP_EnableWrite(pRTMP);// 连接服务器bool b = RTMP_Connect(pRTMP, NULL);if (!b){printf("connect failed! \n");return -1;}// 连接流地址b = RTMP_ConnectStream(pRTMP, 0);if (!b){printf("connect stream failed! \n");return -1;}// 跳过FLV文件头的13个字节fseek(pFile, 9, SEEK_SET);fseek(pFile, 4, SEEK_CUR);// 初使化RTMP报文RTMPPacket packet;RTMPPacket_Reset(&packet);packet.m_body = NULL;packet.m_chunk = NULL;packet.m_nInfoField2 = pRTMP->m_stream_id;uint32_t starttime = RTMP_GetTime();while (true){// 读取TAG头uint8_t type = 0;if (!ReadU8(&type, pFile))break;uint32_t datalen = 0;if (!ReadU24(&datalen, pFile))break;uint32_t timestamp = 0;if (!ReadTime(&timestamp, pFile))break;uint32_t streamid = 0;if (!ReadU24(&streamid, pFile))break;/*// 跳过0x12 Scriptif (type != 0x08 && type != 0x09){fseek(pFile, datalen + 4, SEEK_CUR);continue;}
*/RTMPPacket_Alloc(&packet, datalen);if (fread(packet.m_body, 1, datalen, pFile) != datalen)break;// 组织报文并发送packet.m_headerType = RTMP_PACKET_SIZE_LARGE;packet.m_packetType = type;packet.m_hasAbsTimestamp = 0;packet.m_nChannel = 6;packet.m_nTimeStamp = timestamp;packet.m_nBodySize = datalen;if (!RTMP_SendPacket(pRTMP, &packet, 0)){printf("Send Error! \n");break;}printf("send type:[%d] timestamp:[%d] datasize:[%d] \n", type, timestamp, datalen);// 跳过PreTaguint32_t pretagsize = 0;if (!ReadU32(&pretagsize, pFile))break;// 延时,避免发送太快uint32_t timeago = (RTMP_GetTime() - starttime);if (timestamp > 1000 && timeago < timestamp - 1000){printf("sleep...\n");usleep(100000);}RTMPPacket_Free(&packet);}// 关闭连接,释放RTMP上下文RTMP_Close(pRTMP);RTMP_Free(pRTMP);fclose(pFile);return 0;
}

运行输出:

g++ -o testrtmp2 testrtmp2.cpp -lrtmp
./testrtmp2
send type:[18] timestamp:[0] datasize:[371]
send type:[8] timestamp:[0] datasize:[209]
send type:[9] timestamp:[25] datasize:[9838]
send type:[8] timestamp:[26] datasize:[210]
send type:[8] timestamp:[52] datasize:[210]
send type:[8] timestamp:[78] datasize:[210]
send type:[9] timestamp:[88] datasize:[11181]
send type:[8] timestamp:[104] datasize:[210]
send type:[8] timestamp:[131] datasize:[210]
send type:[9] timestamp:[150] datasize:[11820]
send type:[8] timestamp:[157] datasize:[210]
send type:[8] timestamp:[183] datasize:[210]
send type:[8] timestamp:[209] datasize:[210]
send type:[9] timestamp:[213] datasize:[11995]
send type:[8] timestamp:[235] datasize:[210]
send type:[8] timestamp:[261] datasize:[210]
send type:[9] timestamp:[275] datasize:[11809]
send type:[8] timestamp:[287] datasize:[210]
send type:[8] timestamp:[313] datasize:[210]
send type:[9] timestamp:[338] datasize:[6124]
send type:[8] timestamp:[340] datasize:[210]
send type:[8] timestamp:[366] datasize:[210]
send type:[8] timestamp:[392] datasize:[210]
send type:[9] timestamp:[400] datasize:[4500]
send type:[8] timestamp:[418] datasize:[210]
send type:[8] timestamp:[444] datasize:[210]
send type:[9] timestamp:[463] datasize:[2850]
send type:[8] timestamp:[470] datasize:[210]

拉流用法:

拉流流程:

步骤:

  1. 初使化RTMP上下文
  2. 设置拉流地址
  3. 连接服务器
  4. 连接流地址
  5. 循环拉流,提取媒体数据,保存为文件或者交给解码模块
  6. 拉流完毕,释放资源

简单例子:

下面这个例子演示了使用librtmp库从服务器拉流到本地保存为flv/mp3文件,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>#include <librtmp/rtmp.h>int main(int argc, char* argv[])
{// 初使化RTMP上下文RTMP* pRTMP = RTMP_Alloc();RTMP_Init(pRTMP);// 设置拉流地址RTMP_SetupURL(pRTMP, (char*)"rtmp://127.0.0.1:1935/live/a");// 连接服务器pRTMP->Link.timeout = 10;pRTMP->Link.lFlags |= RTMP_LF_LIVE;bool b = RTMP_Connect(pRTMP, NULL);if (!b){printf("connect failed! \n");return -1;}// 连接流地址b = RTMP_ConnectStream(pRTMP, 0);if (!b){printf("connect stream failed! \n");return -1;}bool bSaveMP3 = true;FILE* pFile = fopen(bSaveMP3 ? "testrtmp.mp3" : "testrtmp.flv", "wb");while (RTMP_IsConnected(pRTMP)){if (bSaveMP3){RTMPPacket packet;RTMPPacket_Reset(&packet);packet.m_body = NULL;packet.m_chunk = NULL;b = RTMP_ReadPacket(pRTMP, &packet);if (!b)break;if (!RTMPPacket_IsReady(&packet))continue;printf("\t headerType:[%d] \n", packet.m_headerType);printf("\t packetType:[%d] \n", packet.m_packetType);printf("\t hasAbsTimestamp:[%d] \n", packet.m_hasAbsTimestamp);printf("\t nChannel:[%d] \n", packet.m_nChannel);printf("\t nTimeStamp:[%d] \n", packet.m_nTimeStamp);printf("\t nInfoField2:[%d] \n", packet.m_nInfoField2);printf("\t nBodySize:[%d] \n", packet.m_nBodySize);printf("\t nBytesRead:[%d] \n", packet.m_nBytesRead);//fwrite(packet.m_body, 1, packet.m_nBodySize, pFile);//fwrite("AAAAAAAAAAAAAAAA", 1, 16, pFile);if (packet.m_packetType == 0x08){fwrite(packet.m_body + 1, 1, packet.m_nBodySize - 1, pFile);}RTMPPacket_Free(&packet);}else{char sBuf[4096] = {0};int bytes = RTMP_Read(pRTMP, sBuf, sizeof(sBuf));printf("RTMP_Read() ret:[%d] \n", bytes);if (bytes <= 0)break;fwrite(sBuf, 1, bytes, pFile);}}fclose(pFile);RTMP_Close(pRTMP);RTMP_Free(pRTMP);return 0;
}

运行输出:

g++ -o testrtmp testrtmp.cpp -lrtmp
./testrtmp
RTMP_Read() ret:[640]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[4096]
RTMP_Read() ret:[4096]
RTMP_Read() ret:[1304]
RTMP_Read() ret:[225]
RTMP_Read() ret:[952]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[1705]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[1142]
RTMP_Read() ret:[225]
RTMP_Read() ret:[225]
RTMP_Read() ret:[1957]
RTMP_Read() ret:[225] 

使用librtmp库进行推流与拉流相关推荐

  1. 科普 4G多卡聚合智能融合通信设备同时支持推流和拉流.

    为什么直播现场的信息,用户通过手机或者网站就能很快的看到呢?为什么有时候网络不稳定,直播效果会有延迟呢?现场的视频信号又是如何传到网络呢? 这些所有问题的产生,都离不开视频直播中常说的两个词:推流和拉 ...

  2. 关于直播类app中的推流、拉流技术(转载)

    关于直播类app中的推流.拉流技术 from:https://blog.csdn.net/lzllzllhl/article/details/80249069 图像处理:iOS端一般会用到GPUIma ...

  3. 视频推流、拉流相关技术

    这里写自定义目录标题 视频的推流与拉流 webrtc 视频的推流与拉流 拉流是指服务器已有直播内容,用指定地址进行拉取的过程. 推流指的是把采集阶段封包好的内容传输到服务器的过程,其实就是将现场的视频 ...

  4. RTSP向ZLM流媒体服务器的推流和拉流鉴权

    文章目录 前言 一.HTTP Hook 二.向ZLM推流鉴权 三.向ZLM拉流 四.参考 前言 本篇博客的测试环境: Windows 10 + Qt 5.12.2 MSVC. 由于项目中使用了RTSP ...

  5. 直播推流 ffmpeg 拉流二次转发 记录

    因 前两天老大给我发了个任务,说让我做一个flask 服务器接口,接收请求,在请求参数中获取 直播拉流地址和 推流地址,然后调用 ffmpeg 到拉流地址上进行拉流 将拉到的流媒体数据推到另一个直播服 ...

  6. Centos7下SRS流式服务器搭建、推流、拉流

    Centos7下SRS流式服务器搭建.推流.拉流 1. 设计目的 2. 实验内容 3. 实验过程 1. 设计目的 本报告从SRS服务器的部署入手,分析视频摄像头RTSP流媒体协议向RTMP流媒体协议的 ...

  7. 推流和拉流的概念以及RTMP和HLS协议

    https://www.bbsmax.com/A/x9J2wZM56o/ 推流为将直播内容推送至服务器的过程:拉流为服务器已有直播内容,用指定地址进行拉取的过程. rtmp rtmp是Real Tim ...

  8. 【流媒体】推流与拉流简介

    本文目录 一.概念 1.1 推流 1.2 拉流 二.示意图 三.RTMP传输协议 四.流媒体协议与格式 一.概念 话不多说,先了解概念,再看示意图更直观: 1.1 推流 推流:将直播的内容推送至服务器 ...

  9. PHP关于实现腾讯云直播的推流和拉流

    PHP关于实现腾讯云直播的推流和拉流 步骤如下 注册一个腾讯云账户,搜产品 :云直播,他们应该会送你20G的流量包,测试的时候足够用了: 设置推流域名和播放(拉流)域名: 推流域名腾讯会直接给你分配一 ...

最新文章

  1. 实现 strStr()
  2. 智能车竞赛提问回复-2021-3-25
  3. EMC virtual provisining and fast vp
  4. matlab 创建批量文件夹_Matlab开发Web App服务器(一)
  5. centos5.5中安装mysql5.5.3
  6. 除了PS,原来这个也可以轻松实现图像处理!
  7. C++一天一个程序(三)
  8. 2018-5-22-Python全栈开发day9-Python开发课程简介part2
  9. ApacheCN 学习资源汇总 2019.3 1
  10. 6种不同画法画平行线_眉毛影响气质!6种经典眉形画法,每一种都让你美丽翻倍...
  11. GNU make manual 翻译(三十)
  12. 均匀三次b样条曲线_西门子数控曲线加工进给速度优化指令
  13. 不要问我有多懒,写个脚本跑django
  14. 史上最牛最全android开发知识汇总
  15. php开发oa系统的插件下载不了,OA系统安装不了office控件的解决方法
  16. 猫眼电影的android源代码!,微信小程序入门demo之猫眼电影
  17. 【转载文章】原 DOS命令学习(从入门到精通)_____附加自己学习笔记
  18. LKA linux kernel architechture
  19. 物联网技术栈之网关技术
  20. 什么是全栈工程师,如何成为全栈工程师

热门文章

  1. python中seaborn是什么_Python数据分析之seaborn常用方法
  2. BZOJ 3159: 决战 解题报告
  3. 【公开课】如何使用Arm-2D在小资源Cortex-M处理器芯片中实现图形界面?
  4. 每天一个---- 吉尔德定律和迈特卡尔定律
  5. 苹果x支持5g吗_【苹果】曝iPhone12双卡下不支持5G?NO!小白实测支持
  6. Docker环境undertow线程数不足问题探究
  7. 计算机主机风扇安装方法,机箱风扇怎么装,详细教您电脑机箱风扇怎么装
  8. 下载安装pip-19.0
  9. SAP 未计划交货费-MIRO发票校验
  10. c#实现循环输入商品编号和购买数量,结账时应付金额并找零