• rtsp 和 rtmp 推流(一)


    rtsp 推流和rtmp推流

    以下为基本推流示意图

    在这里插入图片描述

    1 rtsp 推流

    首先,rtsp 协议有什么好处呢? 相比与rtmp 协议来说,他更为复杂,rtmp协议的好处是比较单一,就是基于tcp协议做的,当然,完全可以修改为udp 协议来做,不过,我们最需要的是实用,如果做创新去使用udp 来做rtmp协议,可以,但是不那么直接,完全可以创新一种协议而不用rtmp,rtsp。 rtsp既可以使用tcp,也可以使用udp协议,所以创新rtmp使用udp,我简单地认为,不如直接使用rtsp协议了。rtsp协议是国际标准,里面使用了sdp协议, rtp协议,rtcp协议,每一样都包含了很多需要学习的东西, sdp协议叫做会话描述协议, rtp协议叫做实时传输协议,rtcp为实时传输控制协议。

    1.1 sdp协议

    SDP全称是Session Description Protocol,翻译过来就是描述会话的协议。主要用于两个会话实体之间的媒体协商。这个知识要读者自行增加一些
    v 表示版本;
    o 表示用户、会话ID、会话版本、网络类型、地址类型、地址;
    s 会话名称 session的简写
    u 会话信息地址;
    e Email 地址;
    p 电话信息;
    c 连接信息 (IN IP4 0.0.0.0) 如果包含在所有媒体中,则不需要该字段;
    b 带宽限制;
    b=as:150;
    b=RR:11250;
    b=RS:2750;
    m= (media name and transport address) # 媒体名称和传输地址
    i=* (media title) # 媒体标题
    c=* (connection information - optional if included at session-level) # 连接信息 — 如果包含在会话层则该字段可选
    k=* (encryption key) # 加密密钥
    a=* (zero or more media attribute lines) #0 个或多个会话属性行

    1.2 rtp协议

    实时传输协议,最重要的就是理解 包大小和mtu大小,理解了才能知道为什么这个才能是实时传输而不是其他协议,按理来说,我自己做的协议也可以实时传输啊,不是这样的,要理解透彻才行

    1.3 rtcp协议

    实时传输控制协议,这个也比较麻烦,实时上,我一直认为这个协议用处不大,但是你偏偏要理解,为什么,很多服务器没有这个,就相当于断了心跳,就会停止传输,尤其是udp 上的 rtsp, 信令虽然是tcp,但是传输层使用的是udp,这样什么时候断了不知道,所以就会加上这个协议,问题是加上rtcp以后又加重了网络传输,cpu 和 带宽同时加重,这是个矛盾,rtmp 没有这个问题。

    2、rtmp 协议

    读者自己制作了rtmp 协议的服务器,在理解的过程中,他的trunk概念本身具有rtp协议的特性, 而且由于简单,就是基于tcp,按照ip 不分包的trunk 来说,小于64k 大小就能,如果按照存储来说,完全可以做到4096,也就是4k来分包,适应操作系统的存储块大小。我这里不细说,细说无益,读者必须自己去深刻理解。

    3 、code

    如果要完整地自己写完各种协议,需要的时间不言而喻,我们可以简单地先调用ffmpeg的方式去推流和取流,客户端可以这样,但是服务端就不能了,服务端必须自己实现这些协议和解析协议。

    bool RGB_2_YUV(){
        
        //BGR 转 YUV
        swrCtxBGRA2YUV = sws_getContext(
            cap_w, cap_h, AV_PIX_FMT_BGR,
            cap_w, cap_h, AV_PIX_FMT_YUV420P,
            SWS_BICUBIC,
            NULL, NULL, NULL
            );
    
        //创建BGRA帧
        frame_bgra = av_frame_alloc();
        frame_bgra->format = AV_PIX_FMT_BGR;
        frame_bgra->width = cap_w;
        frame_bgra->height = cap_h;
        if (av_frame_get_buffer(frame_bgra, 24) < 0) {
            printf("Failed: av_frame_get_buffer\n");
            return false;
        }
        frame_bgra->data[0] = cropImage;
        
    
        //YUV帧
        frame_yuv = av_frame_alloc();
        frame_yuv->width = cap_w;
        frame_yuv->height = cap_h;
        frame_yuv->format = AV_PIX_FMT_YUV420P;
    
        //
        uint8_t *picture_buf = (uint8_t *)av_malloc(cap_w * cap_h * 1.5);
        if (av_image_fill_arrays(frame_yuv->data, frame_yuv->linesize, picture_buf, AV_PIX_FMT_YUV420P, cap_w, cap_h, 1) < 0){
            printf("Failed: av_image_fill_arrays\n");
            return false;
        }
        return true;
    }
    
    
    
    //BGR 转 YUV
        if (sws_scale(swrCtxBGR2YUV,
            frame_bgra->data, frame_bgra->linesize,
            0, cap_h,
            frame_yuv->data, frame_yuv->linesize) < 0)
        {
            printf("fail\n");
            return;
        }
    
        frame_yuv->pts = av_gettime();
    
    frame_yuv->pts设为当前的时间戳,注意rtsp协议不能这样做,rtsp协议里的rtp协议必须保证是90000的时间基。
    
    2. H264编码
    
    3. bool YUV_to_H264(){
        //寻找编码器
        codec_h264 = avcodec_find_encoder(AV_CODEC_ID_H264);
        if (!codec_h264){
            printf("Fail: avcodec_find_encoder\n");
            return false;
        }
    
        //编码器上下文
        codec_ctx_h264 = avcodec_alloc_context3(codec_h264);
        if (!codec_ctx_h264){
            printf("Fail: avcodec_alloc_context3\n");
            return false;
        }
        codec_ctx_h264->pix_fmt = AV_PIX_FMT_YUV420P;
        codec_ctx_h264->codec_type = AVMEDIA_TYPE_VIDEO;
        codec_ctx_h264->width = cap_w;
        codec_ctx_h264->height = cap_h;
        codec_ctx_h264->channels = 3;
        codec_ctx_h264->time_base = { 1, fps};
        codec_ctx_h264->gop_size = fps;   //关键帧(I帧)的距离
        codec_ctx_h264->max_b_frames = 0;
        codec_ctx_h264->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;   //添加PPS、SPS
        
        av_opt_set(codec_ctx_h264->priv_data, "preset", "ultrafast", 0);    //快速编码,但会损失质量
        av_opt_set(codec_ctx_h264->priv_data, "tune", "zerolatency", 0);  
    
    
    
        //打开编码器
        if (avcodec_open2(codec_ctx_h264, codec_h264, NULL) < 0){
            printf("Fail: avcodec_open2\n");
            return false;
        }
    
        pkt_h264 = av_packet_alloc();
    
        return true;
    }
    
    • 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
     ret = avcodec_send_frame(codec_ctx_h264, frame_yuv);
        if (ret < 0){
            printf("send frame fail\n");
            return;
        }
    
        while (ret >= 0)        {
            ret = avcodec_receive_packet(codec_ctx_h264, pkt_h264);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
                break;
            }
    
            if (ret < 0){
                printf("Error during encoding\n");
                break;
            }
    
            pkt_h264->stream_index = videoindex;
            //printf("pkt_h264 timestamp = %d\n", pkt_h264->pts);
    
            if (av_interleaved_write_frame(fmt_ctx, pkt_h264) < 0) {
                printf("Error muxing packet\n");
            }
    
            av_packet_unref(pkt_h264);
        }
    
    • 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
    1. AAC编码
      rtsp由于容器化比较全面,使用了sdp协议,而rtmp充分简单,就是支持mp3和aac,所以rtmp其实是首选协议,简单。注意ffmpeg编码选择FLTP,目前的版本就是支持这个。
    bool PCM_to_AAC(){
    
        codec_aac = avcodec_find_encoder(AV_CODEC_ID_AAC);
        if (!codec_aac) {
            printf("avcodec_find_encoder fail\n");
            return false;
        }
    
        codec_ctx_aac = avcodec_alloc_context3(codec_aac);
        if (!codec_ctx_aac) {
            printf("avcodec_find_encoder fail\n");
            return false;
        }
        codec_ctx_aac->sample_fmt = AV_SAMPLE_FMT_FLTP;
        codec_ctx_aac->codec_type = AVMEDIA_TYPE_AUDIO;
        codec_ctx_aac->channels = channels;
        codec_ctx_aac->channel_layout = av_get_default_channel_layout(channels);
        codec_ctx_aac->sample_rate = sample_rete;
    
        if (avcodec_open2(codec_ctx_aac, codec_aac, NULL) < 0) {
            printf("open codec fail\n");
            return false;
        }
    
        swrCtxS162FLTP = swr_alloc_set_opts(NULL,
            codec_ctx_aac->channel_layout, codec_ctx_aac->sample_fmt, codec_ctx_aac->sample_rate,
            codec_ctx_aac->channel_layout, AV_SAMPLE_FMT_S16, codec_ctx_aac->sample_rate,
            0, 0);
    
        if (!swrCtxS162FLTP)
        {
            printf("swr_alloc_set_opts error\n");
            return false;
        }
        if (swr_init(swrCtxS162FLTP) < 0) {
            printf("open resample fail\n");
            return false;
        }
    
        frame_pcm = av_frame_alloc();
        frame_pcm->nb_samples = nbSamples_; //一帧音频存放的样本数量
        frame_pcm->format = codec_ctx_aac->sample_fmt;
        frame_pcm->channels = codec_ctx_aac->channels;
        frame_pcm->channel_layout = codec_ctx_aac->channel_layout;
    
        if (av_frame_get_buffer(frame_pcm, 0) < 0) {
            printf("av_frame_get_buffer error\n");
            return false;
        }
    
        pkt_aac = av_packet_alloc();
    
        return true;
    }
    
    • 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

    pcm_buff是包含pcm数据的数组

    const uint8_t *pcm[1];
            pcm[0] = pcm_buff;
            int len = swr_convert(swrCtxS162FLTP,
                frame_pcm->data, frame_pcm->nb_samples,
                pcm, nbSamples_);
    
            if (len <= 0) {
                printf("---Encodec:PCM->AAC--- swr_convert fail \n");
                return;
            }
    
            frame_pcm->pts = av_gettime();
    
            //printf("channels = %d\n", frame_pcm->channels);
            //printf("framePCM->linesize = %6d %6d\n", frame_pcm->linesize[0], frame_pcm->linesize[1]);
    
            //AAC编码
            int ret = avcodec_send_frame(codec_ctx_aac, frame_pcm);
            if (ret < 0){
                printf("send frame fail\n");
                return;
            }
    
            ret = avcodec_receive_packet(codec_ctx_aac, pkt_aac);
            
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
                return;
            }
    
            if (ret < 0){
                printf("Error during encoding\n");
                return;
            }
    
            pkt_aac->stream_index = audioindex;
            //printf("pkt_aac timestamp = %d\n", pkt_aac->pts);
    
            if (av_interleaved_write_frame(fmt_ctx, pkt_aac) < 0) {
                printf("Error muxing packet\n");
            }
            av_packet_unref(pkt_aac);
    
    • 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
    1. 推流器
      rtsp 一定要使用tcp协议传输,原因特别重要,1 很多服务器支持的是tcp,2 要上外网,只能是tcp,理解透彻, rtmp本身使用tcp。
    bool init_rtsp_pusher(){
        
        //RTSP
        if (avformat_alloc_output_context2(&fmt_ctx, NULL, "RTSP", RTSP_URL.c_str()) < 0){
            printf("Fail: avformat_alloc_output_context2\n");
            return false;
        }
    
        //传输层使用tcp协议
        av_opt_set(fmt_ctx->priv_data, "rtsp_transport", "tcp", 0);
    
        //没有数据会等待max_interleave_delta微秒,这个最好不要使用,不是很好
        fmt_ctx->max_interleave_delta = 1000000;
    
        //输出视频流
        AVStream *video_s = avformat_new_stream(fmt_ctx, codec_h264);
        if (!video_s){
            printf("Fail: avformat_new_stream\n");
            return false;
        }
        video_s->time_base = { 1, fps };
        videoindex = video_s->id = fmt_ctx->nb_streams - 1;  
    
        //复制AVCodecContext的设置
        if (avcodec_copy_context(video_s->codec, codec_ctx_h264) < 0) {
            printf("Fail: avcodec_copy_context\n");
            return false;
        }
        video_s->codec->codec_tag = 0;
        if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            video_s->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        
        avcodec_parameters_from_context(video_s->codecpar, codec_ctx_h264);
            //输出音频流
            AVStream *audio_s = avformat_new_stream(fmt_ctx, codec_ctx_aac->codec);
            if (!audio_s){
                printf("Fail: avformat_new_stream\n");
                return false;
            }
            audio_s->time_base = { 1, 25 };
            audioindex = audio_s->id = fmt_ctx->nb_streams - 1;
    
            //复制AVCodecContext的设置
            if (avcodec_copy_context(audio_s->codec, codec_ctx_aac) < 0) {
                printf("Fail: avcodec_copy_context\n");
                return false;
            }
            audio_s->codec->codec_tag = 0;
            if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
                audio_s->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            avcodec_parameters_from_context(audio_s->codecpar, codec_ctx_aac);
        
            //printf("fmt_ctx nb_streams = %d\n", fmt_ctx->nb_streams);
    
        av_dump_format(fmt_ctx, 0, fmt_ctx->filename, 1);
        if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {    //???
            //打开输出URL(Open output URL)
            if (avio_open(&fmt_ctx->pb, fmt_ctx->filename, AVIO_FLAG_WRITE) < 0) {
                printf("Fail: avio_open('%s')\n", fmt_ctx->filename);
                return false;
            }
        }
        return true;
    }
    
    • 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

    4、makefile

    cmake_minimum_required(VERSION 3.5)
    
    project(rtsp LANGUAGES CXX)
    
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    
    set(CMAKE_AUTOUIC ON)
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    find_package(Qt5Core)
    
    set(FFMPEG_PREFIX_PATH /path/to/FFmpeg-n5.0.1/install)
    
    include_directories(
        ${FFMPEG_PREFIX_PATH}/include/
    )
    
    link_directories(
        ${FFMPEG_PREFIX_PATH}/lib/ )
    
    add_executable(rtsp
      main.cpp
    )
    target_link_libraries(rtsp avcodec avformat avfilter avutil swresample swscale swscale )
    
    • 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

    5、rtsp 取流

    和rtmp取流基本类似,需要可以修改

    #include 
    
    extern "C" {
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/avutil.h"
    }
    
    int main(int argc, char *argv[])
    {
    
        int status_error_=-1;
        std::string videourl= "rtsp://admin:Admin12345@192.168.3.64:554/Streaming/Channels/1";
        AVFormatContext *pFormatCtx = NULL;
        AVDictionary *options = NULL;
        AVPacket *av_packet = NULL; // AVPacket暂存解码之前的媒体数据
    
    
        avformat_network_init();
        //执行网络库的全局初始化。
        //此函数仅用于解决旧版GNUTLS或OpenSSL库的线程安全问题。
        //一旦删除对较旧的GNUTLS和OpenSSL库的支持,此函数将被弃用,并且此函数将不再有任何用途。
        av_dict_set(&options, "buffer_size", "4096000", 0); //设置缓存大小
        av_dict_set(&options, "rtsp_transport", "tcp", 0);  //以tcp的方式打开,
        av_dict_set(&options, "stimeout", "5000000", 0);    //设置超时断开链接时间,单位us,   5s
        av_dict_set(&options, "max_delay", "500000", 0);    //设置最大时延
    
        pFormatCtx = avformat_alloc_context(); //用来申请AVFormatContext类型变量并初始化默认参数,申请的空间
    
        //打开网络流或文件流
        if (avformat_open_input(&pFormatCtx, videourl.c_str(), NULL, &options) != 0)
        {
    
            std::cout << "Couldn't open input stream.\n"
                      << std::endl;
    
            return status_error_;
        }
    
        //获取视频文件信息
        if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
        {
    
            std::cout << "Couldn't find stream information."<< std::endl;
            return status_error_;
        }
    
        std::cout << "av_dict_get:" << std::endl;
        AVDictionaryEntry *tag = NULL;
        //av_dict_set(&pFormatCtx->metadata, "rotate", "0", 0);这里可以设置一些属性
        while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
        {
            std::string key = tag->key;
            std::string value = tag->value;
            std::cout << "av_dict_get:" << key << ":" << value << std::endl;
        }
    
    
        //查找码流中是否有视频流
        int videoindex = -1;
        unsigned i = 0;
        for (i = 0; i < pFormatCtx->nb_streams; i++)
        {
            if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                videoindex = i;
                break;
            }
        }
        if (videoindex == -1)
        {
    
            std::cout << "Didn't find a video stream.\n"
                      << std::endl;
    
            return status_error_;
        }
    
        av_packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    
        while (true)
        {
            if (av_read_frame(pFormatCtx, av_packet) >= 0)
            {
    
                if (av_packet->stream_index == videoindex)
                {
                    std::cout << "\ndata size is:" << av_packet->size;
                    //这里就是接收到的未解码之前的数据
    
                }
                if (av_packet != NULL)
                    av_packet_unref(av_packet);
            }
    
    
        }
    
        av_free(av_packet);
        avformat_close_input(&pFormatCtx);
    
        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

    linux下运行可执行文件前,可设置从当前文件夹查找so动态库
    export LD_LIBRARY_PATH=./

    5 、其他

    以上讲一些基础知识,rtmp 和 rtsp 的封装推流界面和取流界面,等二再讲了。

  • 相关阅读:
    图像相似度对比分析软件,图像相似度计算方法
    前端面试系列之工程化篇
    cocos2dx查看版本号的方法
    sql更新语句的执行流程
    docker + miniconda + python 环境安装与迁移(详细版)
    JAVASE(复习)——多态
    2023-10-29 LeetCode每日一题(H 指数)
    SpringCloud技术—Docker详解、案例展示
    MySQL事务
    3个ui自动化测试痛点
  • 原文地址:https://blog.csdn.net/qianbo042311/article/details/125516873