• pion 播放H246和Ivf


    IVF视频文件格式:

    https://www.jianshu.com/p/cfbab5c3c8f7

    1. Pion WebRTC
      pion webrtc 是一个纯 golang 的 webrtc 实现开源项目,没有使用 cgo,继承了 golang 跨平台能力,基本所有平台都能使用,mips、ppc64 实测也可以使用。 pion webrtc 还有多个衍生项目,如 ion、ion-sfu 等等。

    2. 性能测试
      性能测试主要使用其一个衍生项目 pion/rtsp-bench。服务端将 rtsp 转封装为 rtp 发布,客户端订阅服务端发布的 rtp 流,周期的增加订阅数量,达到压测的目的。 服务端在转发 rtp 数据的同时,也会周期的记录当前时间、订阅连接数、系统 CPU 使用百分比,这些记录数据会作为测试报告实时输出到 csv 格式文件中。

    3. 原始测试
      原始测试中的 rtsp 源码率大概250kbps,如果想测其他码率可以通过搜索引擎找找公网上还能访问的 rtsp 源。这里我用 live555 搭建了一个rtsp服务器,通过 live555 提供稳定的 rtsp 源。

    编译 live555
    wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz
    tar xvfz live555-latest.tar.gz
    cd live
    ./genMakefiles linux-64bit
    make
    启动 rtsp 服务
    编译成功后,执行make install安装。也可以不安装,进入 mediaServer 目录运行 live555。

    cd mediaServer
    ./live555MediaServer
    运行后出现下面的这样的提示代表启动成功,按照提示把把媒体文件放到 live555 同级目录中,即可通过 rtsp 协议访问了。

    LIVE555 Media Server
            version 1.09 (LIVE555 Streaming Media library version 2021.08.24).
    Play streams from this server using the URL
            rtsp://192.168.0.100/<filename>
    where <filename> is a file present in the current directory.
    Each file's type is inferred from its name suffix:
            ".264" => a H.264 Video Elementary Stream file
            ".265" => a H.265 Video Elementary Stream file
            ".aac" => an AAC Audio (ADTS format) file
            ".ac3" => an AC-3 Audio file
            ".amr" => an AMR Audio file
            ".dv" => a DV Video file
            ".m4e" => a MPEG-4 Video Elementary Stream file
            ".mkv" => a Matroska audio+video+(optional)subtitles file
            ".mp3" => a MPEG-1 or 2 Audio file
            ".mpg" => a MPEG-1 or 2 Program Stream (audio+video) file
            ".ogg" or ".ogv" or ".opus" => an Ogg audio and/or video file
            ".ts" => a MPEG Transport Stream file (a ".tsx" index file - if present - provides server 'trick play' support)
            ".vob" => a VOB (MPEG-2 video with AC-3 audio) file
            ".wav" => a WAV Audio file
            ".webm" => a WebM audio(Vorbis)+video(VP8) file
    See http://www.live555.com/mediaServer/ for additional documentation.
    (We use port 80 for optional RTSP-over-HTTP tunneling).)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    简单改造 live555
    默认的 live555 在文件播放结束后便会停止这个会话,为了长时间压测,修改 live555 源码让他循环播放。改造原理就是当没有数据后seek到最开始的地方。 修改./liveMedia/ByteStreamFileSource.cpp文件的ByteStreamFileSource::doGetNextFrame方法后,再次编译运行即可。

    void ByteStreamFileSource::doGetNextFrame() {
    if (feof(fFid) || ferror(fFid) || (fLimitNumBytesToStream && fNumBytesToStream == 0)) {
    //handleClosure();
    //return;
    fseek(fFid, 0, SEEK_SET);
    }

    #ifdef READ_FROM_FILES_SYNCHRONOUSLY
    doReadFromFile();
    #else
    if (!fHaveStartedReading) {
    envir().taskScheduler().turnOnBackgroundReadHandling(fileno(fFid),
    (TaskScheduler::BackgroundHandlerProc*)&fileReadableHandler, this);
    fHaveStartedReading = True;
    }
    #endif
    }
    编译启动 rtsp-bench server
    修改rtsp-bench/server/main.go,把 rtspURL 换成 live555 的 rtsp 地址(不修改,直接使用示例源码中的也可以,码率大概200Kb)。

    const rtspURL = “rtsp://192.168.0.100:554/1080P.264”
    编译并启动:

    export GO111MODULE=on
    git clone https://github.com/pion/rtsp-bench.git
    cd rtsp-bench/server
    go run main.go
    编译启动 rtsp-bench client
    cd rtsp-bench/client
    go run main.go localhost:8080
    4. 改造测试
    原始测试使用 rtsp 作为视频源,如果不是使用 rtsp-bench 中的示例 rtsp 地址,还需要自己搭建 rtsp 服务,比较麻烦。结合其另一个衍生项目,我对 rtsp-bench 做了一点改动,将 h264 流作为视频源,个人感觉更方便灵活一些,暂且将其称为 file-bench。

    改造 file-bench server

    package main
    
    import (
            "encoding/json"
            "fmt"
            "io"
            "net/http"
            "os"
            "path/filepath"
            "strconv"
            "strings"
            "sync/atomic"
            "time"
    
            "github.com/pion/webrtc/v3"
            "github.com/pion/webrtc/v3/pkg/media"
            "github.com/pion/webrtc/v3/pkg/media/h264reader"
            "github.com/shirou/gopsutil/cpu"
    )
    
    var (
            outboundVideoTrack  *webrtc.TrackLocalStaticSample
            peerConnectionCount int64
    )
    
    
    var (
            videoFilePath      string
            videoFrameDuration time.Duration
    )
    
    func GetCurrentDirectory() string {
            dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
            if err != nil {
                    panic(err)
            }
            return strings.Replace(dir, "\\", "/", -1)
    }
    
    // Generate CSV with columns of timestamp, peerConnectionCount, and cpuUsage
    func reportBuilder() {
            file, err := os.OpenFile("report.csv", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
            if err != nil {
                    panic(err)
            }
    
            if _, err := file.WriteString("timestamp, peerConnectionCount, cpuUsage\n"); err != nil {
                    panic(err)
            }
    
            for range time.NewTicker(3 * time.Second).C {
                    usage, err := cpu.Percent(0, false)
                    if err != nil {
                            panic(err)
                    } else if len(usage) != 1 {
                            panic(fmt.Sprintf("CPU Usage results should have 1 sample, have %d", len(usage)))
                    }
                    if _, err = file.WriteString(fmt.Sprintf("%s, %d, %f\n", time.Now().Format(time.RFC3339), atomic.LoadInt64(&peerConnectionCount), usage[0])); err != nil {
                            panic(err)
                    }
            }
    }
    
    func doSignaling(w http.ResponseWriter, r *http.Request) {
            peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{})
            if err != nil {
                    panic(err)
            }
    
            peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
                    if connectionState == webrtc.ICEConnectionStateDisconnected {
                            atomic.AddInt64(&peerConnectionCount, -1)
                            if err := peerConnection.Close(); err != nil {
                                    panic(err)
                            }
                    } else if connectionState == webrtc.ICEConnectionStateConnected {
                            atomic.AddInt64(&peerConnectionCount, 1)
                    }
            })
    
            if rtpSender, err := peerConnection.AddTrack(outboundVideoTrack); err != nil {
                    panic(err)
            } else {
                    go func() {
                            rtcpBuf := make([]byte, 1500)
                            for {
                                    if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
                                            return
                                    }
                            }
                    }()
            }
    
            var offer webrtc.SessionDescription
            if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
                    panic(err)
            }
    
            if err = peerConnection.SetRemoteDescription(offer); err != nil {
                    panic(err)
            }
    
            gatherCompletePromise := webrtc.GatheringCompletePromise(peerConnection)
    
            answer, err := peerConnection.CreateAnswer(nil)
            if err != nil {
                    panic(err)
            } else if err = peerConnection.SetLocalDescription(answer); err != nil {
                    panic(err)
            }
    
            <-gatherCompletePromise
    
            response, err := json.Marshal(*peerConnection.LocalDescription())
            if err != nil {
                    panic(err)
            }
    
            w.Header().Set("Content-Type", "application/json")
            if _, err := w.Write(response); err != nil {
                    panic(err)
            }
    }
    
    func main() {
            if len(os.Args) < 3 {
                    panic("missing startup parameters: h264FileName h264FileFps")
            }
    
            videoFileName := os.Args[1]
            if videoFileName == "" {
                    panic("invalid video file name")
            }
            fmt.Println("video file:", videoFileName)
            videoFilePath = GetCurrentDirectory() + "/" + videoFileName
    
            fps, fpsParamErr := strconv.Atoi(os.Args[2])
            if fpsParamErr != nil {
                    panic(fpsParamErr)
            }
            fmt.Println("video fps:", fps)
            videoFrameDuration = time.Duration(1000/fps) * time.Millisecond
    
            fmt.Println()
            _, openVideoErr := os.Stat(videoFilePath)
            if os.IsNotExist(openVideoErr) {
                    panic("Could not find `" + videoFilePath + "`")
            }
    
            var err error
            outboundVideoTrack, err = webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{
                    MimeType: webrtc.MimeTypeH264,
            }, "video", "pion")
            if err != nil {
                    panic(err)
            }
    
            go videoFileConsumer()
            go reportBuilder()
    
            http.Handle("/", http.FileServer(http.Dir("./static")))
            http.HandleFunc("/doSignaling", doSignaling)
    
            fmt.Println("Open http://localhost:8080 to access this demo")
            panic(http.ListenAndServe(":8080", nil))
    }
    
    func videoFileConsumer() {
            for {
                    file, h264Err := os.Open(videoFilePath)
                    if h264Err != nil {
                            panic(h264Err)
                    }
    
                    h264, h264Err := h264reader.NewReader(file)
                    if h264Err != nil {
                            panic(h264Err)
                    }
    
                    ticker := time.NewTicker(videoFrameDuration)
                    for ; true; <-ticker.C {
                            nal, h264Err := h264.NextNAL()
                            if h264Err == io.EOF {
                                    fmt.Println("all video frames parsed and sent, loop playback")
                                    break
                            }
                            if h264Err != nil {
                                    panic(h264Err)
                            }
    
                            if h264Err = outboundVideoTrack.WriteSample(media.Sample{Data: nal.Data, Duration: time.Second}); h264Err != nil {
                                    panic(h264Err)
                            }
                    }
                    _ = file.Close()
            }
    }
    
    • 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

    编译并启动:

    export GO111MODULE=on && go build -o server main.go && ./server 1080P.h264 30
    使用 ffmpeg 导出 h264
    导出h264参考:

    ffmpeg -i $INPUT_FILE -an -c:v libx264 -profile:v baseline -level 3.0 -bsf:v h264_mp4toannexb -max_delay 0 -bf 0 $OUTPUT_FILE
    导出ogg参考:

    ffmpeg -i $INPUT_FILE -c:a libopus -page_duration 20000 -vn $OUTPUT_FILE
    5. 测试结果
    在某公有云主机实测,分发 3Mbps 1080P 音视频,2核能够支撑300路转发,性能还是比较理想的。

    参考:
    https://39.99.141.248/archives/pion-webrtc-performance#title-11

  • 相关阅读:
    fwknop服务端编译和使用
    Qt学习总结之布局管理
    阿里云香港云服务器公网带宽价格表及测试IP地址
    【Homeassistant 与Passive Infrared Sensor被动红外传感器握手】
    Android修改aar并重新打包
    港联证券:资金融通构成强支撑 “一带一路”金融合作开新局
    C语言每日一题(14):有序序列判断
    EasyPoi
    去腾讯面试,直接让人出门左拐 :幂等性都不知道!
    文件编码、转换、乱码问题
  • 原文地址:https://blog.csdn.net/shelutai/article/details/127845430