Con este tutorial vamos a mostrar como utilizar la libreria FMOD para cargar y reproducir sonido en un proyecto UE4 válido para plataforma win64 y Android. En un tutorial previo vimos como configurar las rutas para poder utilizar una librería de terceros, ahora podemos comenzar a implementar la clase para usar esta librería.
Parte 1: Configurando las rutas del proyecto
Parte 2: Usando la libreria
Parte 3: Espectro de Frecuencias
Parte 4: Visualizador de espectro para UE4
Parte 5: Algoritmo detector de pulsos
Parte 6: Visualizador de pulso para UE4
Podemos empezar creando dos clases, la priemra de ellas para gestionar la librería y la segunda para poder utilizar esa clase con el editor de UE4.
Fmod tiene tres elementos principales: sistema, canal y sonido.
El primero de ellos es el encargado de controla el objeto que contiene el sistema de FMOD, en su inicialización podemos especificar cuantos canales como máximo puede utilizar nuestro juego, esto limita el número de sonidos simultáneos que se pueden reproducir, cada canal puede contener solo UN sonido. Para este ejemplo solo vamos a necesitar reproducir un sonido por lo que nos bastará con un sólo canal.
Podemos declarar estos tres elementos básicos de FMOD en los miembros de nuestra clase como punteros. También vamos a necesitar algunas funciones para cargar el sonido, ya sea desde una ruta o desde memoria, reproducir, pausar y reanudar este sonido.
SoundManager_Fmod.h
#ifndef SoundManager_Fmod_H #define SoundManager_Fmod_H #include <string> namespace FMOD { class System; class Channel; class Sound; }; class SoundManager_Fmod { public: SoundManager_Fmod(); ~SoundManager_Fmod(); int initialize(); int loadSoundFromPath(std::string pathToFile); int loadSoundFromMemory(char* memoryPtr, unsigned int memorySize); void playSound(); void pauseSound(bool unPause = false); private: FMOD::System* _system; FMOD::Channel* _channel; FMOD::Sound* _sound; }; #endif
Para inicializar el sistema tenemos que llamar al constructor System_Create, y utilizar su función init indicando el número de canales en el primer parámetro:
FMOD_RESULT result = FMOD::System_Create(&_system); // Inicializa el sistema con 1 canal _system->init(1, FMOD_INIT_NORMAL, NULL);
Todas las funciones de FMOD devuelven un valor de error con el que podemos comprobar el estado de su ejecución.
Para cargar un sonido tenemos dos opciones, la primera es cargarlo desde una ruta, para ello se utiliza la función createSound aportando la ruta del archivo de sonido y el puntero a nuestro objeto de sonido Fmod, también podemos añadir algunos flags para alterar la reproducción, por si queremos hacer un bucle.
_system->createSound(pathToFile.c_str(), FMOD_LOOP_NORMAL, 0, &_sound);
La segunda opción es cargar el archivo de sonido en memoria y pasar su puntero a esta función,en este caso tendremos que pasar una estructura FMOD_CREATESOUNDEXINFO para indicar el tamaño de la memoria asignada a ese puntero. Para utilizar esta memoria directamente tenemos que pasar el flag FMOD_OPENMEMORY_POINT en la llamada de la función, para trabajar con una copia de la memoria habrá que usar FMOD_OPENMEMORY es su lugar, tendremos que encargarnos de la liberación de esta memoria una vez dejemos de utilizarla mediante una llamada a _sound->release()
FMOD_CREATESOUNDEXINFO sndinfo = { 0 }; sndinfo.cbsize = sizeof(sndinfo); sndinfo.length = memorySize; _system->createSound(memoryPtr, FMOD_OPENMEMORY | FMOD_LOOP_NORMAL, &sndinfo, &_sound);
To play the sound, we need to specify the channel and the sound object using the playSound function
_system->playSound(_sound, 0, false, &_channel);
To pause the sound we need to pause the channel play
_channel->setPaused(true);
SoundManager_Fmod.cpp
#include "SoundManager_Fmod.h" #include "fmod.hpp" #include <string> using namespace std; SoundManager_Fmod::SoundManager_Fmod():_system(NULL),_channel(NULL),_sound(NULL) { } int SoundManager_Fmod::initialize() { FMOD_RESULT result = FMOD::System_Create(&_system); if (result != FMOD_OK) { return result; } else { _system->init(1, FMOD_INIT_NORMAL, NULL); } return 0; } SoundManager_Fmod::~SoundManager_Fmod() { if (_sound) { _sound->release(); } if (_system) { _system->close(); _system->release(); } } int SoundManager_Fmod::loadSoundFromPath(std::string pathToFile) { FMOD_RESULT result = _system->createSound(pathToFile.c_str(), FMOD_LOOP_NORMAL, 0, &_sound); return result; } int SoundManager_Fmod::loadSoundFromMemory(char* memoryPtr, unsigned int memorySize) { FMOD_CREATESOUNDEXINFO sndinfo = { 0 }; sndinfo.cbsize = sizeof(sndinfo); sndinfo.length = memorySize; FMOD_RESULT result = _system->createSound(memoryPtr, FMOD_OPENMEMORY | FMOD_LOOP_NORMAL, &sndinfo, &_sound); return result; } void SoundManager_Fmod::playSound() { _system->playSound(_sound, 0, false, &_channel); } void SoundManager_Fmod::pauseSound(bool unPause) { bool isPaused; _channel->getPaused(&isPaused); if (isPaused && unPause) { _channel->setPaused(false); } else if(!isPaused && !unPause) { _channel->setPaused(true); } }
Nuestra segunda clase, el blueprint wrapper, solo tendrá que almacenar la instancia del SoundManager y llamar a sus funciones:
AudioManager.h
#pragma once #include "CoreMinimal.h" #include "SoundManager_Fmod.h" #include <memory> #include "UObject/NoExportTypes.h" #include "AudioManager.generated.h" UCLASS(Blueprintable, BlueprintType) class TUTORIAL_SPECTRUM_API UAudioManager : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = Init) int32 InitializeManager(); UFUNCTION(BlueprintCallable, Category = Actions) int32 PlaySong(); UFUNCTION(BlueprintCallable, Category = Actions) void PauseSong(bool unPause); UFUNCTION(BlueprintPure, Category = Access) const FString& GetSongName() const; UAudioManager(); ~UAudioManager(); private: std::unique_ptr<SoundManager_Fmod> _soundManager; FString currentSongName; };
Para encontrar la ruta a nuestro fichero de sonido en la plataforma Android tenemos un problema, el proceso de empaquetado pone nuestra carpeta NonAssets dentro del .apk, tendríamos que extraer el fichero antes de ser capaces de reproducirlo usando loadSoundFromPath. Una alternativa a esto es cargar el sonido en memoria utilizando el Unreal File System (UFS) que es multiplataforma, y pasar el puntero a esta memoria a nuestra segunda función de carga loadSoundFromMemory.
FString songsPath = FPaths::ProjectContentDir() + "NonAssets/"; currentSongName = "A Drop A Day - Fairy Dust"; FString songFile(songsPath + currentSongName + ".wav"); uint8* data; unsigned int dataLength = 0; TArray <uint8> rawFile; FFileHelper::LoadFileToArray(rawFile, *songFile); data = rawFile.GetData(); dataLength = rawFile.Num() * sizeof(uint8);
AudioManager.cpp
#include "AudioManager.h" #include "Paths.h" #include "FileHelper.h" #include <string> UAudioManager::UAudioManager() { } UAudioManager::~UAudioManager() { } int32 UAudioManager::InitializeManager() { _soundManager = std::unique_ptr<SoundManager_Fmod>(new SoundManager_Fmod()); int result = _soundManager->initialize(); if (result > 0) { return result; } return 0; } int32 UAudioManager::PlaySong() { FString songsPath = FPaths::ProjectContentDir() + "NonAssets/"; currentSongName = "A Drop A Day - Fairy Dust"; FString songFile(songsPath + currentSongName + ".wav"); uint8* data; unsigned int dataLength = 0; TArray <uint8> rawFile; FFileHelper::LoadFileToArray(rawFile, *songFile); data = rawFile.GetData(); dataLength = rawFile.Num() * sizeof(uint8); int32 result = _soundManager->loadSoundFromMemory(reinterpret_cast<char*>(data), dataLength); if (result > 0) { return result; //missing file } _soundManager->playSound(); return 0; } void UAudioManager::PauseSong(bool unPause) { _soundManager->pauseSound(unPause); } const FString& UAudioManager::GetSongName() const { return currentSongName; }
Ahora solo tenemos que configurar un par de cosas en nuestro proyecto y realizar la integración final con blueprints.
Lo primero es crear la carpeta NonAssets, dentro de la carpeta Content de nuestro proyecto, para guardar el fichero de sonido
En las opciones de empaquetado tenemos que añadir esta carpeta a la lista de carpetas NonAssets a empaquetar
Blueprints
Nuestro blueprint principal (Actor GameBP) contiene la instancia al AudioManager y un Wigdet para controlar la reproducción del audio.
Esta UI básica tiene un boton para reproducir el sonido y otro para pausar/reanudar la reproducción.
Con esto hemos terminado la integración básica de la librería de terceros FMOD (bajo nivel) con nuestro proyecto, y que funciona tanto en plataforma Win64 como Android.
Tutorial files
2020/06/22 – Actualizado a Unreal Engine 4.24
Te puede interesar:
Ayudanos con este blog!
El último año he estado dedicando cada vez más tiempo a la creación de tutoriales, en su mayoria sobre desarrollo de videojuegos. Si crees que estos posts te han ayudado de alguna manera o incluso inspirado, por favor considera ayudarnos a mantener este blog con alguna de estas opciones. Gracias por hacerlo posible!