You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FMOD与Theoraplay音频流数据拼接问题及代码优化求助

Fixing Two Critical Issues with FMOD & Theoraplay Audio Streaming

Hey there, let’s work through your FMOD/Theoraplay audio streaming problems step by step. I’ve dealt with similar buffer alignment and pointer arithmetic headaches before, so here’s a clear breakdown of what’s causing the issues and how to build a reliable, cross-architecture solution.

Issue A: memcpy_s Errors with void* Pointers

The root cause here is invalid pointer arithmetic on void* types. C++ doesn’t allow arithmetic operations on void* because void has no defined size—your compiler has no idea how many bytes to increment when you do pData + nCurrentOffSet. This is exactly why memcpy_s is throwing errors: those pointer additions are illegal.

Fix: Cast to a Concrete Data Type

Since your code uses sizeof(float) to calculate sample sizes, we know your audio samples are stored as float values. We’ll cast both the source and destination pointers to float* before doing any arithmetic:

  • Convert the FMOD target buffer (pData) to float*
  • Convert Theoraplay’s sample pointer (pCurrent->pRawAudio->samples) to float*
  • All pointer offsets will now work correctly, as float has a defined size (handled automatically via sizeof(float) across architectures).

Issue B: Cross-Architecture Safe Offset Handling

Using byte offsets is risky because:

  1. It’s easy to miscalculate bytes vs. samples, especially with multi-channel audio
  2. Some architectures have strict memory alignment requirements, and byte offsets can lead to unaligned access (causing crashes or performance hits)
  3. Byte-based offsets make it harder to validate if you’re staying within bounds of your audio blocks

Better Approach: Use Sample-Count Offsets

Instead of tracking bytes used, track how many samples you’ve already consumed from the current audio block. This is far more intuitive (audio data is structured around samples) and inherently cross-architecture:

  • Update AudioCallbackQueue::nOffset to store the number of samples used (not bytes)
  • Calculate available samples as total_samples - nOffset, where total_samples = frames * channels
  • Convert sample counts to bytes only when you need to copy data (using fill_samples * sizeof(float)).

Revised Callback Function Code

Here’s the fixed code with all these changes, plus improved error handling and edge case management:

#include <algorithm> // For std::min

FMOD_RESULT F_CALLBACK pcmQueue(FMOD_SOUND* pSound, void* pData, unsigned int nDataLen) {
    FMOD::Sound* pCurrentSound = reinterpret_cast<FMOD::Sound*>(pSound);
    void* pUserData = nullptr;
    AudioCallbackQueue* pCurrent = nullptr;
    FMOD_RESULT fError = FMOD_OK;
    unsigned int nCurrentOffsetBytes = 0;
    unsigned int nRemainingBytes = nDataLen;

    // Check for errors when fetching user data
    fError = pCurrentSound->getUserData(&pUserData);
    if (fError != FMOD_OK) {
        return fError;
    }
    pCurrent = reinterpret_cast<AudioCallbackQueue*>(pUserData);

    if (pCurrent != nullptr) {
        // Cast destination buffer to float* since our samples are float type
        float* pDestBuffer = static_cast<float*>(pData);

        while (nRemainingBytes > 0) {
            // Calculate total samples in current audio block
            unsigned int totalBlockSamples = pCurrent->pRawAudio->frames * pCurrent->pRawAudio->channels;
            // Samples left to use in this block
            unsigned int availableSamples = totalBlockSamples - pCurrent->nOffset;

            // If no samples left in current block, move to next one
            if (availableSamples == 0) {
                pCurrent = pCurrent->pNext;
                if (pCurrent == nullptr) break;
                continue;
            }

            // Max samples we can fit in the remaining buffer space
            unsigned int maxPossibleSamples = nRemainingBytes / sizeof(float);
            // How many samples we'll actually copy this iteration
            unsigned int samplesToCopy = std::min(maxPossibleSamples, availableSamples);
            unsigned int bytesToCopy = samplesToCopy * sizeof(float);

            // Cast source samples to float* and apply sample offset
            float* pSrcSamples = static_cast<float*>(pCurrent->pRawAudio->samples) + pCurrent->nOffset;
            // Safe memcpy_s call with valid pointers
            memcpy_s(
                pDestBuffer + (nCurrentOffsetBytes / sizeof(float)), // Target pointer (float-aligned)
                nRemainingBytes,                                     // Remaining space in target buffer
                pSrcSamples,                                         // Source pointer
                bytesToCopy                                          // Bytes to copy
            );

            // Update our tracking variables
            nCurrentOffsetBytes += bytesToCopy;
            nRemainingBytes -= bytesToCopy;
            pCurrent->nOffset += samplesToCopy;

            // If we've used all samples in this block, move to next
            if (pCurrent->nOffset >= totalBlockSamples) {
                pCurrent = pCurrent->pNext;
                if (pCurrent == nullptr) break;
            }
        }
    }

    // If we didn't fill the entire buffer, handle end of data
    if (nRemainingBytes > 0) {
        // Option 1: Fill remaining space with silence to avoid pop/click sounds
        float* pSilenceStart = static_cast<float*>(pData) + (nCurrentOffsetBytes / sizeof(float));
        memset(pSilenceStart, 0, nRemainingBytes);

        // Option 2: Return EOF error if you want FMOD to stop playback
        // return FMOD_RESULT::FMOD_ERR_FILE_ENDOFDATA;
    }

    // Update the current queue position in FMOD's user data
    pCurrentSound->setUserData(pCurrent);
    return FMOD_RESULT::FMOD_OK;
}

Key Improvements Explained

  • Type-Safe Casting: Uses reinterpret_cast and static_cast instead of C-style casts for better C++ compliance and readability
  • Sample-Based Offsets: Eliminates byte calculation errors and ensures cross-architecture safety
  • Robust Edge Case Handling: Properly handles empty audio blocks, end-of-stream scenarios, and error checking
  • Silence Fill Option: Prevents FMOD from playing garbage audio if you run out of Theoraplay data mid-buffer

内容的提问来源于stack exchange,提问作者Famout

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:40:46