尝试使用 SDL 为音频分配回调变量

Posted

技术标签:

【中文标题】尝试使用 SDL 为音频分配回调变量【英文标题】:trying to assign callback variable with SDL for audio 【发布时间】:2020-05-16 19:16:16 【问题描述】:

当我尝试在 Audiospec 结构中分配回调变量时,当我尝试将类型“void 函数”分配给类型 SDL_AudioCallback 时,编译器不喜欢它。

void mainEngineCW4::myAudioCallback(void* userdata, Uint8* stream, int Glen) 

AudioData* audio = (AudioData*)userdata;

if (audio->length == 0)
    return;

Uint32 length = (Uint32)len;
length = (length > audio->length ? audio->length : length); // if length is more than the audio length, then set length to be the audio.length, if not, set it to be the length passed to the function

SDL_memcpy(stream, audio->position, length);

audio->position += length;
audio->length -= length; 

void mainEngineCW4::playAudio() // this function is for loading an audio device, and playing the audio through that device 

AudioData audio;
audio.position = wavStart;
audio.length = wavLength;

wavSpec.callback = mainEngineCW4::myAudioCallback;
wavSpec.userdata = &audio;

audioDevice = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (audioDevice == 0)

    std::cerr << SDL_GetError() << std::endl;
    return;


SDL_PauseAudioDevice(audioDevice, 0); // mildly confused by why they decided to call the function for starting to play audio for "PauseAudioDevice" but yeah. this plays audio. 

我将控制音频的职责分为三个函数,loadAudio、startAudio 和 endAudio。我已经在 .h 文件中分配了音频所需的变量,因此类中的所有函数都可以访问它。

【问题讨论】:

【参考方案1】:

SDL 回调签名是 void (*)(void* userdata, Uint8* stream, int len) 类型的独立函数。

您的回调签名很接近但不完全匹配:void (mainEngineCW4::*)(void* userdata, Uint8* stream, int len)

主要区别在于它是一个member函数,是其类型的一部分。简而言之,该类型——成员函数——意味着你必须用一个类的实例来调用它,它变成了this指针,例如myEngine-&gt;myAudioCallback( ... )。独立函数没有 this 指针,因此会像 standAloneAudioCallback( ... ) 一样调用。

解决此问题的一种方法是将myAudioCallback 设为静态 成员函数。另一种方法是制作一个非成员(又称独立)函数。

在任何一种情况下,如果您需要访问它当前所属的mainEngineCW4 类的(非静态)成员数据,您将需要访问它,通常通过静态或全局变量或通过使用userdata 参数来存储类实例。我需要多看一点你的程序才能准确地演示它。


更新响应您的 cmets

有几种方法可以做到这一点。可能您想要的是将音频规范中的 userdata 设置为引擎类的实例,并传入使用该指针调用成员函数的静态(或独立)回调:

class mainEngineCW4 

    struct AudioData  /* ... */ ;

    // Private static member function, but could be a stand-alone function
    static void myStaticAudioCallback( void* const userData, Uint8* const stream, const int len )
    
        const auto myEngine = reinterpret_cast<mainEngineCW4*>( userData );
        myEngine->myAudioCallback( stream, len );
    

    void myAudioCallback( const Uint8* const stream, const int len )
    
        // ... Process stream using AudioData struct or whatever
    

public:
    void playAudio()
    
        auto audioSpec = SDL_AudioSpec;
        // ... set the freq and format and what not in the audio spec
        audioSpec.callback = &myStaticAudioCallback;
        audioSpec.userdata = this;

        const auto audioDevice = SDL_OpenAudioDevice( NULL, 0, &audioSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE);
        // ...
    
;

为了简洁起见,我已将其全部写在类定义中,但您可以根据需要将其拆分为.h/.cpp。我还添加了一些const,这是很好的做法,我遵循了“Almost Always Auto”的风格。

【讨论】:

我基本上需要让 audiocallback 不是我的班级的成员,而是我需要让它成为一个不会转换为所述班级成员的函数?虽然我明白了这个概念,但我并不完全理解我应该如何在 mainEngineCW4 类之外声明一个函数,我一直在尝试一些不同的事情,比如在 mainEngineCW4 类之外声明函数,而是在整个类之外声明它。你说你需要看到更多我的程序来演示它。您还需要查看哪些内容以提供更多帮助?如果你不介意,那就是 我对 C++ 还有些陌生,因此对于缺乏信息或缺乏理解深表歉意。不过,非常感谢您的回复。 我已更新答案以尝试回答您的问题。 感谢您的更新,我在下面回答了自己以显示我为修复错误所做的工作。非常感谢你,这让我比以前更好地理解了 C++ 中的层次结构是如何工作的。如果您不介意再次帮助我,我又遇到了一个错误? 我不确定您看到的新错误是什么。如果它不相关,也许会问一个新问题?如果相关,您可以尝试在此处详细说明或编辑您的问题以添加一些新细节。在任何情况下,如果您像我在此答案中所做的那样将其实现为调用成员函数的静态成员函数,则您应该可以从 myAudioCallback() 中访问该类的所有成员,例如您的 AudioData 结构。跨度> 【参考方案2】:

正如 metal 之前回答的那样,错误是我试图在我的 mainEngineCW4 类中定义 myAudioCallback。

class mainEngineCW4

public:
void myAudioCallback();

相反,在类之外声明它。

我最初在执行此操作时遇到的错误是无法访问 struct AudioData,但这也是在类中定义的。只需在类之外定义两者。

class mainEngineCW4

struct AudioData 
Uint8* position;
Uint32 length;;
void myAudioCallback(void* userdata, Uint8* stream, int len);

【讨论】:

以上是关于尝试使用 SDL 为音频分配回调变量的主要内容,如果未能解决你的问题,请参考以下文章

SDL - 有时没有及时调用音频回调函数

指向 SDL 音频回调的成员函数的指针?

C++:SDL 音频回调最终停止工作

为啥 SDL2 为音频样本提供的缓冲区不够大?

使用麦克风录制时,SDL 音频捕获未检测到任何音频

如何分析音频回调