h265webplayer
h265webplayer是金山云的Web端H.265视频播放器,该播放器Web SDK让您可以在支持 WebAssembly 的浏览器上播放MP4格式的点播视频,FLV http-flv协议的直播视频。
支持的功能
1、mp4格式的点播(音频需是aac格式的,其余音频格式待兼容)。
2、flv格式的直播。
目前PC端和移动端都可以使用,使用前请使用播放器提供的isSupportH265接口检查是否支持播放条件。
demo 有两种访问方式
mp4 demo 访问方式
1、ks3直接访问,链接如下:
https://ks3-cn-beijing.ksyun.com/ksplayer/h265/mp4_demo/index.html
flv demo 访问方式
1、ks3直接访问,链接如下:
https://ks3-cn-beijing.ksyun.com/ksplayer/h265/outside_demo/v1.1.3/index.html
2、获取压缩包后本地创建服务访问,步骤如下:
播放器Demo压缩包地址
flv demo zip
https://ks3-cn-beijing.ksyun.com/ai-kie/sdk/h265-pc/h265-pc.zip
播放器Demo运行说明
0. 安装npm包管理器
参见:Node.js官网
1. 安装http服务器
npm install http-server -g
2. 启动服务
cd <demo directory>
npm run start
浏览器访问
http://localhost:8000
说明: 请替换页面中的拉流地址进行测试
集成h265解码器有两种方式
1、直接使用金山自研的h265播放器(推荐)
2、基于H265Decoder开发使用
第一种方式:使用h265播放器
token的意义
用于鉴权,验证用户是否拥有访问的权利以及访问的时长
如何获取token
先与商务沟通达成协议后,产品会根据需求提供一个对应的token
h265播放器初始化参数配置
let player = h265js.createPlayer({
isLive: false,
type: 'mp4'
enableSkipFrame: false,
token: 'f8ce4d1adb97c46f28161a3685232557',
wasmFilePath: 'http://localhost:8000/libqydecoder.wasm',
url: urlInput.value,
timeToDecideWaiting: 50000,
bufferTime: 0,
isShowStatistics: false
},{
audioElement: audioElement,
canvas: canvas,
videoElement: h264VideoEle
});
配置参数说明
string
f8ce4d1adb97c46f28161a368529b557(有效期到年底)
播放器鉴权token。一旦token过期,h265 pc sdk将无法使用,过期通知将通过error事件给出
wasmFilePath
string
https://ks3-cn-beijing.ksyun.com/ksplayer/h265/wasm_resource/libqydecoder.wasm
用于指定wasm解码库的位置
string
https://ks3-cn-beijing.ksyun.com/ksplayer/h265/mp4_resource/moov_head_265.mp4
视频流地址。
timeToDecideWaiting
number
1000(ms)
暂停多久算卡顿
bufferTime
number
0(ms)
启播前缓冲视频时长
isShowStatistics
boolean
false
是否在控制台输出统计信息
audioElement
HTMLAudioElement
音频 audio dom 元素
canvas
HTMLCanvasElement
画布 canvas dom 元素
播放器支持的方法
获取视频编码格式方法
player.on(h265js.Events.MEDIAINFO, function(event, data){
codec = data.codec;
if (codec === 'avc1') { // h.264
// 处理h.264视频相关逻辑
} else if (codec === 'hev1') { // h.265
// 处理h.265视频相关逻辑
});
播放器控制条功能说明
<div class="ks-controls">
<button class="ks-controls-load" onclick="load()">Load</button>
<button onclick="start()">Start</button>
<button onclick="pause()">Pause</button>
<button class="ks-controls-muted" onclick="muted()" data-type="muted">Muted</button>
<button onclick="fullscreen()">Fullscreen</button>
<input type="text" name="ks-seek-to" value="35"/>
<button onclick="seekto()">SeekTo</button>
<div class="ks-time
">
<span class="ks-current">00:00:00</span>
<span class="ks-duration">00:00:00</span>
</div>
</div>
控制条功能
let config = {
wasmFilePath: 'http://localhost:8000/libqydecoder.wasm',
enableSkipFrame: true
let decoder;
//加载并编译wasm解码库
H265Decoder.compileWasmInterfaces(config.wasmFilePath, function () {
decoder = new H265Decoder(config);
//设置解码回调
decoder.set_image_callback(onDecodedFrameCallback);
向解码器队列送数据
decoder.toBeDecodeQueue.push({
nalu: new Uint8Array(naluData), // naluData 为 ArrayBuffer类型数据
pts: 0, //展示时间戳
isDroppable: false }); // 表示是否可以跳帧
decoder的toBeDecodeQueue属性为保存待解码数据的数组,需自行控制待解码队列缓冲区的长度,避免内存溢出
decoder会自动取出toBeDecodeQueue中的数据给底层wasm解码器,并在解码后自动释放传入wasm解码器的待解码数据
设置解码输出图像回调 set_image_callback
decoder.set_image_callback((image) => {
let w = image.get_width(); //获取图像宽度
let h = image.get_height(); //获取图像高度
let pts = image.get_pts(); //获取图像pts时间戳
// let image_data = new Uint8ClampedArray(w * h * 4);
// for (let i = 0; i < w * h; i++) {
// image_data[i * 4 + 3] = 255;
// //转换image中的YUV数据到image_data中的RGB数据
// image.transcode(image_data);
//优化: 直接返回YUV数据
let yuvData = image.getYuvDataNew(); //Uint8Array
});
说明: 解码回调函数的参数image为Image类型,参见h265decoder.js中的定义
decoder.pause();
decoder.resume();
检查是否为暂停状态
if(decoder.isPaused()) {}
启动解码器
decoder.start();
说明: 默认情况初始化解码器时会自动调用启动解码器
销毁解码器
decoder.free();
接收累积跳帧数通知
decoder.on('skip_frame, (skippedframecount) => {
console.log(skippedframecount);
wasm解码器接口说明
接口函数返回码说明
const qy265decoder = {
QY_OK : (0x00000000), // Success
QY_FAIL : (0x80000001), // Unspecified error
QY_OUTOFMEMORY : (0x80000002), // Ran out of memory
QY_POINTER : (0x80000003), // Invalid pointer
QY_NOTSUPPORTED : (0x80000004),// Not support feature encoutnered
QY_AUTH_INVALID : (0x80000005), // authentication invalid
QY_SEARCHING_ACCESS_POINT : (0x00000001), // in process of searching first access point
QY_REF_PIC_NOT_FOUND : (0x80000007), // encode complete
QY_NEED_MORE_DATA : (0x00000008), // need more data
QY_BITSTREAM_ERROR : (0x00000009), // detecting bitstream error, can be ignored
QY_TOKEN_INVALID : (0x0000000A) //token invalid
错误码分为三大类状态:
等于0: QY_OK,表示完全正常
大于0:虽然当前不能正常解码,但解码器本身并没有出错
小于0:解码器本身发生了一些异常和错误
创建解码器 QY265DecoderCreate
let
returnCode = _malloc(4); //为返回码分配空间
setValue(returnCode, 0, "i32"); // 返回码设置为0
let decoder = qy265decoder.QY265DecoderCreate(null, token, returnCode);
if(getValue(returnCode, 'i32') === qy265decoder.QY_OK) { /*解码器创建成功*/ }
销毁编码器 QY265DecoderDestroy
qy265decoder.QY265DecoderDestroy(decoder);
decoder: 通过QY265DecoderCreate接口创建的解码器实例
解码一个NAL单元 QY265DecodeFrameEnSkip
qy265decoder.QY265DecodeFrameEnSkip(decoder, naluData, length, returnCode, pts, shouldSkip);
decoder: 通过QY265DecoderCreate接口创建的解码器实例
naluData: NAL单元数据,Uint8Array类型
length: NAL单元数据字节数
returnCode: 返回码,returnCode对应地址保存0(QY_OK)表示正常
pts: NAL单元对应的显示时间标签
shouldSkip: true/false, 表示是否应该跳过该NAL单元的解码
获取解码输出 QY265DecoderGetDecodedFrameEm
let frame = qy265decoder.QY265DecoderGetDecodedFrameEm(decoder, returnCode, 0);
decoder: 通过QY265DecoderCreate接口创建的解码器实例
returnCode: 返回码
返回值: 解码输出帧
判断解码输出帧是否有效 QY265DecoderGetFrameValid
let framevalid = qy265decoder.QY265DecoderGetFrameValid(frame);
frame: QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
返回值: 解码输出帧是否有效, 1为有效
归还解码输出帧 QY265DecoderReturnDecodedFrame
通知解码器释放该帧占用的相关内存
qy265decoder.QY265DecoderReturnDecodedFrame(decoder, frame);
decoder: 通过QY265DecoderCreate接口创建的解码器实例
frame:QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
获取解码输出帧的宽度 QY265GetFrameWidth
let width = qy265decoder.QY265GetFrameWidth(frame, 0);
frame:QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
返回值: 解码输出帧的宽度
获取解码输出帧的高度 QY265GetFrameHeight
let height = qy265decoder.QY265GetFrameHeight(frame, 0);
frame:QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
返回值: 解码输出帧的高度
获取解码输出帧的显示时间戳 QY265GetFramePts
let pts = qy265decoder.QY265GetFramePts(frame, 0);
frame:QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
返回值: 解码输出帧的显示时间戳
获取解码输出帧的YUV某个分量 QY265DecoderGetFramePlane
let stride = _malloc(2);
let y = qy265decoder.QY265DecoderGetFramePlane(frame, 0, stride);
let u = qy265decoder.QY265DecoderGetFramePlane(frame, 1, stride);
let v = qy265decoder.QY265DecoderGetFramePlane(frame, 2, stride);
frame:QY265DecoderGetDecodedFrameEm接口返回的解码输出帧
index: YUV分量索引,0表示Y分量,1表示U分量,2表示v分量
返回值: YUV某个数据分量的数组,格式为Uint8Array
Flush解码器 QY265DecodeFlush
因为解码NAL单元与获取解码输出为异步关系, 所以解码器中可能存在剩余尚未解码完成的若干NAL单元. 调用本函数将使解码器完成所有已经输入的NAL单元的解码. 一般在码流结束或者播放器拖曳时使用.
qy265decoder.QY265DecodeFlush(decoder, bClearCachedPics, returnCode);
decoder: 通过QY265DecoderCreate接口创建的解码器实例
bClearCachedPics: 是否清除缓冲的图像. 在码流结束时, 置为false, 不清除, 得到所有输出帧;在播放器拖曳或其他情况下, 置为true, 清除之前的图像帧, 重新开始
returnCode: 返回码,returnCode对应地址保存0(QY_OK)表示正常
YUV渲染器文档
初始化YUV渲染器
import WebGLCanvas from 'yuvrender.min.js'; // yuvrender.min.js在压缩包中的demo目录下
let webGLCanvas = new WebGLCanvas({
canvas: this.canvas, //传入一个canvas
width: width, //视频帧宽度
height: height //视频帧高度
});
备注:视频会根据它真实的宽高比自适应视频容器canvas的宽高
<canvas id="videoDisplayCanvas" width="1600" height="600">
渲染一帧YUV数据
// 假设yuvData为解码输出的一帧图像的YUV表示(Uint8Array类型)
let ylen = width * height; //视频宽高
let uvlen = (width / 2) * (height / 2);
webGLCanvas.drawNextOutputPicture({
yData: yuvData.subarray(0, ylen),
uData: yuvData.subarray(ylen, ylen + uvlen),
vData: yuvData.subarray(ylen + uvlen, ylen + uvlen*2)
});
获取解码输出的图像帧的YUV表示
参见: 设置解码输出图像回调 set_image_callback
author: xuyang
email: xuyang6@kingsoft.com