搭建极简GB28181 网守和网关服务器,建立AI推理和3d服务场景,然后开源代码(一)

1 目的

    这里说的开源并非使用开源现有的系统,而是自己写个系统去开源。

    为何要搭建极简GB服务,我们在公司里公司先后使用了nodejs ,go ,c# ,c++ 等等搭建了不同类型的GB服务,这个使用很多都可以搭建起来,也不像很多人说的那样,一定要tcp 才能通,udp 一样可以通,但是GB服务容易,难道网关就不能非常简单的搭建起来,满足我们的简单需求?

    首先是为了视频分析,使用GB28181 接入视频以后,后接AI服务来满足简单需求
搭建极简GB28181 网守和网关服务器,建立AI推理和3d服务场景,然后开源代码(一)_第1张图片

2、分析ps流

最简单的分析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流是标准的,则没有问题。

2 建立简单网关

    我们把它叫做simple gateway,其实是mediaserver,上面的开源地址已经有了, 视频流拉取到以后,我们只是用来上云,并且在服务端接到流以后,发送到AI 推理端进行视频推理,并且在3d数字孪生场景中插入我们的视频服务。
mediaserver 既然负责转协议,那在网络里面就是 网关,也是路由器。

3 如何足够简单?

    这个问题比较复杂了,因为设计一个产品,我们往往会陷入死胡同,往复杂里面设计。
1 建立一个足够简单的GB服务
有多简单,只有一个c++程序,却能够承受10k的并发,能做到吗,当然,必须要有足够的带宽支持。
2 GB服务和媒体网管合成。
如果在云上,我们是可以直接合成的,那么就需要把两者合成,直接使用c++程序,我测试了以下,使用最简单及其简化的c++ 程序写完最基础的代码,编译完就一个几百k的可执行文件,不依赖其他任何动态库。这个后面也会开源出来。

4、播放rtsp

建立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;
}

5 3d 场景

使用threejs 搭建,threejs足够简单,例子繁多,具体在下一章节里写了。

6 c# 或者nodejs建立网守

使用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;
        }
    }

7、播放器

这个下一章节说了

8 产品化

需要一个
1 产品经理
2 一个前端
3 一个后端
4 一个c++
5 一个nodejs 或者c# 人员
6 项目自由人

这样的团队建立起来以后,需要一个比较有经验的人来处理问题,我们把这个叫做项目跟随自由人,往往有这样的一个人,会减少百分之五十的团队人员。这个人需要有足够的经验。

你可能感兴趣的:(c++高级技巧,音视频和c++,java,物联网,服务器,网络,java)