Web Audio Api ile Video Dosyasından Ses Verisi Çıkarma

9 Mart 2018

Birkaç gündür, belki de sadece deneysel amaçlı, video upload ederek birçok dilde otomatik altyazı üretebileceğim GoSubtitle benzeri bir site yapmanın peşindeydim. GoSubtitle'dan daha hızlı işleyen, arayüzü daha anlaşılır, daha kesin sonuçlar veren bir araç olmadığı sürece yaptığımın hiçbir anlamının olmayacağını düşünüyordum.

Google'ın Cloud Speech API'sini incelediğimde, incelemeden önce de tahmin ettiğim gibi, sadece bir ses dosyası gerekli metnin üretilmesi için yeterli olacaktı. İstemci tarafında, yani önyüzde, video upload eden kullanıcının video dosyasından ses dosyasını ayırıp sadece ses dosyasını servisime göndermem, dosya yükleme işleminde büyük tasarruf sağlayacaktı. Ancak bunu önyüzde yapabilmek için gerekli Api desteği mevcut muydu ve modern tarayıcılar tarafından destekleniyor muydu?

Biraz araştırdıktan sonra karşıma Web Audio Api çıktı. Ses kaynaklarının önyüzde işlenmesini sağlayan W3C Audio WG tarafından başlatılmış açık kaynaklı bir kütüphane. Daha çok browser üzerinde çalmakta olan sesi işlemeye ve sentezlemeye yarayan bir javascript API'si.

İlk denemelerimde en sık kullanılan metodları kullanarak işimi görmeye çalıştım, ancak başarısız oldum. Çünkü benim yapmaya çalıştığım şey hali hazırda çalmakta olan sesi işlemek değildi.

// createMediaElementSource kullanmadan önce window.onload olayının gerçekleşmesini bekliyoruz. crbug.com/112368
window.addEventListener('load', function (e) {
    // <video> öğesi ses kaynağı olarak kullanılacak.
    var videoElement = document.getElementById('myVideo');
    soundSource = audioContext.createMediaElementSource(videoElement);
    var analyserNode = audioContext.createAnalyser();
    soundSource.connect(analyserNode);
    analyserNode.connect(audioContext.destination);

    videoElement.play();

    window.setInterval(function () {
        sample = new Float32Array(analyserNode.frequencyBinCount);
        analyserNode.getFloatFrequencyData(sample);  // Daha iyi performans, daha az hassasiyet için getByteFrequencyData kullanılabilir.
        totalSample = Float32Concat(totalSample, sample);
    }, 1000);
}, false);
Browser üzerinde çalan video'dan ses verisi toplamak

Aradığımın çalmakta olan bir medyadan ses dosyası çıkarmak olmadığına emindim. Çünkü kullanıcının yüklediği video dosyasını sonuna kadar oynatıp üzerinden ses verisi toplamak, videonun tamamını servera yüklemekten bile daha verimsiz olurdu.

Daha sonra video dosyasını ArrayBuffer'a çevirip AudioContext sınıfının decodeAudioData metodunu kullanarak video dosyasına ait binary datanın ses verisini çözümlemeyi başardım.

reader.readAsArrayBuffer(blob); // blob olarak video dosyası
reader.onload = function () {
  var videoFileAsBuffer = reader.result; // ArrayBuffer
  audioContext.decodeAudioData(videoFileAsBuffer).then(function (decodedAudioData) {
    console.log(decodedAudioData); // AudioBuffer
  });
};
Bir video dosyasına ait ses verisinin çözümlenmesi

Ancak çözümlenen ham ses verisinin dosya boyutu orjinal video dosyasından bile yüksekti. Bu durumda, ses dosyasını video'dan ayırma işleminin istemci tarafında yapılmasının hiçbir önemi kalmayacaktı. Neyse ki, Web Audio Api'de yapılabilecek tüm işlemler gerçek zamanlı değildi.

OfflineAudioContext sınıfı sayesinde gerçek zamanlı olmadan, mümkün olduğunca hızlı bir şekilde ses dosyalarını işlemek ve sentezlemek mümkün. Örneğin istemci tarafında, bir ses dosyası üzerinde belli başlı düzenlemeler yapmak istiyorsunuz ve bu değişiklikleri kullanıcıya sesi tekrar dinletmek zorunda olmadan, sadece indirme imkanı sağlayarak yapmak istiyorsunuz. OfflineAudioContext sınıfı sayesinde bu tarz işlemler mümkün.

Ben de bu sınıfı, çözümlediğim ses verisini farklı değerlerle (mesela örnek hızı 16Khz'e sabitleyerek) yeniden işlemede kullandım. Bu sayede, servise göndermeden önce her ses dosyasını aynı kalite standardında tutabildim.

var offlineAudioContext = new OfflineAudioContext(numberOfChannels, sampleRate * duration, sampleRate);
var soundSource = offlineAudioContext.createBufferSource();
reader.readAsArrayBuffer(blob); // blob olarak video dosyası
reader.onload = function () {
  var videoFileAsBuffer = reader.result; // ArrayBuffer
  audioContext.decodeAudioData(videoFileAsBuffer).then(function (decodedAudioData) {
    myBuffer = decodedAudioData; // AudioBuffer
    soundSource.buffer = myBuffer;
    soundSource.connect(offlineAudioContext.destination);
    soundSource.start();
    offlineAudioContext.startRendering().then(function (renderedBuffer) {
      console.log(renderedBuffer); // Yeniden işleyerek yeni bir AudioBuffer üretir.
    }).catch(function (err) {
      console.log('Rendering failed: ' + err);
    });
  });
};
Çözümlenen ses verisinin farklı değerlerle tekrar işlenmesi