possible to decode portions, possible to find out how much is still available for one stream decoding

This commit is contained in:
Blue 2018-12-29 21:08:22 +03:00
parent 7a6ebef935
commit aa8ac249a0
3 changed files with 67 additions and 42 deletions

View File

@ -12,41 +12,49 @@
<script src="wrapper.js"></script> <script src="wrapper.js"></script>
<input type="file" id="file" name="file" /> <input type="file" id="file" name="file" />
<script> <script>
var time = 0;
var ctx = new AudioContext();
ctx.suspend();
var decoder;
function onChange(e) { function onChange(e) {
var file = e.target.files[0]; var file = e.target.files[0];
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
decoder = new Module.Decoder();
var buffer = reader.result; var buffer = reader.result;
var src = new Uint8Array(buffer); var src = new Uint8Array(buffer);
var ptr = Module._malloc(buffer.byteLength); var ptr = Module._malloc(buffer.byteLength);
var arr = new Uint8Array(Module.HEAPU8.buffer, ptr, buffer.byteLength); var arr = new Uint8Array(Module.HEAPU8.buffer, ptr, buffer.byteLength);
var ctx = new AudioContext();
var time = 0;
arr.set(src, 0); arr.set(src, 0);
var decoder = new Module.Decoder();
decoder.addFragment(ptr, buffer.byteLength); decoder.addFragment(ptr, buffer.byteLength);
schedule();
while (decoder.hasMore()) { ctx.resume();
var audio = decoder.decode(1); }
if (audio === undefined) { reader.readAsArrayBuffer(file);
if (decoder.hasMore() === false) {
break; }
}
} function schedule() {
if (decoder.hasMore()) {
var audio = decoder.decode(50);
if (audio) {
var source = ctx.createBufferSource(); var source = ctx.createBufferSource();
source.buffer = audio; source.buffer = audio;
source.connect(ctx.destination); source.connect(ctx.destination);
source.start(time); source.start(time);
time += audio.duration; time += audio.duration;
setTimeout(schedule, Math.floor(audio.duration * 200));
} else {
decoder.delete();
} }
} else {
decoder.delete(); decoder.delete();
} }
reader.readAsArrayBuffer(file);
} }
document.getElementById("file").addEventListener("change", onChange, false); document.getElementById("file").addEventListener("change", onChange, false);
</script> </script>
</body> </body>

View File

@ -1,5 +1,6 @@
#include "wrapper.h" #include "wrapper.h"
#include <string> #include <string>
#include <iostream>
MadHeader * mad_header_create() MadHeader * mad_header_create()
{ {
@ -74,6 +75,7 @@ Decoder::Decoder():
sampleRate(0), sampleRate(0),
channels(0), channels(0),
cachedLength(0), cachedLength(0),
samplesPerFrame(0),
currentBuffer(0), currentBuffer(0),
currentBufferLength(0), currentBufferLength(0),
synth(), synth(),
@ -121,29 +123,36 @@ void Decoder::addFragment(intptr_t bufferPtr, unsigned long length)
framesLeft(); framesLeft();
} }
emscripten::val Decoder::decode(int count) emscripten::val Decoder::decode(uint32_t count)
{ {
emscripten::val ret = emscripten::val::undefined(); emscripten::val ret = emscripten::val::undefined();
int result = -1;
int available = framesLeft(count); int available = framesLeft(count);
if (available > 0) { if (available > 0) {
ret = context.call<val>("createBuffer", channels, synth.pcm.length, sampleRate); ret = context.call<val>("createBuffer", channels, available * samplesPerFrame, sampleRate);
}
std::vector<emscripten::val> chans(channels, emscripten::val::undefined());
if (result == 0) { for (int i = 0; i < channels; ++i) {
mad_synth_frame(&synth, &frame); chans[i] = ret.call<val>("getChannelData", i);
ret = context.call<val>("createBuffer", synth.pcm.channels, synth.pcm.length, synth.pcm.samplerate); }
for (int i = 0; i < synth.pcm.channels; ++i) {
emscripten::val chan = ret.call<val>("getChannelData", i); for (int i = 0; i < available; ++i) {
mad_fixed_t* sChan = synth.pcm.samples[i]; mad_frame_decode(&frame, &stream);
for (int j = 0; j < synth.pcm.length; ++j) { mad_synth_frame(&synth, &frame);
float value = mad_f_todouble(sChan[j]);
chan.set(std::to_string(j), val(value)); for (int j = 0; j < samplesPerFrame; ++j) {
for (int k = 0; k < channels; ++k) {
float value = mad_f_todouble(synth.pcm.samples[k][j]);
chans[k].set(std::to_string(i * samplesPerFrame + j), val(value));
}
} }
} }
return ret; cachedLength -= available;
if (cachedLength == 0) {
framesLeft();
}
} }
return ret; return ret;
@ -158,38 +167,43 @@ bool Decoder::hasMore() const
return stream.error != MAD_ERROR_BUFLEN; return stream.error != MAD_ERROR_BUFLEN;
} }
uint64_t Decoder::framesLeft(uint64_t max) uint32_t Decoder::framesLeft(uint32_t max)
{ {
if (currentBuffer == 0) { if (currentBuffer == 0) {
return 0; return 0;
} }
if (!stream.sync) {
int result = mad_stream_sync(&stream);
if (result != 0) {
return 0;
}
}
if (cachedLength == 0) { if (cachedLength == 0) {
mad_stream probe; mad_stream probe;
mad_header ph; mad_header ph;
mad_stream_init(&probe); mad_stream_init(&probe);
mad_header_init(&ph); mad_header_init(&ph);
mad_stream_buffer(&probe, currentBuffer, currentBufferLength);
mad_stream_skip(&probe, stream.skiplen); mad_stream_skip(&probe, stream.skiplen);
while (probe.error != MAD_ERROR_BUFLEN || cachedLength < max) {
while (cachedLength < max) {
if (mad_header_decode(&ph, &probe) == 0) { if (mad_header_decode(&ph, &probe) == 0) {
if (sampleRate == 0) { if (sampleRate == 0) {
sampleRate = ph.samplerate; sampleRate = ph.samplerate;
channels = MAD_NCHANNELS(&ph); channels = MAD_NCHANNELS(&ph);
samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source
} else { } else {
if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph)) { if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) {
break; break;
} }
} }
++cachedLength; ++cachedLength;
} else {
if (!MAD_RECOVERABLE(probe.error)) {
std::cout << "framesLeft::" << probe.error << std::endl;
break;
}
} }
} }
mad_header_finish(&ph);
mad_stream_finish(&probe);
} }
return std::min(cachedLength, max); return std::min(cachedLength, max);

View File

@ -124,14 +124,15 @@ public:
~Decoder(); ~Decoder();
void addFragment(intptr_t bufferPtr, unsigned long length); void addFragment(intptr_t bufferPtr, unsigned long length);
val decode(int count); val decode(uint32_t count = UINT32_MAX);
bool hasMore() const; bool hasMore() const;
uint64_t framesLeft(uint64_t max = UINT64_MAX); uint32_t framesLeft(uint32_t max = UINT32_MAX);
private: private:
uint32_t sampleRate; uint32_t sampleRate;
uint8_t channels; uint8_t channels;
uint64_t cachedLength; uint32_t cachedLength;
uint16_t samplesPerFrame;
unsigned char* currentBuffer; unsigned char* currentBuffer;
unsigned long currentBufferLength; unsigned long currentBufferLength;
@ -236,6 +237,7 @@ EMSCRIPTEN_BINDINGS(jsmad) {
function("mad_stream_init", &mad_stream_init, allow_raw_pointers()); function("mad_stream_init", &mad_stream_init, allow_raw_pointers());
function("mad_stream_finish", &mad_stream_finish, allow_raw_pointers()); function("mad_stream_finish", &mad_stream_finish, allow_raw_pointers());
function("mad_stream_buffer", &mad_stream_buffer, allow_raw_pointers()); function("mad_stream_buffer", &mad_stream_buffer, allow_raw_pointers());
function("mad_stream_sync", &mad_stream_sync, allow_raw_pointers());
class_<mad_pcm>("mad_pcm"); class_<mad_pcm>("mad_pcm");
class_<MadPCM, base<mad_pcm>>("MadPCM") class_<MadPCM, base<mad_pcm>>("MadPCM")
@ -259,6 +261,7 @@ EMSCRIPTEN_BINDINGS(jsmad) {
.constructor<>() .constructor<>()
.function("addFragment", &Decoder::addFragment) .function("addFragment", &Decoder::addFragment)
.function("hasMore", &Decoder::hasMore) .function("hasMore", &Decoder::hasMore)
.function("framesLeft", &Decoder::framesLeft)
.function("decode", &Decoder::decode); .function("decode", &Decoder::decode);
} }