У меня проблема с загрузкой музыки из памяти с помощью SDL_mixer.
В следующем «минимальном» примере, включающем небольшую проверку ошибок, всегда происходит сбой с нарушением прав доступа в 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 из файла работает нормально. Это означает, что ничего не падает, и музыка играет правильно, начинают заканчиваться.
Музыкальный класс от моего понимания слишком большой. Я просто храню буфер m_file, а также SDL_RW, чтобы убедиться, что проблема не в том, что эти данные освобождаются. Запуск Mix_LoadMUS_RW с SDL_FALSE также должен убедиться, что RW не освобожден.
Примечательно, что аналогичный пример загрузки файла wav из того же архива с помощью Mix_LoadWAV_RW работает просто отлично:
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();
В первой строке будет скопирован объект типа классная музыка справа в новый называется Музыка. Это приведет к вектору M_FILE копируется, включая данные в нем. Данные для вектора нашего нового объекта Музыка очевидно, будет храниться в другом месте памяти, чем у вектора объекта, возвращаемого loadMusic. Затем объект возвращается loadMusic будет удален из стека, а данные его вектора будут освобождены, что сделает недействительными ранее созданные Mix_Music объект и вызывает нарушение прав доступа во второй строке.
Это можно исправить, только создав один Музыка объект, например, создав его через новый в кучу и имея loadMusic вернуть указатель на этот объект.
Music* music = loadMusic("Sound/music/XYZ.ogg");
music->play();
В любом случае, может быть лучшим выбором выделить память для всего файла в куче, а не в стеке, хотя я бы предположил, что векторы делают это внутренне.
Итак, короткая версия, это была (что я считаю) ошибка новичка, и я был слишком зациклен на обвинении SDL_Mixer. Плохая идея.