第二篇在这里
第二篇 rtsp rtp gb28181转html5播放
udpserver
假定服务端有一个端口接收rtp over udp,接收到数据以后转成html5能展示的,这种效果有作用否?
我们知道ffmpeg是可以直接发送rtp流的,gb28181 也是可以发送rtp流的,那么我们制作一个简洁的服务器,在udp端口接收到以后转成tcp,让后在网页上展示。
ffmpeg发送命令ffmpeg \
ffmpeg -re -i video.mp4 -an -c:v copy -f rtp -sdp_file video.sdp “rtp://192.168.1.109:5004”
使用ffmpeg来发送文件,去除音频,当然也可以接收自己写的程序发送rtp,下面,我们使用asio来制作一个异步的服务器,来接收udp流。
code
#pragma once
#include <cstdlib>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include "c_rtp.h"
using boost::asio::ip::udp;
using namespace boost;
#include "c_hub.h"
#include "c_rtp.h"
#define DEFINE_EC \
boost::system::error_code ec;
class c_flvserver;
class c_udpserver:public std::enable_shared_from_this<c_udpserver>
{
//asio::strand<asio::io_context::executor_type> v_strand;
asio::io_context &v_context;
asio::ip::udp::socket v_socket;
c_flvserver *v_flvserver = NULL;
unordered_map<uint32_t, s_rtp_h264_context*> v_ctxs;
//s_rtp_h264_context v_rtp;
s_rtp_h264_context * getctx(uint32_t ssrc)
{
auto it = v_ctxs.find(ssrc);
if (it != v_ctxs.end())
return it->second;
else
{
s_rtp_h264_context *sc = new s_rtp_h264_context();
v_ctxs[ssrc] = sc;
return sc;
}
}
void data_call(s_rtp_h264_context *ctx);
s_flvhub * func_context(uint32_t ssrc);
int live_rtp_unpack_h264(s_rtp_h264_context *ctx, uint8_t *data, int inlen);
//int live_rtp_unpack_h265(s_rtp_h264_context *ctx, uint8_t *data, int inlen);
//int live_rtp_unpack_aac(s_rtp_h264_context *ctx, uint8_t *data, int inlen);
//int live_rtp_unpack_opus(s_rtp_h264_context *ctx, uint8_t *data, int inlen);
//int live_rtp_unpack(s_rtp_h264_context *ctx, uint8_t *data, int inlen);
public:
c_udpserver(asio::io_context& io_context, short port, c_flvserver *flvserver):
//v_strand(io_context.get_executor()),
v_context(io_context), v_flvserver(flvserver),
v_socket(v_context)
//v_socket(v_context, udp::endpoint(udp::v4(), port))
{
//boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("192.168.1.206"),port);
udp::endpoint ep(udp::v4(), port);
v_socket.open(ep.protocol());
v_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
boost::asio::socket_base::receive_buffer_size recv_option(1*1024*1024);
v_socket.set_option(recv_option);
v_socket.bind(ep);
}
~c_udpserver()
{
//v_context_maps.clear();
}
//to flv server
void do_receive();
#ifdef _WEBRTC_
void do_receive_webrtc()
{
//std::cout << "start to receive" << std::endl;
//auto self(shared_from_this());
udp::endpoint from;
v_socket.async_receive_from(asio::buffer(data_, max_length),
from, [this](boost::system::error_code ec, std::size_t len)
{
uint8_t *d = (uint8_t*)&(data_[0]);
if (len < 12) //如果RTP包的长度小于12,说明包传输有错误
return;
if ((*d >> 6) != 2) /* RTP version number(rtp版本号必须为2) */
return;
if (*d & 0x20)
{
std::cout << "padding" << std::endl;
}
RTP_FIXED_HEADER *rtp_hdr = (RTP_FIXED_HEADER*)d;
uint32_t ssrc = *((uint32_t*)(d + 8));
int type = (*(d+1)) & 0x7f;
ssrc = ntohl(ssrc);
//std::cout << "seq:" << ntohs(rtp_hdr->seq_no) <<
// "--payload-->"<<type<<std::endl;
std::cout<<"len is:"<<len<<"-->" << "the ssrc:""--" << ssrc << std::endl;
s_hub * hub = func_context(ssrc);
auto iter = hub->v_list.begin();
while (iter != hub->v_list.end())
{
std::cout << "send data" << std::endl;
bool ret = (*iter)->send_data_need_protect(
1, 1, len, data_);
if (!ret)
{
hub->v_list.erase(iter++);
}
else
iter++;
}
do_receive_webrtc();
});
#endif
#if 0
boost::asio::spawn(v_context,
[this](asio::yield_context yield)
{
std::cout << "ddd" << std::endl;
udp::endpoint se;
DEFINE_EC
for (;;)
{
if (v_socket.is_open())
{
size_t len = v_socket.async_receive_from(boost::asio::buffer(data_, max_length),
se, yield[ec]);
RTP_FIXED_HEADER *rtp_hdr = (RTP_FIXED_HEADER*)&data_[0];
uint32_t ssrc = rtp_hdr->ssrc;
std::cout << "the ssrc:" << ssrc << std::endl;
s_hub * hub = func_context(ssrc);
auto iter = hub->v_list.begin();
while (iter != hub->v_list.end())
{
bool ret = (*iter)->send_data_need_protect(
1, 1, len, data_);
if (!ret)
{
hub->v_list.erase(iter++);
}
else
iter++;
}
}
else
v_context.post(yield);
}
});
#endif
void do_send(std::size_t length);
void do_judge_spspps(s_rtp_h264_context *ctx);
private:
udp::endpoint sender_endpoint_;
enum { max_length = 1500};
char data_[max_length];
};
//int main(int argc, char* argv[])
//{
// try
// {
// if (argc != 2)
// {
// std::cerr << "Usage: async_udp_echo_server <port>\n";
// return 1;
// }
//
// boost::asio::io_context io_context;
//
// server s(io_context, 6000);
//
// io_context.run();
// }
// catch (std::exception& e)
// {
// std::cerr << "Exception: " << e.what() << "\n";
// }
//
// return 0;
//}
udp 接收细节
void c_udpserver::do_receive()
{
udp::endpoint from;
v_socket.async_receive_from(asio::buffer(data_, max_length),
from, [this](boost::system::error_code ec, std::size_t len)
{
uint8_t *data = (uint8_t*)&(data_[0]);
if (len < 12) //if rtp payload length <12,the error occur
return;
if ((*data >> 6) != 2) /* RTP version number is 2*/
return;
if (*data & 0x20)
{
std::cout << "padding" << std::endl;
}
rtp_header *rtp = (rtp_header *)data;
//big(net) to little(home)
uint32_t ssrc = b2l(rtp->ssrc);
uint8_t payload = rtp->type;
s_rtp_h264_context *ctx = getctx(ssrc);
ctx->v_payload = payload;
switch (ctx->v_payload)
{
case 96:
live_rtp_unpack_h264(ctx, data, (int)len);
break;
case 97:
//live_rtp_unpack_aac(ctx, data, len);
break;
}
//0 :no frame 1:one frame
/*if (live_rtp_unpack(ctx, data, (int)len) == 1)
{
data_call(ctx);
}*/
do_receive();
});
}
解包函数
int c_udpserver::live_rtp_unpack_h264(s_rtp_h264_context *ctx, uint8_t *data, int inlen)
{
int jump = 0;
int len = 0;
uint8_t *buffer = rtp_payload(data, inlen, &len,ctx->v_last_ts , ctx->v_ssrc,
ctx->v_seq, ctx->v_payload);
if (len < 1) {
printf("nalu is null\n");
return -1;
}
uint8_t m = *(data + 1) & 0x80; //m!=1 then the data is end
/* H.264 depay */
uint8_t fragment = *buffer & 0x1F;
/* Frame manipulation */
if ((fragment > 0) && (fragment < 24)) {
//Add a start code here
//maybe single < MTU data, maybe sps or pps in here
switch (fragment)// == 0x07) //sps
{
case 0x07:
ctx->set_sps(buffer, len);
break;
case 0x08:
ctx->set_pps(buffer, len);
do_judge_spspps(ctx);
break;
default:
{
uint8_t *temp = ctx->v_buffer + ctx->v_len;
*temp = 0;
*(temp + 1) = 0;
*(temp + 2) = 0;
*(temp + 3) = 1;
ctx->v_len += 4;
memcpy(ctx->v_buffer + ctx->v_len, buffer, len);
ctx->v_len += len;
}
break;
}
//std::cout << " AAAAA ";
}
else if (fragment == 24) { /*18 STAP-A */
/* De-aggregate the NALs and write each of them separately */
/*STAP-A single time ,maybe sps pps in there ,like ffmpeg */
/**/
buffer++;
int tot = len - 1;
uint16_t psize = 0;
int is_add = 0;
while (tot > 0) {
memcpy(&psize, buffer, 2);
psize = b2l(psize);
buffer += 2;
tot -= 2;
/* Now we have a single NAL */
uint8_t nal = *(buffer) & 0x1F;
switch (nal)
{
case 0x07:
ctx->set_sps(buffer, psize);
break;
case 0x08:
ctx->set_pps(buffer, psize);
do_judge_spspps(ctx);
break;
default:
is_add = 1;
break;
}
if (is_add)
{
//data_call_1(ctx, buffer, psize);
#if 1 //this is copy to buffer
uint8_t *temp = ctx->v_buffer + ctx->v_len;
*temp = 0x00;
*(temp + 1) = 0x00;
*(temp + 2) = 0x00;
*(temp + 3) = 0x01;
ctx->v_len += 4;
memcpy(ctx->v_buffer + ctx->v_len, buffer, psize);
ctx->v_len += psize;
#endif
}
/* Go on */
buffer += psize;
tot -= psize;
}
//ctx->reset();
//std::cout << "STAP-A";
}
else if ((fragment == 28) || (fragment == 29)) {
/* fix me just for FU-A, not for FU-B */
uint8_t indicator = *buffer;
uint8_t header = *(buffer + 1);
jump = 2;
len -= 2;
if ((header & 0x80) && !(header &0x40)) {
/* First part of fragmented packet (S bit set) */
ctx->v_current_FU = 1;
uint8_t *temp = ctx->v_buffer + ctx->v_len;
*temp = 0x00;
*(temp + 1) = 0x00;
*(temp + 2) = 0x00;
*(temp + 3) = 0x01;
*(temp + 4) = (indicator & 0xE0) | (header & 0x1F);
ctx->v_len += 5;
memcpy(ctx->v_buffer + ctx->v_len, buffer + jump, len);
ctx->v_len += len;
}
else if (!(header & 0x80) && !(header & 0x40))
{
if (ctx->v_current_FU)
{
ctx->v_current_FU++;
memcpy(ctx->v_buffer + ctx->v_len, buffer + jump, len);
ctx->v_len += len;
}
else//分片丢失,RTP丢包
{
ctx->reset();
//ctx->v_current_FU = 0;
return -1;
}
}
else if (!(header & 0x80) && (header & 0x40)) {
/* Last part of fragmented packet (E bit set) */
if (ctx->v_current_FU) {
ctx->v_current_FU = 0;
memcpy(ctx->v_buffer + ctx->v_len, buffer + jump, len);
ctx->v_len += len;
//data_call(ctx);
//ctx->reset();
//return 0;
//printf("end of m:%d", m);
//cout << m <<" come here " <<"end "<< endl;
}
else
{
ctx->reset();
return -1;
}
}
else if ((header & 0x80) && (header & 0x40))
{//fragment error 分片错误,RTP丢包
cout << "error rtp" << endl;
ctx->reset();
return -1;
}
}
if (m > 0)
{
data_call(ctx);
//cout << "the ts is " << ctx->v_last_ts << endl;
ctx->reset();
}
else
{
//cout << "come here" << endl;
}
return 0;
}
转html5
三种方式:
1、一种是直接的es流
2、第二是转成fmp4
3 、转flv,使用flv.js去播放
三种都可以选择,
等待后续。。。