这里说的开源并非使用开源现有的系统,而是自己写个系统去开源。
为何要搭建极简GB服务,我们在公司里公司先后使用了nodejs ,go ,c# ,c++ 等等搭建了不同类型的GB服务,这个使用很多都可以搭建起来,也不像很多人说的那样,一定要tcp 才能通,udp 一样可以通,但是GB服务容易,难道网关就不能非常简单的搭建起来,满足我们的简单需求?
首先是为了视频分析,使用GB28181 接入视频以后,后接AI服务来满足简单需求
最简单的分析ps流方法是使用ffmpeg直接解析出流,流媒体服务器自己做,做了一个基本的流媒体服务器,开源在gitee,目前是第一版,需要读者自己优化,地址在下方
https://gitee.com/guanzhi0319/mediaserver
以上建立服务器是一种选择,另外在网关里面我们再可以有更简单的方式
分析ps流方法
struct buffer_data {
uint8_t* ptr;
size_t size;
};
static int read_packet(void* opaque, uint8_t* buf, int buf_size)
{
struct buffer_data* bd = (struct buffer_data*)opaque;
buf_size = FFMIN(buf_size, bd->size);
if (!buf_size)
return AVERROR_EOF;
printf("ptr:%p size:%zu\n", bd->ptr, bd->size);
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
return buf_size;
}
int main(int argc, char* argv[])
{
AVFormatContext* fmt_ctx = NULL;
AVIOContext* avio_ctx = NULL;
uint8_t* buffer = NULL, * avio_ctx_buffer = NULL;
size_t buffer_size, avio_ctx_buffer_size = 4096;
char* input_filename = NULL;
int ret = 0;
struct buffer_data bd = { 0 };
input_filename =(char*)"D:/ps file/ps0.264";
/* slurp file content into buffer */
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
return -1;
/* fill opaque structure used by the AVIOContext read callback */
bd.ptr = buffer;
bd.size = buffer_size;
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
return -1;
}
avio_ctx_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
return -1;
}
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx) {
ret = AVERROR(ENOMEM);
return -1;
}
fmt_ctx->pb = avio_ctx;
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
std::cout << "Could not open input" << std::endl;
return -1;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
std::cout << "Could not find stream information" << std::endl;
return -1;
}
av_dump_format(fmt_ctx, 0, input_filename, 0);
AVPacket pkt;
av_read_frame(fmt_ctx, &pkt);
avformat_close_input(&fmt_ctx);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (avio_ctx) {
av_freep(&avio_ctx->buffer);
av_freep(&avio_ctx);
}
av_file_unmap(buffer, buffer_size);
if (ret < 0) {
std::cout << "Error occurred!" << std::endl;
return 1;
}
return 0;
}
以上代码输入一个ps文件,是可以输出一帧的,如果ps流是标准的,则没有问题。
我们把它叫做simple gateway,其实是mediaserver,上面的开源地址已经有了, 视频流拉取到以后,我们只是用来上云,并且在服务端接到流以后,发送到AI 推理端进行视频推理,并且在3d数字孪生场景中插入我们的视频服务。
mediaserver 既然负责转协议,那在网络里面就是 网关,也是路由器。
这个问题比较复杂了,因为设计一个产品,我们往往会陷入死胡同,往复杂里面设计。
1 建立一个足够简单的GB服务
有多简单,只有一个c++程序,却能够承受10k的并发,能做到吗,当然,必须要有足够的带宽支持。
2 GB服务和媒体网管合成。
如果在云上,我们是可以直接合成的,那么就需要把两者合成,直接使用c++程序,我测试了以下,使用最简单及其简化的c++ 程序写完最基础的代码,编译完就一个几百k的可执行文件,不依赖其他任何动态库。这个后面也会开源出来。
建立rtsp链接当然有很多方法,我们可以自己写rtsp客户端,这是一种方式,既然这里要求最简单,那就是使用ffmpeg直接拉流,以下代码是例子。
int ffmpeg_rtsp_client()
{
// Allocate an AVFormatContext
AVFormatContext* format_ctx = avformat_alloc_context();
// open rtsp: Open an input stream and read the header. The codecs are not opened
const char* url = "rtsp://127.0.0.1/out.264";
int ret = -1;
AVDictionary* opts = NULL;
av_dict_set(&opts, "rtsp_transport","tcp", 0);
av_dict_set(&opts, "buffer_size", "1048576", 0);
av_dict_set(&opts, "fpsprobesize", "2", 0);
av_dict_set(&opts, "analyzeduration", "5000000", 0);
//设置 最大延迟
av_dict_set(&opts, "max_delay", "500", 0);
//rtmp、rtsp延迟控制到最小
av_dict_set(&opts, "fflags", "nobuffer", 0);
//设置 阻塞超时,否则可能在流断开时连接发生阻塞
av_dict_set(&opts, "stimeout", "3000000", 0);
ret = avformat_open_input(&format_ctx, url, nullptr, &opts);
if (ret != 0) {
fprintf(stderr, "fail to open url: %s, return value: %d\n", url, ret);
return -1;
}
// Read packets of a media file to get stream information
ret = avformat_find_stream_info(format_ctx, nullptr);
if (ret < 0) {
fprintf(stderr, "fail to get stream information: %d\n", ret);
return -1;
}
// audio/video stream index
int video_stream_index = -1;
int audio_stream_index = -1;
fprintf(stdout, "Number of elements in AVFormatContext.streams: %d\n", format_ctx->nb_streams);
for (int i = 0; i < format_ctx->nb_streams; ++i) {
const AVStream* stream = format_ctx->streams[i];
fprintf(stdout, "type of the encoded data: %d\n", stream->codecpar->codec_id);
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
fprintf(stdout, "dimensions of the video frame in pixels: width: %d, height: %d, pixel format: %d\n",
stream->codecpar->width, stream->codecpar->height, stream->codecpar->format);
}
else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_index = i;
fprintf(stdout, "audio sample format: %d\n", stream->codecpar->format);
}
}
if (video_stream_index == -1) {
fprintf(stderr, "no video stream\n");
return -1;
}
if (audio_stream_index == -1) {
fprintf(stderr, "no audio stream\n");
}
int cnt = 0;
AVPacket pkt;
while (1) {
if (++cnt > 100) break;
ret = av_read_frame(format_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "error or end of file: %d\n", ret);
continue;
}
if (pkt.stream_index == video_stream_index) {
fprintf(stdout, "video stream, packet size: %d\n", pkt.size);
}
if (pkt.stream_index == audio_stream_index) {
fprintf(stdout, "audio stream, packet size: %d\n", pkt.size);
}
av_packet_unref(&pkt);
}
avformat_free_context(format_ctx);
return 0;
}
使用threejs 搭建,threejs足够简单,例子繁多,具体在下一章节里写了。
使用nodejs 和 c# 的好处依然是足够简单,使用java也是可以,但没有以上两种语言简单。建立http服务的时候,如果使用nodejs express,是非常快速方便的。使用c# 来做的时候,也是够简单快速,并且跨平台,也可以直接在平台上直接编译另外一个平台的可执行文件,这一点和go语言类似,语法又和java类似,以下是在gb28181 建立服务的时候,需要使用本地IP地址,代码如下所示。上云的时候我们需要使用本地的IP地址而不是外网的IP地址。不要奇怪,是真的,外网的地址只是在摄像头或者nvr里面使用。
class IPUtil
{
public static string IPV4()
{
string ipv4 = GetLocalIPv4(NetworkInterfaceType.Wireless80211);
if (ipv4 == "")
{
ipv4 = GetLocalIPv4(NetworkInterfaceType.Ethernet);
if (ipv4 == "")
{
ipv4 = GetLoacalIPMaybeVirtualNetwork();
}
}
return ipv4;
}
private static string GetLoacalIPMaybeVirtualNetwork()
{
string name = Dns.GetHostName();
IPAddress[] ipadrlist = Dns.GetHostAddresses(name);
foreach (IPAddress ipa in ipadrlist)
{
if (ipa.AddressFamily == AddressFamily.InterNetwork)
{
return ipa.ToString();
}
}
return "没有连接网络,请链接网络后重试!";
}
public static string GetLocalIPv4(NetworkInterfaceType _type)
{
string output = "";
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
{
//Console.WriteLine(item.NetworkInterfaceType.ToString());
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
}
这个下一章节说了
需要一个
1 产品经理
2 一个前端
3 一个后端
4 一个c++
5 一个nodejs 或者c# 人员
6 项目自由人
这样的团队建立起来以后,需要一个比较有经验的人来处理问题,我们把这个叫做项目跟随自由人,往往有这样的一个人,会减少百分之五十的团队人员。这个人需要有足够的经验。