14

Edit: This post is no duplicate of mine. I am trying to extract the audio data as binary, got no problems with playing the audio file as separate as I mentioned before.

I am trying to extract audio from a video file on client-side by using Web Audio Api.

var audioContext = new(window.AudioContext || window.webkitAudioContext)();
fileData = new Blob([input.files[0]]);
var videoFileAsBuffer = new Promise(getBuffer);
videoFileAsBuffer.then(function (data) {
    audioContext.decodeAudioData(data).then(function (decodedAudioData) {
        mySoundBuffer = decodedAudioData;
        soundSource = audioContext.createBufferSource();
        soundSource.buffer = mySoundBuffer;
        // soundSource.connect(audioContext.destination);
        // soundSource.start();
    });

When I uncomment the two lines at the end, I am hearing the sound of the uploaded video file. Though, when I create a link to download the file with the help of getChannelData method, it's almost the same size as video file.

I was expecting decodedAudioData to have only audio binary data, and send that to my webservice, which is the only one I need. However, that didn't work out as I expected. Anyone knows a way to extract audio of a video file on client-side? Thanks in advance.

Here is the getBuffer method in case anyone wants to know:

function getBuffer(resolve) {
    var reader = new FileReader();
    reader.onload = function () {
        var arrayBuffer = reader.result;
        resolve(arrayBuffer);
    }
    reader.readAsArrayBuffer(fileData);
}

Edit: I was able to decode the video file and get audiobuffer by using OfflineAudioContext inside decodeAudioData function.

var offlineAudioContext = new OfflineAudioContext(2, 44100 * 100, 44100);
var soundSource = offlineAudioContext.createBufferSource();
...
soundSource.connect(offlineAudioContext.destination);
soundSource.start();
offlineAudioContext.startRendering().then(function (renderedBuffer) {
    console.log(renderedBuffer); // outputs audiobuffer
    var song = audioContext.createBufferSource();
    song.buffer = renderedBuffer;
    song.connect(audioContext.destination);
    song.start();
}).catch(function (err) {
    console.log('Rendering failed: ' + err);
});

renderedBuffer is an audiobuffer, had no problem outputting the data, tested with Audacity's import raw data option. But the problem is, the new file (filled with renderedBuffer.getChannelData(0)) has higher size than the original video has. Isn't that supposed to have lower size since it only contains audio of the video file?

starkm
  • 859
  • 1
  • 10
  • 21
  • Looks like there's a rather simple avenue: https://stackoverflow.com/questions/34984501/get-audio-from-html5-video – jmcgriz Mar 06 '18 at 21:24
  • Possible duplicate of [Get audio from HTML5 video](https://stackoverflow.com/questions/34984501/get-audio-from-html5-video) – jmcgriz Mar 06 '18 at 21:24
  • @jmcgriz In that post there is no such thing related to extracting binary audio data from video **file** as I asked for. It just crops the audio data from the video **element** and **plays it** with the help of _createMediaElementSource_ method. – starkm Mar 08 '18 at 20:49

3 Answers3

6

Ok, I actually had the answer already. Raw audio data is huge, that's why it's even greater than the video itself in size.

var offlineAudioContext = new OfflineAudioContext(numberOfChannels, sampleRate * duration, sampleRate);
var soundSource = offlineAudioContext.createBufferSource();
...
reader.readAsArrayBuffer(blob); // video file
reader.onload = function () {
  var videoFileAsBuffer = reader.result; // arraybuffer
  audioContext.decodeAudioData(videoFileAsBuffer).then(function (decodedAudioData) {
    myBuffer = decodedAudioData;
    soundSource.buffer = myBuffer;
    soundSource.connect(offlineAudioContext.destination);
    soundSource.start();

    offlineAudioContext.startRendering().then(function (renderedBuffer) {
      console.log(renderedBuffer); // outputs audiobuffer
    }).catch(function (err) {
      console.log('Rendering failed: ' + err);
    });
  });
};

After that, I was able to convert the audiobuffer (renderedbuffer) into a wav file by using audiobuffer-to-wav library. OfflineAudioContext is just needed to modify the cropped audio.

Edit: Here is the js fiddle example. decodedAudioData method will suffice if you don't want to override the audio data.

starkm
  • 859
  • 1
  • 10
  • 21
  • Could you please provide a full example, like with all the code all put together? A JSFiddle would work as well. – ayunami2000 Mar 23 '18 at 23:29
  • @SamuelWilliams Actually, this is the full example already. You just need to get the video file e.g. for getting it as input `var file = document.getElementById('file').files[0];` – starkm Mar 24 '18 at 23:42
  • @SamuelWilliams Check out the [js fiddle example.](https://jsfiddle.net/8w5xs9o5/23/) – starkm Mar 25 '18 at 00:04
  • Thank you. Turns out iOS Safari doesn't like `OfflineAudioContent.startRendering()` for some reason. It just never finishes rendering. I'll look into it. – ayunami2000 Mar 25 '18 at 18:26
  • 1
    Ok well I guess I'm not gonna be able to convert video to WAV on iOS Safari. (At least until the [bug](https://bugs.webkit.org/show_bug.cgi?id=181939) is fixed.) [Here's my version that works everywhere else.](https://jsfiddle.net/ayunami2000/c5xdL6xL/) – ayunami2000 Mar 25 '18 at 21:51
  • How to do this if I am having video url only and not the file locally on my system? – EdG Sep 24 '18 at 13:13
2

I'm using this code to extract audio:

// initialize the audioContext
var audioContext = new webkitAudioContext();
var video = document.getElementById("myVideo");
var mediaSource = audioContext.createMediaElementSource(video);
var analyser = audioContext.createAnalyser();
mediaSource.connect(analyser);
analyser.connect(audioContext.destination);

// this will give you the sound data

video.play();

function getSoundData() {
   var sample = new Float32Array(analyser.frequencyBinCount);
   return analyser.getFloatFrequencyData(sample);  
}

EDIT:

I've not run this code so far but I think this should work also:

Fetching an XMLHttpRequest to your videourl (working also with local files) instead of using WebAudio API.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://test.com/myvideo.mp4', true);
xhr.responseType = 'blob';

xhr.onload = function(e) {
    var binaryData = this.response;
    console.log(binaryData)
};

xhr.send();

EDIT 2:

var audioFileUrl = 'video.mp3';

fetch(audioFileUrl)
  .then(function(res) {
    res.blob().then(function(blob) {
      var size = blob.size;
      var type = blob.type;

      var reader = new FileReader();
      reader.addEventListener("loadend", function() {

        var base64FileData = reader.result.toString();

        var mediaFile = {
          fileUrl: audioFileUrl,
          size: blob.size,
          type: blob.type,
          src: base64FileData
        };

      });

      reader.readAsDataURL(blob);

    });
  });

or try using some kind of code like this:

<audio id="audio" src="http://yourvideo.com/video.mp4" controls autoplay></audio>


function getAudio() {
  var elem = document.getElementById("audio")

  // get Base 64 string from elem.src
}

I hope it is helpful because for me these few lines are working quite fine so far :)

  • Hmm, I've also checked this [post](https://stackoverflow.com/questions/17531692/how-can-audio-be-extracted-from-a-video-in-html5), but couldn't work it out. Does this mean I have to run the video 'till the end? – starkm Mar 06 '18 at 21:30
  • In this case, using the code above: **YES**. You have to use some interval to get the SoundData for the current position and you can write this into an array or object! @starkm –  Mar 06 '18 at 21:35
  • So, let's say if the video is at 2 hours at length, will user have to wait around 2 hours to extract the audio from the video file to save from upload size? Or do we have a fast-forward like method? A real world example would be awesome. – starkm Mar 06 '18 at 21:43
  • In this case (using AWI, yeah). Have a look at to different suggestions in my updated answer! @starkm –  Mar 06 '18 at 22:35
0

I think you should change your code from

var soundSource = offlineAudioContext.createBufferSource();
......
soundSource.connect(offlineAudioContext.destination);

to these

 const soundSource = audioContext.createBufferSource()
 soundSource.buffer = decodedAudioData
 soundSource.connect(audioContext.destination)

in order to play the audio