From 1f40231b9ba519d936d72937c7ecb319597d507d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=93=D1=83=D0=B1=D0=B8=D1=87?= Date: Mon, 14 Jan 2019 05:19:37 +0300 Subject: [PATCH] Basic decoding with edge effects --- CMakeLists.txt | 4 + decoder.cpp | 362 +++++++++++++++++++++++++++++++++++++++++++++++++ decoder.h | 66 +++++++++ index.html | 62 +++++++-- wrapper.cpp | 139 ------------------- wrapper.h | 25 +--- 6 files changed, 486 insertions(+), 172 deletions(-) create mode 100644 decoder.cpp create mode 100644 decoder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf85b4a..6fe1ca7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.0) + +set(EMO libmad.bc) function(em_file name) execute_process(COMMAND emcc --bind ${CMAKE_SOURCE_DIR}/libmad.bc + ${CMAKE_SOURCE_DIR}/decoder.cpp ${CMAKE_SOURCE_DIR}/${name}.cpp -o ${CMAKE_BINARY_DIR}/${name}.js -s WASM=0 @@ -13,3 +16,4 @@ endfunction(em_file) configure_file(index.html index.html) em_file(wrapper) + diff --git a/decoder.cpp b/decoder.cpp new file mode 100644 index 0000000..7485b4b --- /dev/null +++ b/decoder.cpp @@ -0,0 +1,362 @@ +#include "decoder.h" +#include + +Decoder::Decoder(): +state(empty), +sampleRate(0), +channels(0), +cachedLength(0), +samplesPerFrame(0), +glue(new uint8_t[GLUE_LENGTH]), +lastSync(-1), +cached(false), +synth(), +stream(), +frame(), +context(0), +pending() +{ + for (int i = 0; i < GLUE_LENGTH; ++i) { + glue[i] = 0; + } + + mad_frame_init(&frame); + mad_stream_init(&stream); + mad_synth_init(&synth); + + emscripten::val AudioContext = emscripten::val::global("AudioContext"); + if (!AudioContext.as()) { + AudioContext = emscripten::val::global("webkitAudioContext"); + } + + context = AudioContext.new_(); +} + +Decoder::~Decoder() +{ + context.call("close"); + + mad_synth_finish(&synth); + mad_stream_finish(&stream); + mad_frame_finish(&frame); + + delete[] glue; +} + +void Decoder::addFragment(intptr_t bufferPtr, uint32_t length) +{ + if (length < GLUE_LENGTH / 2) { + std::cout << "An attempt to add fragment smaller then half of the glue buffer, ignoring"; + return; + } + std::cout << "Adding new fragment " << length << " bytes long" << std::endl; + uint8_t* buffer = (uint8_t(*))bufferPtr; + RawBuffer rb = {buffer, length, 0, 0}; + pending.push_back(rb); + + std::cout << "The state now is " << state << std::endl; + switch (state) { + case empty: + mad_stream_buffer(&stream, buffer, length); + + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[i] = buffer[length - GLUE_LENGTH/2 + i]; + } + + state = onBufferHalf; + prepareNextBuffer(); + break; + case onBufferHalf: + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = buffer[i]; + } + + state = onBufferFull; + break; + case onBufferFull: + break; + case onGlueHalf: + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = buffer[i]; + } + + state = onGlueFull; + cached = false; + prepareNextBuffer(); + break; + case onGlueFull: + break; + } +} + +emscripten::val Decoder::decode(uint32_t count) +{ + emscripten::val ret = emscripten::val::undefined(); + + int available = framesLeft(count); + int success = 0; + if (available > 0) { + ret = context.call("createBuffer", channels, available * samplesPerFrame, sampleRate); + + std::vector chans(channels, emscripten::val::undefined()); + for (int i = 0; i < channels; ++i) { + chans[i] = ret.call("getChannelData", i); + } + + for (int i = 0; success < available; ++i) { + int res = mad_frame_decode(&frame, &stream); + + if (res == 0) { + lastSync = stream.bufend - stream.this_frame; + ++success; + } else { + if (MAD_RECOVERABLE(stream.error)) { + + std::cout << "Unexpected error during the decoding process: " << mad_stream_errorstr(&stream) << std::endl; + continue; + } else { + break; + } + } + + mad_synth_frame(&synth, &frame); + + 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(success * samplesPerFrame + j), emscripten::val(value)); + } + } + } + + cachedLength -= available; + std::cout << "Processed " << available << " frames, " << success << " successfully, last error " << mad_stream_errorstr(&stream) << std::endl; + + if (cachedLength == 0) { + cached = false; + prepareNextBuffer(); + } + } + + return ret; +} + +bool Decoder::hasMore() const +{ + if (pending.size() == 1) { + return stream.error != MAD_ERROR_BUFLEN; + } else { + return true; + } +} + +uint32_t Decoder::framesLeft(uint32_t max) +{ + if (state == empty || state == onGlueHalf) { + cached = true; + return 0; + } + + if (cached == false) { + mad_stream probe; + mad_header ph; + initializeProbe(probe); + mad_header_init(&ph); + + while (cachedLength < max) { + if (mad_header_decode(&ph, &probe) == 0) { + if (sampleRate == 0) { + sampleRate = ph.samplerate; + channels = MAD_NCHANNELS(&ph); + samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source + } else { + if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) { + break; + } + } + if (probe.next_frame > probe.this_frame) { + ++cachedLength; + } + } else { + std::cout << "framesLeft::" << mad_stream_errorstr(&probe) << std::endl; + if (!MAD_RECOVERABLE(probe.error)) { + break; + } + } + } + + mad_header_finish(&ph); + mad_stream_finish(&probe); + std::cout << cachedLength << " frames are available for decoding" << std::endl; + cached = true; + } + + return std::min(cachedLength, max); +} + +void Decoder::pullBuffer() +{ + mad_header dummy; + mad_header_init(&dummy); + while(stream.error == 0 || MAD_RECOVERABLE(stream.error)) { + mad_header_decode(&dummy, &stream); + std::cout << "pullBuffer:: " << mad_stream_errorstr(&stream) << std::endl; + } + mad_header_finish(&dummy); +} + +void Decoder::changeBuffer() +{ + uint32_t left; + switch (state) { + case empty: + std::cout << "Wrong state on switchBuffer method - empty, aborting" << std::endl; + case onBufferHalf: + switchToGlue(); + state = onGlueHalf; + break; + case onBufferFull: + switchToGlue(); + state = onGlueFull; + break; + case onGlueHalf: + std::cout << "Wrong state on switchBuffer method - onGlueHalf, aborting" << std::endl; + break; + case onGlueFull: + std::cout << "Having another fragment " << pending[0].length << " bytes long" << std::endl; + left = (uint64_t)stream.bufend - (uint64_t)stream.next_frame; +// std::cout << "MD_LENGTH_BEFORE: " << stream.anc_bitlen << std::endl; + mad_stream_buffer(&stream, pending[0].ptr, pending[0].length); + + + //mad_stream_skip(&stream, left); + stream.error = MAD_ERROR_NONE; + if (lastSync != -1) { + if (lastSync > GLUE_LENGTH / 2) { + std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit LASTSYNC (" << lastSync << ")" << std::endl; + throw 1; + } + uint64_t target = (uint64_t)stream.buffer + GLUE_LENGTH / 2 - lastSync; + while ((uint64_t)(stream.this_frame) < target) { +// std::cout << "pulling" << std::endl; + mad_frame_decode(&frame, &stream); + } + stream.error = MAD_ERROR_NONE; + +// stream.this_frame = stream.buffer + GLUE_LENGTH / 2 - lastSync; +// stream.next_frame = stream.buffer + GLUE_LENGTH / 2 - left; +// stream.sync = 1; + + lastSync = -1; + +// mad_bitptr next_frame; +// mad_bit_init(&next_frame, stream.next_frame); +// stream.anc_ptr = stream.ptr; +// stream.anc_bitlen = mad_bit_length(&stream.ptr, &next_frame); +// mad_bit_finish(&next_frame); +// +// std::cout << ", MD_LENGTH_AFTER: " << stream.anc_bitlen << std::endl; + } + + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[i] = pending[0].ptr[pending[0].length - GLUE_LENGTH/2 + i]; + } + + state = onBufferHalf; + + if (pending.size() > 1) { + for (int i = 0; i < GLUE_LENGTH/2; ++i) { + glue[GLUE_LENGTH/2 + i] = pending[1].ptr[i]; + } + + state = onBufferFull; + } + } + + cached = false; +} + +void Decoder::prepareNextBuffer() +{ + bool shift; + do { + shift = false; + framesLeft(); + if (cachedLength == 0 && state != empty && state != onGlueHalf) { + pullBuffer(); + changeBuffer(); + shift = true; + } + } while (shift); +} + +void Decoder::initializeProbe(mad_stream& probe) +{ + mad_stream_init(&probe); + + probe.buffer = stream.buffer; + probe.bufend = stream.bufend; + probe.skiplen = stream.skiplen; + probe.sync = stream.sync; + probe.freerate = stream.freerate; + probe.this_frame = stream.this_frame; + probe.next_frame = stream.next_frame; + probe.ptr.byte = stream.ptr.byte; + probe.ptr.cache = stream.ptr.cache; + probe.ptr.cache = stream.ptr.cache; + probe.anc_ptr.byte = stream.anc_ptr.byte; + probe.anc_ptr.cache = stream.anc_ptr.cache; + probe.anc_ptr.cache = stream.anc_ptr.cache; + probe.anc_bitlen = stream.anc_bitlen; + //probe.main_data = stream.main_data; + //probe.md_len = stream.md_len; + probe.options = stream.options; + probe.error = stream.error; +} + +void Decoder::switchToGlue() +{ + std::cout << "Switching to glue" << std::endl; + uint32_t left = (uint64_t)stream.bufend - (uint64_t)stream.next_frame; + if (left > GLUE_LENGTH / 2) { + std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit (" << left << ")" << std::endl; + throw 1; + } +// std::cout << "MD_LENGTH_BEFORE: " << stream.anc_bitlen << std::endl; + mad_stream_buffer(&stream, glue, GLUE_LENGTH); + + + if (lastSync != -1) { + if (lastSync > GLUE_LENGTH / 2) { + std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit LASTSYNC (" << lastSync << ")" << std::endl; + throw 1; + } + uint64_t target = (uint64_t)stream.bufend - GLUE_LENGTH / 2 - lastSync; + while ((uint64_t)(stream.this_frame) < target) { +// std::cout << "pulling" << std::endl; + mad_frame_decode(&frame, &stream); + } + stream.error = MAD_ERROR_NONE; +// stream.this_frame = stream.bufend - GLUE_LENGTH / 2 - lastSync; +// stream.next_frame = stream.bufend - GLUE_LENGTH / 2 - left; +// stream.sync = 1; + +// mad_bitptr next_frame; +// mad_bit_init(&next_frame, stream.next_frame); +// stream.anc_ptr = stream.ptr; +// stream.anc_bitlen = mad_bit_length(&stream.ptr, &next_frame); +// mad_bit_finish(&next_frame); +// +// std::cout << ", MD_LENGTH_AFTER: " << stream.anc_bitlen << std::endl; + + lastSync = -1; + } + //mad_stream_skip(&stream, left); + stream.error = MAD_ERROR_NONE; + + //std::cout << "SKIPLEN (" << stream.skiplen << ")" << std::endl; + + std::cout << "Freeing the drained fragment" << std::endl; + free(pending[0].ptr); + pending.pop_front(); +} diff --git a/decoder.h b/decoder.h new file mode 100644 index 0000000..159fd34 --- /dev/null +++ b/decoder.h @@ -0,0 +1,66 @@ +#ifndef DECODER_H +#define DECODER_H + +/** + * @todo write docs + */ +#include + +#include +#include "mad.h" + +#define GLUE_LENGTH 10240 + +class Decoder { +public: + Decoder(); + ~Decoder(); + + void addFragment(intptr_t bufferPtr, uint32_t length); + emscripten::val decode(uint32_t count = UINT32_MAX); + bool hasMore() const; + uint32_t framesLeft(uint32_t max = UINT32_MAX); + +private: + + enum State { + empty, + onBufferHalf, + onBufferFull, + onGlueHalf, + onGlueFull + }; + + struct RawBuffer { + uint8_t* ptr; + uint32_t length; + uint32_t offset; + uint32_t gueard; + }; + + State state; + + uint32_t sampleRate; + uint8_t channels; + uint32_t cachedLength; + uint16_t samplesPerFrame; + uint8_t* glue; + int32_t lastSync; + bool cached; + + mad_synth synth; + mad_stream stream; + mad_frame frame; + emscripten::val context; + + std::deque pending; + +private: + void pullBuffer(); + void changeBuffer(); + void prepareNextBuffer(); + void initializeProbe(mad_stream& probe); + void switchToGlue(); +}; + +#endif // DECODER_H diff --git a/index.html b/index.html index 2930041..10fbcc7 100644 --- a/index.html +++ b/index.html @@ -12,24 +12,24 @@ diff --git a/wrapper.cpp b/wrapper.cpp index 8446acd..3daa793 100644 --- a/wrapper.cpp +++ b/wrapper.cpp @@ -70,142 +70,3 @@ void MadSynth::frame(const mad_frame* frame) { mad_synth_frame(this, frame); } - -Decoder::Decoder(): - sampleRate(0), - channels(0), - cachedLength(0), - samplesPerFrame(0), - currentBuffer(0), - currentBufferLength(0), - synth(), - stream(), - frame(), - context(0) -{ - mad_frame_init(&frame); - mad_stream_init(&stream); - mad_synth_init(&synth); - - emscripten::val AudioContext = emscripten::val::global("AudioContext"); - if (!AudioContext.as()) { - printf("No global AudioContext, trying webkitAudioContext\n"); - AudioContext = emscripten::val::global("webkitAudioContext"); - } - - printf("Got an AudioContext\n"); - context = AudioContext.new_(); -} - -Decoder::~Decoder() -{ - context.call("close"); - - mad_synth_finish(&synth); - mad_stream_finish(&stream); - mad_frame_finish(&frame); - - if (currentBuffer != 0) { - free(currentBuffer); - } -} - -void Decoder::addFragment(intptr_t bufferPtr, unsigned long length) -{ - if (currentBuffer != 0) { - printf("Adding more then 1 fragment is not supported yet"); - throw 1; - } - currentBuffer = (unsigned char(*))bufferPtr; - currentBufferLength = length; - - mad_stream_buffer(&stream, currentBuffer, currentBufferLength); - framesLeft(); -} - -emscripten::val Decoder::decode(uint32_t count) -{ - emscripten::val ret = emscripten::val::undefined(); - - int available = framesLeft(count); - if (available > 0) { - ret = context.call("createBuffer", channels, available * samplesPerFrame, sampleRate); - - std::vector chans(channels, emscripten::val::undefined()); - for (int i = 0; i < channels; ++i) { - chans[i] = ret.call("getChannelData", i); - } - - for (int i = 0; i < available; ++i) { - mad_frame_decode(&frame, &stream); - mad_synth_frame(&synth, &frame); - - 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)); - } - } - } - - cachedLength -= available; - - if (cachedLength == 0) { - framesLeft(); - } - } - - return ret; -} - -bool Decoder::hasMore() const -{ - if (currentBuffer == 0) { - return false; - } - - return stream.error != MAD_ERROR_BUFLEN; -} - -uint32_t Decoder::framesLeft(uint32_t max) -{ - if (currentBuffer == 0) { - return 0; - } - - if (cachedLength == 0) { - mad_stream probe; - mad_header ph; - mad_stream_init(&probe); - mad_header_init(&ph); - - mad_stream_buffer(&probe, currentBuffer, currentBufferLength); - mad_stream_skip(&probe, stream.skiplen); - - while (cachedLength < max) { - if (mad_header_decode(&ph, &probe) == 0) { - if (sampleRate == 0) { - sampleRate = ph.samplerate; - channels = MAD_NCHANNELS(&ph); - samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source - } else { - if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) { - break; - } - } - ++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); -} - diff --git a/wrapper.h b/wrapper.h index 7dc7d67..505a4d2 100644 --- a/wrapper.h +++ b/wrapper.h @@ -5,6 +5,7 @@ #include #include "mad.h" +#include "decoder.h" using namespace emscripten; @@ -118,29 +119,7 @@ public: MadSynth* mad_synth_create(); -class Decoder { -public: - Decoder(); - ~Decoder(); - - void addFragment(intptr_t bufferPtr, unsigned long length); - val decode(uint32_t count = UINT32_MAX); - bool hasMore() const; - uint32_t framesLeft(uint32_t max = UINT32_MAX); - -private: - uint32_t sampleRate; - uint8_t channels; - uint32_t cachedLength; - uint16_t samplesPerFrame; - unsigned char* currentBuffer; - unsigned long currentBufferLength; - - mad_synth synth; - mad_stream stream; - mad_frame frame; - val context; -}; + EMSCRIPTEN_BINDINGS(jsmad) { enum_("mad_layer")