Skip to content

Commit

Permalink
Fix decode audio recorded by Apple's Telegram
Browse files Browse the repository at this point in the history
  • Loading branch information
morethanwords committed Jun 1, 2020
1 parent 4644db7 commit 855f712
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 72 deletions.
2 changes: 1 addition & 1 deletion dist-unminified/decoderWorker.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/decoderWorker.min.js

Large diffs are not rendered by default.

Binary file added example/White_noise.ogg
Binary file not shown.
71 changes: 4 additions & 67 deletions example/decoder.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,36 +56,18 @@ <h2>Recordings</h2>
wavSampleRate: desiredSampleRate
});


var use16 = true;
var text = use16 ? 'INT16' : 'FLOAT32';
var int16Array = [];
var float32Array = [];
decoderWorker.onmessage = function(e){
var data = e.data;
// null means decoder is finished
if(!data || data.type == 'done') {
console.log('waveform:', data && data.waveform);
wavWorker.postMessage({ command: 'done' });
} else { // e.data contains decoded buffers as float32 values
//console.log('got data from decoder', data);
wavWorker.postMessage({
command: 'encode',
buffers: e.data
}, e.data.map(function(typedArray){
var int16 = typedArray instanceof Int16Array;
for(var i = 0; i < typedArray.length; ++i) {
var sample = typedArray[i];
if(!int16) {
sample = Math.floor(sample * 32767.5 - 0.5);
float32Array.push(sample);
} else {
sample = Math.floor(sample - 0.5);
int16Array.push(sample);
}

text += sample + ',';
}

return typedArray.buffer;
}));
}
Expand All @@ -103,70 +85,24 @@ <h2>Recordings</h2>
audio.controls = true;
audio.src = URL.createObjectURL( dataBlob );

var blob = new Blob([text], {type: "text/plain;charset=utf-8"});
url = URL.createObjectURL(blob);
fileName = new Date().toISOString() + ".txt";

var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;

console.log(int16Array, float32Array);

var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);

recordingslist.appendChild(li);

if(!float32Array.length) {
decoderWorker.postMessage({
command:'init',
decoderSampleRate: desiredSampleRate,
outputBufferSampleRate: desiredSampleRate
});

wavWorker.postMessage({
command:'init',
wavBitDepth: parseInt(bitDepth.value,10),
wavSampleRate: desiredSampleRate
});

setTimeout(() => {
console.log('pass typedArray', typedArray);
decoderWorker.postMessage({
//command: 'decode',
//command: 'waveform',
//command: 'decode16',
command: 'decode',
pages: typedArray,
waveform: true
}, [typedArray.buffer] );
}, 500);
} else {
var diff = [];
for(var i = 0; i < int16Array.length; ++i) {
var first = int16Array[i];
var second = float32Array[i];
if(Math.abs(second - first) == 1) continue;
else if(first != second) {
diff.push({index: i, values: [first, second]});
}
}
console.log(diff);
}
}
};

decoderWorker.postMessage({
command: 'decode',
//command: 'waveform',
//command: 'decode16',
//command: use16 ? 'decode16' : 'decode',
pages: typedArray,
waveform: true
}/* , [typedArray.buffer] */ );
}, [typedArray.buffer] );
};

fileInput.onchange = function(e){
Expand All @@ -181,7 +117,8 @@ <h2>Recordings</h2>

remoteButton.onclick = function(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "./mono.opus", true);
//xhr.open("GET", "./mono.opus", true);
xhr.open("GET", "./macOS_audio.oga", true);
xhr.responseType = "arraybuffer";

xhr.onload = function(e) {
Expand Down
196 changes: 196 additions & 0 deletions example/decoder_16and32.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<!DOCTYPE html>

<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Decode Example</title>
<style type="text/css">
ul { list-style: none; }
li { margin: 1em; }
audio { display: block; }
</style>
</head>

<body>
<h1>Decoder Example</h1>
<p>Choose either a file from disk, or a file from a server.</p>
<p>File is decoded to pcm buffers and then played back as Riff</p>

<h2>Options</h2>

<div>
<label>Decoder output sample rate</label>
<input id="sampleRate" type="number" value="48000" />
</div>

<div>
<label>Wave file bit depth</label>
<input id="bitDepth" type="number" value="16" />
</div>

<h2>Commands</h2>
<input type="file" id="fileInput"/>
<button type="button" id="remoteButton">Load using XMLHttpRequest</button>

<h2>Recordings</h2>
<ul id="recordingslist"></ul>

<script>
function decodeOgg(arrayBuffer){

var typedArray = new Uint8Array(arrayBuffer);
var decoderWorker = new Worker('../dist-unminified/decoderWorker.js');
var wavWorker = new Worker('../dist-unminified/waveWorker.js');
var desiredSampleRate = parseInt(sampleRate.value,10);

decoderWorker.postMessage({
command:'init',
decoderSampleRate: desiredSampleRate,
outputBufferSampleRate: desiredSampleRate
});

wavWorker.postMessage({
command:'init',
wavBitDepth: parseInt(bitDepth.value,10),
wavSampleRate: desiredSampleRate
});


var use16 = true;
var text = use16 ? 'INT16' : 'FLOAT32';
var int16Array = [];
var float32Array = [];
decoderWorker.onmessage = function(e){
var data = e.data;
// null means decoder is finished
if(!data || data.type == 'done') {
console.log('waveform:', data && data.waveform);
wavWorker.postMessage({ command: 'done' });
} else { // e.data contains decoded buffers as float32 values
wavWorker.postMessage({
command: 'encode',
buffers: e.data
}, e.data.map(function(typedArray){
var int16 = typedArray instanceof Int16Array;
for(var i = 0; i < typedArray.length; ++i) {
var sample = typedArray[i];
if(!int16) {
sample = Math.floor(sample * 32767.5 - 0.5);
float32Array.push(sample);
} else {
sample = Math.floor(sample - 0.5);
int16Array.push(sample);
}

text += sample + ',';
}

return typedArray.buffer;
}));
}
};

wavWorker.onmessage = function(e){
var data = e.data;
// null signifies that no more data is expected and worker is closed.
if (data && data.page) {
var fileName = new Date().toISOString() + ".wav";
var dataBlob = new Blob( [ data.page ], { type: "audio/wav" } );
var url = URL.createObjectURL( dataBlob );

var audio = document.createElement('audio');
audio.controls = true;
audio.src = URL.createObjectURL( dataBlob );

var blob = new Blob([text], {type: "text/plain;charset=utf-8"});
url = URL.createObjectURL(blob);
fileName = new Date().toISOString() + ".txt";

var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.innerHTML = link.download;

console.log(int16Array, float32Array);

var li = document.createElement('li');
li.appendChild(link);
li.appendChild(audio);

recordingslist.appendChild(li);

if(!float32Array.length) {
decoderWorker.postMessage({
command:'init',
decoderSampleRate: desiredSampleRate,
outputBufferSampleRate: desiredSampleRate
});

wavWorker.postMessage({
command:'init',
wavBitDepth: parseInt(bitDepth.value,10),
wavSampleRate: desiredSampleRate
});

setTimeout(() => {
console.log('pass typedArray', typedArray);
decoderWorker.postMessage({
//command: 'decode',
//command: 'waveform',
//command: 'decode16',
command: 'decode',
pages: typedArray,
waveform: true
}, [typedArray.buffer] );
}, 500);
} else {
var diff = [];
for(var i = 0; i < int16Array.length; ++i) {
var first = int16Array[i];
var second = float32Array[i];
if(Math.abs(second - first) == 1) continue;
else if(first != second) {
diff.push({index: i, values: [first, second]});
}
}
console.log(diff);
}
}
};

decoderWorker.postMessage({
command: 'decode',
//command: 'waveform',
//command: 'decode16',
//command: use16 ? 'decode16' : 'decode',
pages: typedArray,
waveform: true
}/* , [typedArray.buffer] */ );
};

fileInput.onchange = function(e){
var fileReader = new FileReader();

fileReader.onload = function() {
decodeOgg(this.result);
};

fileReader.readAsArrayBuffer( e.target.files[0] );
};

remoteButton.onclick = function(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "./mono.opus", true);
xhr.responseType = "arraybuffer";

xhr.onload = function(e) {
decodeOgg(this.response);
};

xhr.send();
};

</script>
</body>
</html>
Binary file added example/macOS_audio.oga
Binary file not shown.
28 changes: 25 additions & 3 deletions src/decoderWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,22 @@ function setBits(bytes, bitOffset, value) {

OggOpusDecoder.prototype.decode = function(typedArray, withWaveform) {
var dataView = new DataView(typedArray.buffer);
this.getPageBoundaries(dataView).map(function(pageStart) {
var pages = this.getPageBoundaries(dataView);

/* нужно убрать это отсюда в случае декодирования чанками,
в таком случае нужно разобраться с аудиосообщениями,
записанными на маке или айфоне,
т.к. в их последнем заголовке нет флага 0x04: unset = not last page of logical bitstream
https://xiph.org/vorbis/doc/framing.html
*/
var maxPageIndex = pages.length - 1;

pages.map(function(pageStart, idx) {
var headerType = dataView.getUint8(pageStart + 5, true);
var pageIndex = dataView.getUint32(pageStart + 18, true);

//console.log('got page:', pageStart, headerType, pageIndex);

// Beginning of stream
if(headerType & 2) {
this.numberOfChannels = dataView.getUint8(pageStart + 37, true);
Expand All @@ -184,9 +196,12 @@ OggOpusDecoder.prototype.decode = function(typedArray, withWaveform) {
this.decoderBuffer.set(typedArray.subarray(segmentTableIndex, segmentTableIndex += packetLength), this.decoderBufferIndex);
this.decoderBufferIndex += packetLength;

//console.log('packetLength:', packetLength);

if(packetLength < 255) {
var outputSampleLength = this._opus_decode_float(this.decoder, this.decoderBufferPointer, this.decoderBufferIndex, this.decoderOutputPointer, this.decoderOutputMaxLength, 0);

//console.log('outputSampleLength', outputSampleLength);

if(withWaveform && outputSampleLength > 0) {
var sampleBuffer = this.HEAPF32.subarray(this.decoderOutputPointer >> 2, (this.decoderOutputPointer >> 2) + this.decoderOutputMaxLength);
//console.log('sampleBuffer:', sampleBuffer);
Expand All @@ -203,8 +218,11 @@ OggOpusDecoder.prototype.decode = function(typedArray, withWaveform) {
}
}

//console.log('decoded page');

// End of stream
if(headerType & 4) {
if(headerType & 4 || (maxPageIndex == idx)) {
//console.log('end of stream', this.outputBuffers);
this.sendLastBuffer();
}
}
Expand All @@ -220,6 +238,8 @@ OggOpusDecoder.prototype.getPageBoundaries = function(dataView) {
}
}

//console.log('diff:', i, pageBoundaries, i - pageBoundaries[pageBoundaries.length - 1]);

return pageBoundaries;
};

Expand Down Expand Up @@ -290,6 +310,8 @@ OggOpusDecoder.prototype.sendToOutputBuffers = function(mergedBuffers) {
var dataIndex = 0;
var mergedBufferLength = mergedBuffers.length / this.numberOfChannels;

//console.log('sendToOutputBuffers', mergedBufferLength, mergedBuffers);

while(dataIndex < mergedBufferLength) {
var amountToCopy = Math.min(mergedBufferLength - dataIndex, this.config.bufferLength - this.outputBufferIndex);

Expand Down

0 comments on commit 855f712

Please sign in to comment.