From bbd0b63c6bc69b5e14901456510fa29f1bb7e100 Mon Sep 17 00:00:00 2001 From: Vinlic Date: Sun, 24 Dec 2023 14:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AEvideoDecode?= =?UTF-8?q?rHardwareAcceleration=E9=80=89=E9=A1=B9=E6=8C=87=E7=A4=BA?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=A7=A3=E7=A0=81=E5=99=A8=E7=A1=AC=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/ChunkVideo.js | 8 ++++++-- api/SingleVideo.js | 8 ++++++-- api/WebVideoCreator.js | 2 ++ core/CaptureContext.js | 2 ++ core/Page.js | 5 +++-- docs/api-reference-high-level.md | 5 +++++ docs/api-reference-low-level.md | 5 +++++ media/VideoCanvas.js | 15 ++++++++++----- 8 files changed, 39 insertions(+), 11 deletions(-) diff --git a/api/ChunkVideo.js b/api/ChunkVideo.js index 175eb4a..94d4e44 100644 --- a/api/ChunkVideo.js +++ b/api/ChunkVideo.js @@ -75,6 +75,7 @@ export default class ChunkVideo extends VideoChunk { * @param {boolean} [options.autostartRender=true] - 是否自动启动渲染,如果为false请务必在页面中执行 captureCtx.start() * @param {boolean} [options.consoleLog=false] - 是否开启控制台日志输出 * @param {boolean} [options.videoPreprocessLog=false] - 是否开启视频预处理日志输出 + * @param {string} [options.videoDecoderHardwareAcceleration] - VideoDecoder硬件加速指示 * @param {WaitForOptions} [options.pageWaitForOptions] - 页面等待选项 * @param {Viewport} [options.pageViewport] - 页面视窗参数 * @param {Function} [options.pagePrepareFn] - 页面预处理函数 @@ -83,7 +84,7 @@ export default class ChunkVideo extends VideoChunk { constructor(options = {}) { super(options); assert(_.isObject(options), "options must be Object"); - const { url, content, startTime, autostartRender, consoleLog, videoPreprocessLog, pageWaitForOptions, pageViewport, pagePrepareFn, timeActions } = options; + const { url, content, startTime, autostartRender, consoleLog, videoPreprocessLog, pageWaitForOptions, pageViewport, pagePrepareFn, videoDecoderHardwareAcceleration, timeActions } = options; assert(_.isUndefined(url) || util.isURL(url), `url ${url} is not valid URL`); assert(_.isUndefined(content) || _.isString(content), "page content must be string"); assert(!_.isUndefined(url) || !_.isUndefined(content), "page url or content must be provide"); @@ -93,6 +94,7 @@ export default class ChunkVideo extends VideoChunk { assert(_.isUndefined(pageWaitForOptions) || _.isObject(pageWaitForOptions), "pageWaitForOptions must be Object"); assert(_.isUndefined(pageViewport) || _.isObject(pageViewport), "pageViewport must be Object"); assert(_.isUndefined(pagePrepareFn) || _.isFunction(pagePrepareFn), "pagePrepareFn must be Function"); + assert(_.isUndefined(videoDecoderHardwareAcceleration) || _.isString(videoDecoderHardwareAcceleration), "videoDecoderHardwareAcceleration must be string"); assert(_.isUndefined(timeActions) || _.isObject(timeActions), "timeActions must be Object"); timeActions && Object.keys(timeActions).forEach(key => { key = Number(key) @@ -108,6 +110,7 @@ export default class ChunkVideo extends VideoChunk { this.pageWaitForOptions = pageWaitForOptions; this.pageViewport = pageViewport; this.pagePrepareFn = pagePrepareFn; + this.videoDecoderHardwareAcceleration = videoDecoderHardwareAcceleration; this.timeActions = timeActions; } @@ -154,7 +157,7 @@ export default class ChunkVideo extends VideoChunk { async #synthesize() { const page = await this.#acquirePage(); try { - const { url, content, width, height, fps, startTime, duration, pageWaitForOptions, pageViewport = {} } = this; + const { url, content, width, height, fps, startTime, duration, pageWaitForOptions, pageViewport = {}, videoDecoderHardwareAcceleration } = this; // 监听页面实例发生的某些内部错误 page.on("error", err => this._emitError("Page error:\n" + err.stack)); // 监听页面是否崩溃,当内存不足或过载时可能会崩溃 @@ -210,6 +213,7 @@ export default class ChunkVideo extends VideoChunk { fps, startTime, duration, + videoDecoderHardwareAcceleration, autostart: this.autostartRender }); // 监听并等待录制完成 diff --git a/api/SingleVideo.js b/api/SingleVideo.js index d6124f1..494ec26 100644 --- a/api/SingleVideo.js +++ b/api/SingleVideo.js @@ -70,6 +70,7 @@ export default class SingleVideo extends Synthesizer { * @param {number} [options.parallelWriteFrames=10] - 并行写入帧数 * @param {Viewport} [options.pageViewport] - 页面视窗参数 * @param {Function} [options.pagePrepareFn] - 页面预处理函数 + * @param {string} [options.videoDecoderHardwareAcceleration] - VideoDecoder硬件加速指示 * @param {{[key: number]: Function}} [options.timeActions] - 动作序列 * @param {WaitForOptions} [options.pageWaitForOptions] - 页面等待选项 * @param {boolean} [options.showProgress=false] - 是否在命令行展示进度 @@ -80,7 +81,7 @@ export default class SingleVideo extends Synthesizer { */ constructor(options = {}) { super(options); - const { url, content, startTime, autostartRender, consoleLog, videoPreprocessLog, pageWaitForOptions, pageViewport, pagePrepareFn, timeActions } = options; + const { url, content, startTime, autostartRender, consoleLog, videoPreprocessLog, pageWaitForOptions, pageViewport, pagePrepareFn, videoDecoderHardwareAcceleration, timeActions } = options; assert(_.isUndefined(url) || util.isURL(url), `url ${url} is not valid URL`); assert(_.isUndefined(content) || _.isString(content), "page content must be string"); assert(!_.isUndefined(url) || !_.isUndefined(content), "page url or content must be provide"); @@ -90,6 +91,7 @@ export default class SingleVideo extends Synthesizer { assert(_.isUndefined(pageWaitForOptions) || _.isObject(pageWaitForOptions), "pageWaitForOptions must be Object"); assert(_.isUndefined(pageViewport) || _.isObject(pageViewport), "pageViewport must be Object"); assert(_.isUndefined(pagePrepareFn) || _.isFunction(pagePrepareFn), "pagePrepareFn must be Function"); + assert(_.isUndefined(videoDecoderHardwareAcceleration) || _.isString(videoDecoderHardwareAcceleration), "videoDecoderHardwareAcceleration must be string"); assert(_.isUndefined(timeActions) || _.isObject(timeActions), "timeActions must be Object"); timeActions && Object.keys(timeActions).forEach(key => { key = Number(key) @@ -105,6 +107,7 @@ export default class SingleVideo extends Synthesizer { this.pageViewport = pageViewport; this.pageWaitForOptions = pageWaitForOptions; this.pagePrepareFn = pagePrepareFn; + this.videoDecoderHardwareAcceleration = videoDecoderHardwareAcceleration; this.timeActions = timeActions; } @@ -151,7 +154,7 @@ export default class SingleVideo extends Synthesizer { async #synthesize() { const page = await this.#acquirePage(); try { - const { url, content, width, height, fps, startTime, duration, pageWaitForOptions, pageViewport = {} } = this; + const { url, content, width, height, fps, startTime, duration, pageWaitForOptions, pageViewport = {}, videoDecoderHardwareAcceleration } = this; // 监听页面实例发生的某些内部错误 page.on("error", err => this._emitError("Page error:\n" + err.stack)); // 监听页面是否崩溃,当内存不足或过载时可能会崩溃 @@ -202,6 +205,7 @@ export default class SingleVideo extends Synthesizer { fps, startTime, duration, + videoDecoderHardwareAcceleration, autostart: this.autostartRender }); // 监听并等待录制完成 diff --git a/api/WebVideoCreator.js b/api/WebVideoCreator.js index ad3f7b1..ae4df73 100644 --- a/api/WebVideoCreator.js +++ b/api/WebVideoCreator.js @@ -80,6 +80,7 @@ export default class WebVideoCreator { * @param {boolean} [options.autostartRender=true] - 是否自动启动渲染,如果为false请务必在页面中执行 captureCtx.start() * @param {boolean} [options.consoleLog=false] - 是否开启控制台日志输出 * @param {boolean} [options.videoPreprocessLog=false] - 是否开启视频预处理日志输出 + * @param {string} [options.videoDecoderHardwareAcceleration] - VideoDecoder硬件加速指示 * @param {WaitForOptions} [options.pageWaitForOptions] - 页面等待选项 * @param {Viewport} [options.pageViewport] - 页面视窗参数 * @param {Function} [options.pagePrepareFn] - 页面预处理函数 @@ -158,6 +159,7 @@ export default class WebVideoCreator { * @param {boolean} [options.autostartRender=true] - 是否自动启动渲染,如果为false请务必在页面中执行 captureCtx.start() * @param {boolean} [options.consoleLog=false] - 是否开启控制台日志输出 * @param {boolean} [options.videoPreprocessLog=false] - 是否开启视频预处理日志输出 + * @param {string} [options.videoDecoderHardwareAcceleration] - VideoDecoder硬件加速指示 * @param {WaitForOptions} [options.pageWaitForOptions] - 页面等待选项 * @param {Viewport} [options.pageViewport] - 页面视窗参数 * @param {Function} [options.pagePrepareFn] - 页面预处理函数 diff --git a/core/CaptureContext.js b/core/CaptureContext.js index 9da5504..0930f9f 100644 --- a/core/CaptureContext.js +++ b/core/CaptureContext.js @@ -657,6 +657,8 @@ export default class CaptureContext { volume: (e.getNumberAttribute("volume") || e.volume || 1) * 100, // 视频是否自动播放 autoplay: e.getBooleanAttribute("autoplay"), + // 解码器硬件加速方法提示 + hardwareAcceleration: this.config.videoDecoderHardwareAcceleration, // 视频是否静音 muted: e.getBooleanAttribute("muted"), // 拉取失败时重试拉取次数 diff --git a/core/Page.js b/core/Page.js index d4f9a92..fbacb59 100644 --- a/core/Page.js +++ b/core/Page.js @@ -338,12 +338,13 @@ export default class Page extends EventEmitter { */ async startScreencast(options = {}) { await this.#asyncLock.acquire("startScreencast", async () => { - let { fps, startTime = 0, duration, frameCount, autostart = true } = options; + let { fps, startTime = 0, duration, frameCount, autostart = true, videoDecoderHardwareAcceleration } = options; assert(this.isReady(), "Page state must be ready"); assert(_.isUndefined(fps) || _.isFinite(fps), "fps must be number"); assert(_.isFinite(startTime), "startTime must be number"); assert(_.isUndefined(duration) || _.isFinite(duration), "duration must be number"); assert(_.isUndefined(frameCount) || _.isFinite(frameCount), "frameCount must be number"); + assert(_.isUndefined(videoDecoderHardwareAcceleration) || _.isString(videoDecoderHardwareAcceleration), "videoDecoderHardwareAcceleration must be string"); // 指定时长时将计算总帧数 if (_.isFinite(duration)) frameCount = util.durationToFrameCount(duration, fps); @@ -365,7 +366,7 @@ export default class Page extends EventEmitter { Object.assign(captureCtx.config, config); // 如果准备后还未启动且自动启动选项开启时渲染则开始 !captureCtx.ready() && captureCtx.config.autostart && captureCtx.start(); - }, _.pickBy({ fps, startTime, duration, frameCount, autostart }, v => !_.isUndefined(v))); + }, _.pickBy({ fps, startTime, duration, frameCount, autostart, videoDecoderHardwareAcceleration }, v => !_.isUndefined(v))); }); } diff --git a/docs/api-reference-high-level.md b/docs/api-reference-high-level.md index e077d01..d7c3467 100644 --- a/docs/api-reference-high-level.md +++ b/docs/api-reference-high-level.md @@ -341,6 +341,11 @@ boolean 是否开启视频预处理日志输出 + + videoDecoderHardwareAcceleration + string + VideoDecoder硬件加速指示,默认值 prefer-hardware + parallelWriteFrames number diff --git a/docs/api-reference-low-level.md b/docs/api-reference-low-level.md index 1e65059..0dcacc1 100644 --- a/docs/api-reference-low-level.md +++ b/docs/api-reference-low-level.md @@ -331,6 +331,11 @@ const { ... } = core; boolean 是否自动启动渲染,默认true + + videoDecoderHardwareAcceleration + string + VideoDecoder硬件加速指示,默认值 prefer-hardware + diff --git a/media/VideoCanvas.js b/media/VideoCanvas.js index b4c9bf5..238954f 100644 --- a/media/VideoCanvas.js +++ b/media/VideoCanvas.js @@ -35,6 +35,8 @@ export default class VideoCanvas { volume; /** @type {boolean} - 是否自动播放 */ autoplay; + /** @type {string} - 解码器硬件加速方法提示 */ + hardwareAcceleration; /** @type {boolean} - 是否静音 */ muted; /** @type {number} - 重试下载次数 */ @@ -104,13 +106,14 @@ export default class VideoCanvas { * @param {number} [options.volume=100] - 视频音频音量(0-100) * @param {boolean} [options.loop=false] - 是否循环播放 * @param {boolean} [options.muted=false] - 是否静音 + * @param {string} [options.hardwareAcceleration="prefer-hardware"] - 解码器硬件加速方法提示 * @param {boolean} [options.retryFetchs=2] - 重试下载次数 * @param {boolean} [options.ignoreCache=false] - 是否忽略本地缓存 */ constructor(options) { const u = ____util; u.assert(u.isObject(options), "VideoCanvas options must be Object"); - const { url, maskUrl, startTime, endTime, audioId, format, seekStart, seekEnd, fadeInDuration, fadeOutDuration, autoplay, volume, loop, muted, retryFetchs, ignoreCache } = options; + const { url, maskUrl, startTime, endTime, audioId, format, seekStart, seekEnd, fadeInDuration, fadeOutDuration, autoplay, volume, loop, muted, hardwareAcceleration, retryFetchs, ignoreCache } = options; u.assert(u.isString(url), "url must be string"); u.assert(u.isNumber(startTime), "startTime must be number"); u.assert(u.isNumber(endTime), "endTime must be number"); @@ -125,6 +128,7 @@ export default class VideoCanvas { u.assert(u.isUndefined(volume) || u.isNumber(volume), "volume must be number"); u.assert(u.isUndefined(loop) || u.isBoolean(loop), "loop must be boolean"); u.assert(u.isUndefined(muted) || u.isBoolean(muted), "muted must be boolean"); + u.assert(u.isUndefined(hardwareAcceleration) || u.isString(hardwareAcceleration), "hardwareAcceleration must be string"); u.assert(u.isUndefined(retryFetchs) || u.isNumber(retryFetchs), "retryFetchs must be number"); u.assert(u.isUndefined(ignoreCache) || u.isBoolean(ignoreCache), "ignoreCache must be boolean"); this.url = url; @@ -141,6 +145,7 @@ export default class VideoCanvas { this.volume = u.defaultTo(volume, 100); this.loop = u.defaultTo(loop, false); this.muted = u.defaultTo(muted, false); + this.hardwareAcceleration = u.defaultTo(hardwareAcceleration, "prefer-hardware"); this.retryFetchs = u.defaultTo(retryFetchs, 2); this.ignoreCache = u.defaultTo(ignoreCache, false); } @@ -227,7 +232,7 @@ export default class VideoCanvas { return true; } catch (err) { - console.log(err); + console.error(err); this.destory(); return false; } @@ -513,7 +518,7 @@ export default class VideoCanvas { u.assert(u.isFunction(onError), "onError must be Function"); const decoder = (isMask ? this.maskDecoder : this.decoder) || new VideoDecoder({ output: onFrame.bind(this), - error: onError.bind(this) + error: err => onError.bind(this)(new Error(err)) }); const demuxer = new ____MP4Demuxer(); let timer; @@ -523,8 +528,8 @@ export default class VideoCanvas { decoder.configure({ // 视频信息配置 ...config, - // 指示优先使用硬件加速解码 - hardwareAcceleration: "prefer-hardware", + // 解码器硬件加速指示 + hardwareAcceleration: this.hardwareAcceleration, // 关闭延迟优化,让解码器批量处理解码,降低负载 optimizeForLatency: true });