播放H264和Ivf

news/2024/7/10 21:44:12 标签: ffmpeg, 网络

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).)

简单改造 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()
        }
}

编译并启动:

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


http://www.niftyadmin.cn/n/1738801.html

相关文章

ffmpeg 录屏及转h264

录屏代码&#xff1a; ffmpeg -video_size 1920x1080 -framerate 25 -f x11grab -i ${DISPLAY}0,0 -vcodec libx264 ./video$(date %F-%H-%M-%S).mp4ffmpeg -video_size 1920x1080 -framerate 25 -f x11grab -i ${DISPLAY}0,0 -vcode vp8 ./video$(date %F-%H-%M…

xterm.js4 Vue版本实现

基于xterm.js 实现Vue版本终端terminal npm install --save xterm npm install --save xterm-addon-fit xterm-addon-attach xterm.js的附加组件&#xff0c;用于附加到Web Socket npm install --save xterm-addon-attach <template><div id"xterm" cl…

gin-vue-admin V2.5手动创建后台API和调用流程

gin-vue-admin V2.5手动创建后台API和调用流程 1. /server/api/目录下创建自定义api myapi 文件夹&#xff0c;myapi package 文件夹中&#xff0c;enter.go定义MyApi 的ApiGroup package myapitype ApiGroup struct {// Code generated by github.com/flipped-aurora/gin-v…

go afero 用法

用法1&#xff1a;var AppFs afero.NewOsFs() 之后&#xff0c;把AppFs当os使用&#xff0c; os.Open(“/tmp/foo”) 则afero用法为&#xff1a; AppFs.Open(“/tmp/foo”) package mainimport ("fmt""github.com/spf13/afero" )func main() {var AppFs …

golang jwt-go 一个案例搞懂jwt鉴权

golang jwt-go 一个案例搞懂jwt鉴权 MyClaims&#xff0c;自定义Claims&#xff0c;保存一用户ID等信息 MySecret&#xff0c;服务器端加密解密使用&#xff0c;加密通过&#xff1a;token.SignedString(MySecret) 解密通过&#xff1a; jwt.ParseWithClaims(tokenss, &MyC…

CSS样式选择器

CSS样式选择器 一.基本选择器 类选择器 .name{} ID选择器 #name{} 元素选择器 name{} 通配符选择器(编译不通过) *{} 包含选择器 p c{}&#xff1a;parent下所有的同一名字的child 子元素选择器 p>c{}&#xff1a;parent下一层所有的同一名字的child 邻近兄弟元素选择器 c1c…

C语言多线程实例

下面做一个简单的示例&#xff0c;通过调用多线程来打印数字 //test1.c #include<stdio.h> #include<pthread.h> #define NUM_THREADS 5void *PrintTh(void *th){int i *((int*)th);printf("Hello, Im thread %d\n", i);return 0; }int main(){int i,r…

chrome扩展开发资料

chrome插件最新版本开发指南来了 https://blog.csdn.net/qq_34998786/article/details/121782426? https://juejin.cn/post/7051466934948200461 https://developer.chrome.com/docs/extensions/reference/sockets_tcpServer/#type https://developer.chrome.com/docs/ext…