Comments (19)
Thanks @shanewho ! I actually just solved the problem too, hi-5 for both of us!
To anyone else interested, it seems we're required to convert Float32Array to Int16Array. To do that, I used a function I found on StackOverflow ( http://stackoverflow.com/a/29342909/2434324 ). This function looks very similar to the answer you posted.
var data = buffer.getChannelData(0);
var len = data.length, i = 0;
var dataAsInt16Array = new Int16Array(len);
while(i < len){
dataAsInt16Array[i] = convert(data[i++]);
}
function convert(n) {
var v = n < 0 ? n * 32768 : n * 32767; // convert in range [-32768, 32767]
return Math.max(-32768, Math.min(32768, v)); // clamp
}
var samples = dataAsInt16Array;
Thanks again for all your help - and for workin on a Saturday! 😆
from lamejs.
OHHHH I forgot about that. I just found this code hidden in a function I didn't realize was getting called and I had this exact same problem when I tried this. Those are values from -1 to +1 but lamejs expects them to be much higher. So basically you have the right data it is just really really quiet. Try something like this:
var lo = this.leftBuffer; //the decoded data range: -1 +1
var ro = this.rightBuffer;
var l = new Float32Array(lo.length); //The transformed data, this is what you will pass to lame instead
var r = new Float32Array(ro.length);
//Convert to required format
for(var i=0;i<lo.length;i++) {
l[i] = lo[i]*32767.5;
r[i] = ro[i]*32767.5;
}
Basically loop through all of your decoded wave data (the small numbers) and make them larger by the same amount. I bet that will do it.
from lamejs.
Thank you everybody for this great post, following the advice in this thread i made it work with the code you see here below, now it records and encodes in real time from mic and then uploads and writes to server an mp3 successfully, yay, however i have two issues and a question:
a) the very beginning of recording gets distorted and a bit cut, the last second of recording also gets cut, so if i record 8 seconds, the first half second and last half second get cut, and also the first second has breaks-cuts in it, the intermediate 6 seconds are ok
b) sound seems to sound somehow lower frequency than the voice recorded, is that an EQ issue or an encoder issue or is there a problem in my code?
c) final thing, at the moment the encoding is not happening in a web worker, how could i modify this code to make the encoding happen in the background in a web worker to improve performance etc?
thank you so so so much and here below is my code:
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
liblame = new lamejs(); var mymp3Encoder=new liblame.Mp3Encoder(1,44100,320);
function upload(blobOrFile) {
var form = new FormData(); form.append("blobbie", blobOrFile);
var xhr = new XMLHttpRequest(); xhr.open('POST', './upload.php', true);xhr.send(form);
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4) { if (xhr.status==200) { cap=xhr.responseText; }}};
xhr.onload = function(e) {};
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {a = (e.loaded / e.total) * 100;
console.log (e.loaded+"/"+e.total+"/"+a);}};
}
function record() {
thetext=$("#statusbit");
navigator.getUserMedia({audio: true}, function (stream) {
var stopRecording = beginRecording(stream);
var start = +new Date();
thetext.text('Recording... 3.0');
// Run a function every 100 ms to update the UI
var timer = setInterval(function () {
var duration = new Date() - start;
if (duration < 8000) {
thetext.text('Recording... ' + ((8000 - duration) / 1000).toFixed(1));
} else {
var buffers = stopRecording();
clearInterval(timer);
console.log ("Get MP3");
mp3Buffer=mymp3Encoder.flush();
if (mp3Buffer.length>0) buffers.push(mp3Buffer);
var blob=new Blob(buffers, {type: 'audio/mpeg'});
upload(blob);
}
}, 100);
}, function (error) {thetext.text('Error! Try again.');});
}
function beginRecording(stream) {
// Set up Web Audio API to process data from the media stream (microphone).
var context = new AudioContext();
var microphone = context.createMediaStreamSource(stream);
var processor = context.createScriptProcessor(0, 1, 1);
var buffers = [];
processor.onaudioprocess = function (event) {
var data = event.inputBuffer.getChannelData(0);
var lo = data; //the decoded data range: -1 +1
var l = new Float32Array(lo.length);
for(var i=0;i<lo.length;i++) {l[i] = lo[i]*32767.5;}
var buffer = mymp3Encoder.encodeBuffer(l);
buffers.push(buffer)
};
// Begin retrieving microphone data.
microphone.connect(processor);
processor.connect(context.destination);
// Return a function which will stop recording and return all MP3 data.
return function stopRecording() {
console.log ("FINISHED RECORDING");
// Clean up the Web Audio API resources.
if (context.close) context.close();
microphone.disconnect();
processor.disconnect();
processor.onaudioprocess = null;
// Return the buffers array. Note that there may be more buffers pending here.
console.log ("Finished cleaning");
return buffers;
};
}
from lamejs.
ok now i know why, it was simple,
rate of encoder has to match rate of audio context, so with code below all works well always
var context = new AudioContext();
var conrate=context.sampleRate;
console.log("mic sample rate:"+conrate);
liblame = new lamejs(); var mymp3Encoder=new liblame.Mp3Encoder(1,conrate,128);
new version is here
http://torchprinciple.com/tests/rec1/alexrec.html
now i just wish to put the encoding process in a web worker which im not sure how to do
from lamejs.
This has worked for me (getting samples from getChannelData(0)). This isn't a full sample but basically what I do is this:
var sampleRate = audioBuffer.sampleRate;
var numberOfChannels = audioBuffer.numberOfChannels;
var ld = audioBuffer.getChannelData(0);
var rd = numberOfChannels > 1 ? audioBuffer.getChannelData(1) : null;
//this code has a bug, assumes 2 channel
function lameEncode(l, r, sampleRate, progress) {
var liblame = new lamejs();
var mp3Encoder = new liblame.Mp3Encoder(2, sampleRate, 128); //2 channel hard coded :-/
var blockSize = 1152;
var blocks = [];
var mp3Buffer;
var length = l.length;
for (var i = 0; i < length; i += blockSize) {
progress((i/length)*100);
var lc = l.subarray(i, i + blockSize);
var rc = r.subarray(i, i + blockSize);
mp3Buffer = mp3Encoder.encodeBuffer(lc, rc);
if (mp3Buffer.length > 0) blocks.push(mp3Buffer);
}
mp3Buffer = mp3Encoder.flush();
if (mp3Buffer.length > 0) blocks.push(mp3Buffer);
progress(100);
return new Blob(blocks, {type: 'audio/mpeg'});
}
from lamejs.
Thanks @shanewho for all the assistance on this. I tried your code, but it looks like the same thing happens - I get an mp3 of silence. But the play length seems right! It looks like a valid mp3 file: 48000 Hz, 128 kb/s, mono mp3 - It's just that it's silent...
I'm trying to figure out the difference between what I'm using and the example code. Probably the only thing I can see is different Sample Rate (my machine uses 48000 and so the audioBuffer I created is 48000) The example uses a source wav with 44100 sample rate. Could that be the issue? Still debugging. Here's your code I plugged into mine, for a mono audioBuffer:
var sampleRate = buffer.sampleRate;
var numberOfChannels = buffer.numberOfChannels;
var ld = buffer.getChannelData(0);
var rd = numberOfChannels > 1 ? buffer.getChannelData(1) : null;
blob = lameEncode(ld, rd, sampleRate);
forceDownload();
function lameEncode(l, r, sampleRate) {
var liblame = new lamejs();
var mp3Encoder = new liblame.Mp3Encoder(numberOfChannels, sampleRate, 128); //2 channel hard coded :-/
var blockSize = 1152;
var blocks = [];
var mp3Buffer;
var length = l.length;
for (var i = 0; i < length; i += blockSize) {
var lc = l.subarray(i, i + blockSize);
//var rc = r.subarray(i, i + blockSize);
mp3Buffer = mp3Encoder.encodeBuffer(lc);
if (mp3Buffer.length > 0) blocks.push(mp3Buffer);
}
mp3Buffer = mp3Encoder.flush();
if (mp3Buffer.length > 0) blocks.push(mp3Buffer);
return new Blob(blocks, {type: 'audio/mpeg'});
}
function forceDownload(){
console.log('forceDownload');
//var blob = new Blob(mp3Data, {type:'audio/mpeg'});
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = window.document.createElement('a');
link.href = url;
link.download = 'output.mp3';
var click = document.createEvent("Event");
click.initEvent("click", true, true);
link.dispatchEvent(click);
}
from lamejs.
With the code you just posted (mixed with mine), you are only passing the left buffer to encode so you'd have problems if numberOfChannels
wasn't equal to 1
. But I doubt that's the issue. Check your input samples to see if they look like what you'd expect. It might be that it is actually encoding the audio but it is really quiet. I think the floats should go from -1 to +1, so if they are all really close to 0, you might have an issue of just really quiet sound. Other than that, I have no idea, sorry!
from lamejs.
ok @shanewho & @zhuker, I've created a test case that should make it obvious (though not to me) what's wrong. The following code works for fn2, but not for fn1. Everything else about the code is the same for both cases. fn1 uses Web Audio API: AudioContext.decodeAudioData() and fn2 uses a function built into lamejs, as well as explicitly creating an Int16Array. When I compare the mp3Data for both, both arrays are the same length, but different slightly different values. Any thoughts?
<script src='lame.all.js'></script>
<script>
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var lib = new lamejs();
var mp3Data = [];
var xhr = new XMLHttpRequest();
xhr.open("get", "testdata/Left.wav", true);
xhr.responseType = "arraybuffer";
xhr.addEventListener('load', fn1); /////////////////////////////////
//xhr.addEventListener('load', fn2); ////////////////////////////////
xhr.send();
function fn1(e){
//THIS FUNCTION USES WEB AUDIO API: AudioContext.decodeAudioData();
context.decodeAudioData(e.target.response, function(buffer){
encodeAudio(buffer.getChannelData(0), buffer.numberOfChannels, buffer.sampleRate);
});
}
function fn2(e){
//THIS FUNCTION USES LAMEJS FUNCTION TO READ WAV HEADER AND CREATE INT16ARRAY;
var buffer = e.target.response;
var wav = lib.WavHeader.readHeader(new DataView(buffer));
var samples = new Int16Array(buffer, wav.dataOffset, wav.dataLen / 2);
encodeAudio(samples, wav.channels, wav.sampleRate);
}
function encodeAudio(samples, channels, sampleRate){
var kbps = 128;
var mp3encoder = new lib.Mp3Encoder(channels, sampleRate, kbps);
var sampleBlockSize = 1152;
var sampleChunk;
for(var i = 0; i < samples.length; i += sampleBlockSize){
sampleChunk = samples.subarray(i, i + sampleBlockSize);
var mp3buf = mp3encoder.encodeBuffer(sampleChunk);
if(mp3buf.length > 0){
mp3Data.push(mp3buf);
}
}
mp3buf = mp3encoder.flush();
if(mp3buf.length > 0){
mp3Data.push(mp3buf);
forceDownload();
console.debug(mp3Data);
}
}
function forceDownload(){
var blob = new Blob(mp3Data, {type:'audio/mpeg'});
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = window.document.createElement('a');
link.href = url;
link.download = 'output.mp3';
var click = document.createEvent("Event");
click.initEvent("click", true, true);
link.dispatchEvent(click);
}
</script>
from lamejs.
With the script above (the same wav source), it seems that LAMEJS's samples look like this:
[0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0, 0, 0, 1, 1, 0, -1, -1, -1, -1, 0, 0, 0, 2, 3, 3, 2, 2, 2, 2, 2, 1, 0, -2, -2, -2, -3, -5, -5, -6, -5, -4, -2, 0, 2, 2, 2, 1, -1, -5, -7, -8, -7, -7, -7, -8, -5, -1, 1, 1, 2, 2, 0, -2, -5, -6, -6, -3, 1…]
while buffer.getChannelData(0)'s samples look like this:
[0, 0, 0, 0, 0, 0, 0, 0, 0, -0.000030517578125, -0.000030517578125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.000030518509447574615, 0.000030518509447574615, 0.000030518509447574615, 0.000030518509447574615, 0.000030518509447574615, 0.000030518509447574615, 0.000030518509447574615, 0.00006103701889514923, 0.000030518509447574615, 0, 0, 0, 0.000030518509447574615, 0.000030518509447574615, 0, -0.000030517578125, -0.000030517578125, -0.000030517578125, -0.000030517578125, 0, 0, 0, 0.00006103701889514923, 0.00009155552834272385, 0.00009155552834272385, 0.00006103701889514923, 0.00006103701889514923, 0.00006103701889514923, 0.00006103701889514923, 0.00006103701889514923, 0.000030518509447574615, 0, -0.00006103515625, -0.00006103515625, -0.00006103515625, -0.000091552734375, -0.000152587890625, -0.000152587890625, -0.00018310546875, -0.000152587890625, -0.0001220703125, -0.00006103515625, 0, 0.00006103701889514923, 0.00006103701889514923, 0.00006103701889514923, 0.000030518509447574615, -0.000030517578125, -0.000152587890625, -0.000213623046875, -0.000244140625, -0.000213623046875, -0.000213623046875, -0.000213623046875, -0.000244140625, -0.000152587890625, -0.000030517578125, 0.000030518509447574615, 0.000030518509447574615, 0.00006103701889514923, 0.00006103701889514923, 0, -0.00006103515625, -0.000152587890625, -0.00018310546875, -0.00018310546875, -0.000091552734375, 0.000030518509447574615…]
Why, and how can this be fixed? Presumably it involves LAMEJS's Int16Array vs audioBuffer's Float32Array? @shanewho You mentioned getChannelData() worked for you - how were you able to overcome the difference above? Why didn't @zhuker use decodeAudioData() to read the loaded wav? So many questions! 😖
from lamejs.
👍
FWIW, you can use a Float32Array in -32768 32767 (not just an Int16Array) and keep the decimals if needed/wanted. Probably won't notice any difference converting to int16 but might lose some minor fidelity in the rounding.
from lamejs.
good times @shanewho, thanks for your input
@calipoop problem solved? should i close the issue?
from lamejs.
Closed. Thanks all!
from lamejs.
i just confirmed that my code is recording the sound between 1 and 2 semitones lower in frequency than what it should..... just recorded some piano notes, and what comes out is between 1 and 2 semitones lower... why is that happening? code is above, best
from lamejs.
My only guess, and it's a weak one because I don't see anything in your code that tells me this is happening.. Is it possible that you are somehow encoding the same samples multiple times? i.e., when you stretch a wave it lowers the pitch. For example when a 10 second audio is played over 20 seconds it would be lower pitch. You might check the expected length of your input audio and see if it matches the length of the output. Other than that, good luck!
from lamejs.
Shane, thank you very much for the reply, mmm, i don´t see how that could be happening because the length of the recorded audio seems to be the same, look i have put the code online here:
http://torchprinciple.com/tests/rec1/alexrec.html
and it generates a download url every time in case any of you wants to test it by yourselves very quickly,
thank you again for any tips :)
http://torchprinciple.com/tests/rec1/alexrec.html
from lamejs.
i just made a test with a different library here:
http://torchprinciple.com/tests/rec2/
it comes from this library: https://github.com/nusofthq/Recordmp3js
and the recorded sound is perfect in frequency, no issues at all. So obviously something is wrong in my code. That other library works perfect but it has two things missing : conversion to mp3 is not in real time so it´s much slower to produce the result and also the minified lame encoder is 6 or 7 times bigger than yours here.
Ideally a combination of both would be great :) I love your library because your minified lame encoder is so so small and because real time encoding with your library is so so fast, now i just wish i can have the sound come out as stable as in http://torchprinciple.com/tests/rec2/
:)
from lamejs.
All right i found the reason, here
var mymp3Encoder=new liblame.Mp3Encoder(1,48000,128);
changing 44100 to 48000, solved the problem and now the frequency is all correct,
interesting right? why is it that with 48000 works perfect and with 44100 goes slower?
apart from that all the rest works well except that the very beginning and end of clip i kind of have to find the way stretch them more
but anyway, great, so the last thing i would like to now is, at the moment the encoding is not using a web worker, how could i pass the encoding to a web worker?
best wishes and thank u
from lamejs.
now problem is that
on laptop all is working, but on mobile firefox is recording empty mp3 and chrome also having problems, so on mobile there are problems, why would that happen?
from lamejs.
it works on mobile firefox (it was an issue with the position of audiocontext declaration) but on mobile chrome and opera the buffers come always empty with zeroes 0 0 0 0 ,
any idea why?
on laptop and mobile firefox buffers come with info all well
from lamejs.
Related Issues (20)
- lamejs not working with angular 8 -- Uncaught ReferenceError: Lame is not defined HOT 3
- Silent MP3 file when converting output from Azure Speech-to-Text javascript SDK HOT 2
- Is it possible to use VBR (variable bit rate) HOT 1
- Blob to MP3 HOT 1
- lamejs.WavHeader.readHeader - Uncaught (in promise) TypeError: Cannot read property 'dataOffset' of undefined HOT 3
- There is a problem with the sound quality in the node environment
- Deleted
- recorder wavdata to mp3 causes ui rendering block HOT 2
- window not defined
- "MPEGMode is not defined" HOT 21
- [Firefox] Data from right channel is not passed to encoder despite there are expected two channels
- MPEGMode is not defined HOT 5
- No sound in trimmed & encoded audio blob, Vue 2. Why?
- wav转mp3的时候lampjs报"ReferenceError: MPEGMode is not defined" HOT 10
- Cannot read properties of undefined (reading 'println') HOT 1
- can i convert amr to mp3?
- License clarification
- zhuker/lamejs 打包报错 HOT 1
- Why does this code work on Firefox not on Chromium?
- Maintainer needed?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from lamejs.