如何操作音频标签的内容并从中创建衍生音频标签?
How to manipulate the contents of an audio tag and create derivative audio tags from it?
在我的网页上,我的标签内有一个音频文件。
<!DOCTYPE html>
<html>
<audio src="myTrack.mp3" controls preload="auto"></audio>
</html>
我想将这个存储在标签中的文件分割成多个 10 秒的音频文件,然后我可以将它们作为单独的 <audio>
标签中自己的音频文件插入到网页中。
是否可以在 javascript 中执行此操作?
是的,当然可以! :)
- 确保音频满足 CORS 要求,以便我们可以使用 AJAX 加载它(从与页面相同的来源加载当然会满足此要求)。
- 将文件加载为
ArrayBuffer
并使用 AudioContext
对其进行解码
- 计算片段的数量和每个片段的长度(我在下面使用独立于通道的基于时间的长度)
- 将主缓冲区拆分为较小的缓冲区
- 为新缓冲区创建一个文件包装器(下面我为演示制作了一个简单的 WAVE 包装器)
- 通过 Object-URL 将其作为 Blob 提供给音频元素的新实例
- 跟踪对象-URL,以便您可以在不再需要时释放它们 (
revokeObjectURL()
)。
一个缺点当然是在处理之前必须将整个文件加载到内存中。
例子
希望我用于演示的文件可以通过当前用于允许使用 CORS 的 CDN 获得(我拥有版权,可以随意将其用于测试,但仅用于测试!!:) ).加载和解码可能需要一些时间,具体取决于您的系统和连接,所以请耐心等待...
理想情况下,您应该使用异步方法拆分缓冲区,但该演示仅针对使缓冲区段可用作新文件片段所需的步骤。
另请注意,我没有考虑到最后一段比其他段更短(我使用 floor,你应该使用 ceil 作为段数并缩短最后一个块的长度)。我将把它留作 reader...
的练习
var actx = new(AudioContext || webkitAudioContext)(),
url = "//dl.dropboxusercontent.com/s/7ttdz6xsoaqbzdl/war_demo.mp3";
// STEP 1: Load audio file using AJAX ----------------------------------
fetch(url).then(function(resp) {return resp.arrayBuffer()}).then(decode);
// STEP 2: Decode the audio file ---------------------------------------
function decode(buffer) {
actx.decodeAudioData(buffer, split);
}
// STEP 3: Split the buffer --------------------------------------------
function split(abuffer) {
// calc number of segments and segment length
var channels = abuffer.numberOfChannels,
duration = abuffer.duration,
rate = abuffer.sampleRate,
segmentLen = 10,
count = Math.floor(duration / segmentLen),
offset = 0,
block = 10 * rate;
while(count--) {
var url = URL.createObjectURL(bufferToWave(abuffer, offset, block));
var audio = new Audio(url);
audio.controls = true;
audio.volume = 0.75;
document.body.appendChild(audio);
offset += block;
}
}
// Convert a audio-buffer segment to a Blob using WAVE representation
function bufferToWave(abuffer, offset, len) {
var numOfChan = abuffer.numberOfChannels,
length = len * numOfChan * 2 + 44,
buffer = new ArrayBuffer(length),
view = new DataView(buffer),
channels = [], i, sample,
pos = 0;
// write WAVE header
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length - 8
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numOfChan);
setUint32(abuffer.sampleRate);
setUint32(abuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit (hardcoded in this demo)
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// write interleaved data
for(i = 0; i < abuffer.numberOfChannels; i++)
channels.push(abuffer.getChannelData(i));
while(pos < length) {
for(i = 0; i < numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // scale to 16-bit signed int
view.setInt16(pos, sample, true); // update data chunk
pos += 2;
}
offset++ // next source sample
}
// 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;
}
}
audio {display:block;margin-bottom:1px}
在我的网页上,我的标签内有一个音频文件。
<!DOCTYPE html>
<html>
<audio src="myTrack.mp3" controls preload="auto"></audio>
</html>
我想将这个存储在标签中的文件分割成多个 10 秒的音频文件,然后我可以将它们作为单独的 <audio>
标签中自己的音频文件插入到网页中。
是否可以在 javascript 中执行此操作?
是的,当然可以! :)
- 确保音频满足 CORS 要求,以便我们可以使用 AJAX 加载它(从与页面相同的来源加载当然会满足此要求)。
- 将文件加载为
ArrayBuffer
并使用AudioContext
对其进行解码
- 计算片段的数量和每个片段的长度(我在下面使用独立于通道的基于时间的长度)
- 将主缓冲区拆分为较小的缓冲区
- 为新缓冲区创建一个文件包装器(下面我为演示制作了一个简单的 WAVE 包装器)
- 通过 Object-URL 将其作为 Blob 提供给音频元素的新实例
- 跟踪对象-URL,以便您可以在不再需要时释放它们 (
revokeObjectURL()
)。
一个缺点当然是在处理之前必须将整个文件加载到内存中。
例子
希望我用于演示的文件可以通过当前用于允许使用 CORS 的 CDN 获得(我拥有版权,可以随意将其用于测试,但仅用于测试!!:) ).加载和解码可能需要一些时间,具体取决于您的系统和连接,所以请耐心等待...
理想情况下,您应该使用异步方法拆分缓冲区,但该演示仅针对使缓冲区段可用作新文件片段所需的步骤。
另请注意,我没有考虑到最后一段比其他段更短(我使用 floor,你应该使用 ceil 作为段数并缩短最后一个块的长度)。我将把它留作 reader...
的练习var actx = new(AudioContext || webkitAudioContext)(),
url = "//dl.dropboxusercontent.com/s/7ttdz6xsoaqbzdl/war_demo.mp3";
// STEP 1: Load audio file using AJAX ----------------------------------
fetch(url).then(function(resp) {return resp.arrayBuffer()}).then(decode);
// STEP 2: Decode the audio file ---------------------------------------
function decode(buffer) {
actx.decodeAudioData(buffer, split);
}
// STEP 3: Split the buffer --------------------------------------------
function split(abuffer) {
// calc number of segments and segment length
var channels = abuffer.numberOfChannels,
duration = abuffer.duration,
rate = abuffer.sampleRate,
segmentLen = 10,
count = Math.floor(duration / segmentLen),
offset = 0,
block = 10 * rate;
while(count--) {
var url = URL.createObjectURL(bufferToWave(abuffer, offset, block));
var audio = new Audio(url);
audio.controls = true;
audio.volume = 0.75;
document.body.appendChild(audio);
offset += block;
}
}
// Convert a audio-buffer segment to a Blob using WAVE representation
function bufferToWave(abuffer, offset, len) {
var numOfChan = abuffer.numberOfChannels,
length = len * numOfChan * 2 + 44,
buffer = new ArrayBuffer(length),
view = new DataView(buffer),
channels = [], i, sample,
pos = 0;
// write WAVE header
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length - 8
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numOfChan);
setUint32(abuffer.sampleRate);
setUint32(abuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit (hardcoded in this demo)
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// write interleaved data
for(i = 0; i < abuffer.numberOfChannels; i++)
channels.push(abuffer.getChannelData(i));
while(pos < length) {
for(i = 0; i < numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // scale to 16-bit signed int
view.setInt16(pos, sample, true); // update data chunk
pos += 2;
}
offset++ // next source sample
}
// 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;
}
}
audio {display:block;margin-bottom:1px}