потоковое декодирование mp3 в браузере - PullRequest
0 голосов
/ 14 января 2019

Я пытаюсь настроить потоковый приемник mp3 в браузере, используя emscripten и libmad.
Мне удалось декодировать mp3-файл с низкоуровневым API, полностью загрузив его в память. Мой следующий шаг - загрузить его кусками.
В приведенном примере я эмулирую фрагментированные пакеты с выделенными буферами случайного размера (от 20 до 40 кбайт) и копирую файл по частям в эти буферы.

Мой алгоритм декодирования соотносится с ответом в на этот вопрос , но он немного другой. Основным объектом является Decoder, он получает фрагменты методом addFragment. У декодера есть тяга ожидающих фрагментов и буфер клея. Когда пользователь добавляет первый фрагмент, его хвост копируется в первую половину буфера клея. Когда второй фрагмент добавлен, он начинает копироваться во вторую половину клея. Когда декодер достигает конца активного конца буфера, он переключается на клей, и наоборот, когда клей заканчивается. Я удостоверяюсь, что все эти части буфера согласованы, и mad_stream указывает на тот же логический байт, на который он указывал перед переключением.

Значимые фрагменты из decoder.cpp

void Decoder::addFragment          //adds the fragment to decoding queue
(intptr_t bufferPtr, uint32_t length)
{
    if (length < GLUE_LENGTH / 2) {
        return;
    }
    uint8_t* buffer = (uint8_t(*))bufferPtr;
    RawBuffer rb = {buffer, length};
    pending.push_back(rb);

    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 onGlueHalf:
            for (int i = 0; i < GLUE_LENGTH/2; ++i) {
                glue[GLUE_LENGTH/2 + i] = buffer[i];
            }

            state = onGlueFull;
            cached = false;
            prepareNextBuffer();
            break;
        default:
            break;
    }
}

emscripten::val Decoder::decode     //decodes up to requested amount of frames
(uint32_t count)
{
    emscripten::val ret = emscripten::val::undefined();

    int available = framesLeft(count);
    if (available > 0) {
        ret = context.call<emscripten::val>("createBuffer", channels, available * samplesPerFrame, sampleRate);

        std::vector<emscripten::val> chans(channels, emscripten::val::undefined());
        for (int i = 0; i < channels; ++i) {
            chans[i] = ret.call<emscripten::val>("getChannelData", i);
        } 

        for (int i = 0; i < available; ++i) {
            int res = mad_frame_decode(&frame, &stream);

            if (res != 0) {
                if (MAD_RECOVERABLE(stream.error)) {
                    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;
        if (cachedLength == 0) {
            cached = false;
            prepareNextBuffer();
        }
    }
    return ret;
}


//tells how many frames can be decoded on the same
//sample rate, same amount of channels without switching the buffers
//it is required in Decoder::decode method to understand the size of 
//allocating AudioContext::AudioBuffer.

uint32_t Decoder::framesLeft(uint32_t max)
{
    if (state == empty || state == onGlueHalf) {
        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;
                } else {
                    if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) {
                        break;
                    }
                }
                if (probe.next_frame > probe.this_frame) {
                    ++cachedLength;
                }
            } else {
                if (!MAD_RECOVERABLE(probe.error)) {
                    break;
                }
            }
        }

        cachedNext = probe.next_frame;
        cachedThis = probe.this_frame;
        cachedError = probe.error;
        mad_header_finish(&ph);
        mad_stream_finish(&probe);
        cached = true;
    }

    return std::min(cachedLength, max);
}

//this method fastforwards the stream
//to the cached end
void Decoder::pullBuffer()
{
    if (cached == false) {
        throw 2;
    }
    stream.this_frame = cachedThis;
    stream.next_frame = cachedNext;
    stream.error = cachedError;
}

//this method switches the stream to glue buffer
//or to the next pending buffer
//copies the parts to the glue buffer if required

void Decoder::changeBuffer()
{
    uint32_t left;
    switch (state) {
        case empty:
            throw 3;
        case onBufferHalf:
            switchToGlue();
            state = onGlueHalf;
            break;
        case onBufferFull:
            switchToGlue();
            state = onGlueFull;
            break;
        case onGlueHalf:
            throw 4;
            break;
        case onGlueFull:
            switchBuffer(pending[0].ptr, pending[0].length);

            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;
}

//this method seeks the decodable data in pending buffers
//prepares if any proper data has been found
void Decoder::prepareNextBuffer()
{
    bool shift;
    do {
        shift = false;
        framesLeft();
        if (cachedLength == 0 && state != empty && state != onGlueHalf) {
            pullBuffer();
            changeBuffer();
            shift = true;
        }
    } while (shift);
}

//low level method to switch to glue buffer, also frees the drained fragment
void Decoder::switchToGlue()
{
    switchBuffer(glue, GLUE_LENGTH);
    stream.error = MAD_ERROR_NONE;

    free(pending[0].ptr);
    pending.pop_front();
}

//low level method which actually switch mad_stream
//to another buffer
void Decoder::switchBuffer(uint8_t* bufferPtr, uint32_t length)
{
    uint32_t left;
    left = stream.bufend - stream.next_frame;
    mad_stream_buffer(&stream, bufferPtr + GLUE_LENGTH / 2 - left, length - (GLUE_LENGTH / 2 - left));
    stream.error = MAD_ERROR_NONE;
}

Здесь - это мой репозиторий с полным кодом. Чтобы попробовать, вам нужно собрать его с помощью CMake (предполагается, что emscripten установлен) и открыть index.html из каталога сборки в вашем браузере.

Проблема
Воспроизведение искажено. Я попытался проверить байты вокруг последнего успешного кадра до и после сдвига, все различные подструктуры mad_stream - кажется, что все работает правильно, но все еще не работает. Мой последний прогресс построен и размещен здесь . Я действительно застрял, и я не знаю, что делать, чтобы устранить искажения при воспроизведении.

Я был бы очень признателен, если бы кто-нибудь мне помог.

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Я нашел это! MAD работает отлично, просто из-за внутреннего счетчика я пропускал первые декодированные кадры на выходе.

for (int i = 0; success < available; ++i) {
            int res = mad_frame_decode(frame, stream);

            if (res == 0) {
                ++**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));
                }
            }
        }

изменено успех на i , и это сработало.

0 голосов
/ 14 января 2019

Здесь есть несколько разных ответов, но одно решение - через socket.io . Вот пример того, кто настраивает потоковый двоичный файл для клиента. Это позволит избежать сегментации на сервере и поручить клиенту сделать это за вас.

Недостающая часть здесь запускает двоичный файл через mp3-декодер. Некоторые библиотеки могут автоматически определять формат, но вам также может потребоваться передать тип кодировки, чтобы он знал, как анализировать двоичный поток. Может быть, это уже предлагается в Авроре, но я незнаком с этим.

...