Files
itgmania212121/src/arch/Sound/RageSoundDriver_JACK.cpp
T

256 lines
7.1 KiB
C++
Raw Normal View History

2013-08-06 19:24:55 -04:00
#include "global.h"
#include "RageSoundDriver_JACK.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "PrefsManager.h"
#include "ProductInfo.h"
2023-04-20 12:34:12 +02:00
#include <cstdint>
2013-08-06 19:24:55 -04:00
REGISTER_SOUND_DRIVER_CLASS( JACK );
RageSoundDriver_JACK::RageSoundDriver_JACK() :
RageSoundDriver()
{
2019-06-22 12:35:38 -07:00
client = nullptr;
port_l = nullptr;
port_r = nullptr;
2013-08-06 19:24:55 -04:00
}
RageSoundDriver_JACK::~RageSoundDriver_JACK()
{
2019-06-22 12:35:38 -07:00
// If Init failed, it cleaned up already and set client to nullptr
if (client == nullptr)
2013-08-26 08:40:52 -04:00
return;
// Clean up and shut down client
jack_deactivate(client);
jack_port_unregister(client, port_r);
jack_port_unregister(client, port_l);
2013-08-06 19:24:55 -04:00
jack_client_close(client);
}
RString RageSoundDriver_JACK::Init()
{
jack_status_t status;
RString error;
// Open JACK client and call it "StepMania" or whatever
client = jack_client_open(PRODUCT_FAMILY, JackNoStartServer, &status);
2019-06-22 12:35:38 -07:00
if (client == nullptr)
2013-08-06 19:24:55 -04:00
return "Couldn't connect to JACK server";
2013-08-08 14:43:15 -04:00
sample_rate = jack_get_sample_rate(client);
LOG->Trace("JACK connected at %u Hz", sample_rate);
2013-08-06 19:24:55 -04:00
2013-08-20 09:33:35 -04:00
// Start this before callbacks
StartDecodeThread();
2013-08-06 19:24:55 -04:00
// Set callback for processing audio
if (jack_set_process_callback(client, ProcessTrampoline, this))
{
error = "Couldn't set JACK process callback";
goto out_close;
}
2013-08-08 14:43:15 -04:00
if (jack_set_sample_rate_callback(client, SampleRateTrampoline, this))
{
error = "Couldn't set JACK sample-rate callback";
goto out_close;
}
2013-08-06 19:24:55 -04:00
// TODO Set a jack_on_shutdown callback as well? Probably just stop
// caring about sound altogether if that happens.
// Create output ports
port_l = jack_port_register(client, "out_l", JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
2019-06-22 12:35:38 -07:00
if (port_l == nullptr)
2013-08-06 19:24:55 -04:00
{
error = "Couldn't create JACK port out_l";
goto out_close;
}
port_r = jack_port_register(client, "out_r", JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
2019-06-22 12:35:38 -07:00
if (port_r == nullptr)
2013-08-06 19:24:55 -04:00
{
error = "Couldn't create JACK port out_r";
goto out_unreg_l;
}
// Go!
if (jack_activate(client))
{
error = "Couldn't activate JACK client";
goto out_unreg_r;
}
error = ConnectPorts();
if (!error.empty())
// Eh. Not fatal. JACK *is* running and we successfully created
// our source ports, so it's unlkely any other driver will
// function.
LOG->Warn( "RageSoundDriver_JACK: Couldn't connect ports: %s", error.c_str() );
2013-08-06 19:24:55 -04:00
// Success!
LOG->Trace("JACK sound driver started successfully");
2013-08-06 19:24:55 -04:00
return RString();
// Not success!
out_unreg_r:
jack_port_unregister(client, port_r);
out_unreg_l:
jack_port_unregister(client, port_l);
out_close:
jack_client_close(client);
2019-06-22 12:35:38 -07:00
client = nullptr;
2013-08-06 19:24:55 -04:00
return error;
}
RString RageSoundDriver_JACK::ConnectPorts()
{
std::vector<RString> portNames;
2013-08-06 19:24:55 -04:00
split(PREFSMAN->m_iSoundDevice.Get(), ",", portNames, true);
2019-06-22 12:35:38 -07:00
const char *port_out_l = nullptr, *port_out_r = nullptr;
const char **ports = nullptr;
if( portNames.size() == 0 )
2013-08-06 19:24:55 -04:00
{
2023-04-20 12:34:12 +02:00
// The user has NOT specified any ports to connect to. Search
// for all physical sinks and use the first two.
2019-06-22 12:35:38 -07:00
ports = jack_get_ports( client, nullptr, nullptr, JackPortIsInput | JackPortIsPhysical );
if( ports == nullptr )
2014-02-08 21:35:43 -05:00
return "Couldn't get JACK ports";
2019-06-22 12:35:38 -07:00
if( ports[0] == nullptr )
2014-02-08 21:35:43 -05:00
{
jack_free( ports );
return "No physical sinks!";
2014-02-08 21:35:43 -05:00
}
port_out_l = ports[0];
2019-06-22 12:35:38 -07:00
if( ports[1] == nullptr )
// Only one physical sink. We're going mono!
port_out_r = ports[0];
else
port_out_r = ports[1];
2013-08-06 19:24:55 -04:00
}
else
{
// The user has specified ports to connect to. Loop through
2023-04-20 12:34:12 +02:00
// them to find two that are valid, then use them. If we find
// only one that is valid, connect both channels to it.
// Use jack_port_by_name to ensure ports exist, then
2023-04-20 12:34:12 +02:00
// jack_port_name to use their canonical name. (I'm not sure
// if that second step is necessary, I've seen something about
// "aliases" in the docs.)
for ( RString const &portName : portNames )
{
jack_port_t *out = jack_port_by_name( client, portName );
// Make sure the port is a sink.
2014-02-08 21:28:57 -05:00
if( ! ( jack_port_flags( out ) & JackPortIsInput ) )
continue;
2013-08-06 19:24:55 -04:00
2019-06-22 12:35:38 -07:00
if( out != nullptr )
{
2019-06-22 12:35:38 -07:00
if( port_out_l == nullptr )
port_out_l = jack_port_name( out );
else
{
port_out_r = jack_port_name( out );
break;
}
}
}
2019-06-22 12:35:38 -07:00
if( port_out_l == nullptr )
return "All specified sinks are invalid.";
2023-04-20 12:34:12 +02:00
2019-06-22 12:35:38 -07:00
if( port_out_r == nullptr )
// Only found one valid sink. Going mono!
port_out_r = port_out_l;
}
2023-04-20 12:34:12 +02:00
RString ret = RString();
2013-08-06 19:24:55 -04:00
2014-02-08 21:35:43 -05:00
if( jack_connect( client, jack_port_name(port_l), port_out_l ) != 0 )
ret = "Couldn't connect left JACK port";
2014-02-08 21:35:43 -05:00
else if( jack_connect( client, jack_port_name(port_r), port_out_r ) != 0 )
ret = "Couldn't connect right JACK port";
2013-08-06 19:24:55 -04:00
2019-06-22 12:35:38 -07:00
if( ports != nullptr )
2014-02-08 21:35:43 -05:00
jack_free( ports );
2013-08-06 19:24:55 -04:00
return ret;
2013-08-06 19:24:55 -04:00
}
2023-04-20 12:34:12 +02:00
std::int64_t RageSoundDriver_JACK::GetPosition() const
2013-08-06 19:24:55 -04:00
{
return jack_frame_time(client);
}
int RageSoundDriver_JACK::GetSampleRate() const
{
2013-08-08 14:43:15 -04:00
// For now, let's pretend there isn't a race condition between this and
// SampleRateCallback().
return sample_rate;
2013-08-06 19:24:55 -04:00
}
int RageSoundDriver_JACK::ProcessCallback(jack_nframes_t nframes)
{
jack_default_audio_sample_t *bufs[2];
2013-08-06 19:24:55 -04:00
bufs[0] = (jack_default_audio_sample_t *) jack_port_get_buffer(port_l, nframes);
bufs[1] = (jack_default_audio_sample_t *) jack_port_get_buffer(port_r, nframes);
jack_time_t now = jack_last_frame_time(client);
MixDeinterlaced( bufs, 2, nframes, now, now + nframes * 2 );
2013-08-06 19:24:55 -04:00
return 0;
}
2013-08-08 14:43:15 -04:00
int RageSoundDriver_JACK::SampleRateCallback(jack_nframes_t nframes)
{
// For now, let's pretend there isn't a race condition between this and
// GetSampleRate().
sample_rate = jack_get_sample_rate(client);
return 0;
}
2013-08-06 19:24:55 -04:00
// Static callback trampoline
int RageSoundDriver_JACK::ProcessTrampoline(jack_nframes_t nframes, void *arg)
{
return ((RageSoundDriver_JACK *) arg)->ProcessCallback(nframes);
}
2013-08-08 14:43:15 -04:00
int RageSoundDriver_JACK::SampleRateTrampoline(jack_nframes_t nframes, void *arg)
{
return ((RageSoundDriver_JACK *) arg)->SampleRateCallback(nframes);
}
2013-08-06 19:24:55 -04:00
/*
* (c) 2013 Devin J. Pohly
* All rights reserved.
2023-04-20 12:34:12 +02:00
*
2013-08-06 19:24:55 -04:00
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
2023-04-20 12:34:12 +02:00
*
2013-08-06 19:24:55 -04:00
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/