2017-03-02 31 views
8

Chcę połączyć dwa źródła dźwięku, umieszczając jedną piosenkę jako tło drugiej w jednym źródle.Mieszanie dwóch buforów audio, umieść jeden na drugim, używając Web Audio Api

na przykład, mam wejście:

<input id="files" type="file" name="files[]" multiple onchange="handleFilesSelect(event)"/> 

I skrypt do dekodowania plików to:

window.AudioContext = window.AudioContext || window.webkitAudioContext; 
var context = new window.AudioContext(); 
var sources = []; 
var files = []; 
var mixed = {}; 

function handleFilesSelect(event){ 
    if(event.target.files.length <= 1) 
      return false; 

    files = event.target.files; 
    readFiles(mixAudioSources); 
} 

function readFiles(index, callback){ 
    var freader = new FileReader(); 
    var i = index ? index : 0; 

    freader.onload = function (e) {  
     context.decodeAudioData(e.target.result, function (buf) { 

      sources[i] = context.createBufferSource(); 
      sources[i].connect(context.destination); 
      sources[i].buffer = buf; 

      if(files.length > i+1){ 
       readFiles(i + 1, callback); 
      } else { 
       if(callback){ 
        callback(); 
       } 
      } 
     }); 
    }; 

    freader.readAsArrayBuffer(files[i]); 
} 

function mixAudioSources(){ 
    //So on our scenario we have here two decoded audio sources in "sources" array. 
    //How we can mix that "sources" into "mixed" variable by putting "sources[0]" as background of "sources[1]" 
} 

Więc jak mogę mieszać tego źródła do jednego źródła? Na przykład mam dwa pliki, w jaki sposób mogę umieścić jedno źródło jako tło innego i umieścić ten miks w jednym źródle?

Inny scenariusz: jeśli na przykład odczytywam strumień wejściowy z mikrofonu i chcę wstawić to wejście do piosenki w tle (jakiś rodzaj karaoke), możliwe jest wykonanie tej pracy na kliencie z obsługą html5? A co z wydajnością? Może lepszy sposób na wymieszanie źródeł audio po stronie serwera?

Jeśli to możliwe, to jaka jest możliwa implementacja funkcji mixAudioSources?

Dzięki.

+0

Co próbowałeś do tej pory? –

+1

Zobacz sekcję przejściową https://www.html5rocks.com/en/tutorials/webaudio/intro/ –

+0

Witam. W tym artykule najbliższym przykładem na to pytanie jest odtwarzanie dwóch źródeł w tym samym czasie. Nie pomaga mi to tak bardzo, ponieważ potrzebuję jednego źródła do przesłania i zapisania na serwerze. –

Odpowiedz

3

Dwie metody pierwotnie opublikowane pod numerem Is it possible to mix multiple audio files on top of each other preferably with javascript, dostosowane do przetwarzania obiektów File pod numerem change zdarzenia elementu <input type="file">.

Pierwsze podejście wykorzystuje OfflineAudioContext(), AudioContext.createBufferSource(), AudioContext.createMediaStreamDestination(), Promise konstruktor, Promise.all(), MediaRecorder() się mieszać ścieżek audio, a następnie oferować mieszany plik audio do pobrania.

var div = document.querySelector("div"); 
 

 
function handleFilesSelect(input) { 
 
    div.innerHTML = "loading audio tracks.. please wait"; 
 
    var files = Array.from(input.files); 
 
    var duration = 60000; 
 
    var chunks = []; 
 
    var audio = new AudioContext(); 
 
    var mixedAudio = audio.createMediaStreamDestination(); 
 
    var player = new Audio(); 
 
    var context; 
 
    var recorder; 
 
    var description = ""; 
 
    
 
    player.controls = "controls"; 
 
    
 
    function get(file) { 
 
    description += file.name.replace(/\..*|\s+/g, ""); 
 
    return new Promise(function(resolve, reject) { 
 
     var reader = new FileReader; 
 
     reader.readAsArrayBuffer(file); 
 
     reader.onload = function() { 
 
     resolve(reader.result) 
 
     } 
 
    }) 
 
    } 
 

 
    function stopMix(duration, ...media) { 
 
    setTimeout(function(media) { 
 
     media.forEach(function(node) { 
 
     node.stop() 
 
     }) 
 
    }, duration, media) 
 
    } 
 

 
    Promise.all(files.map(get)).then(function(data) { 
 
     var len = Math.max.apply(Math, data.map(function(buffer) { 
 
     return buffer.byteLength 
 
     })); 
 
     context = new OfflineAudioContext(2, len, 44100); 
 
     return Promise.all(data.map(function(buffer) { 
 
      return audio.decodeAudioData(buffer) 
 
      .then(function(bufferSource) { 
 
       var source = context.createBufferSource(); 
 
       source.buffer = bufferSource; 
 
       source.connect(context.destination); 
 
       return source.start() 
 
      }) 
 
     })) 
 
     .then(function() { 
 
      return context.startRendering() 
 
     }) 
 
     .then(function(renderedBuffer) { 
 
      return new Promise(function(resolve) { 
 
      var mix = audio.createBufferSource(); 
 
      mix.buffer = renderedBuffer; 
 
      mix.connect(audio.destination); 
 
      mix.connect(mixedAudio); 
 
      recorder = new MediaRecorder(mixedAudio.stream); 
 
      recorder.start(0); 
 
      mix.start(0); 
 
      div.innerHTML = "playing and recording tracks.."; 
 
      // stop playback and recorder in 60 seconds 
 
      stopMix(duration, mix, recorder) 
 

 
      recorder.ondataavailable = function(event) { 
 
       chunks.push(event.data); 
 
      }; 
 

 
      recorder.onstop = function(event) { 
 
       var blob = new Blob(chunks, { 
 
       "type": "audio/ogg; codecs=opus" 
 
       }); 
 
       console.log("recording complete"); 
 
       resolve(blob) 
 
      }; 
 
      }) 
 
     }) 
 
     .then(function(blob) { 
 
      console.log(blob); 
 
      div.innerHTML = "mixed audio tracks ready for download.."; 
 
      var audioDownload = URL.createObjectURL(blob); 
 
      var a = document.createElement("a"); 
 
      a.download = description + "." + blob.type.replace(/.+\/|;.+/g, ""); 
 
      a.href = audioDownload; 
 
      a.innerHTML = a.download; 
 
      document.body.appendChild(a); 
 
      a.insertAdjacentHTML("afterend", "<br>"); 
 
      player.src = audioDownload; 
 
      document.body.appendChild(player); 
 
     }) 
 
    }) 
 
    .catch(function(e) { 
 
     console.log(e) 
 
    }); 
 

 
}
<!DOCTYPE html> 
 
<html> 
 

 
<head> 
 
</head> 
 

 
<body> 
 
    <input id="files" 
 
     type="file" 
 
     name="files[]" 
 
     accept="audio/*" 
 
     multiple 
 
     onchange="handleFilesSelect(this)" /> 
 
    <div></div> 
 
</body> 
 

 
</html>

Drugie podejście wykorzystuje AudioContext.createChannelMerger(), AudioContext.createChannelSplitter()

var div = document.querySelector("div"); 
 

 
function handleFilesSelect(input) { 
 

 
    div.innerHTML = "loading audio tracks.. please wait"; 
 
    var files = Array.from(input.files); 
 
    var chunks = []; 
 
    var channels = [ 
 
    [0, 1], 
 
    [1, 0] 
 
    ]; 
 
    var audio = new AudioContext(); 
 
    var player = new Audio(); 
 
    var merger = audio.createChannelMerger(2); 
 
    var splitter = audio.createChannelSplitter(2); 
 
    var mixedAudio = audio.createMediaStreamDestination(); 
 
    var duration = 60000; 
 
    var context; 
 
    var recorder; 
 
    var audioDownload; 
 
    var description = ""; 
 

 
    player.controls = "controls"; 
 

 
    function get(file) { 
 
    description += file.name.replace(/\..*|\s+/g, ""); 
 
    console.log(description); 
 
    return new Promise(function(resolve, reject) { 
 
     var reader = new FileReader; 
 
     reader.readAsArrayBuffer(file); 
 
     reader.onload = function() { 
 
     resolve(reader.result) 
 
     } 
 
    }) 
 
    } 
 

 
    function stopMix(duration, ...media) { 
 
    setTimeout(function(media) { 
 
     media.forEach(function(node) { 
 
     node.stop() 
 
     }) 
 
    }, duration, media) 
 
    } 
 

 
    Promise.all(files.map(get)).then(function(data) { 
 
     return Promise.all(data.map(function(buffer, index) { 
 
      return audio.decodeAudioData(buffer) 
 
      .then(function(bufferSource) { 
 
       var channel = channels[index]; 
 
       var source = audio.createBufferSource(); 
 
       source.buffer = bufferSource; 
 
       source.connect(splitter); 
 
       splitter.connect(merger, channel[0], channel[1]);   
 
       return source 
 
      }) 
 
     })) 
 
     .then(function(audionodes) { 
 
      merger.connect(mixedAudio); 
 
      merger.connect(audio.destination); 
 
      recorder = new MediaRecorder(mixedAudio.stream); 
 
      recorder.start(0); 
 
      audionodes.forEach(function(node, index) { 
 
      node.start(0) 
 
      }); 
 
      
 
      div.innerHTML = "playing and recording tracks.."; 
 
      
 
      stopMix(duration, ...audionodes, recorder); 
 

 
      recorder.ondataavailable = function(event) { 
 
      chunks.push(event.data); 
 
      }; 
 

 
      recorder.onstop = function(event) { 
 
      var blob = new Blob(chunks, { 
 
       "type": "audio/ogg; codecs=opus" 
 
      }); 
 
      audioDownload = URL.createObjectURL(blob); 
 
      var a = document.createElement("a"); 
 
      a.download = description + "." + blob.type.replace(/.+\/|;.+/g, ""); 
 
      a.href = audioDownload; 
 
      a.innerHTML = a.download; 
 
      player.src = audioDownload; 
 
      document.body.appendChild(a); 
 
      document.body.appendChild(player); 
 
      }; 
 
     }) 
 
    }) 
 
    .catch(function(e) { 
 
     console.log(e) 
 
    }); 
 
}
<!DOCTYPE html> 
 
<html> 
 

 
<head> 
 
</head> 
 

 
<body> 
 
    <input id="files" 
 
     type="file" 
 
     name="files[]" 
 
     accept="audio/*" 
 
     multiple onchange="handleFilesSelect(this)" /> 
 
    <div></div> 
 
</body> 
 

 
</html>

+0

@OlegYudovich Korzystanie z GainNode do zmniejszania głośności ścieżki audio wybranej jako "tło" plnkr plnkr.co/edit/5c36gNgO6bvtFGjDoh02?p=preview – guest271314

+0

Świetna odpowiedź. Dzięki –