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


    1 目的

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

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

        首先是为了视频分析,使用GB28181 接入视频以后,后接AI服务来满足简单需求
    在这里插入图片描述

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    以上代码输入一个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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    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;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    7、播放器

    这个下一章节说了

    8 产品化

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

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

  • 相关阅读:
    k8s pod 绑核
    Python断言(assert)
    Unity团结引擎使用总结
    机器学习中的偏差漂移:挑战与缓解
    C++socket网络编程实战http服务器(支持php)(上)
    动态代理(cglib与jdk)
    【TensorFlow2 之015】 在 TF 2.0 中实现 AlexNet
    链栈的基本操作c语言
    leetcode 1328.破坏回文串
    一道概率论趣题-直觉与数学原理是否吻合呢?
  • 原文地址:https://blog.csdn.net/qianbo042311/article/details/125446558