• 3516DV300 推流


    3516DV300 推流

    基于ffmpeg将编码后的264文件(或者直接推流)推流出去,使用ffplay进行播放和验证。
    ffmpeg版本:N-109124-g63db6a02a7
    RELEASE:5.1.git

    ffmpeg udp文件推流 命令行

    这里用的是开发板编码出的码流,直接用vlc可以进行播放,但是用ffmpeg udp推流后就不能用vlc正常播放了,不知道啥原因。文件是海思例程保存的264文件。

    1. 推流
      地址和端口是远端的
    ffmpeg -re -i stream_chn0.h264 -vcodec copy -f mpegts udp://192.168.3.13:1234
    
    • 1
    1. 播放
      本地端口
    ffplay -protocol_whitelist "file,udp,rtp" -i udp://192.168.3.13:1234 -fflags nobuffer
    
    • 1

    ffmpeg udp文件推流 代码

    读取文件,并推流到远端端口。

    int file_udp_main()
    {
    	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
        AVStream *in_stream, *out_stream;
    	AVPacket pkt;
    	int ret, i;
    	int videoindex = -1;
    	int frame_index = 0;
    	int64_t start_time = 0;
        
    	const char *in_filename = "stream_chn0.h264";//输入URL(Input file URL)
    	const char *out_filename = "udp://192.168.3.13:1234";//输出 URL  udp	
     
     
    	// 打开输入文件
    	ifmt_ctx = NULL;
    	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL)) < 0)
    	{
    		return ret;
    	}
    
        ifmt_ctx->probesize = 1024*1024;
        ifmt_ctx->max_analyze_duration = 3000000; //最大分析3秒
        ifmt_ctx->flags |= AVFMT_FLAG_NOBUFFER; //不缓存, 减小直播延时
        
        ret = avformat_find_stream_info(ifmt_ctx, NULL);
        if (ret < 0)
        {
            printf("avformat_find_stream_info failed\n");
            goto end;
        }
     
        for(unsigned int i = 0; i < ifmt_ctx->nb_streams; i++)
        {
            if (ifmt_ctx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
            {
                //HTTP—FLV只支持 AAC和MP3 音频格式
                if ((ifmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_AAC
                    && ifmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_MP3)
                    || ifmt_ctx->streams[i]->codecpar->sample_rate == 0)
                {
                    hflv_audio = false;
                }
                else
                {
                    hflv_audio = true;
                }
            }
            else if (ifmt_ctx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
            {
                //HTTP—FLV只支持 h264 视频格式
                if (ifmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_H264)
                {
                    goto end;
                }
     
                videoindex = i;
            }
        }
     
        if (videoindex == -1)
        {
            goto end;
        }
     
    	av_dump_format(ifmt_ctx, 0, in_filename, 0);
     
    
        avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);
        if (!ofmt_ctx)
        {
            printf( "Could not create output context\n");
            goto end;
        }
        printf("avformat_alloc_output_context2\n");
     
        //将输入流音视频编码信息复制到输出流中
        for (unsigned int i = 0; i < ifmt_ctx->nb_streams; i++)
        {
            if (!hflv_audio)
            {
                if(ifmt_ctx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
                {
                    continue;
                }
            }
     
            AVStream *in_stream = ifmt_ctx->streams[i];
            AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
            if (!out_stream)
            {
                goto end;
            }
            if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
            {
                goto end;
            }
     
            out_stream->codecpar->codec_tag = 0;
        }
        printf("copy codec context \n");
     
        av_dump_format(ofmt_ctx, 0, out_filename, 1);
    
    
         if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
        {
            ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
            if (ret < 0)
            {
                printf( "Could not open output URL '%s'", out_filename);
                goto end;
            }
            printf("avio_open \n");
        }
     
        ret = avformat_write_header(ofmt_ctx, NULL);
        if (ret < 0)
        {
            printf( "Error occurred when opening output URL\n");
            goto end;
        }
     
        printf("start push stream \n");
     
    
        while (1)
        {
            //获取每一帧数据
            ret = av_read_frame(ifmt_ctx, &pkt);
            if (ret < 0)
            {
                break;
            }
     
            if (!hflv_audio && pkt.stream_index != videoindex)
            {
                av_packet_unref(&pkt);
                continue;
            }
     
            in_stream = ifmt_ctx->streams[pkt.stream_index];
     
            if (!hflv_audio && pkt.stream_index == videoindex)
            {
                out_stream = ofmt_ctx->streams[0];
                pkt.stream_index = 0;
            }
            else
            {
                out_stream = ofmt_ctx->streams[pkt.stream_index];
            }
     
            if (pkt.pts == AV_NOPTS_VALUE)
            {
                //Write PTS
                AVRational time_base1 = in_stream->time_base;
                //Duration between 2 frames (us)
                int64_t calc_duration = (int64_t)((double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate));
                //Parameters
                pkt.pts = (int64_t)((double)(frame_index * calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE));
                pkt.dts = pkt.pts;
                pkt.duration = (int64_t)((double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE));
            }
     
            //指定时间戳
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                                       (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                                       (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.duration = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
     
            if (pkt.stream_index == videoindex)
            {            
                //printf("Send %8d video frames to output URL, [%d]\n",frame_index, pkt.flags);
                frame_index++;
            }
     
            ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
     
            if (ret < 0)
            {
                printf("Error muxing packet.error code %d\n", ret);
                break;
            }
     
            //释放 packet,否则会内存泄露
            av_packet_unref(&pkt);
            
            // 添加延时,不然很快就播放完了
            av_usleep(40000);
        }
     
        av_write_trailer(ofmt_ctx);
     
    end:
     
        // 该函数会释放用户自定义的IO buffer
        // 上面不再释放,否则会corrupted double-linked list
        avformat_close_input(&ifmt_ctx);
        avformat_free_context(ifmt_ctx);
     
        if (ofmt_ctx && !(ofmt_ctx->flags & AVFMT_NOFILE))
            avio_close(ofmt_ctx->pb);
     
        if (ofmt_ctx)
        {
            avformat_free_context(ofmt_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
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214

    效果还可以,代码里面要加一个延时,不然文件一下子就读完了。
    在这里插入图片描述

    ffmpeg udp内存推流 代码

    采用回调函数的方式读取文件,然后进行推流。和文件推流类似,区别在于使用了回调函数进行数据的读取(ffmpeg会不断调用该函数进行数据的获取)。

    
    #define READ_DATA_LEN (1920*1080)
    #define true 1
    #define false 0
    int fd;
    int hflv_audio = 0;
    
    
    int read_init(char *filename)
    {
        fd = open(filename,O_RDONLY);
        if(fd < 0)
        {
            perror("open file failed");
            return -1;
        }
        return 1;
    }
    
    int read_packet(void *opaque, uint8_t *buf, int buf_size)
    {
       return  read(fd,buf,buf_size);
    }
    
    
    
    
    int mem_udp_main()
    {
        char *in_filename = "stream_chn0.h264";
        char *out_filename = "udp://192.168.3.13:1234";
    
        read_init(in_filename);
        int ret = 0;
        int videoindex = -1;
        AVPacket pkt;
        int frame_index = 0;
        AVStream *in_stream, *out_stream;
     
        AVFormatContext *ictx = NULL;
        const AVInputFormat* ifmt = NULL;
     
        AVFormatContext *octx = NULL;
        AVOutputFormat *ofmt = NULL;
        AVIOContext *avio = NULL;
    
    
        unsigned char * iobuffer = (unsigned char *)av_malloc(READ_DATA_LEN);
        avio = avio_alloc_context(iobuffer, READ_DATA_LEN, 0, NULL, read_packet, NULL, NULL);
        if (!avio)
        {
            printf( "avio_alloc_context for input failed\n");
            goto end;
        }
     
        //探测流封装格式
        ret = av_probe_input_buffer(avio, &ifmt, "", NULL, 0, 0);
        if (ret < 0)
        {
            printf("av_probe_input_buffer failed\n");
            goto end;
        }
        printf("av_probe_input_buffer format:%s[%s]\n",ifmt->name, ifmt->long_name);
     
        ictx = avformat_alloc_context();
        ictx->pb = avio;
        ictx->flags=AVFMT_FLAG_CUSTOM_IO;
     
        ret = avformat_open_input(&ictx, "", NULL, NULL);
        if (ret < 0)
        {
            printf("avformat_open_input failed\n");
            goto end;
        }
     
        //获取音频视频的信息
        ictx->probesize = 1024*1024;
        ictx->max_analyze_duration = 3000000; //最大分析3秒
        ictx->flags |= AVFMT_FLAG_NOBUFFER; //不缓存, 减小直播延时
     
        ret = avformat_find_stream_info(ictx, NULL);
        if (ret < 0)
        {
            printf("avformat_find_stream_info failed\n");
            goto end;
        }
     
        for(unsigned int i = 0; i < ictx->nb_streams; i++)
        {
            if (ictx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
            {
                //HTTP—FLV只支持 AAC和MP3 音频格式
                if ((ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_AAC
                    && ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_MP3)
                    || ictx->streams[i]->codecpar->sample_rate == 0)
                {
                    hflv_audio = false;
                }
                else
                {
                    hflv_audio = true;
                }
            }
            else if (ictx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
            {
                // //HTTP—FLV只支持 h264 视频格式
                // if (ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_H264)
                // {
                //     goto end;
                // }
     
                videoindex = i;
            }
        }
     
        if (videoindex == -1)
        {
            goto end;
        }
     
        av_dump_format(ictx, 0, "", 0);
     
        avformat_alloc_output_context2(&octx, NULL, "mpegts", out_filename);
        if (!octx)
        {
            printf( "Could not create output context\n");
            goto end;
        }
        printf("avformat_alloc_output_context2\n");
     
        //将输入流音视频编码信息复制到输出流中
        for (unsigned int i = 0; i < ictx->nb_streams; i++)
        {
            if (!hflv_audio)
            {
                if(ictx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
                {
                    continue;
                }
            }
     
            AVStream *in_stream = ictx->streams[i];
     
            AVStream *out_stream = avformat_new_stream(octx, NULL);
            if (!out_stream)
            {
                goto end;
            }
     
            if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
            {
                goto end;
            }
     
            out_stream->codecpar->codec_tag = 0;
        }
        printf("copy codec context \n");
     
        av_dump_format(octx, 0, out_filename, 1);
     
        //打开输出URL,准备推流
        if (!(octx->oformat->flags & AVFMT_NOFILE))
        {
            ret = avio_open(&octx->pb, out_filename, AVIO_FLAG_WRITE);
            if (ret < 0)
            {
                printf( "Could not open output URL '%s'", out_filename);
                goto end;
            }
            printf("avio_open \n");
        }
     
        ret = avformat_write_header(octx, NULL);
        if (ret < 0)
        {
            printf( "Error occurred when opening output URL\n");
            goto end;
        }
     
        printf("start push stream \n");
     
        while (1)
        {
            //获取每一帧数据
            ret = av_read_frame(ictx, &pkt);
            if (ret < 0)
            {
                break;
            }
     
            if (!hflv_audio && pkt.stream_index != videoindex)
            {
                av_packet_unref(&pkt);
                continue;
            }
     
            in_stream = ictx->streams[pkt.stream_index];
     
            if (!hflv_audio && pkt.stream_index == videoindex)
            {
                out_stream = octx->streams[0];
                pkt.stream_index = 0;
            }
            else
            {
                out_stream = octx->streams[pkt.stream_index];
            }
     
            if (pkt.pts == AV_NOPTS_VALUE)
            {
                //Write PTS
                AVRational time_base1 = in_stream->time_base;
                //Duration between 2 frames (us)
                int64_t calc_duration = (int64_t)((double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate));
                //Parameters
                pkt.pts = (int64_t)((double)(frame_index * calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE));
                pkt.dts = pkt.pts;
                pkt.duration = (int64_t)((double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE));
            }
     
            //指定时间戳
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                                       (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                                       (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.duration = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
     
            if (pkt.stream_index == videoindex)
            {            
                //printf("Send %8d video frames to output URL, [%d]\n",frame_index, pkt.flags);
                frame_index++;
            }
     
            ret = av_interleaved_write_frame(octx, &pkt);
     
            if (ret < 0)
            {
                printf("Error muxing packet.error code %d\n", ret);
                break;
            }
    
            //释放 packet,否则会内存泄露
            av_packet_unref(&pkt);
    
            av_usleep(40000);
        }
     
        av_write_trailer(octx);
     
    end:
     
        // 该函数会释放用户自定义的IO buffer
        // 上面不再释放,否则会corrupted double-linked list
        avformat_close_input(&ictx);
        avformat_free_context(ictx);
     
        if (octx && !(ofmt->flags & AVFMT_NOFILE))
            avio_close(octx->pb);
     
        if (octx)
        {
            avformat_free_context(octx);
        }
    
    }
    
    • 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
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266

    效果感觉和直接文件推流差不多:
    在这里插入图片描述

    开发板推流

    这里的主要难点是编码数据的获取和ffmpeg库(需要交叉编译)的链接。

    1. 编码数据的获取
      例程里面已经有了数据的获取例程,把它保存到一个队列里面。
      使用有名管道。
    • 数据写入:
      根据文件名创建有名管道,将编码数据写入。
      在这里插入图片描述
      在这里插入图片描述

    • 数据读取
      直接读取文件即可。
      和上面的从内存中读取文件一模一样。
      在这里插入图片描述

    1. ffmpeg静态库的链接
      这一步要修改Makefile,仿照SDK中的例子进行修改:
      文件:
      在这里插入图片描述
      Makefile 修改:
      在这里插入图片描述

    2. 效果
      能推流出来,但是延时不行,而且越来越大(无线和有线都是这样)。
      在这里插入图片描述

    3. 延时问题
      设置ffmpeg推流的帧率为30帧,无线局域网测试延时在1s左右。

     for(unsigned int i = 0; i < ictx->nb_streams; i++)
        {
            if (ictx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
            {
                //HTTP—FLV只支持 AAC和MP3 音频格式
                if ((ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_AAC
                    && ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_MP3)
                    || ictx->streams[i]->codecpar->sample_rate == 0)
                {
                    hflv_audio = false;
                }
                else
                {
                    hflv_audio = true;
                }
            }
            else if (ictx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
            {
                // //HTTP—FLV只支持 h264 视频格式
                // if (ictx->streams[i]->codecpar->codec_id != AV_CODEC_ID_H264)
                // {
                //     goto end;
                // }
     
                videoindex = i;
            }
            av_opt_set(ictx->streams[i]->priv_data,"tune","zerolatency",0);
            ictx->streams[i]->avg_frame_rate.num = 30;
            ictx->streams[i]->r_frame_rate.num = 30;
            ictx->streams[i]->r_frame_rate.den = 1;
            
        }
    
    • 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

    TODO

    1. 优化推流延时
    2. 提升画质(提高码率)
    3. 经过摄像头的图像有变形

    参考文章

    1. ffmpeg-pusher
    2. 使用FFmpeg命令行进行UDP、RTP推流(H264、TS),ffplay接收
    3. 基于FFmpeg的推流器(UDP推流)
    4. ffmpeg内存读取数据推流rtmp
  • 相关阅读:
    一个简单的HTML 个人网页
    使用ServiceSelf解决.NET应用程序做服务的难题
    springboot整合mybatis实现增删改查
    【目标检测】two-stage------SSP-Net浅析-2014
    海康摄像机取流RTSP地址规则说明
    公司预防机密信息泄露的有效措施
    什么叫硬编码?如何避免硬编码
    servlet -> spring-mvc -> spring-boot-> spring-security目录
    代码随想录算法训练营第四十一天【动态规划part03】 | 343. 整数拆分、96.不同的二叉搜索树
    Verilog 显示任务($display, $write, $strobe, $monitor)
  • 原文地址:https://blog.csdn.net/weixin_45090728/article/details/128009361