使用webCodec完成视频播放器(2)-音频数据播放

概要

本文主要讲解mp4中音频相关的处理方案,大致流程如下:

处理流程

mp4box解码

js 复制代码
// 请求视频资源
function queryVideo() {
    // fetch到的是mp4刻度流,包含视频,音频等多个track
    fetch(videoUrl.value)
        .then(res => res.arrayBuffer())
        .then(buffer => {
            videoBuffer = buffer;
            buffer.fileStart = 0;
            mp4boxfile.appendBuffer(buffer);
            // flush后即可触发onReady事件
            mp4boxfile.flush();
        })
}

function initMP4Box() {
    mp4boxfile = MP4Box.createFile();

    mp4boxfile.onReady = async (info) => {
        audioTrack = info.audioTracks?.[0];
        // 播放音频
        await getPcmData();
        startPlayAudio();
    }
}

和处理视频时的第一步一致,直接用Mp4Box解析出视频容器中的音频轨道即可。

这时候可以拿到音频的取样率(sampleRate)以及频道数(channelNum),为后续解码做准备。

decodeAudioData解码

js 复制代码
// 开始获取音频Sample
async function getPcmData() {
    if(!audioContext) {
        audioContext = new AudioContext({
            sampleRate: audioTrack.audio.sample_rate
        });
    }
    audioPcm = extractPCM4AudioBuffer(await audioContext.decodeAudioData(videoBuffer));
}

/**
 * 从 AudioBuffer 中提取 PCM
 */
function extractPCM4AudioBuffer(ab) {
    return Array(ab.numberOfChannels)
        .fill(0)
        .map((_, idx) => {
            return ab.getChannelData(idx);
        });
}

decodeAudioData配合刚刚获取到的sampleRate即可对视频数据进行解码。(直接将视频的ArrayBuffer喂给decodeAudioData就行,甚至不需要先拆分出音频)

播放

js 复制代码
let frameOffset = 0;
let endIdx = 0;
function tick(deltaTime, chan0, chan1) {
    // 这个方法有待商榷,不知move_duration是否确定为ms
    if(frameOffset >= audioTrack.movie_duration / 1000) {
        return { audio: [], state: "done"}; 
    }
    const frameCnt = Math.ceil(
        deltaTime * audioTrack.audio.sample_rate,
    );
    const audio = [
        ringSliceFloat32Array(chan0, endIdx,  endIdx + frameCnt),
        ringSliceFloat32Array(chan1, endIdx,  endIdx + frameCnt),
    ];
    endIdx = endIdx + frameCnt;
    frameOffset += deltaTime;
    return { audio, state: "success"};
}

/**
 *  循环 即 环形取值,主要用于截取 PCM
 */
function ringSliceFloat32Array(
  data,
  start,
  end
) {
    const cnt = end - start;
    const rs = new Float32Array(cnt);
    let i = 0;
    while (i < cnt) {
        rs[i] = data[(start + i) % data.length];
        i += 1;
    }
    return rs;
}

// 开始播放音频
function startPlayAudio() {
    // 每次取1s的数据
    const duration = 0.1;
    let startAt = 0;
    const ctx = new AudioContext();
    function play() {
        console.log("play audio");
        const {audio, state} = tick(duration, audioPcm[0], audioPcm[1]);
        if(state === "done") {
            return;
        }
        const [chan0, chan1] = audio;
        const buf = ctx.createBuffer(audioTrack.audio.channel_count, chan0?.length || 0, audioTrack.audio.sample_rate);
        buf.copyToChannel(chan0, 0);
        buf.copyToChannel(chan1, 1);
        const source = ctx.createBufferSource();
        source.buffer = buf;
        source.connect(ctx.destination);
        startAt = Math.max(ctx.currentTime, startAt);
        source.start(startAt);
        startAt += buf.duration;
        play()
    }
    play()
}

播放的关键点主要就是分片

  1. tick方法将pcm数据进行分段。因为之前得到了音频的sampleRate(取样率)采样大小,所以可以直接用deltaTime * audioTrack.audio.sample_rate获取特定时长的pcm数据。
  2. tick分片后,对这些pcm片段创建多个source进行分片播放,这样就不需要等待所有音频处理完成后再播放。

思考

现在可以使用pcm数据进行直接播放了,对比浏览器提供的video标签,有什么收益呢?

音量调节

音量调节的原理就是调节声音的振幅

我们可以看到,最终拿到的pcm数据如下:

只要将这些数据乘以想要调节的倍数,就可以做对应大小调节了,非常方便。

倍速播放

倍速播放的原理就是缩短每个音频片段的播放时长

只需要在上述地方做改动就行(如果不适用tick分片,直接播放一整段片段就不太好实现了)

声道处理

声道处理的原理就是处理单个声道的pcm数据

由于我们之前已经分别拿到了两条声道的数据,就可以很方便的处理单个声道的数据啦。

例如我们只需要一条声道的数据,直接注释一行代码就行,这时候耳机里就只有一边有声音了。

相关推荐
jiangzhihao05153 小时前
前端自动翻译插件webpack-auto-i18n-plugin的使用
前端·webpack·node.js
软件技术NINI5 小时前
html css网页制作成品——HTML+CSS盐津铺子网页设计(5页)附源码
前端·css·html
mapbar_front6 小时前
面试问题—我的问题问完了,你还有什么想问我的吗?
前端·面试
哔哩哔哩技术6 小时前
B站多模态精细画质分析模型在 ICCV2025 大赛获得佳绩
音视频开发
quweiie6 小时前
thinkphp8+layui多图上传,带删除\排序功能
前端·javascript·layui
李鸿耀6 小时前
React 项目 SVG 图标太难管?用这套自动化方案一键搞定!
前端
闲蛋小超人笑嘻嘻6 小时前
树形结构渲染 + 选择(Vue3 + ElementPlus)
前端·javascript·vue.js
叶梅树7 小时前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法
巴博尔7 小时前
uniapp的IOS中首次进入,无网络问题
前端·javascript·ios·uni-app
Asthenia04127 小时前
技术复盘:从一次UAT环境CORS故障看配置冗余的危害与最佳实践
前端