Browse Source

Merge pull request #1805 from MikuAuahDark/setadev

Support for Audio Device Selection and Disconnection Callback
Alex Szpakowski 3 years ago
parent
commit
502adfcb15

+ 5 - 0
src/modules/audio/Audio.cpp

@@ -78,6 +78,11 @@ bool Audio::setMixWithSystem(bool mix)
 #endif
 }
 
+void Audio::setPlaybackDevice(const char */*name*/)
+{
+	throw love::Exception("Re-setting output device is not supported.");
+}
+
 StringMap<Audio::DistanceModel, Audio::DISTANCE_MAX_ENUM>::Entry Audio::distanceModelEntries[] =
 {
 	{"none", Audio::DISTANCE_NONE},

+ 15 - 0
src/modules/audio/Audio.h

@@ -297,6 +297,21 @@ public:
 	virtual void pauseContext() = 0;
 	virtual void resumeContext() = 0;
 
+	/**
+	 * Get current playback device name.
+	 */
+	virtual std::string getPlaybackDevice() = 0;
+
+	/**
+	 * Retrieve list of available playback devices.
+	 */
+	virtual void getPlaybackDevices(std::vector<std::string> &list) = 0;
+
+	/**
+	 * Set the current playback device to specified device name.
+	 */
+	virtual void setPlaybackDevice(const char *name);
+
 private:
 
 	static StringMap<DistanceModel, DISTANCE_MAX_ENUM>::Entry distanceModelEntries[];

+ 9 - 0
src/modules/audio/null/Audio.cpp

@@ -211,6 +211,15 @@ void Audio::resumeContext()
 {
 }
 
+std::string Audio::getPlaybackDevice()
+{
+	return "";
+}
+
+void Audio::getPlaybackDevices(std::vector<std::string> &/*list*/)
+{
+}
+
 
 } // null
 } // audio

+ 3 - 0
src/modules/audio/null/Audio.h

@@ -89,6 +89,9 @@ public:
 	void pauseContext();
 	void resumeContext();
 
+	std::string getPlaybackDevice();
+	void getPlaybackDevices(std::vector<std::string> &list);
+
 private:
 	float volume;
 	DistanceModel distanceModel;

+ 65 - 5
src/modules/audio/openal/Audio.cpp

@@ -93,6 +93,18 @@ ALenum Audio::getFormat(int bitDepth, int channels)
 	return AL_NONE;
 }
 
+static const char *getDeviceSpecifier(ALCdevice *device)
+{
+#ifndef ALC_ALL_DEVICES_SPECIFIER
+	constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013;
+#endif
+	static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") == ALC_TRUE
+		? ALC_ALL_DEVICES_SPECIFIER
+		: ALC_DEVICE_SPECIFIER;
+
+	return alcGetString(device, deviceEnum);
+}
+
 Audio::Audio()
 	: device(nullptr)
 	, context(nullptr)
@@ -100,6 +112,9 @@ Audio::Audio()
 	, poolThread(nullptr)
 	, distanceModel(DISTANCE_INVERSE_CLAMPED)
 {
+	attribs.push_back(0);
+	attribs.push_back(0);
+
 	// Before opening new device, check if recording
 	// is requested.
 	if (getRequestRecordingPermission())
@@ -122,12 +137,11 @@ Audio::Audio()
 			throw love::Exception("Could not open device.");
 
 #ifdef ALC_EXT_EFX
-		ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 };
-#else
-		ALint *attribs = nullptr;
+		attribs.insert(attribs.begin(), ALC_MAX_AUXILIARY_SENDS);
+		attribs.insert(attribs.begin() + 1, MAX_SOURCE_EFFECTS);
 #endif
 
-		context = alcCreateContext(device, attribs);
+		context = alcCreateContext(device, attribs.data());
 
 		if (context == nullptr)
 			throw love::Exception("Could not create context.");
@@ -165,7 +179,7 @@ Audio::Audio()
 
 	try
 	{
-		pool = new Pool();
+		pool = new Pool(device);
 	}
 	catch (love::Exception &)
 	{
@@ -314,6 +328,52 @@ void Audio::resumeContext()
 		alcMakeContextCurrent(context);
 }
 
+std::string Audio::getPlaybackDevice()
+{
+	const char *dev = getDeviceSpecifier(device);
+
+	if (dev == nullptr)
+		throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device)));
+
+	return dev;
+}
+
+void Audio::getPlaybackDevices(std::vector<std::string> &list)
+{
+	const char *devices = getDeviceSpecifier(nullptr);
+
+	if (devices == nullptr)
+		throw Exception("Failed to enumerate devices: %s", alcGetString(nullptr, alcGetError(nullptr)));
+
+	for (const char *device = devices; *device; device++)
+	{
+		list.emplace_back(device);
+		device += list.back().length();
+	}
+}
+
+void Audio::setPlaybackDevice(const char* name)
+{
+#ifndef ALC_SOFT_reopen_device
+	typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device,
+		const ALCchar *deviceName, const ALCint *attribs);
+#endif
+	static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT = alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE
+		? (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT")
+		: nullptr;
+
+	if (alcReopenDeviceSOFT == nullptr)
+	{
+		// Default implementation throws exception. To make
+		// error message consistent, call the base class.
+		love::audio::Audio::setPlaybackDevice(name);
+		return;
+	}
+
+	if (alcReopenDeviceSOFT(device, (const ALCchar *) name, attribs.data()) == ALC_FALSE)
+		throw love::Exception("Cannot set output device: %s", alcGetString(device, alcGetError(device)));
+}
+
 void Audio::setVolume(float volume)
 {
 	alListenerf(AL_GAIN, volume);

+ 5 - 0
src/modules/audio/openal/Audio.h

@@ -127,6 +127,10 @@ public:
 
 	bool getEffectID(const char *name, ALuint &id);
 
+	std::string getPlaybackDevice();
+	void getPlaybackDevices(std::vector<std::string> &list);
+	void setPlaybackDevice(const char *name);
+
 private:
 	void initializeEFX();
 	// The OpenAL device.
@@ -137,6 +141,7 @@ private:
 
 	// The OpenAL context.
 	ALCcontext *context;
+	std::vector<ALCint> attribs;
 
 	// The OpenAL effects
 	struct EffectMapStorage

+ 50 - 2
src/modules/audio/openal/Pool.cpp

@@ -20,6 +20,7 @@
 
 #include "Pool.h"
 
+#include "event/Event.h"
 #include "Source.h"
 
 namespace love
@@ -29,8 +30,20 @@ namespace audio
 namespace openal
 {
 
-Pool::Pool()
-	: sources()
+static Variant::SharedTable *putSourcesAsSharedTable(std::vector<audio::Source *> &sources)
+{
+	Variant::SharedTable *table = new Variant::SharedTable();
+
+	for (int i = 0; i < sources.size(); i++)
+		table->pairs.emplace_back((double) (i + 1), Variant(&Source::type, sources[i]));
+
+	return table;
+}
+
+Pool::Pool(ALCdevice *device)
+	: device(device)
+	, sources()
+	, disconnectNotified(false)
 	, totalSources(0)
 {
 	// Clear errors.
@@ -101,8 +114,43 @@ bool Pool::isPlaying(Source *s)
 
 void Pool::update()
 {
+#ifndef ALC_CONNECTED
+	constexpr ALCenum ALC_CONNECTED = 0x313;
+#endif
+
 	thread::Lock lock(mutex);
 
+	static bool disconnectExtSupported = alcIsExtensionPresent(device, "ALC_EXT_Disconnect") == ALC_TRUE;
+
+	// Device disconnection event
+	if (disconnectExtSupported)
+	{
+		auto eventModule = Module::getInstance<event::Event>(Module::M_EVENT);
+		if (eventModule)
+		{
+			ALCint connected;
+			alcGetIntegerv(device, ALC_CONNECTED, 1, &connected);
+
+			if (connected)
+				disconnectNotified = false;
+			else if (!disconnectNotified)
+			{
+				// Get all sources in this Pool then stop it
+				// since they're all internally stopped.
+				std::vector<audio::Source *> sources = getPlayingSources();
+				Source::stop(sources);
+
+				std::vector<Variant> vargs;
+				vargs.emplace_back(putSourcesAsSharedTable(sources));
+
+				StrongRef<event::Message> msg(new event::Message("audiodisconnected", vargs), Acquire::NORETAIN);
+				eventModule->push(msg);
+
+				disconnectNotified = true;
+			}
+		}
+	}
+
 	std::vector<Source *> torelease;
 
 	for (const auto &i : playing)

+ 7 - 1
src/modules/audio/openal/Pool.h

@@ -64,7 +64,7 @@ class Pool
 {
 public:
 
-	Pool();
+	Pool(ALCdevice *device);
 	~Pool();
 
 	/**
@@ -101,9 +101,15 @@ private:
 	// Maximum possible number of OpenAL sources the pool attempts to generate.
 	static const int MAX_SOURCES = 64;
 
+	// Current OpenAL device
+	ALCdevice *device;
+
 	// OpenAL sources
 	ALuint sources[MAX_SOURCES];
 
+	// Is device disconnection has been notified?
+	bool disconnectNotified;
+
 	// Total number of created sources in the pool.
 	int totalSources;
 

+ 49 - 0
src/modules/audio/wrap_Audio.cpp

@@ -543,6 +543,52 @@ int w_setMixWithSystem(lua_State *L)
 	return 1;
 }
 
+int w_getPlaybackDevice(lua_State* L)
+{
+	std::string device;
+
+	luax_catchexcept(L, [&]() { device = instance()->getPlaybackDevice(); });
+	luax_pushstring(L, device);
+	return 1;
+}
+
+int w_getPlaybackDevices(lua_State* L)
+{
+	std::vector<std::string> list;
+
+	luax_catchexcept(L, [&]() { instance()->getPlaybackDevices(list); });
+	lua_createtable(L, 0, (int) list.size());
+	for (int i = 0; i < (int) list.size(); i++)
+	{
+		lua_pushnumber(L, i + 1);
+		lua_pushstring(L, list[i].c_str());
+		lua_rawset(L, -3);
+	}
+
+	return 1;
+}
+
+int w_setPlaybackDevice(lua_State* L)
+{
+	const char *device = luaL_optstring(L, 1, nullptr);
+
+	try
+	{
+		instance()->setPlaybackDevice(device);
+		luax_pushboolean(L, true);
+		return 1;
+	}
+	catch (love::Exception& e)
+	{
+		luax_pushboolean(L, false);
+		lua_pushstring(L, e.what());
+		return 2;
+	}
+
+	// To avoid compiler warning
+	return 0;
+}
+
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
@@ -574,6 +620,9 @@ static const luaL_Reg functions[] =
 	{ "getMaxSourceEffects", w_getMaxSourceEffects },
 	{ "isEffectsSupported", w_isEffectsSupported },
 	{ "setMixWithSystem", w_setMixWithSystem },
+	{ "getPlaybackDevice", w_getPlaybackDevice },
+	{ "getPlaybackDevices", w_getPlaybackDevices },
+	{ "setPlaybackDevice", w_setPlaybackDevice },
 
 	{ 0, 0 }
 };

+ 5 - 0
src/modules/love/callbacks.lua

@@ -129,6 +129,11 @@ function love.createhandlers()
 		localechanged = function ()
 			if love.localechanged then return love.localechanged() end
 		end,
+		audiodisconnected = function (sources)
+			if not love.audiodisconnected or not love.audiodisconnected(sources) then
+				love.audio.setPlaybackDevice()
+			end
+		end,
 	}, {
 		__index = function(self, name)
 			error("Unknown event: " .. name)