简体   繁体   English

浏览器中的mp3流解码

[英]mp3 stream decoding in browser

I am trying to set up an mp3 stream receiver in browser using emscripten and libmad. 我正在尝试使用emscripten和libmad在浏览器中设置mp3流接收器。
I managed to decode mp3 file with low-level api loading it completely to the memory. 我设法用低级api解码mp3文件,并将其完全加载到内存中。 My next step was to load it in chunks. 我的下一步是将其分块加载。
In given example I emulate fragmented packages with allocated buffers of random size (from 20 to 40 kbyte) and copy file part by part to those buffers. 给定的示例中,我模拟了分配了随机大小(从20到40 KB)的缓冲区的碎片包,然后将文件部分复制到这些缓冲区中。

My algorithm of decoding correlate with an answer in this question but it is a bit different. 我的解码算法与此问题中的答案相关,但是有点不同。 The main object is Decoder, it receives fragments by addFragment method. 主要对象是Decoder,它通过addFragment方法接收片段。 Decoder has a pull of pending fragments and a glue buffer. 解码器具有待处理的片段和粘合缓冲区。 When user adds first fragment its tail is copied in the first half of the glue buffer. 当用户添加第一个片段时,其尾部将被复制到胶水缓冲区的前半部分。 When the second fragment is added it's beginning being copied to the second half of the glue. 当添加第二个片段时,它开始被复制到胶水的后半部分。 When decoder reaches the end of active buffer end it switches to glue, and vice versa when glue finishes. 当解码器到达活动缓冲区末端时,它将切换为粘连,反之,当粘连结束时,则相反。 I make sure all those buffers parts are consistent and mad_stream points to the same logical byte it was pointing before switching. 我确保所有这些缓冲区部分都是一致的,并且mad_stream指向切换之前指向的相同逻辑字节。

Significant fragments from decoder.cpp 来自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;
}

Here is my repo with full code. 是我的完整代码仓库。 To try it you need to build it with CMake (emscripten is supposed to be installed) and open index.html from the build directory in your browser. 要尝试使用它,您需要使用CMake进行构建(应该安装了脚本),然后从浏览器的build目录中打开index.html。

The problem 问题
The playback is distorted. 播放失真。 I tried to check the bytes around last successful frame before and after shift, all of the different substructures of mad_stream - everything seems to work properly but it still doesn't. 我尝试检查移位前后所有上次成功帧周围的字节,mad_stream的所有不同子结构-一切似乎都能正常工作,但仍然无法正常工作。 My latest progress is built and hosted here . 我的最新进展在此处构建和托管。 I'am really stuck and I don't know what to do to eliminate distortion in the playback. 我真的很困,我不知道该怎么做才能消除播放中的失真。

I would really appreciate if someone helps me. 如果有人帮助我,我将不胜感激。

There are multiple different answers here, but one solution is through socket.io . 这里有多个不同的答案,但是一种解决方案是通过socket.io Here's an example of someone setting up streaming binary to a client. 这是某人为客户端设置流二进制文件的示例 This would avoid doing the segmentation on the server and entrust the client to do it for you. 这样可以避免在服务器上进行分段,而委托客户端为您完成分段。

The missing piece here is running the binary through an mp3 decoder. 这里缺少的部分是通过mp3解码器运行二进制文件。 Some libraries can automatically determine the format but you might have to also pass the encoding type so it knows how to parse the binary stream. 一些库可以自动确定格式,但是您可能还必须传递编码类型,以便它知道如何解析二进制流。 It might be that's already offered in aurora but I'm unfamiliar with it. 可能已经在Aurora中提供了,但是我不熟悉它。

I've found it! 我找到了! MAD works perfectly, just because of my inner counter I kept skipping first decoded frames in output. 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));
                }
            }
        }

changed success to i and it worked. 改变了我的 成功 ,并且成功了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM