antimatter15 / whammy

A real time javascript webm encoder based on a canvas hack
http://antimatter15.github.com/whammy/clock.html
MIT License
1.02k stars 182 forks source link

Whammy gives Black Screen Webm video on latest Google Chrome #70

Open hoxily opened 3 years ago

hoxily commented 3 years ago

Backgrounds

We used whammy for about 1 year, and it works well. Recently, some customers report that the generated video is black screen.

Google Chrome Versions

I have checked an older version of Google Chrome 90.0.4430.212 (64-bit),it works well. But whammy gives black screen when on latest Google Chrome 92.0.4515.107 (64-bit)。

Guessing Cause

The lastest Google Chrome updated webp image format, and whammy parse latest webp incorrectly, then it get a black screen webm video file.

Can anyone help on webp and webm format? webm-ok.zip webm-blackscreen.zip

xuejianxianzun commented 3 years ago

I ran into the same problem. In addition, the black screen file generated by this tool is larger than the normal file before.

Using the hexadecimal viewer, I found that the black screen video has some extra characters, and there are many google logos, which are not in the previous videos. I guess this piece of data will be added in front of every picture. QQ截图20210731131023.png

xuejianxianzun commented 3 years ago

修改 whammy.js,移除这行代码:

            var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))))

替换为

            var str = atob(frame.image.slice(23))

            // Process for WEBPVP8X encoding
            if(str.includes('WEBPVP8X')){
              // save 0-15
              // The length to be removed in the middle is 562 B, which is to remove 16-577
              // save 578-end
              var p1 = str.substr(0,15)
              var p2 = str.substr(577)
              str = p1 + p2
            }

            var webp = parseWebP(parseRIFF(str))

问题解决。 如果你需要使用其下方的 fromImageArray 函数,那么在 fromImageArray 函数里也需要这样修改。

xuejianxianzun commented 3 years ago

解决的原理: Chrome 最近的更新修改了 webp 图片的编码方法,从 WEBPVP8 改为了 WEBPVP8XWEBPVP8X 编码的图片多出了一些内容,我们需要移除这些内容,否则生成的 webm 视频是不能使用的。

以前的版本:WEBPVP8 old_webp2.png

新版本:WEBPVP8X new_webp2.png

移除选中的部分,图片就变得和旧版本 Chrome 相同了。所以视频也恢复正常了。

hoxily commented 3 years ago

非常感谢。 基于RIFF的Chunk规则,我写了一个更好的方法,忽略其他不需要的Chunk,仅提取出VP8 Bit Stream Chunk数据。相比 @xuejianxianzun 使用写死的常量 [0,15], [577, end] ,会更健壮一些。

    /**
     * 读取指定偏移量处的小端模式的32位无符号整数
     * @param buffer - 数据缓冲区
     * @param offset - 起始偏移量
     * @returns 32位无符号整数
     */
    function readUint32LittleEndian(buffer: string, offset: number): number {
      let val = parseInt(
        buffer
          .substr(offset, 4)
          .split("")
          .map(function(i) {
            var unpadded = i.charCodeAt(0).toString(2);
            return new Array(8 - unpadded.length + 1).join("0") + unpadded;
          })
          .reverse() // 注意需要翻转字节序才是小端编码
          .join(""),
        2
      );
      return val;
    }

    /**
     * 对于 VP8X,需要提取出其中的 VP8 或 VP8L bit stream chunk。
     * 关于 VP8X 格式,参见 Extended file format: https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
     * @param buffer VP8X Chunk数据,不含 "VP8X" tag
     */
    function extractBitStreamFromVp8x(buffer: string): ChunkSizeAndBinaryData {
      //console.log("VP8X buffer:", buffer);

      /*
       跳过以下VP8X头:
       32bit VP8X Chunk size
       8bit Flags: Rsv I L E X A R 
       24bit Reserved
       24bit Canvas Width Minus One
       24bit Canvas Height Minus One
      */
      let offset = 4 + 1 + 3 + 3 + 3;
      // 搜索第一个"VP8 "或"VP8L" bit stream chunk
      while (offset < buffer.length) {
        let chunkTag = buffer.substr(offset, 4);
        //console.log(`chunkTag: \"${chunkTag}\"`);
        offset += 4;
        let chunkSize = readUint32LittleEndian(buffer, offset);
        //console.log("chunkSize:", chunkSize);
        offset += 4;
        switch (chunkTag) {
          case "VP8 ":
          case "VP8L":
            const size = buffer.substr(offset - 4, 4);
            const body = buffer.substr(offset, chunkSize);
            return size + body; 
          default:
            // 跳过不关心的数据块
            offset += chunkSize;
            break;
        }
      }
      console.error("VP8X format error: missing VP8/VP8L chunk.");
    }

    function parseRIFF(string: string): RiffChunk {
      //console.log("binary string:", string);
      var offset = 0;
      var chunks: RiffChunk = {};

      while (offset < string.length) {
        var id = string.substr(offset, 4);
        chunks[id] = chunks[id] || [];
        if (id == "RIFF" || id == "LIST") {
          var len = readUint32LittleEndian(string, offset + 4)
          var data = string.substr(offset + 4 + 4, len);
          // console.log(data);
          offset += 4 + 4 + len;
          chunks[id].push(parseRIFF(data));
        } else if (id == "WEBP") {
          let vpVersion = string.substr(offset + 4, 4);
          switch (vpVersion) {
            case "VP8X":
              chunks[id].push(extractBitStreamFromVp8x(string.substr(offset + 8)));
              break;
            case "VP8 ":
            case "VP8L":
              // Use (offset + 8) to skip past "VP8 " / "VP8L" field after "WEBP"
              chunks[id].push(string.substr(offset + 8));
              break;
            default:
              console.error(`not supported webp version: \"${vpVersion}\"`);
              break;
          }
          offset = string.length;
        } else {
          // Unknown chunk type; push entire payload
          chunks[id].push(string.substr(offset + 4));
          offset = string.length;
        }
      }
      return chunks;
    }

type ChunkSizeAndBinaryData = string;

interface RiffChunk {
  [tag: string]: (ChunkSizeAndBinaryData | RiffChunk)[];
}
xuejianxianzun commented 3 years ago

厉害了

Akimyou commented 2 years ago

Update on the https://github.com/Akimyou/ts-whammy/issues/29 Please try the ts-whammy.

A modern typescript version of whammy. You can use it to encode images(webp) to webm video.

ts-whammy only include the core function of whammy then with modern frontend technology stack.