Skip to content

The onRead callback function of ma_decoder_init() was not called #1073

@huenrong

Description

@huenrong

First of all, thank you very much for providing such an excellent library.

My use case involves retrieving MP3 data from a WebSocket and then putting it into a queue (m_down_audio_queue).

My code is as follows

queue_t m_down_audio_queue;
ma_device m_ma_device;
ma_decoder m_ma_decoder;
uint32_t m_decoder_read_end_count = 0;
uint32_t m_pcm_read_end_count = 0;
bool m_pcm_empty = true;

ma_result DialogSystem::on_ma_decoder_read_proc(ma_decoder *pDecoder, void *pBufferOut, size_t bytesToRead,
                                                size_t *pBytesRead)
{
    DialogSystem *self = (DialogSystem *)pDecoder->pUserData;

    // 0: get data directly without waiting
    int ret = queue_get_data_with_timeout(&self->m_down_audio_queue, (uint8_t *)pBufferOut, bytesToRead, 0);
    if (ret == -1)
    {
        USER_LOG_BASIC_WARN("decoder read failed. ret: {}, size: {}", ret,
                            queue_get_current_size(self->m_down_audio_queue));
        *pBytesRead = 0;

        return MA_AT_END;
    }

    if (ret == 0)
    {
        self->m_decoder_read_end_count++;
        *pBytesRead = 0;

        return MA_SUCCESS;
    }

    self->m_decoder_read_end_count = 0;
    *pBytesRead = ret;

    return MA_SUCCESS;
}

ma_result DialogSystem::on_ma_decoder_seek_proc(ma_decoder *pDecoder, ma_int64 byteOffset, ma_seek_origin origin)
{
    // TODO: is it feasible to directly return MA_SUCCESS?
    return MA_SUCCESS;
}

void DialogSystem::on_ma_device_data_proc(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
{
    DialogSystem *self = (DialogSystem *)pDevice->pUserData;

    ma_uint64 frames_read = 0;
    ma_result ret = ma_decoder_read_pcm_frames(&self->m_ma_decoder, pOutput, frameCount, &frames_read);
    if (ret == MA_AT_END)
    {
        self->m_pcm_read_end_count++;
        self->m_pcm_empty = true;
    }
    else
    {
        self->m_pcm_read_end_count = 0;
        self->m_pcm_empty = false;
    }
}

void DialogSystem::play_audio_thread(void)
{
    bool initialized = false;
    uint8_t wait_count = 0;

    while (true)
    {
        if (initialized)
        {
            uint32_t queue_current_size = queue_get_current_size(m_down_audio_queue);
            if ((m_pcm_empty) && (queue_current_size != 0))
            {
                USER_LOG_BASIC_WARN("pcm empty is true, but down audio queue is not empty. queue size: {}, "
                                    "decoder read end count: {}, pcm read end count: {}",
                                    queue_current_size, m_decoder_read_end_count, m_pcm_read_end_count);
            }

            bool need_uninit = false;
            if ((queue_current_size == 0) && (m_pcm_empty))
            {
                if (wait_count++ >= 5)
                {
                    need_uninit = true;
                }
            }

            if (need_uninit)
            {
                ma_device_uninit(&m_ma_device);
                ma_decoder_uninit(&m_ma_decoder);
                queue_clear(&m_down_audio_queue);
                initialized = false;
                USER_LOG_BASIC_INFO("uninit decoder and device success");
            }
            else
            {
                wait_count = 0;
            }
        }
        else
        {
            if (queue_get_current_size(m_down_audio_queue) < 2048)
            {
                usleep(10 * 1000);

                continue;
            }

            m_decoder_read_end_count = 0;
            m_pcm_read_end_count = 0;
            m_pcm_empty = false;

            ma_decoder_config decoder_config = ma_decoder_config_init(ma_format_s16, 1, 16000);
            ma_result ret = ma_decoder_init(&DialogSystem::on_ma_decoder_read_proc,
                                            &DialogSystem::on_ma_decoder_seek_proc, this, &decoder_config,
                                            &m_ma_decoder);
            if (ret != MA_SUCCESS)
            {
                USER_LOG_BASIC_WARN("init decoder failed, will retry. ret: {}", (int)ret);
                usleep(10 * 1000);

                continue;
            }
            USER_LOG_BASIC_INFO("init decoder success");

            ma_device_config device_config = ma_device_config_init(ma_device_type_playback);
            device_config.playback.format = ma_format_s16;
            device_config.playback.channels = 1;
            device_config.sampleRate = 16000;
            device_config.dataCallback = &DialogSystem::on_ma_device_data_proc;
            device_config.pUserData = this;

            ret = ma_device_init(NULL, &device_config, &m_ma_device);
            if (ret != MA_SUCCESS)
            {
                USER_LOG_BASIC_WARN("init playback device failed, will retry. ret: {}", (int)ret);
                ma_decoder_uninit(&m_ma_decoder);
                usleep(10 * 1000);

                continue;
            }

            ret = ma_device_start(&m_ma_device);
            if (ret != MA_SUCCESS)
            {
                USER_LOG_BASIC_WARN("start playback device failed, will retry. ret: {}", (int)ret);
                ma_device_uninit(&m_ma_device);
                ma_decoder_uninit(&m_ma_decoder);
                usleep(10 * 1000);

                continue;
            }

            initialized = true;
            USER_LOG_BASIC_INFO("init playback device success");
        }

        usleep(100 * 1000);
    }
}

I've noticed that sometimes the onRead callback function on_ma_decoder_read_proc() of ma_decoder_init() is not called.
I discovered the issue was caused by pMP3->atEnd=MA_TRUE in the ma_dr_mp3_decode_next_frame_ex__callbacks() function.

static ma_uint32 ma_dr_mp3_decode_next_frame_ex__callbacks(ma_dr_mp3* pMP3, ma_dr_mp3d_sample_t* pPCMFrames, ma_dr_mp3dec_frame_info* pMP3FrameInfo, const ma_uint8** ppMP3FrameData)
{
    ma_uint32 pcmFramesRead = 0;
    MA_DR_MP3_ASSERT(pMP3 != NULL);
    MA_DR_MP3_ASSERT(pMP3->onRead != NULL);

    if (pMP3->atEnd) {
        return 0;
    }

    for (;;) {
        ma_dr_mp3dec_frame_info info;
        if (pMP3->dataSize < MA_DR_MP3_MIN_DATA_CHUNK_SIZE) {
            size_t bytesRead;
            if (pMP3->pData != NULL) {
                MA_DR_MP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize);
            }
            pMP3->dataConsumed = 0;
            if (pMP3->dataCapacity < MA_DR_MP3_DATA_CHUNK_SIZE) {
                ma_uint8* pNewData;
                size_t newDataCap;
                newDataCap = MA_DR_MP3_DATA_CHUNK_SIZE;
                pNewData = (ma_uint8*)ma_dr_mp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks);
                if (pNewData == NULL) {
                    return 0;
                }
                pMP3->pData = pNewData;
                pMP3->dataCapacity = newDataCap;
            }
            bytesRead = ma_dr_mp3__on_read_clamped(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
            if (bytesRead == 0) {
                if (pMP3->dataSize == 0) {
                    // TODO: Here, atEnd=MA_TRUE is set.
                    printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx atEnd will is true\n");
                    pMP3->atEnd = MA_TRUE;
                    return 0;
                }
            }
            pMP3->dataSize += bytesRead;
        }
        
        ...
    };
    return pcmFramesRead;
}

Some logs

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx atEnd will is true
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx atEnd is true
[2025-12-01 09:48:14.479] [W] [dialog_system.cpp:507] pcm empty is true, but down audio queue is not empty. queue size: 576, decoder read end count: 29, pcm read end count: 13

My questions:

  1. Is there a problem with the on_ma_decoder_seek_proc() function always returning MA_SUCCESS even though it doesn't perform any operation?
  2. Is it because the WebSocket data is too slow, and there isn't enough data in the queue (m_down_audio_queue), causing ma_dr_mp3_decode_next_frame_ex__callbacks to think there's no data and then set atEnd to MA_TRUE?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions