前言
本文主要介紹rtmp協(xié)議收流流程,在linux上搭建rtmp服務(wù)器,通過(guò)自研的rtmp收流庫(kù)發(fā)起取流請(qǐng)求,使用ffmpeg+qt實(shí)現(xiàn)視頻流的解碼與播放。
關(guān)于rtmp協(xié)議基礎(chǔ)介紹可查看:https://blog.csdn.net/www_dong/article/details/131026072
環(huán)境搭建
nginx-rtmp-module下載
# 下載地址,下載zip包即可
https://github.com/arut/nginx-rtmp-module
nginx編譯與安裝
- 下載nginx
# 下載地址
http://nginx.org/en/download.html
- 解壓,將nginx-rtmp-module拷貝至nginx-1.24.0目錄,如下所示:
- 配置nginx編譯環(huán)境
# 步驟1
yum -y install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel
# 步驟2
yum -y install libxml2 libxml2-dev
yum -y install libxslt-devel
- 生成Makefile文件
# 在/home/rtmp/nginx-1.24.0下執(zhí)行如下命令
./configure --add-module=./nginx-rtmp-module-master --with-http_ssl_module --with-http_ssl_module --with-http_xslt_module --with-http_flv_module --with-debug --with-http_gzip_static_module
- 修改nginx.conf文件,進(jìn)入conf/文件夾下,編譯nginx.conf在末尾增加如下內(nèi)容:
rtmp {
server {
listen 1935; #監(jiān)聽(tīng)端口,若被占用,可以更改
chunk_size 4000; #上傳flv文件塊兒的大小
application live { #創(chuàng)建一個(gè)叫l(wèi)ive的應(yīng)用
live on; #開(kāi)啟live的應(yīng)用
allow publish 192.168.191.100;#192.168.191.100為服務(wù)器ip
allow play all;
}
}
}
- 編譯、安裝
# 在/home/rtmp/nginx-1.24.0下執(zhí)行如下命令
make
make install
- 啟動(dòng)nginx
# 測(cè)試配置文件
cd /usr/local/nginx
./sbin/nginx -t
# 有如下打印說(shuō)明配置正常
[root@localhost nginx]# ./sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
# 啟動(dòng)nginx
./sbin/nginx
# 查看是否正常啟動(dòng)
ps -aux | grep nginx
- 網(wǎng)頁(yè)訪(fǎng)問(wèn),出現(xiàn)如下內(nèi)容說(shuō)明啟動(dòng)成功
ffmepg推流
- 安裝ffmepg,將視頻文件拷貝至安裝路徑下,如圖所示:
- 執(zhí)行如下命令
ffmpeg -re -stream_loop -1 -i test.h264 -c copy -f flv rtmp://192.168.191.100:1935/live/testrtmp
以上步驟執(zhí)行完即完成rtmp服務(wù)器搭建。
設(shè)計(jì)
拉流流程
說(shuō)明:客戶(hù)端—自研的rtmp收流庫(kù),服務(wù)器—上述搭建的rtmp服務(wù)器
- 客戶(hù)端發(fā)送tcp握手請(qǐng)求,和服務(wù)器完成tcp握手和rtmp握手;
- 客戶(hù)端發(fā)送命令消息中的“連接”(connect)到服務(wù)器,請(qǐng)求與一個(gè)服務(wù)應(yīng)用實(shí)例建立連接;
- 客戶(hù)端發(fā)送網(wǎng)絡(luò)連接命令的“創(chuàng)建流”(createStream)消息到服務(wù)端,以創(chuàng)建消息通信的邏輯通道。音頻、視頻和元數(shù)據(jù)的發(fā)布通過(guò)使用createStream命令創(chuàng)建的流通道執(zhí)行。服務(wù)端發(fā)送createStream的“應(yīng)答消息”(_result);
- 客戶(hù)端發(fā)送網(wǎng)絡(luò)流命令中的“播放”(play)到服務(wù)端;
- 服務(wù)端發(fā)送協(xié)議控制消息中的“設(shè)置塊大小”(Set Chunk Size)到客戶(hù)端設(shè)置chunk大小;
- 服務(wù)器發(fā)送另一個(gè)協(xié)議控制消息(用戶(hù)控制),指定事件“StreamIsRecorded”和該消息中的流ID。消息在前2字節(jié)中攜帶事件類(lèi)型,在后4字節(jié)中攜帶流ID;
- 服務(wù)端發(fā)送用戶(hù)控制消息中的“流開(kāi)始”(StreamBegin)消息到客戶(hù)端,通知客戶(hù)端流成功創(chuàng)建,可用于通信;
- 如果客戶(hù)端發(fā)送的播放命令成功,則服務(wù)器發(fā)送onStatus命令消息NetStream.Play.Start和NetStream.Play.Reset。僅當(dāng)客戶(hù)端發(fā)送的播放命令設(shè)置了重置標(biāo)志時(shí),服務(wù)器才會(huì)發(fā)送NetStream.Play.Reset。如果未找到要播放的流,服務(wù)器將發(fā)送onStatus消息NetStream.Play.StreamNotFound;
- 服務(wù)端發(fā)送音視頻數(shù)據(jù)到客戶(hù)端;
代碼設(shè)計(jì)
- 上層url解析
// rtmp://192.168.191.100:1935/live/testrtmp
// appName:"live"
// streamPath: "testrtmp"
// tcurl: "rtmp://192.168.191.100:1935/live"
int CRtmpTcpStreamReceiver::ParseUrl_(std::string& appName, std::string& streamPath, std::string& tcurl)
{
if (m_rtmpUrl.empty())
return -1;
std::string url = m_rtmpUrl;
std::string::size_type pos_0 = url.rfind("/");
if (std::string::npos == pos_0)
return -1;
// "testrtmp"
streamPath = url.substr(pos_0+1, url.length());
// "rtmp://192.168.191.100:1935/live"
tcurl = url.substr(0, pos_0);
std::string tmpUrl = tcurl;
std::string::size_type pos_1 = url.rfind("rtmp://");
if (std::string::npos == pos_1)
return -1;
// ippUrl: "192.168.191.100:1935/live"
std::string ippUrl = tmpUrl.substr(pos_1+7, tmpUrl.length());
std::string::size_type pos_2 = ippUrl.rfind("/");
if (std::string::npos == pos_2)
return -1;
// "live"
appName = ippUrl.substr(pos_2+1, ippUrl.length());
// "192.168.191.100:1935"
std::string ipp = ippUrl.substr(0, pos_2);
std::string::size_type pos_3 = ipp.rfind(":");
if (std::string::npos == pos_3)
return -1;
m_rtmpIP = ipp.substr(0, pos_3);
m_rtmpPort = atoi(ipp.substr(pos_3+1, ipp.length()).c_str());
return 0;
}
- 開(kāi)始tcp握手和rtmp握手
int CRtmpTcpStreamReceiver::InitRtmpSession_()
{
// rtmp url解析
std::string appName = "", streamPath = "", turl = "";
if (0 != ParseUrl_(appName, streamPath, turl))
{
return -1;
}
do
{
// tcp連接
m_tcpClient = std::make_shared<ZDTcpClient>(nullptr, this);
if (!m_tcpClient.get()
|| 0 != m_tcpClient->TcpCreate()
|| 0 != m_tcpClient->TcpConnect(m_rtmpIP.c_str(), m_rtmpPort)
|| 0 != m_tcpClient->TcpSetNoBlock(false)
|| 0 != m_tcpClient->TcpRecvTimeout(5))
{
break;
}
// CRtmpCommand為librtmp庫(kù)接口封裝類(lèi)
m_command = std::make_shared<CRtmpCommand>(m_tcpClient, m_func, m_user);
if (!m_command.get())
{
break;
}
// 創(chuàng)建接收實(shí)例
if (0 != m_command->Create(appName, streamPath, turl)
|| 0 != m_command->Start(1))
{
break;
}
return 0;
} while (0);
UnInitRtmpSession_();
return -1;
}
- 啟動(dòng)線(xiàn)程接收數(shù)據(jù)
void CRtmpTcpStreamReceiver::RtmpWorker()
{
std::shared_ptr<char> dataPacket(new char[RTMP_STREAM_DATA_SIZE], std::default_delete<char[]>());
memset(dataPacket.get(), 0x00, RTMP_STREAM_DATA_SIZE);
int recvLen = 0;
while (m_running)
{
// tcp數(shù)據(jù)接收
recvLen = m_tcpClient->TcpRecv(dataPacket.get(), RTMP_STREAM_DATA_SIZE);
if (recvLen <= 0)
{
continue;
}
// 塞數(shù)據(jù)
if (0 != m_command->InputData(dataPacket.get(), recvLen))
{
break;
}
memset(dataPacket.get(), 0x00, RTMP_STREAM_DATA_SIZE);
}
}
抓包
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-773893.html
效果
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-773893.html
到了這里,關(guān)于音視頻學(xué)習(xí)(二十一)——rtmp收流(tcp方式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!