基于media-server简单的rtsp服务端实现

前言:学习一个协议总是枯燥乏味,如果能快速做出个小成品来,然后根据协议不断完善其功能,那就好多了。下面分享一个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;
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值