代码之家  ›  专栏  ›  技术社区  ›  Blue

浏览器中的mp3流解码

  •  0
  • Blue  · 技术社区  · 7 年前

    我正在尝试使用emscripten和libmad在浏览器中设置mp3流接收器。
    我设法用低级api解码mp3文件,将其完全加载到内存中。我的下一步是将其分块加载。
    given example 我使用随机大小(从20到40kbyte)的分配缓冲区模拟碎片包,并将文件一部分一部分地复制到这些缓冲区。

    this question 但是有点不同。主要对象是解码器,它通过addFragment方法接收片段。解码器有一个待处理片段的拉取和一个粘合缓冲区。当用户添加第一个片段时,它的尾部被复制到胶水缓冲区的前半部分。当添加第二个片段时,它开始被复制到胶水的后半部分。当解码器到达活动缓冲区末端时,它将切换到粘合,粘合完成时,反之亦然。我确保所有这些缓冲区部分都是一致的,并且mad_流指向切换前指向的同一逻辑字节。

    来自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 是我的回购协议的完整代码。要尝试它,您需要使用CMake(应该安装emscripten)构建它,并从浏览器的构建目录中打开index.html。

    问题
    播放失真了。我试着在shift前后检查最后一个成功帧周围的字节,mad_stream的所有不同子结构-一切似乎都正常工作,但仍然不正常。我的最新进展是建立和托管 here . 我真的被卡住了,我不知道该怎么做才能消除播放中的失真。

    2 回复  |  直到 7 年前
        1
  •  1
  •   Blue    7 年前

    我找到了!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));
                    }
                }
            }
    

    改变 成功

        2
  •  0
  •   GenericUser    7 年前

    这里有多个不同的答案,但有一个解决方案是通过 socket.io example 有人将二进制数据流设置到客户端。这将避免在服务器上进行细分,并委托客户机为您进行细分。

    这里缺少的部分是通过mp3解码器运行二进制文件。有些库可以自动确定格式,但您可能还必须传递编码类型,以便它知道如何解析二进制流。它可能已经在极光提供,但我不熟悉它。

    推荐文章