日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
Node+FFmpeg實(shí)現(xiàn)Canvas動(dòng)畫(huà)導(dǎo)出視頻

導(dǎo)言

專(zhuān)注于為中小企業(yè)提供網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)長(zhǎng)壽免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了近千家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

Canvas為前端提供了動(dòng)畫(huà)展示的平臺(tái),隨著現(xiàn)在視頻娛樂(lè)的流行,你是否想過(guò)把Canvas動(dòng)畫(huà)導(dǎo)出視頻?目前純前端的視頻編碼轉(zhuǎn)換(例如WebM Encoder Whammy)還存在許多限制,較為成熟的方案是將每幀圖片傳給后端實(shí)現(xiàn),由后端調(diào)用FFmpeg進(jìn)行視頻轉(zhuǎn)碼。整體流程并不復(fù)雜,這篇文章將帶大家實(shí)現(xiàn)這個(gè)過(guò)程。

整體方案

  • 由前端記錄Canvas動(dòng)畫(huà)的每幀圖像,以base64字符串形式傳給后端
  • 利用node fluent-ffmpeg模塊,調(diào)用FFmpeg將圖片合并成視頻,并將視頻存儲(chǔ)在server端,并返回相應(yīng)下載url
  • 前端通過(guò)請(qǐng)求得到視頻文件

前端部分

每幀圖片生成

圖片生成可以通過(guò)canvas原生接口toDataURL實(shí)現(xiàn),最終返回base64形式的圖像數(shù)據(jù)。 

 
 
 
 
  1. generatePng () { 
  2.   ... 
  3.   var imgData = canvas.toDataURL("image/png"); 
  4.   return imgData; 
  5. }  

動(dòng)畫(huà)錄制與圖片流傳輸

動(dòng)畫(huà)的記錄與傳送是個(gè)異步過(guò)程,這里返回一個(gè)Promise,等待后端處理完畢,收到回應(yīng)后,即完成此異步過(guò)程。

以下代碼將canvas每幀動(dòng)畫(huà)信息存入一個(gè)圖片數(shù)組imgs中,將數(shù)組轉(zhuǎn)成字符串的形式傳給后端。注意這里contentType設(shè)置為“text/plain”。

 
 
 
 
  1. generateVideo () { 
  2.   var that = this; 
  3.   return new Promise ( 
  4.     function (resolve, reject) { 
  5.       var imgs = []; 
  6.       ... 
  7.       window.requestAnimationFrame(that.recordTick.bind(that, imgs, resolve, reject)); 
  8.     } 
  9.   ) 
  10. }   
 
 
 
 
  1. recordTick (imgs, resolve, reject) { 
  2.   ...//每幀動(dòng)畫(huà)的記錄信息,如時(shí)間戳等 
  3.  
  4.   if (...) {//動(dòng)畫(huà)終止條件 
  5.     this.stopPlay(); 
  6.     imgs.push(this.generatePng()); 
  7.     $.ajax({ 
  8.       url: '/video/record', 
  9.       data: imgs.join(' '), 
  10.       method: 'POST', 
  11.       contentType: 'text/plain', 
  12.       success: function (data, textStatus, jqXHR) { 
  13.         resolve(data); 
  14.       }, 
  15.       error: function (jqXHR, textStatus, errorThrown) { 
  16.         reject(errorThrown); 
  17.       } 
  18.     }); 
  19.   } else { 
  20.     ...//每幀動(dòng)畫(huà)展示的代碼 
  21.  
  22.     imgs.push(this.generatePng()); 
  23.     window.requestAnimationFrame(this.recordTick.bind(this, imgs, resolve, reject)); 
  24.   } 
  25. }  

視頻下載

上一節(jié)代碼中,動(dòng)畫(huà)停止時(shí),會(huì)通過(guò)post請(qǐng)求給后端傳送所有圖片數(shù)據(jù),后端處理完畢后,返回?cái)?shù)據(jù)中包含一個(gè)url,此url即為視頻文件的下載地址。

為了支持瀏覽器端用戶(hù)點(diǎn)擊下載,我們需要用到a標(biāo)簽的download屬性,此屬性可以支持點(diǎn)擊a標(biāo)簽后下載指定文件。

 
 
 
 
  1. editor.generateVideo().then(function (data) { 
  2.   videoRecordingModal.setDownloadLink(data.url, data.filename); 
  3.   videoRecordingModal.changeStatus('recorded'); 
  4. });   
 
 
 
 
  1. setDownloadLink: function (url, filename) { 
  2.   this.config.$dom.find('.video-download').attr('href', url); 
  3.   this.config.$dom.find('.video-download').attr('download', filename); 
  4. }  

后端部分

圖片序列生成

接收到前端傳送的圖片數(shù)據(jù)后,我們首先需要將圖片解析、存儲(chǔ)在服務(wù)器中,我們建立以當(dāng)前時(shí)間戳命名的文件夾,將圖片序列以一定格式存儲(chǔ)于其中。由于每張圖片寫(xiě)入都是異步過(guò)程,為確保所有圖片都已處理完畢后,才執(zhí)行視頻轉(zhuǎn)碼過(guò)程,我們需要用到Promise.all。

 
 
 
 
  1. Promise.all(imgs.map(function (value, index) { 
  2.   var img = decodeBase64Image(value) 
  3.   var data = img.data 
  4.   var type = img.type 
  5.   return new Promise(function (resolve, reject) { 
  6.     fs.writeFile(path.resolve(__dirname, (folder + '/img' + index + '.' + type)), data, 'base64', function(err) { 
  7.       if (err) { 
  8.         reject(err) 
  9.       } else { 
  10.         resolve() 
  11.       } 
  12.     }) 
  13.   }) 
  14. })).then(function () { 
  15.   …//視頻轉(zhuǎn)碼 
  16. })  

其中decodeBase64Image函數(shù)參考這里。

視頻生成

視頻生成利用FFmpeg轉(zhuǎn)碼工具。首先確保server端安裝了FFmpeg

 
 
 
 
  1. brew install ffmpeg 

在項(xiàng)目中安裝fluent-ffmpeg,這是node調(diào)用ffmpeg的接口模塊

 
 
 
 
  1. npm install fluent-ffmpeg --save 

結(jié)合上一節(jié)圖片序列存儲(chǔ)的代碼,整個(gè)接口代碼如下:

 
 
 
 
  1. app.post('/video/record', function(req, res) { 
  2.   var imgs = req.text.split(' ') 
  3.   var timeStamp = Date.now() 
  4.   var folder = 'images/' + timeStamp 
  5.   if (!fs.existsSync(resolve(folder))){ 
  6.     fs.mkdirSync(resolve(folder)); 
  7.   } 
  8.  
  9.   Promise.all(imgs.map(function (value, index) { 
  10.     var img = decodeBase64Image(value) 
  11.     var data = img.data 
  12.     var type = img.type 
  13.     return new Promise(function (resolve, reject) { 
  14.       fs.writeFile(path.resolve(__dirname, (folder + '/img' + index + '.' + type)), data, 'base64', function(err) { 
  15.         if (err) { 
  16.           reject(err) 
  17.         } else { 
  18.           resolve() 
  19.         } 
  20.       }) 
  21.     }) 
  22.   })).then(function () { 
  23.     var proc = new ffmpeg({ source: resolve(folder + '/img%d.png'), nolog: true }) 
  24.       .withFps(25) 
  25.       .on('end', function() { 
  26.         res.status(200) 
  27.         res.send({ 
  28.           url: '/video/mpeg/' + timeStamp, 
  29.           filename: 'jianshi' + timeStamp + '.mpeg' 
  30.         }) 
  31.       }) 
  32.       .on('error', function(err) { 
  33.         console.log('ERR: ' + err.message) 
  34.       }) 
  35.       .saveToFile(resolve('video/jianshi' + timeStamp + '.mpeg')) 
  36.   }) 
  37. })  

視頻下載

最終將視頻文件傳輸給前端的接口代碼如下:

 
 
 
 
  1. app.get('/video/mpeg/:timeStamp', function(req, res) { 
  2.   res.contentType('mpeg'); 
  3.   var rstream = fs.createReadStream(resolve('video/jianshi' + req.params.timeStamp + '.mpeg')); 
  4.   rstream.pipe(res, {end: true}); 
  5. })  

效果預(yù)覽 

  

注:此功能是個(gè)人項(xiàng)目”簡(jiǎn)詩(shī)”的一部分,完整代碼可以查看https://github.com/moyuer1992...


新聞標(biāo)題:Node+FFmpeg實(shí)現(xiàn)Canvas動(dòng)畫(huà)導(dǎo)出視頻
URL標(biāo)題:http://www.5511xx.com/article/dphjjse.html