资讯详情

资讯详情

Remotion 音频可视化指南:频谱柱状图、示波波形与低音律动效果的完整实现

Remotion 音频可视化指南频谱柱状图、示波波形与低音律动效果的完整实现【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion导读本指南围绕 Remotion 生态中remotion/media-utils的音频可视化能力展开讲解如何在 React 驱动的视频中实现频谱柱状图Spectrum Bars、平滑示波波形Waveform/Oscilloscope、低音响应律动Bass-Reactive Effects以及基于音量而非频域的简化波形并在文末给出缓解低频主导问题的后处理方案。读完本文你将掌握从加载音频数据、逐帧读取频域与时域信息到用 FFT 频谱与 SVG 路径生成各类可视化图形的完整套路并理解每个 API 在底层本仓库 packages/media-utils/src是如何实现的从而写出可复现、可在 Remotion Studio 中编辑的代码。该文档是 remotion-markup 技能集中专门处理音频可视化场景的技术指南本文以其为骨架展开并结合仓库源码做纵深剖析。前置条件安装 remotion/media-utils音频可视化涉及的所有 Hook 与纯函数都来自remotion/media-utils包。使用 Remotion 官方推荐的方式安装版本会自动与当前项目对齐npx remotion add remotion/media-utils该包内部不依赖具体音频播放组件提供两类能力HooksReact 侧useWindowedAudioData()、useAudioData()等负责异步解码并返回结构化音频数据纯函数每帧计算visualizeAudio()、visualizeAudioWaveform()、getWaveformPortion()、createSmoothSvgPath()等负责把音频数据加工成可直接驱动的可视结果。加载音频数据useWindowedAudioData 与时间窗口机制视频渲染与纯播放不同Remotion 需要逐帧、可确定性地拿到当前帧对应时刻的音频。传统做法一次性解码整个文件长音频会消耗大量内存useWindowedAudioData()则把音频切成若干固定长度的时间窗口只解码当前播放位置附近的窗口。import { useWindowedAudioData } from remotion/media-utils; import { staticFile, useCurrentFrame, useVideoConfig } from remotion; const frame useCurrentFrame(); const { fps } useVideoConfig(); const { audioData, dataOffsetInSeconds } useWindowedAudioData({ src: staticFile(podcast.wav), frame, fps, windowInSeconds: 30, });参数说明与 use-windowed-audio-data.ts 源码对应参数类型含义srcstring音频地址本地文件用staticFile()指向public/也可直接传远程 URLframenumber当前渲染帧通常来自useCurrentFrame()fpsnumber视频帧率来自useVideoConfig()windowInSecondsnumber单个时间窗口的秒数决定每次解码的音频跨度channelIndexnumber解码哪个声道默认0超出声道数会抛错requestInitRequestInit可选透传给 Mediabunny 的UrlSource如credentials仅首次渲染时捕获之后的对象变更被忽略返回值有两个字段audioData解码完成的音频数据类型为MediaUtilsAudioData窗口尚未加载完成或已超出音频总时长时为nulldataOffsetInSeconds当前返回的窗口数据在整条音频中的起始偏移秒数必须传给后续visualizeAudio()/visualizeAudioWaveform()否则每帧取样的时间基准会错位。源码揭示的几个关键行为读 use-windowed-audio-data.ts 可以确认以下事实窗口数量与预取策略源码以Math.floor(currentTime / windowInSeconds)计算当前窗口索引并始终维护当前窗口 ± 1共至多三个窗口use-windowed-audio-data.ts#L210-L229。解码完成后跨窗口的Float32Array会被拼接combineFloat32Arrays因此传入的窗口边缘处可视化也是连续的。加载优先级网络或解码较慢时优先加载当前播放位置所在窗口随后并行加载前后窗口use-windowed-audio-data.ts#L348-L369保证用户正在观看的时刻最早可用。windowInSeconds不允许动态变化若在渲染中途改变该值Hook 会直接抛出错误use-windowed-audio-data.ts#L75-L77。这是因为窗口索引、请求去重与拼接都依赖固定窗长。渲染会等待数据Hook 内部使用useDelayRender()/continueRender()通知 Remotion 挂起渲染直到窗口数据就绪杜绝首帧没数据导致画面缺一块的竞态use-windowed-audio-data.ts#L426-L447。超出音频末尾时audioData返回nulluse-windowed-audio-data.ts#L449你的组件应做好空态兜底见下文各例的if (!audioData) return null;。Matroska/WebM 的警告检测到 Matroska/WebM 容器时受格式限制解码必须从文件头开始会打印性能提示日志建议对超大文件转成 MP3/AAC 再使用use-windowed-audio-data.ts#L264-L270。另外若目标是短音频一次性加载全部数据同包还提供useAudioData()useWindowedAudioData()适合播客、歌曲等较长素材。频谱柱状图visualizeAudio 逐帧输出频率分布拿到audioData后用visualizeAudio()得到当前这一帧的频率分布数组数值范围0~1可直接作为柱高比例import { useWindowedAudioData, visualizeAudio } from remotion/media-utils; import { staticFile, useCurrentFrame, useVideoConfig } from remotion; const frame useCurrentFrame(); const { fps } useVideoConfig(); const { audioData, dataOffsetInSeconds } useWindowedAudioData({ src: staticFile(music.mp3), frame, fps, windowInSeconds: 30, }); if (!audioData) { return null; } const frequencies visualizeAudio({ fps, frame, audioData, numberOfSamples: 256, optimizeFor: speed, dataOffsetInSeconds, }); return ( div style{{ display: flex, alignItems: flex-end, height: 200 }} {frequencies.map((v, i) ( div key{i} style{{ flex: 1, height: ${v * 100}%, backgroundColor: #0b84f3, margin: 0 1px, }} / ))} /div );使用要点numberOfSamples必须是 2 的幂32、64、128、256、512、1024。若传入非 2 的幂底层会抛出TypeError见 get-visualization.ts#L31-L36返回值 0~1 表示归一化强度数组左侧是低频低音右侧是高频高音optimizeFor: speed适合 Lambda 渲染或大样本数场景accuracy则使用更精确但更慢的 FFT 变体dataOffsetInSeconds必须与useWindowedAudioData返回的偏移保持一致。源码视角它其实做了什么在 visualize-audio.ts 与 fft/get-visualization.ts 中可以看到传入的numberOfSamples会被放大一倍sampleSize: numberOfSamples * 2作为一次 FFT 的采样窗口长度visualize-audio.ts#L45-L46从波形流中按Math.floor((frame / fps - dataOffsetInSeconds) * sampleRate)定位起点切出与当前时刻对齐的 PCM 片段转成Int16后送入 FFTget-visualization.ts#L48-L57optimizeFor: accuracy走精确 FFT、speed走快速近似 FFT二者结果再经幅度谱计算与平滑get-visualization.ts#L58-L63幅度被maxInt整个音频最大可能幅度与sampleSize / 2归一化所以输出稳定落在 0~1。两点工程细节值得注意默认开启时域平滑smoothing默认为true此时会把frame-1、frame、frame1三帧的频谱取平均再返回visualize-audio.ts#L75-L98避免柱子逐帧跳动过快追求更高响应速度时可在选项里设smoothing: false。结果带缓存同resultId frame fps numberOfSamples组合会被模块级缓存命中同一时刻重复调用不会重复计算visualize-audio.ts#L38-L41。重要提醒frame 要随 audioData 一起传给子组件当把可视化逻辑抽成子组件时frame必须由父组件传入不要在子组件内各自调用useCurrentFrame()。因为子组件一旦放在带偏移的Sequence里其内部useCurrentFrame()已被 Sequence 重定向多个子组件拿到的当前帧并不指向同一物理时刻频谱/波形会呈现跳变与不连续。示波器式波形visualizeAudioWaveform createSmoothSvgPath当需要示波器/振荡器式的连续波形线而不是离散频谱柱时使用visualizeAudioWaveform()取当前时刻前后一小段窗口的时域采样再用createSmoothSvgPath()把采样点拟合成平滑的 SVG 三次贝塞尔曲线import { createSmoothSvgPath, useWindowedAudioData, visualizeAudioWaveform, } from remotion/media-utils; import { staticFile, useCurrentFrame, useVideoConfig } from remotion; const frame useCurrentFrame(); const { width, fps } useVideoConfig(); const HEIGHT 200; const { audioData, dataOffsetInSeconds } useWindowedAudioData({ src: staticFile(voice.wav), frame, fps, windowInSeconds: 30, }); if (!audioData) { return null; } const waveform visualizeAudioWaveform({ fps, frame, audioData, numberOfSamples: 256, windowInSeconds: 0.5, dataOffsetInSeconds, }); const path createSmoothSvgPath({ points: waveform.map((y, i) ({ x: (i / (waveform.length - 1)) * width, y: HEIGHT / 2 (y * HEIGHT) / 2, })), }); return ( svg width{width} height{HEIGHT} path d{path} fillnone stroke#0b84f3 strokeWidth{2} / /svg );参数语义与源码约束visualizeAudioWaveform()的完整参数见 visualize-audio-waveform.tsaudioData、frame、fps、windowInSeconds、numberOfSamples另有可选的channel、dataOffsetInSeconds与normalize默认false。其内部行为是以当前时刻为中心取窗startTimeInSeconds frame / fps - windowInSeconds / 2即窗口横跨当前帧前后各一半visualize-audio-waveform.ts#L48-L61返回纯数值数组把getWaveformPortion的结果映射为amplitude数组值域为-1~1minus-one-to-one所以示例中y HEIGHT / 2 (y * HEIGHT) / 2恰好把 -1..1 铺满 0..HEIGHT 画布需要满足数据量约束若windowInSeconds × sampleRate numberOfSamples采样点数不足会直接抛TypeError提示你增大 windowInSeconds 或减小 numberOfSamplesvisualize-audio-waveform.ts#L28-L35。createSmoothSvgPath()create-smooth-svg-path.ts实现的是经典的 Catmull-Rom 转三次贝塞尔对每个点计算基于相邻点方向、长度为间距 ×0.2 的控制点输出M ... C ...路径。它只与点集有关因此既能画波形也适用于任何需要平滑折线转曲线如地图轨迹、折线统计图的场景。仓库里的真实参考实现oscilloscope.element.tsx官方示例中完整的可编辑音频示波器组件。它用useWindowedAudioData窗长 10sgetWaveformPortionoutputRange: minus-one-to-one、normalize: false取当前时间前后windowInSeconds默认 0.35s的 128 个采样点再经createSmoothSvgPath生成 SVG 路径并用两层相同路径叠加出带辉光的线条外层strokeOpacity: 0.22粗描边 内层实线是很实用的视觉技巧。VoiceVisualization.tsx展示visualizeAudioWaveform的另一种进阶玩法——先对frame做抽帧posterizationMath.round(frame / posterization) * posterization再喂给函数实现波形每隔若干帧才更新一次的定格/帧动画效果适合节奏感强的语音卡片。低音响应效果把低频段提炼成一个节拍强度音乐视频最常见的需求是让画面跟着鼓点动。因为频谱数组左边是低频直接对低频段求平均即可得到一个 0~1 的bassIntensity标量再驱动缩放、透明度等const frequencies visualizeAudio({ fps, frame, audioData, numberOfSamples: 128, optimizeFor: speed, dataOffsetInSeconds, }); const lowFrequencies frequencies.slice(0, 32); const bassIntensity lowFrequencies.reduce((sum, v) sum v, 0) / lowFrequencies.length; const scale 1 bassIntensity * 0.5; const opacity Math.min(0.6, bassIntensity * 0.8);要点解读numberOfSamples: 128时共 128 条频段slice(0, 32)取出最左侧约 1/4 的频段即 20Hz~250Hz 附近的低频能量具体频率边界与采样率相关reduce(...)/length求平均后得到 0~1 的平滑强度bassIntensity还可继续叠加interpolate()与Easing做非线性映射例如配合Easing.bezier制作更有弹性的鼓点缩放参见 remotion-markup/SKILL.md 中推荐的Easing.spring/Easing.bezier用法。注意由于频谱结果自带smoothing三帧平均与底层归一化bassIntensity天然较稳若仍想响应更炸可调低平滑smoothing: false或对强度做指数映射如bassIntensity ** 1.5。基于音量的简化波形getWaveformPortion某些场景如音频进度条、全曲概览缩略图不需要频率分解只要某一段有多响。此时用getWaveformPortion()直接取音量包络它返回{ index, amplitude }对象数组amplitude默认范围0~1import { getWaveformPortion } from remotion/media-utils; import { useCurrentFrame, useVideoConfig } from remotion; const frame useCurrentFrame(); const { fps } useVideoConfig(); const currentTimeInSeconds frame / fps; const waveform getWaveformPortion({ audioData, startTimeInSeconds: currentTimeInSeconds, durationInSeconds: 5, numberOfSamples: 50, }); // Returns array of { index, amplitude } objects (amplitude: 0-1) waveform.map((bar) ( div key{bar.index} style{{ height: bar.amplitude * 100 }} / ));getWaveformPortion的源码实现get-waveform-portion.ts进一步解释了它的健壮性内部用sampleRate把秒换算成采样点区间并支持dataOffsetInSeconds校正窗口数据在原始时间轴上的起点get-waveform-portion.ts#L58-L64请求的区间超出音频实际范围时缺出的部分用 0 填充padStart/padEnd而不是报错或截断get-waveform-portion.ts#L72-L83这让片头片尾淡入淡出等边缘场景依然有平滑的形状波形被等分成numberOfSamples个块每块取采样绝对值的平均作为该柱振幅见 get-wave-form-samples.ts#L16-L30normalize默认true会把结果规整到输出范围outputRange支持zero-to-one默认与minus-one-to-one多声道音频可通过channel指定声道索引。因此它和visualizeAudioWaveform()的分工是前者给你一段固定时长的音量柱常做静态或半静态的进度图、封面条后者给你跟随当前帧滑动的示波线。后处理用 dB 对数缩放平衡低频主导人耳感知与频谱能量都偏向低频——未经处理的频谱柱视觉上左边永远最高高频细节被淹没。推荐先把强度转成对数分贝域再做线性映射const minDb -100; const maxDb -30; const scaled frequencies.map((value) { const db 20 * Math.log10(value); return (db - minDb) / (maxDb - minDb); });原理说明20 * Math.log10(value)把 0~1 的线性强度换算成分贝由于value可能为 0实际工程中通常先夹取最小值如Math.max(value, 1e-5)再取对数避免-InfinityminDb / maxDb定义了你要压扁的动态范围-100dB以下视为静音、-30dB以上视为满格(db - minDb) / (maxDb - minDb)重新映射回 0~1映射后的柱高在各频段间更均衡视觉上音乐频谱的观感更接近专业播放器。由于返回的是纯函数结果可直接套用本章前面任一柱状渲染结构只需把v替换为scaled[i]并配合Math.max下限保护、对越界值clamp。常见问题速查现象原因与对策useWindowedAudioData抛错 windowInSeconds cannot be changed dynamically该 Hook 不支持运行时改窗长应在渲染树中固定窗长参见 use-windowed-audio-data.ts#L75-L77报 must be a power of two / Audio data is not big enoughnumberOfSamples不是 2 的幂或当前窗口数据短于一次 FFT 所需长度见 get-visualization.ts#L31-L46波形报 does not have N barswindowInSeconds × sampleRate numberOfSamples需增大窗口或减小样本数画面出现时有时无的可视化子组件各自调用useCurrentFrame()且放在Sequence中导致帧错位frame 应由父组件传入Matroska/WebM 大文件加载慢容器限制导致必须从文件头解码转成 MP3/AAC 后可显著改善首帧无可视化内容数据尚未就绪时audioData为null用if (!audioData) return null;占位渲染会自动等待Hook 内部已接入delayRender柱子左侧过高见dB 对数缩放后处理章节若还需更复杂的音频能力——裁剪、变速、变调、音量曲线请参阅本技能库同目录的 audio.md添加配音的流程见 voiceover.md。快速验证与落地在实际编排中所有数值都应直接依赖frame由interpolate、Easing或上述可视化函数换算不要使用 CSStransition/animation因为它们在 Remotion 的确定性逐帧渲染中不会生效规则见 SKILL.md。可以随时在浏览器预览与单帧校验npx remotion studio --no-open # 快速检查第 30 帧30fps 下即第 1 秒的波形布局 npx remotion still [composition-id] --scale0.25 --frame30值得反复对照的仓库参考物还有AudioVisualization频谱柱状完整示例packages/example/src/AudioVisualization/index.tsx、左右对称的镜像频谱packages/example/src/mirrored-spectrum.element.tsx。把本指南中的函数与这些成品示例结合即可在几分钟内搭建出从频谱柱到低音律动视频的完整链路。【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →