js实现视频转音频(无需通过服务器转码,客户端纯js实现,用户文件保密)

由于最近接到一个需求,需要通过前端技术提取视频内音乐,并且不需要上传视频资源到服务器中。因此需要使用到js将mp4格式转为mp3。

需要实现这个操作,我将它分为以下三步

  • 提取mp4文件blob arrayBuffer
  • 将mp4格式文件转高清wav格式音乐
  • 将wav格式音乐压缩为mp3格式(wav格式音乐文件过大)

提取mp4文件blob arrayBuffer

提取mp4文件arrayBuffer,这里分为两种,通过上传文件获取,或通过下载链接获取,这里我们讲述网络下载的方式

fetch(fileUrl)
      .then((response) => {
        return response.arrayBuffer()
      })
      .then((buffer) => {
      // arrayBuffer转wav音频格式
        blobToAwv(buffer, fileName, this.maxConcurrentDownloads, cd, this.numActiveDownloads)
        // this.downloadFileMp3(buffer, fileName)
      })
      .catch((error) => {
        console.error("Error downloading file:", error);
      })
      .finally(() => {
      // 这里为下载队列的属性,不需要多文件转换下载,请忽略
        this.numActiveDownloads--;
        this.processDownloadQueue(cd);
      });

将mp4格式文件转高清wav格式音乐

我们需要提取mp4内音频文件,需要使用到AudioContext音频对象,将mp4内音频文件用wav格式导出。(前提是获取到视频的blob对象,blob对象可通过下载链接获取或直接上传文件提取)

export const bufferToWavBlob = (buffer: any, fileName: string, total: number, cd: any) =>  {
  // 创建音频上下文
  const audioCtx = new AudioContext();
  // arrayBuffer转audioBuffer
  audioCtx.decodeAudioData(buffer, async function (audioBuffer) {
  // 提取音乐内的wavblob
    const blob = bufferToWav(audioBuffer, audioBuffer.sampleRate * audioBuffer.duration);
    });
}
function bufferToWav(abuffer, len) {
  var numOfChan = abuffer.numberOfChannels,
  length = len * numOfChan * 2 + 44,
  buffer = new ArrayBuffer(length),
  view = new DataView(buffer),
  channels = [], i, sample,
  offset = 0,
  pos = 0;
  // write WAVE header
  // "RIFF"
  setUint32(0x46464952);
  // file length - 8                      
  setUint32(length - 8);
  // "WAVE"                     
  setUint32(0x45564157);
  // "fmt " chunk
  setUint32(0x20746d66);  
  // length = 16                       
  setUint32(16);  
  // PCM (uncompressed)                               
  setUint16(1); 
  setUint16(numOfChan);
  setUint32(abuffer.sampleRate);
  // avg. bytes/sec
  setUint32(abuffer.sampleRate * 2 * numOfChan);
  // block-align
  setUint16(numOfChan * 2);
  // 16-bit (hardcoded in this demo)
  setUint16(16);                           
  // "data" - chunk
  setUint32(0x61746164); 
  // chunk length                   
  setUint32(length - pos - 4);                   
  // write interleaved data
  for(i = 0; i < abuffer.numberOfChannels; i++)
      channels.push(abuffer.getChannelData(i));

  while(pos < length) {
       // interleave channels
      for(i = 0; i < numOfChan; i++) {
      // 这里需要对channels[i][offset]做非undefied判断
          // clamp
          sample = Math.max(-1, Math.min(1, channels[i][offset])); 
          // scale to 16-bit signed int
          sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; 
          // write 16-bit sample
          view.setInt16(pos, sample, true);          
          pos += 2;
      }
      // next source sample
      offset++                                     
  }

  // create Blob
  return new Blob([buffer], {type: "audio/wav"});
  function setUint16(data) {
      view.setUint16(pos, data, true);
      pos += 2;
  }
  function setUint32(data) {
      view.setUint32(pos, data, true);
      pos += 4;
  }

将wav格式音乐压缩为mp3格式(wav格式音乐文件过大)

在这里就能获取到wav格式音频文件,但是这个音频文件过大。考虑到节省成本,后面就需要将wav格式音频转mp3.这里使用过lamejs但是效果很差。
这里我们用到了一个js录音库。极力推荐yyds。 xiangyuecn/**Recorder.感谢该库作者提供wav转mp3格式。
https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?idf=self_base_demo

import Recorder from "recorder-core"; //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
//import './你clone的目录/src/recorder-core.js' //clone源码可以按这个方式引入,下同
//require('./你clone的目录/src/recorder-core.js') //clone源码可以按这个方式引入,下同
//<script src="你clone的目录/src/recorder-core.js"> //这是html中script方式引入,下同

//按需引入你需要的录音格式支持文件,如果需要多个格式支持,把这些格式的编码引擎js文件统统引入进来即可
import "recorder-core/src/engine/mp3";
import "recorder-core/src/engine/mp3-engine";
import "recorder-core/src/engine/wav";
Recorder.Wav2Other = function (newSet, wavBlob, True, False) {
  const reader: any = new FileReader();
  reader.onloadend = function () {
    //检测wav文件头
    const wavView = new Uint8Array(reader.result);
    const eq = function (p, s) {
      for (var i = 0; i < s.length; i++) {
        if (wavView[p + i] != s.charCodeAt(i)) {
          return false;
        }
      }
      return true;
    };
    let pcm;
    if (eq(0, "RIFF") && eq(8, "WAVEfmt ")) {
      var numCh = wavView[22];
      if (wavView[20] == 1 && (numCh == 1 || numCh == 2)) {
        //raw pcm 单或双声道
        var sampleRate =
          wavView[24] +
          (wavView[25] << 8) +
          (wavView[26] << 16) +
          (wavView[27] << 24);
        var bitRate = wavView[34] + (wavView[35] << 8);
        //搜索data块的位置
        var dataPos = 0; // 44 或有更多块
        for (var i = 12, iL = wavView.length - 8; i < iL; ) {
          if (
            wavView[i] == 100 &&
            wavView[i + 1] == 97 &&
            wavView[i + 2] == 116 &&
            wavView[i + 3] == 97
          ) {
            //eq(i,"data")
            dataPos = i + 8;
            break;
          }
          i += 4;
          i +=
            4 +
            wavView[i] +
            (wavView[i + 1] << 8) +
            (wavView[i + 2] << 16) +
            (wavView[i + 3] << 24);
        }
        console.log("wav info", sampleRate, bitRate, numCh, dataPos);
        if (dataPos) {
          if (bitRate == 16) {
            pcm = new Int16Array(wavView.buffer.slice(dataPos));
          } else if (bitRate == 8) {
            pcm = new Int16Array(wavView.length - dataPos);
            //8位转成16位
            for (var j = dataPos, d = 0; j < wavView.length; j++, d++) {
              var b = wavView[j];
              pcm[d] = (b - 128) << 8;
            }
          }
        }
        if (pcm && numCh == 2) {
          //双声道简单转单声道
          var pcm1 = new Int16Array(pcm.length / 2);
          for (var i = 0; i < pcm1.length; i++) {
            pcm1[i] = (pcm[i * 2] + pcm[i * 2 + 1]) / 2;
          }
          pcm = pcm1;
        }
      }
    }
    if (!pcm) {
      False && False("非单或双声道wav raw pcm格式音频,无法转码");
      return;
    }

    var rec = Recorder(newSet).mock(pcm, sampleRate);
    rec.stop(function (blob, duration) {
      True(blob, duration, rec);
    }, False);
  };
  reader.readAsArrayBuffer(wavBlob);
};

export const transWavBlob: any = async (wavBlob: any, fileName: string, total: number, cd) => {
  if (!wavBlob) {
    return;
  }
  var set = {
    type: "mp3",
    sampleRate: 48000,
    bitRate: 96,
  };
  // let resultBlob =null
  //数据格式一 Blob
  Recorder.Wav2Other(
    set,
    wavBlob,
    function (blob, duration, rec) {
      console.log(
        blob,
        (window.URL || webkitURL).createObjectURL(blob),
        "log——audio", total + "process"
      );
      // 下载进度计数
      window.toAudioCount ++ 
      // 下载文件
      const url = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.setAttribute("download", fileName + ".mp3");
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
      if (window.toAudioCount === total - 1) {
        cd();
      }
      // resultBlob = blob
    },
    function (msg) {}
  );
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值