Webcodecs 遇到的一些坑
背景:
由于需要在web端做一款音视频剪辑软件,目前已经做到了软变软解,准备介入硬件webcodec解码的时候,遇到了一些问题,现在把问题整理如下。
问题一:通过c++调用Decoder,输出帧回调一直没有回调
C++ EM_JS(int, DecodePutBuffer, (int key_flag, int timestamp, int duration, void *pData, int nDataSize), { let buffer = new Uint8Array(Module.HEAPU8.buffer, pData, nDataSize); let key_string=null; if (key_flag) { key_string = "key"; } else { key_string = "delta"; } let chunk = new EncodedVideoChunk({ type: key_string, timestamp: timestamp, duration: duration, data: buffer }); Module.decoder.decode(chunk);
}); |
上图中描述应该比较清楚了,我们c++线程 一直在往decoder送数据,这个任务一直在执行,导致输出帧回调一直没有及时得到调用,具体原因跟js异步执行又关系,大家可以参看我另外一个博客:
https://blog.csdn.net/c553110519/article/details/129357494?spm=1001.2014.3001.5501
显然上述设计方式满足不了这个要求,后来对js异步做了深入的学习,为了解决这个问题设计了两个方案
一 主线程转发
由于我们主线程加载wasm资源的时候,内部创建线程的内存跟主线程可以共享的,所以 主线程创建播放器线程,播放器线程创建解码器线程,一旦发现需要硬件解码,在一块内存上写上标识为,主线程轮训知道需要创建work解码线程,就去创建work, 主线程与子线程通过共享内存方式,主线程与解码器work通过postMessage方式进行通信,这样也能满足上述的要求,但是增加主线程的负担。
二 子线程监听与共享内存方案
这个方案跟上边不同的是,在主线程要创建worker解码线程的时候,把共享内存的包装SharedBuffers 传递给worker,worker 就可以跟 解码器直接通过共享内存进行交互,主线程只有在刚开始建立连接的时候发挥一次作用,后边完全交给两者了,
C++ typedef struct { MInt32 dwErrorCode; MInt32 lKey_Width_Height;//high 1bit is key and mid 15 bit is width, low 16bit is height MInt32 dwDuration; MInt32 dwTimeStamp; MInt32 lConsumerStatus;//播放器请求解码器状态 MInt32 lConsumerBufLen; MInt32 lConsumerUseLen; MInt32 dwConsumerBufferStatus; MInt32 pConsumerBuffer;
MInt32 lProducerStatus;//worker解码当前自身状态,根据llConsumerStatus 来确认状态 MInt32 lProducerBufLen; MInt32 lProducerUseLen; MInt32 dwProducerBufferStatus; MInt32 pProducerBuffer;
MInt32 dwProfile; MInt32 dwProfileCompatibility; MInt32 dwLevel; MInt32 dwWorking_NoMoreOutput; }QVET_WEBCODEC_SHARED_DATA; |
整体关系是生产者与消费者的关系。
问题二:解码一段时间output只回调了几十次,后边在也不回调了
我也很奇怪,明明已经异步了,怎么还是只输出几十帧,调查了最后发先输出的VideoFrame,在使用后要Close,这样VideoFrame资源js就会回首,后边回调才能及时过来
问题三:导出视频的第一帧绿屏
这个调查花的时间相对少一点,主要是看了api,发现
C++ VideoFrame.copyTo() The copyTo() method of the VideoFrame interface copies the contents of the VideoFrame to an ArrayBuffer. |
C++ Return value A Promise that resolves to the layout of the copy when the copy has completed. |
可以看出来当我把解码器copyto到自己的共享内存的时候,它是异步的,而我取的时候,以为拷贝完成了,其实没有,导致第一帧是绿色的,知道原因就好改了
JavaScript async function SyncOutputFrame() { if (queue.isEmpty) return ; let dwBufferStatus = Atomics.load(sharedBuffers, DECODER_SHARED.dwProducerBufferStatus); //客户端正在读取缓冲区 if (dwBufferStatus == BUFFER_STATE.READING) { return; } //已经写了缓存一帧还没有读取 else if (dwBufferStatus == BUFFER_STATE.WRITEED) { return; } else if (dwBufferStatus == BUFFER_STATE.WRITEING) { return ; } Atomics.store( sharedBuffers, DECODER_SHARED.dwProducerBufferStatus, BUFFER_STATE.WRITEING); let videoFrame = queue.dequeue(); let pData = sharedBuffers[DECODER_SHARED.pProducerBuffer]; let nDataSize = sharedBuffers[DECODER_SHARED.lProducerBufLen]; let buffer = new Uint8Array(module.buffer, pData, nDataSize); await videoFrame.copyTo(buffer); sharedBuffers[DECODER_SHARED.lProducerUseLen] = videoFrame.allocationSize(); Atomics.store( sharedBuffers, DECODER_SHARED.dwProducerBufferStatus, BUFFER_STATE.WRITEED); // console.log("write frame size = " + videoFrame.allocationSize());
videoFrame.close(); } |
只要await下就好了
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)