2015-06-30 4 views
0

У меня возникла проблема с загрузкой музыки из памяти с помощью SDL_mixer. Следующий «минимальный» пример, включающий в себя проверку ошибок, всегда будет сбой при нарушении доступа в Music :: play.Функция SDL_mixer Mix_LoadMUS_RW вызывает нарушение доступа

#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 работает точно, что также можно увидеть, потому что ucommenting блок, который записывает буфер в файл и создание 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.

ответ

0
Music music = loadMusic("Sound/music/XYZ.ogg"); 
music.play(); 

Первая строка будет копировать объект типа класса Music по праву в новом называется музыки. Это приведет к копированию вектора m_file, включая данные в нем. Данные для вектора нашего нового объекта музыка, очевидно, будет храниться в другом месте памяти, чем у вектора объекта, возвращаемого loadMusic. Затем объект, возвращаемый loadMusic, будет удален из стека, и данные его вектора будут освобождены, что приведет к аннулированию ранее созданного объекта Mix_Music и приведет к нарушению доступа во второй строке.

Это может быть исправлено только когда-либо создать один Music объект, например, путем создания его с помощью нового в куче и с loadMusic возвращает указатель на этот объект.

Music* music = loadMusic("Sound/music/XYZ.ogg"); 
music->play(); 

Это все равно может быть лучшим выбором для выделения памяти для целого файла в куче, а не на стеке, хотя я предположил бы, что векторы этого внутренне.

Так короткая версия, это было (что я считаю) ошибкой новобранец, и я был слишком зациклен на обвинении SDL_Mixer. Плохая идея.