基于webrtc的p2p H265播放器实现一

    由于H265受版权的影响,浏览器都不愿意支持,所以webrtc不能实现H265的解码播放。

    因为很多编码芯片基本上都是支持H264和H265的,VP9和AV1基本上硬件都不支持,大量的显示需要H265来减少流量,特别是4G应用,H265比H264 要少接近一半的流量。为了学习编解码和webrtc以及wasm相关技术,结合网上开源的播放器基础,重新实现了一个基于webrtc的播放器,编码是rv1126 的H265硬编,传输采用webrtc datachannel,信令采用mqtt,播放采用webgl,使用了多worker模式,数据采用指针传递和缓存模式。

    浏览器端采用了比较成熟的ffmpeg wasm 技术,感谢开源项目(排名不分先后)https://github.com/goldvideo/decoder_wasm和GitHub - sonysuqin/WasmVideoPlayer: Play file/stream with wasm & webgl & web audio api, using ffmpeg for multi codec support, especially for h265,support http, websocket, http-flv stream.

hhnumberwolf/h265web.js: HEVC/H.265 网页直播/点播播放器 支持H.265的HttpFLV/HLS/MP4/TS/FLV/M3U8/Websocket播放。 A HEVC/H.265 Web Player, for LIVE/VOD stream. Support H.265 Codec with HttpFLV/HLS/MP4/TS/FLV/M3U8/Websocket. (github.com)

https://github.com/langhuihui/jessibuca/tree/v3 

这两个项目是我对比了,觉得结构最清晰,仿写最容易的,想学习wasm的也可以从这两个项目开始。以下是核心的解码worker

self.Module = {
    onRuntimeInitialized: function () {
        onWasmLoaded();
    }
};

self.importScripts("common.js");
self.importScripts("libffmpeg_264_265.js");


function Decoder() {
    this.logger             = new Logger("Decoder");
    this.coreLogLevel       = 1;
    this.accurateSeek       = true;
    this.wasmLoaded         = false;
    this.tmpReqQue          = [];
    this.cacheBuffer        = null;
    this.decodeTimer        = null;
    this.videoCallback      = null;
    this.audioCallback      = null;
    this.requestCallback    = null;

}
var frameBuffer= [];
var pts =0;
Decoder.prototype.initDecoder = function (fileSize, chunkSize) {

    var ret=0;
    this.logger.logInfo("initDecoder return " + ret + ".");

    var objData = {
        t: kInitDecoderRsp,
        e: ret
    };
    self.postMessage(objData);
};

Decoder.prototype.uninitDecoder = function () {

    this.logger.logInfo("Uninit ffmpeg decoder return " + ret + ".");
    if (this.cacheBuffer != null) {
        Module._free(this.cacheBuffer);
        this.cacheBuffer = null;
    }
};
var LOG_LEVEL_JS = 0;
var LOG_LEVEL_WASM = 1;
var LOG_LEVEL_FFMPEG = 2;
var DECODER_H264 = 0;
var DECODER_H265 = 1;
var decoder_type = DECODER_H265;
Decoder.prototype.openDecoder = function () {
    var paramCount = 7, paramSize = 4;

    var ret = Module._openDecoder(decoder_type, this.videoCallback, LOG_LEVEL_WASM)

    this.logger.logInfo("openDecoder return " + ret);

    
   var ret =0;
    var objData = {
        t: kOpenDecoderRsp,
        e: ret,
        v: {
            d: 0,
            p: 1,
            w: 1289,
            h: 720
        },

    };
    self.postMessage(objData);
};

Decoder.prototype.closeDecoder = function () {
    this.logger.logInfo("closeDecoder.");
    if (this.decodeTimer) {
        clearInterval(this.decodeTimer);
        this.decodeTimer = null;
        this.logger.logInfo("Decode timer stopped.");
    }

  
    this.logger.logInfo("Close ffmpeg decoder return " + ret + ".");

    var objData = {
        t: kCloseDecoderRsp,
        e: 0
    };
    self.postMessage(objData);
};

Decoder.prototype.startDecoding = function (interval) {
    this.logger.logInfo("Start decoding.");
    if (this.decodeTimer) {
        clearInterval(this.decodeTimer);
    }
    this.decodeTimer = setInterval(this.decode, interval);
};

Decoder.prototype.pauseDecoding = function () {
    this.logger.logInfo("Pause decoding.");
    if (this.decodeTimer) {
        clearInterval(this.decodeTimer);
        this.decodeTimer = null;
    }
};
Decoder.prototype.getBufferTimerLength = function() {
    if (!frameBuffer || frameBuffer.length == 0) {
        return 0;
    }

    let oldest = frameBuffer[0];
    let newest = frameBuffer[frameBuffer.length - 1];
    return newest.s - oldest.s;
};
Decoder.prototype.decode = function () {
    console.log("Decoding")

    if(frameBuffer.length>0){
    var typedArray = frameBuffer.shift();//frameBuffer[0];
    var size = typedArray.length;
    this.cacheBuffer = Module._malloc(size);

    Module.HEAPU8.set(typedArray, this.cacheBuffer);

    Module._decodeData(this.cacheBuffer, size, pts++)
    if (this.cacheBuffer != null) {
        Module._free(this.cacheBuffer);
        this.cacheBuffer = null;
    }
    
   }

};

Decoder.prototype.sendData = function (data) {
    var typedArray = new Uint8Array(data);

    frameBuffer.push(typedArray);

};

Decoder.prototype.seekTo = function (ms) {
    var accurateSeek = this.accurateSeek ? 1 : 0;
    var ret = Module._seekTo(ms, accurateSeek);
    var objData = {
        t: kSeekToRsp,
        r: ret
    };
    self.postMessage(objData);
};

Decoder.prototype.processReq = function (req) {
    //this.logger.logInfo("processReq " + req.t + ".");
    switch (req.t) {
        case kInitDecoderReq:
            this.initDecoder(req.s, req.c);
            break;
        case kUninitDecoderReq:
            this.uninitDecoder();
            break;
        case kOpenDecoderReq:
            this.openDecoder();
            break;
        case kCloseDecoderReq:
            this.closeDecoder();
            break;
        case kStartDecodingReq:
            this.startDecoding(req.i);
            break;
        case kPauseDecodingReq:
            this.pauseDecoding();
            break;
        case kFeedDataReq:
            this.sendData(req.d);
            break;
        case kSeekToReq:
            this.seekTo(req.ms);
            break;
        default:
            this.logger.logError("Unsupport messsage " + req.t);
    }
};

Decoder.prototype.cacheReq = function (req) {
    if (req) {
        this.tmpReqQue.push(req);
    }
};

Decoder.prototype.onWasmLoaded = function () {
    this.logger.logInfo("Wasm loaded.");
    this.wasmLoaded = true;


    var videoSize = 0;
    this.videoCallback  = Module.addFunction(function (addr_y, addr_u, addr_v, stride_y, stride_u, stride_v, width, height, pts) {
        console.log("[%d]In video callback, size = %d * %d, pts = %d", ++videoSize, width, height, pts)
        let size = width * height + (width / 2)  * (height / 2) + (width / 2)  * (height / 2)
        let data = new Uint8Array(size)
        let pos = 0
        for(let i=0; i< height; i++) {
            let src = addr_y + i * stride_y
            let tmp = HEAPU8.subarray(src, src + width)
            tmp = new Uint8Array(tmp)
            data.set(tmp, pos)
            pos += tmp.length
        }
        for(let i=0; i< height / 2; i++) {
            let src = addr_u + i * stride_u
            let tmp = HEAPU8.subarray(src, src + width / 2)
            tmp = new Uint8Array(tmp)
            data.set(tmp, pos)
            pos += tmp.length
        }
        for(let i=0; i< height / 2; i++) {
            let src = addr_v + i * stride_v
            let tmp = HEAPU8.subarray(src, src + width / 2)
            tmp = new Uint8Array(tmp)
            data.set(tmp, pos)
            pos += tmp.length
        }

        var duration        = pts;//paramArray[0];
        var videoPixFmt     = 1;//paramArray[1];
        var videoWidth      = width;//paramArray[2];
        var videoHeight     = height;//paramArray[3];

       var ret =0;
        var objData = {
            t: kVideoParameters,
            e: ret,
            v: {
                d: duration,
                p: videoPixFmt,
                w: videoWidth,
                h: videoHeight
            },

        };
         self.postMessage(objData);

        var objData = {
            t: kVideoFrame,
            s: pts,
            d: data
        };
        self.postMessage(objData, [objData.d.buffer]);
        // displayVideoFrame(obj);
    });


    this.requestCallback = Module.addFunction(function (offset, availble) {
        var objData = {
            t: kRequestDataEvt,
            o: offset,
            a: availble
        };
        self.postMessage(objData);
    }, 'vii');

    while (this.tmpReqQue.length > 0) {
        var req = this.tmpReqQue.shift();
        this.processReq(req);
    }
};

self.decoder = new Decoder;

self.onmessage = function (evt) {
    if (!self.decoder) {
        console.log("[ER] Decoder not initialized!");
        return;
    }

    var req = evt.data;
    if (!self.decoder.wasmLoaded) {
        self.decoder.cacheReq(req);
        self.decoder.logger.logInfo("Temp cache req " + req.t + ".");
        return;
    }

    self.decoder.processReq(req);
};

function onWasmLoaded() {
    if (self.decoder) {
        self.decoder.onWasmLoaded();
    } else {
        console.log("[ER] No decoder!");
    }
}

基于webrtc的p2p H265播放器实现一_第1张图片

开源地址如下https://github.com/xiangxud/webrtc_H265player

基于webrtc的p2p H265播放器实现一_第2张图片

你可能感兴趣的:(metaRTC,图像智能,笔记,p2p,webrtc,网络协议)