隨便扣個(gè)GB/BB視頻。(JS實(shí)現(xiàn),內(nèi)含代碼適度食用)
最近動(dòng)態(tài)里一大堆的GB/BB的視頻。覺(jué)得挺有意思。JS扣一下康康??
魯迅同志曾說(shuō)過(guò),得把引用寫上:源文件是這個(gè)的 1 和 22 P


聲明一下:我知道webgl做效率高很多。但是由于webgl代碼我寫了你們也不一定能看懂。所以只貼一些原理性的東西。。
好吧先看結(jié)果:(直接截圖了)


準(zhǔn)備必要的東西
為了通用性更強(qiáng)一點(diǎn),所以先做了視頻背景顏色的分析:
渲染用的幕布
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
不說(shuō)啥了。這很好理解,畢竟得展示出來(lái)。
離屏幕布
let offscreenCanvas ?= document.createElement('canvas');
let offCtx = offscreenCanvas.getContext('2d')
分析和色彩計(jì)算,如果渲染出來(lái)效率會(huì)非常低。眾所周知,JS的渲染進(jìn)程和執(zhí)行進(jìn)程相互阻塞。所以需要一個(gè)離屏的幕布。
分析時(shí)候用的一些變量
const colorcount = {}??// 記錄色彩
let analyseCount = 0;
const LIMIT_ANALYSE = 10 // 最多分析的幀數(shù)。
const?Y_RANGE =?180; // 允許的色彩偏差范圍值
const?UV_RANGE =?80;// 允許的色彩偏差范圍值
這里需要注意幾點(diǎn)
1)colorcount的聲明,我用的對(duì)象。為啥不是數(shù)組?[{color: count}]的模式,熟悉JS的小伙伴應(yīng)該知道 如果聲明成數(shù)組。對(duì)于之后的排序會(huì)友好很多。但是,數(shù)組set的時(shí)候你需要做二叉查找。而對(duì)象,這樣寫會(huì)變成hash查找。對(duì)于排序我們就在分析結(jié)束的時(shí)候做一次。而設(shè)置顏色的Count。每一幀每一個(gè)像素都要做。所以是對(duì)象。
? ? ? 視頻當(dāng)然也得是離屏的
????? let video = document.createElement('video')
??????video.src = './gb.mp4'
??????video.muted = true;?
????????
? ? ? 幕布顏色值。做的比較簡(jiǎn)單。(不要學(xué)我瞎寫)
??????let yuvcolor = undefined
2. 視頻分析
if(analyseCount < LIMIT_ANALYSE) {
? 記錄色彩(見后面)
? analyseCount++
? requestAnimationFrame(analyse)
} else {
????分析(見后面)
}
記錄色彩部分
離屏繪制
offCtx.drawImage(video, 0,0, offscreenCanvas.width, offscreenCanvas.height, 0,0, video.videoWidth, video.videoHeight)
不多說(shuō),把像素畫到離屏的幕布上
var idata = offCtx.getImageData(0,0, offscreenCanvas.width, offscreenCanvas.height)
取到像素值
if (idata && idata.data) { // 這是一個(gè)好習(xí)慣!!
????for(let i=0;i<idata.data.length;i+=4) {
????????// 每個(gè)像素4位
????????if(idata.data[i + 3]!= 0) {
????????// 制造key
????????let colorindex = idata.data[i] + ',' + idata.data[i+1] + ',' + idata.data[i+2]
????????// 不存在時(shí)候,創(chuàng)建色彩,并計(jì)數(shù)1
????????if (!colorcount[colorindex]) colorcount[colorindex] = 1;
????????// 存在的時(shí)候更新數(shù)目
????????else colorcount[colorindex] ++;
????}
}
特殊說(shuō)明:如果是webgl做分析。這里可以搞texture來(lái)存儲(chǔ)。效率高很多。
分析顏色
let?maxcolor = '0,0,0'
let maxcount = 0
for(i in colorcount) { // 非常差勁!?。?!for(xxx;xxx;xxx)的寫法會(huì)更好!
????if(colorcount[i] > maxcount) {
????????maxcolor = i
????????maxcount = colorcount[i]
????}
}
// 分析色彩和轉(zhuǎn)換YUV
let color = [parseInt(maxcolor.split(",")[0]), parseInt(maxcolor.split(",")[1]), parseInt(maxcolor.split(",")[2])]
let y = 0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2]
let u =-0.169 * color[0] - 0.331 * color[1] + 0.500 * color[2]
let v= 0.500 * color[0] - 0.439 * color[1] - 0.081 * color[2]
yuvcolor = [y,u,v]
首先 這段代碼寫得非常差。原因如下
? ??????1. 我圖省事寫了 for(i in xxx)眾所周知。這個(gè)效率非常低
????????2. 其實(shí)不用YUV也可以。這樣的話,去掉綠幕的時(shí)候每一幀都得轉(zhuǎn)。另外,YUV轉(zhuǎn)換可以抽出來(lái)成方法的。。所以非常差勁
3. 去掉綠幕:
還是先在離屏幕布畫圖!
offCtx.drawImage(video, 0,0, video.videoWidth, video.videoHeight, 0,0, video.videoWidth, video.videoHeight)
還是取畫面
var idata = offCtx.getImageData(0,0, video.videoWidth, video.videoHeight)
for (let i=0; i<idata.data.length; i+=4) {
????// YUV轉(zhuǎn)換
????let y = 0.299 * idata.data[i] + 0.587 * idata.data[i+1] + 0.114 *idata.data[i+2]
????let u =-0.169 * idata.data[i] - 0.331 * idata.data[i+1] + 0.500 * idata.data[i+2]
????let v= 0.500 * idata.data[i] - 0.439 * idata.data[i+1] - 0.081 * idata.data[i+2]
????// 很好理解吧。范圍內(nèi)的值直接把透明度設(shè)置為0
????if(y < yuvcolor[0] + Y_RANGE
????????&& y > yuvcolor[0] - Y_RANGE
????????&& u < yuvcolor[1] + UV_RANGE
????????&& u > yuvcolor[1] - UV_RANGE
????????&& v < yuvcolor[2] + UV_RANGE
????????&& v > yuvcolor[2] - UV_RANGE
????????) {
????????idata.data[i+3] = 0;
????}
}
// 畫圖
ctx.putImageData(idata, 0,0)
requestAnimationFrame(draw)
4. 處理時(shí)機(jī):
analyse 在 canplay的時(shí)候就行了。。。
播放的時(shí)候處理去綠幕
5. 一些說(shuō)明
老生長(zhǎng)談:視頻壓縮會(huì)有數(shù)據(jù)損失。所以不會(huì)是那個(gè)顏色值。加了個(gè)RANGE。請(qǐng)叫我調(diào)參工程師。。。
6. 代碼爛在哪?
暗切線同學(xué)啊,,誰(shuí)?教你寫全局變量的??
就說(shuō)這么多吧。這篇文章非常水。。。感覺(jué)完全是灌水的。。。你們隨意噴吧。。。

交流群:711929228
本期問(wèn)題:yuv420p指的是啥?
答案:像素格式。