SDL_mixer 函数 Mix_LoadMUS_RW 导致访问冲突
SDL_mixer function Mix_LoadMUS_RW causes Access Violation
我在使用 SDL_mixer 从内存中加载音乐时遇到问题。
下面的 "minimal" 示例包括一些错误检查将始终因 Music::play.
中的访问冲突而崩溃
#include <SDL\SDL_mixer.h>
#include <SDL\SDL.h>
#include <vector>
#include <iostream>
#include <string>
#include <fstream>
class Music {
public:
void play(int loops = 1);
SDL_RWops* m_rw;
std::vector<unsigned char> m_file;
Mix_Music * m_music = nullptr;
};
void Music::play(int loops) {
if (Mix_PlayMusic(m_music, loops) == -1)
std::cout << "Error playing music " + std::string(Mix_GetError()) + " ...\n";
}
void readFileToBuffer(std::vector<unsigned char>& buffer, std::string filePath) {
std::ifstream file(filePath, std::ios::binary);
file.seekg(0, std::ios::end);
int fileSize = file.tellg();
file.seekg(0, std::ios::beg);
fileSize -= file.tellg();
buffer.resize(fileSize);
file.read((char *)&(buffer[0]), fileSize);
file.close();
}
void writeFileToBuffer(std::vector<unsigned char>& buffer, std::string filePath) {
std::ofstream file(filePath, std::ios::out | std::ios::binary);
for (size_t i = 0; i < buffer.size(); i++)
file << buffer[i];
file.close();
}
Music loadMusic(std::string filePath) {
Music music;
readFileToBuffer(music.m_file, filePath);
music.m_rw = SDL_RWFromMem(&music.m_file[0], music.m_file.size());
// Uncommenting the next block runs without problems
/*
writeFileToBuffer(music.m_file, filePath);
music.m_rw = SDL_RWFromFile(filePath.c_str(), "r");
*/
if (music.m_rw == nullptr)
std::cout << "Error creating RW " + std::string(Mix_GetError()) + " ...\n";
music.m_music = Mix_LoadMUSType_RW(music.m_rw, Mix_MusicType::MUS_OGG, SDL_FALSE);
if (music.m_music == nullptr)
std::cout << "Error creating music " + std::string(Mix_GetError()) + " ...\n";
return music;
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_AUDIO);
Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG);
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 1024);
Music music = loadMusic("Sound/music/XYZ.ogg");
music.play();
std::cin.ignore();
return 0;
}
我的 ArchiveManager 确实可以工作,这也可以看出,因为对将缓冲区写入文件的块进行注释并从中创建 SDL_RW 将 运行 就好了。
我加载的音乐文件只是假定为 ogg 文件,在本例中就是这样,因此从该文件创建 SDL_RW 效果很好。这意味着没有任何崩溃,音乐播放正常开始到结束。
音乐class离我的理解太大了。我只是保留缓冲区 m_file 以及 SDL_RW 以确保问题不是来自被释放的数据。 运行 Mix_LoadMUS_RW 和 SDL_FALSE 还应确保 RW 未被释放。
值得注意的是,使用 Mix_LoadWAV_RW 从同一存档加载 wav 文件的类似示例工作得很好:
Mix_Chunk * chunk;
std::vector<unsigned char> fileBuf = ArchiveManager::loadFileFromArchive(filePath);
chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(&fileBuf[0], fileBuf.size()), SDL_TRUE);
在这里,我什至没有保留缓冲区,直到调用 Mix_PlayCannel。同样在这里,我使用 SDL_TRUE 调用加载函数,因为我没有创建显式的 SDL_RW。尝试类似的方法来加载音乐不会有什么不同。
我研究了SDL_mixer源代码,但对我没有帮助。可能是我知识不够,也可能是漏掉了一些重要的东西。
进入正题:访问冲突从何而来,我该如何预防?
编辑:更改了示例代码,因此任何人都可以直接重现它。所以没有 ArchiveManager 或类似的东西,只是将 ogg 直接读入内存。关键部分就是loadMusic中的那几行。
Music music = loadMusic("Sound/music/XYZ.ogg");
music.play();
第一行将右边class Music类型的对象复制到名为music的新对象中。这将导致向量 m_file 被复制,包括其中的数据。我们的新对象 music 的向量数据显然将存储在与由 loadMusic 编辑的对象 return 的向量不同的内存位置。然后 loadMusic 编辑的对象 return 将从堆栈中删除,其向量数据将被释放,从而使先前创建的 [=41= 无效] 对象并在第二行导致访问冲突。
这可以通过只创建一个 Music 对象来补救,例如通过 new 在堆上创建它并让 loadMusic return 指向该对象的指针。
Music* music = loadMusic("Sound/music/XYZ.ogg");
music->play();
无论如何,在堆上而不是在堆栈上为整个文件分配内存可能是更好的选择,尽管我猜向量会在内部执行此操作。
这么短的版本,这是(我认为的)新手错误,我太执着于责备 SDL_Mixer。坏主意。
我在使用 SDL_mixer 从内存中加载音乐时遇到问题。 下面的 "minimal" 示例包括一些错误检查将始终因 Music::play.
中的访问冲突而崩溃#include <SDL\SDL_mixer.h>
#include <SDL\SDL.h>
#include <vector>
#include <iostream>
#include <string>
#include <fstream>
class Music {
public:
void play(int loops = 1);
SDL_RWops* m_rw;
std::vector<unsigned char> m_file;
Mix_Music * m_music = nullptr;
};
void Music::play(int loops) {
if (Mix_PlayMusic(m_music, loops) == -1)
std::cout << "Error playing music " + std::string(Mix_GetError()) + " ...\n";
}
void readFileToBuffer(std::vector<unsigned char>& buffer, std::string filePath) {
std::ifstream file(filePath, std::ios::binary);
file.seekg(0, std::ios::end);
int fileSize = file.tellg();
file.seekg(0, std::ios::beg);
fileSize -= file.tellg();
buffer.resize(fileSize);
file.read((char *)&(buffer[0]), fileSize);
file.close();
}
void writeFileToBuffer(std::vector<unsigned char>& buffer, std::string filePath) {
std::ofstream file(filePath, std::ios::out | std::ios::binary);
for (size_t i = 0; i < buffer.size(); i++)
file << buffer[i];
file.close();
}
Music loadMusic(std::string filePath) {
Music music;
readFileToBuffer(music.m_file, filePath);
music.m_rw = SDL_RWFromMem(&music.m_file[0], music.m_file.size());
// Uncommenting the next block runs without problems
/*
writeFileToBuffer(music.m_file, filePath);
music.m_rw = SDL_RWFromFile(filePath.c_str(), "r");
*/
if (music.m_rw == nullptr)
std::cout << "Error creating RW " + std::string(Mix_GetError()) + " ...\n";
music.m_music = Mix_LoadMUSType_RW(music.m_rw, Mix_MusicType::MUS_OGG, SDL_FALSE);
if (music.m_music == nullptr)
std::cout << "Error creating music " + std::string(Mix_GetError()) + " ...\n";
return music;
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_AUDIO);
Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG);
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 1024);
Music music = loadMusic("Sound/music/XYZ.ogg");
music.play();
std::cin.ignore();
return 0;
}
我的 ArchiveManager 确实可以工作,这也可以看出,因为对将缓冲区写入文件的块进行注释并从中创建 SDL_RW 将 运行 就好了。 我加载的音乐文件只是假定为 ogg 文件,在本例中就是这样,因此从该文件创建 SDL_RW 效果很好。这意味着没有任何崩溃,音乐播放正常开始到结束。
音乐class离我的理解太大了。我只是保留缓冲区 m_file 以及 SDL_RW 以确保问题不是来自被释放的数据。 运行 Mix_LoadMUS_RW 和 SDL_FALSE 还应确保 RW 未被释放。
值得注意的是,使用 Mix_LoadWAV_RW 从同一存档加载 wav 文件的类似示例工作得很好:
Mix_Chunk * chunk;
std::vector<unsigned char> fileBuf = ArchiveManager::loadFileFromArchive(filePath);
chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(&fileBuf[0], fileBuf.size()), SDL_TRUE);
在这里,我什至没有保留缓冲区,直到调用 Mix_PlayCannel。同样在这里,我使用 SDL_TRUE 调用加载函数,因为我没有创建显式的 SDL_RW。尝试类似的方法来加载音乐不会有什么不同。
我研究了SDL_mixer源代码,但对我没有帮助。可能是我知识不够,也可能是漏掉了一些重要的东西。
进入正题:访问冲突从何而来,我该如何预防?
编辑:更改了示例代码,因此任何人都可以直接重现它。所以没有 ArchiveManager 或类似的东西,只是将 ogg 直接读入内存。关键部分就是loadMusic中的那几行。
Music music = loadMusic("Sound/music/XYZ.ogg");
music.play();
第一行将右边class Music类型的对象复制到名为music的新对象中。这将导致向量 m_file 被复制,包括其中的数据。我们的新对象 music 的向量数据显然将存储在与由 loadMusic 编辑的对象 return 的向量不同的内存位置。然后 loadMusic 编辑的对象 return 将从堆栈中删除,其向量数据将被释放,从而使先前创建的 [=41= 无效] 对象并在第二行导致访问冲突。
这可以通过只创建一个 Music 对象来补救,例如通过 new 在堆上创建它并让 loadMusic return 指向该对象的指针。
Music* music = loadMusic("Sound/music/XYZ.ogg");
music->play();
无论如何,在堆上而不是在堆栈上为整个文件分配内存可能是更好的选择,尽管我猜向量会在内部执行此操作。
这么短的版本,这是(我认为的)新手错误,我太执着于责备 SDL_Mixer。坏主意。