前言:学习一个协议总是枯燥乏味,如果能快速做出个小成品来,然后根据协议不断完善其功能,那就好多了。下面分享一个rtsp服务端的示例小程序,代码200多行,VLC和ffmpeg能正常拉多路流,适合新手探究rtsp协议,做rtsp服务器。
工程压缩包:下载链接(csdn: 简单rtsp服务端实现, 百度云盘:链接: https://pan.baidu.com/s/18BsIqEcvGY9cXw25iyub0A 提取码: 72hi ),包含以下代码,以及编译好的用到的media-server头文件和库文件,以及一段h264录像(做视频源)。
源码: 服务基于开源的media-server库在windows上实现,用来封装rtsp码流的数据包,信令部分直接固定字符串返回,配合wireshark抓包学rtsp协议很方便。
media-server源码链接:https://github.com/ireader/media-server
编译media-server需要用到sdk库,源码链接:https://github.com/ireader/sdk
主函数源码:
#include <iostream>
#include <WinSock2.h>
#include "rtspsession.h"
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "librtp.lib")
SOCKET Listener = 0;
std::vector<RtspSessionPtr> AllRtspSession;
int ListenLocalSvc(int port);
int main()
{
std::cout << "hello rtsp server!" << std::endl;
if (0 > ListenLocalSvc(554) ||
0 > RtspSession::ReadSourceStream("miniH264.h264"))
{
return -1;
}
unsigned int startTime, usedTime;
while (true)
{
startTime = GetTickCount();
struct sockaddr_in clent;
int len = sizeof(clent);
int clientSocket = accept(Listener, (sockaddr*)&clent, &len);
if (clientSocket > 0)
{
printf("accept a client %d\n", clientSocket);
int value = 10 * 1024 * 1024;
setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(value));
AllRtspSession.push_back(std::make_shared<RtspSession>(clientSocket));
}
for (auto it = AllRtspSession.begin(); it != AllRtspSession.end(); )
{
it = (0 > (*it)->Run()) ? AllRtspSession.erase(it) : ++it;
}
usedTime = GetTickCount() - startTime;
if (usedTime < 5)
Sleep(5 - usedTime);
}
return 0;
}
int ListenLocalSvc(int port)
{
WSADATA WSAData;
WSAStartup(MAKEWORD(2, 0), &WSAData);
Listener = socket(AF_INET, SOCK_STREAM, 0);
unsigned long ul = 1;
int ret = ioctlsocket(Listener, FIONBIO, (unsigned long *)&ul);//设置成非阻塞模式。
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(port);
if (bind(Listener, (struct sockaddr*) &sin, sizeof(sin)) < 0)
{
printf("bind port %d failed", port);
closesocket(Listener);
return -1;
}
if (listen(Listener, 20) < 0)
{
printf("listen port %d failed", port);
closesocket(Listener);
return -1;
}
return 0;
}
RtspSession类头文件:
#pragma once
#include <memory>
#include <vector>
enum RS_Status
{
RS_INIT,
RS_SIGNALLING,
RS_PAUSE,
RS_SENDSTREAM,
RS_ERROR
};
class RtspSession
{
unsigned int mFrameSeq;
unsigned long mLastFrameTS;
void *mPacker;
int SendStream();
int RecvSignaling();
public:
int mSocket;
RS_Status mStatus;
RtspSession(int sock);
~RtspSession();
static int ReadSourceStream(char *fileName);
int Run();
};
using RtspSessionPtr = std::shared_ptr<RtspSession>;
RtspSession类源文件:
#include "rtspsession.h"
#include <WinSock2.h>
#include <string>
#include "rtp-payload.h"
char buffer[5 * 1024];
char rtpPkg[1600];
std::vector<char*> StreamFrame;
std::string GetMember(const char* source, const char* memberName)
{
const char * pStart = strstr(source, memberName);
if (pStart != NULL)
{
const char * pEnd = strchr(pStart, '\n');
if (pEnd != NULL)
{
if (*(pEnd - 1) == '\r')
pEnd--;
return std::string(pStart, pEnd);
}
}
return "";
}
void* RTPAlloc(void* param, int bytes)
{
return rtpPkg + 4;
}
void RTPFree(void* param, void *packet)
{
}
int RTPPacket(void* param, const void *packet, int bytes, uint32_t, int)
{
rtpPkg[0] = '$';
rtpPkg[1] = 0;
*(short*)(rtpPkg + 2) = htons(bytes);
if (0 > send(((RtspSession*)param)->mSocket, rtpPkg, bytes + 4, 0))
{
((RtspSession*)param)->mStatus = RS_ERROR;
}
return 0;
}
struct rtp_payload_t rtpfunc = { RTPAlloc, RTPFree, RTPPacket, };
RtspSession::RtspSession(int sock):
mSocket(sock),
mFrameSeq(0),
mPacker(NULL),
mLastFrameTS(0),
mStatus(RS_INIT)
{
}
RtspSession::~RtspSession()
{
if(mPacker)
rtp_payload_encode_destroy(mPacker);
closesocket(mSocket);
printf("close socket %d\n", mSocket);
}
int RtspSession::ReadSourceStream(char *fileName)
{
FILE *fp = fopen(fileName, "rb");
if (fp == NULL)
{
printf("open file:%s error\n", fileName);
return -1;
}
unsigned int size;
while (true)
{
if (4 != fread(&size, 1, 4, fp))
break;
char *frame = (char*)malloc(size + 5);
memcpy(frame, &size, 4);
if (size != fread(frame + 4, 1, size, fp))
break;;
StreamFrame.push_back(frame);
}
fclose(fp);
return 0;
}
int RtspSession::Run()
{
if (mStatus == RS_ERROR)
{
return -1;
}
else if (mStatus == RS_SENDSTREAM)
{
SendStream();
}
return RecvSignaling();
}
int RtspSession::RecvSignaling()
{
int len = recv(mSocket, buffer, 5 * 1024, 0);
if (len <= 0)
{
return 0;
}
buffer[len] = 0;
printf("recv:%s\n", buffer);
std::string rsp;
if (std::string(buffer, 7) == "OPTIONS")
{
rsp = "RTSP/1.0 200 OK\r\n"\
"CSeq: 0\r\n"\
"Date: Tue, 11 Nov 2020 11:11:11 GMT\r\n"\
"Public: DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE,ANNOUNCE,RECORD,GET_PARAMETER,SET_PARAMETER\r\n"\
"Content-Length: 0\r\n\r\n";
}
else if (std::string(buffer, 8) == "DESCRIBE")
{
rsp = "RTSP/1.0 200 OK\r\n"\
"CSeq: 0\r\n"\
"Date: Tue, 11 Nov 2020 11:11:11 GMT\r\n"\
"Content-Type: application/sdp\r\n"\
"Content-Length: 270\r\n\r\n";
std::string sdp = "v=0\n"\
"o=- 0 0 IN IP4 0.0.0.0\n"\
"s=rtsp://127.0.0.0:554/virtual_channel\n"\
"c=IN IP4 0.0.0.0\n"\
"t=0 0\n"\
"a=range:npt=now-\n"\
"a=recvonly\n"\
"a=control:*\n"\
"m=video 0 RTP/AVP 97\n"\
"a=rtpmap:97 H264/90000\n"\
"a=fmtp:97\n"\
"a=control:track0\n";
rsp.replace(rsp.find("Content-Length: 270") + 16, 3, std::to_string(sdp.size()));
rsp += sdp;
}
else if (strstr(buffer, "RTP/AVP"))
{
rsp = "RTSP/1.0 461 Unsupported Transport\r\n"\
"CSeq: 0\r\n"\
"Date: Tue, 11 Nov 2020 11:11:11 GMT\r\n"\
"Transport: \r\n"\
"Content-Length: 0\r\n\r\n";
if (strstr(buffer, "RTP/AVP/TCP"))
{
rsp = "RTSP/1.0 200 OK\r\n"\
"CSeq: 0\r\n"\
"Date: Tue, 11 Nov 2020 11:11:11 GMT\r\n"\
"Session: 0x7fc1b8005d88\r\n"\
"Transport: RTP/AVP/TCP;interleaved=0-1\r\n"\
"Content-Length: 0\r\n\r\n";
}
}
else if (std::string(buffer, 4) == "PLAY")
{
rsp = "RTSP/1.0 200 OK\r\n"\
"CSeq: 0\r\n"\
"Date: Tue, 11 Nov 2020 11:11:11 GMT\r\n"\
"Session: 0x7fc1b8005d88\r\n"\
"Range: npt=0.000- \r\n"\
"RTP-Info: url=rtsp://127.0.0.0:554/virtual_channel;seq=0;rtptime=0\r\n"\
"Content-Length: 0\r\n\r\n";
mStatus = RS_SENDSTREAM;
}
printf("send:%s\n", rsp.c_str());
if (!rsp.empty())
{
std::string seq = GetMember(buffer, "CSeq");
if (!seq.empty())
{
rsp.replace(rsp.find("CSeq: 0"), 7, seq.c_str());
}
return send(mSocket, rsp.c_str(), rsp.size(), 0);
}
return 0;
}
int RtspSession::SendStream()
{
if(mPacker == NULL)
mPacker = rtp_payload_encode_create(97, "H264", 0, 0xffff, &rtpfunc, this);
if (GetTickCount() - mLastFrameTS > 40)
{
mLastFrameTS = GetTickCount();
char *frame = StreamFrame[mFrameSeq%StreamFrame.size()];
rtp_payload_encode_input(mPacker, frame + 4, *(int*)frame, mFrameSeq * 3600);
mFrameSeq++;
}
return 0;
}