Webcodecs 遇到的一些坑

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


});

Webcodecs 遇到的一些坑_第1张图片

 

上图中描述应该比较清楚了,我们c++线程 一直在往decoder送数据,这个任务一直在执行,导致输出帧回调一直没有及时得到调用,具体原因跟js异步执行又关系,大家可以参看我另外一个博客:

https://blog.csdn.net/c553110519/article/details/129357494?spm=1001.2014.3001.5501

显然上述设计方式满足不了这个要求,后来对js异步做了深入的学习,为了解决这个问题设计了两个方案

主线程转发

Webcodecs 遇到的一些坑_第2张图片

 

由于我们主线程加载wasm资源的时候,内部创建线程的内存跟主线程可以共享的,所以 主线程创建播放器线程,播放器线程创建解码器线程,一旦发现需要硬件解码,在一块内存上写上标识为,主线程轮训知道需要创建work解码线程,就去创建work,  主线程与子线程通过共享内存方式,主线程与解码器work通过postMessage方式进行通信,这样也能满足上述的要求,但是增加主线程的负担。

子线程监听与共享内存方案

Webcodecs 遇到的一些坑_第3张图片

 

这个方案跟上边不同的是,在主线程要创建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就会回首,后边回调才能及时过来

C++
videoFrame.close();

问题三:导出视频的第一帧绿屏

  这个调查花的时间相对少一点,主要是看了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下就好了

你可能感兴趣的:(webassembly,java,开发语言,js)