update upstream-2021-08-15

This commit is contained in:
Chengyi Zhao 2021-08-15 02:35:23 +08:00
parent c8653c13fa
commit 1703683def
297 changed files with 91782 additions and 54869 deletions

View file

@ -665,9 +665,21 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
pa_xfree(c->config_file);
c->config_file = NULL;
const char *default_config_file = DEFAULT_CONFIG_FILE;
#ifdef HAVE_RUNNING_FROM_BUILD_TREE
if (pa_run_from_build_tree()) {
pa_log_notice("Detected that we are run from the build tree, fixing default daemon.conf file path.");
#ifdef MESON_BUILD
default_config_file = PA_BUILDDIR PA_PATH_SEP "src" PA_PATH_SEP "daemon" PA_PATH_SEP "daemon.conf";
#else
default_config_file = PA_BUILDDIR PA_PATH_SEP "daemon.conf";
#endif // Endof #ifdef MESON_BUILD
}
#endif // Endof #ifdef HAVE_RUNNING_FROM_BUILD_TREE
f = filename ?
pa_fopen_cloexec(c->config_file = pa_xstrdup(filename), "r") :
pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
pa_open_config_file(default_config_file, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file);
if (!f && errno != ENOENT) {
pa_log_warn(_("Failed to open configuration file: %s"), pa_cstrerror(errno));

View file

@ -67,6 +67,12 @@ load-module module-coreaudio-detect
### Use the static hardware detection module (for systems that lack udev support)
load-module module-detect
.endif
ifelse(@OS_IS_FREEBSD@, 1, [dnl
### FreeBSD devd is used in addition to static detection (only handles hotplug)
.ifexists module-devd-detect@PA_SOEXT@
load-module module-devd-detect
.endif
])dnl
### Automatically connect sink and source if JACK server is present
.ifexists module-jackdbus-detect@PA_SOEXT@
@ -121,17 +127,6 @@ load-module module-gsettings
.endif
])dnl
ifelse(@HAVE_GCONF@, 1, [dnl
### Load additional modules from GConf settings. This can be configured with the paprefs tool.
### Please keep in mind that the modules configured by paprefs might conflict with manually
### loaded modules.
.ifexists module-gconf@PA_SOEXT@
.nofail
load-module module-gconf
.fail
.endif
])dnl
### Automatically restore the default sink/source when changed by the user
### during runtime
### NOTE: This should be loaded as early as possible so that subsequent modules
@ -173,3 +168,8 @@ load-module module-filter-apply
### Make some devices default
#set-default-sink output
#set-default-source input
### Allow including a default.pa.d directory, which if present, can be used
### for additional configuration snippets.
.nofail
.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/default.pa.d

View file

@ -60,6 +60,12 @@
#include <systemd/sd-daemon.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#include <aclapi.h>
#include <sddl.h>
#endif
#include <pulse/client-conf.h>
#include <pulse/mainloop.h>
#include <pulse/mainloop-signal.h>
@ -101,7 +107,7 @@
#ifdef DISABLE_LIBTOOL_PRELOAD
/* FIXME: work around a libtool bug by making sure we have 2 elements. Bug has
* been reported: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29576 */
const lt_dlsymlist lt_preloaded_symbols[] = {
LT_DLSYM_CONST lt_dlsymlist lt_preloaded_symbols[] = {
{ "@PROGRAM@", NULL },
{ NULL, NULL }
};
@ -156,7 +162,95 @@ static void signal_callback(pa_mainloop_api* m, pa_signal_event *e, int sig, voi
}
}
#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
#if defined(OS_IS_WIN32)
static int change_user(void) {
pa_log_info("Overriding system runtime/config base dir to '%s'.", pa_win32_get_system_appdata());
/* On other platforms, these paths are compiled into PulseAudio. This isn't
* suitable on Windows. Firstly, Windows doesn't follow the FHS or use Unix
* paths and the build system can't handle Windows-style paths properly.
* Secondly, the idiomatic location for a service's state and shared data is
* ProgramData, and the location of special folders is dynamic on Windows.
* Also, this method of handling paths is consistent with how they are
* handled on Windows in other parts of PA. Note that this is only needed
* in system-wide mode since paths in user instances are already handled
* properly.
*/
char *run_path = pa_sprintf_malloc("%s" PA_PATH_SEP "run", pa_win32_get_system_appdata());
char *lib_path = pa_sprintf_malloc("%s" PA_PATH_SEP "lib", pa_win32_get_system_appdata());
/* https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings */
/* https://docs.microsoft.com/en-us/windows/win32/secauthz/modifying-the-acls-of-an-object-in-c-- */
/* https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertstringsecuritydescriptortosecuritydescriptora */
{
mkdir(run_path);
PSECURITY_DESCRIPTOR sd;
if (ConvertStringSecurityDescriptorToSecurityDescriptorA(
"D:PAI" /* DACL, disable inheritance from parent, enable propagation to children */
"(A;OICI;FA;;;SY)" /* give system full access */
"(A;OICI;FA;;;CO)" /* give owner full access */
"(A;OICI;FA;;;BA)" /* give administrators full access */
"(A;OICI;0x1200a9;;;WD)", /* give everyone read/write/execute access */
SDDL_REVISION_1, &sd, NULL
)) {
PACL acl;
BOOL acl_present, acl_default;
if (GetSecurityDescriptorDacl(sd, &acl_present, &acl, &acl_default)) {
if (SetNamedSecurityInfo(run_path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, acl, NULL) != ERROR_SUCCESS) {
pa_log_warn("Failed to set DACL for runtime dir: failed to apply DACL: error %lu.", GetLastError());
}
LocalFree(acl);
} else {
pa_log_warn("Failed to set DACL for runtime dir: failed to get security descriptor DACL: error %lu.", GetLastError());
}
} else {
pa_log_warn("Failed to set DACL for runtime dir: failed to parse security descriptor: error %lu.", GetLastError());
}
}
{
mkdir(lib_path);
PSECURITY_DESCRIPTOR sd;
if (ConvertStringSecurityDescriptorToSecurityDescriptorA(
"D:PAI" /* DACL, disable inheritance from parent, enable propagation to children */
"(A;OICI;FA;;;SY)" /* give system full access */
"(A;OICI;FA;;;CO)" /* give owner full access */
"(A;OICI;FA;;;BA)", /* give administrators full access */
SDDL_REVISION_1, &sd, NULL
)) {
PACL acl;
BOOL acl_present, acl_default;
if (GetSecurityDescriptorDacl(sd, &acl_present, &acl, &acl_default)) {
if (SetNamedSecurityInfo(lib_path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, acl, NULL) != ERROR_SUCCESS) {
pa_log_warn("Failed to set DACL for lib dir: failed to apply DACL: error %lu.", GetLastError());
}
LocalFree(acl);
} else {
pa_log_warn("Failed to set DACL for lib dir: failed to get security descriptor DACL: error %lu.", GetLastError());
}
} else {
pa_log_warn("Failed to set DACL for lib dir: failed to parse security descriptor: error %lu.", GetLastError());
}
}
pa_set_env("HOME", run_path);
if (!getenv("PULSE_RUNTIME_PATH"))
pa_set_env("PULSE_RUNTIME_PATH", run_path);
if (!getenv("PULSE_CONFIG_PATH"))
pa_set_env("PULSE_CONFIG_PATH", lib_path);
if (!getenv("PULSE_STATE_PATH"))
pa_set_env("PULSE_STATE_PATH", lib_path);
pa_xfree(run_path);
pa_xfree(lib_path);
pa_log_info("Not changing user for system instance on Windows.");
return 0;
}
#elif defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
static int change_user(void) {
struct passwd *pw;
@ -377,7 +471,45 @@ fail:
}
#endif
#ifdef OS_IS_WIN32
#define SVC_NAME "PulseAudio"
static bool is_svc = true;
static int argc;
static char **argv;
static int real_main(int s_argc, char *s_argv[]);
static SERVICE_STATUS_HANDLE svc_status;
DWORD svc_callback(DWORD ctl, DWORD evt, LPVOID data, LPVOID userdata) {
pa_mainloop **m = userdata;
switch (ctl) {
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
if (m) {
pa_log_info("Exiting.");
pa_mainloop_get_api(*m)->quit(pa_mainloop_get_api(*m), 0);
}
return NO_ERROR;
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
int main(int p_argc, char *p_argv[]) {
argc = p_argc;
argv = p_argv;
if (StartServiceCtrlDispatcherA((SERVICE_TABLE_ENTRYA[]){
{SVC_NAME, (LPSERVICE_MAIN_FUNCTIONA) real_main},
{0},
})) return 0;
is_svc = false;
return real_main(0, NULL);
}
static int real_main(int s_argc, char *s_argv[]) {
#else
int main(int argc, char *argv[]) {
#endif
pa_core *c = NULL;
pa_strbuf *buf = NULL;
pa_daemon_conf *conf = NULL;
@ -402,6 +534,23 @@ int main(int argc, char *argv[]) {
bool start_server;
#endif
#ifdef OS_IS_WIN32
if (is_svc && !(svc_status = RegisterServiceCtrlHandlerExA(SVC_NAME, (LPHANDLER_FUNCTION_EX) svc_callback, &mainloop))) {
pa_log("Failed to register service control handler.");
goto finish;
}
if (is_svc) {
SetServiceStatus(svc_status, &(SERVICE_STATUS){
.dwServiceType = SERVICE_WIN32,
.dwCurrentState = SERVICE_START_PENDING,
.dwControlsAccepted = 0,
.dwWin32ExitCode = NO_ERROR,
.dwWaitHint = 3000,
});
}
#endif
pa_log_set_ident("pulseaudio");
pa_log_set_level(PA_LOG_NOTICE);
pa_log_set_flags(PA_LOG_COLORS|PA_LOG_PRINT_FILE|PA_LOG_PRINT_LEVEL, PA_LOG_RESET);
@ -480,7 +629,13 @@ int main(int argc, char *argv[]) {
pa_unblock_sigs(-1);
pa_reset_priority();
/* Load locale from the environment. */
setlocale(LC_ALL, "");
/* Set LC_NUMERIC to C so that floating point strings are consistently
* formatted and parsed across locales. */
setlocale(LC_NUMERIC, "C");
pa_init_i18n();
conf = pa_daemon_conf_new();
@ -916,7 +1071,6 @@ int main(int argc, char *argv[]) {
pa_set_env_and_record("PULSE_SYSTEM", conf->system_instance ? "1" : "0");
pa_log_info("This is PulseAudio %s", PACKAGE_VERSION);
pa_log_debug("Compilation host: %s", CANONICAL_HOST);
pa_log_debug("Compilation CFLAGS: %s", PA_CFLAGS);
#ifdef HAVE_LIBSAMPLERATE
@ -1067,6 +1221,10 @@ int main(int argc, char *argv[]) {
c->server_type = conf->local_server_type;
#endif
pa_core_check_idle(c);
c->state = PA_CORE_RUNNING;
pa_cpu_init(&c->cpu_info);
pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);
@ -1163,6 +1321,18 @@ int main(int argc, char *argv[]) {
sd_notify(0, "READY=1");
#endif
#ifdef OS_IS_WIN32
if (is_svc) {
SetServiceStatus(svc_status, &(SERVICE_STATUS){
.dwServiceType = SERVICE_WIN32,
.dwCurrentState = SERVICE_RUNNING,
.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN,
.dwWin32ExitCode = NO_ERROR,
.dwWaitHint = 0,
});
}
#endif
retval = 0;
if (pa_mainloop_run(mainloop, &retval) < 0)
goto finish;
@ -1173,6 +1343,18 @@ int main(int argc, char *argv[]) {
sd_notify(0, "STOPPING=1");
#endif
#ifdef OS_IS_WIN32
if (is_svc) {
SetServiceStatus(svc_status, &(SERVICE_STATUS){
.dwServiceType = SERVICE_WIN32,
.dwCurrentState = SERVICE_STOP_PENDING,
.dwControlsAccepted = 0,
.dwWin32ExitCode = NO_ERROR,
.dwWaitHint = 2000,
});
}
#endif
finish:
#ifdef HAVE_DBUS
if (server_bus)
@ -1240,5 +1422,17 @@ finish:
dbus_shutdown();
#endif
#ifdef OS_IS_WIN32
if (is_svc) {
SetServiceStatus(svc_status, &(SERVICE_STATUS){
.dwServiceType = SERVICE_WIN32,
.dwCurrentState = SERVICE_STOPPED,
.dwControlsAccepted = 0,
.dwWin32ExitCode = retval ? ERROR_PROCESS_ABORTED : NO_ERROR,
.dwWaitHint = 0,
});
}
#endif
return retval;
}

View file

@ -31,7 +31,7 @@ executable('pulseaudio',
include_directories : [configinc, topinc],
link_args : ['-ffast-math'],
link_with : [libpulsecore, libpulsecommon, libpulse],
dependencies : [ltdl_dep, cap_dep, dbus_dep, libsystemd_dep, dl_dep, libintl_dep],
dependencies : [ltdl_dep, cap_dep, dbus_dep, libsystemd_dep, dl_dep, libintl_dep, platform_dep, platform_socket_dep],
c_args : pa_c_args,
)
@ -47,6 +47,14 @@ if x11_dep.found()
install_dir : bindir,
)
configure_file(
input : 'start-pulseaudio-x11.in',
output : '00-pulseaudio-x11',
configuration : conf,
install : true,
install_dir : join_paths(sysconfdir, 'xdg', 'Xwayland-session.d'),
)
desktop_file = i18n.merge_file(
input : 'pulseaudio.desktop.in',
output : 'pulseaudio.desktop',
@ -92,11 +100,9 @@ default_conf = configuration_data()
default_conf.merge_from(cdata)
default_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY'))
default_conf.set('PA_SOEXT', cdata.get_unquoted('PA_SOEXT'))
default_conf.set10('HAVE_AF_UNIX', cc.has_header('sys/un.h'))
default_conf.set10('HAVE_AF_UNIX', cc.has_header('sys/un.h') ? true : cc.has_header('winsock2.h'))
default_conf.set10('OS_IS_WIN32', host_machine.system() == 'windows')
default_conf.set10('HAVE_MKFIFO', cc.has_function('mkfifo'))
# We don't support the deprecated GConf option in meson
default_conf.set10('HAVE_GCONF', 0)
default_template_file = configure_file(
input : 'default.pa.in',
@ -115,9 +121,7 @@ custom_target('default.pa',
)
system_conf = configuration_data()
system_conf.merge_from(cdata)
system_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY'))
system_conf.set('PA_SOEXT', cdata.get_unquoted('PA_SOEXT'))
system_conf.merge_from(default_conf)
system_template_file = configure_file(
input : 'system.pa.in',
@ -144,6 +148,8 @@ endif
if systemd_dep.found()
sd_user_service_conf = configuration_data()
sd_user_service_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY'))
sd_user_service_conf.set('PACTL_BINARY', join_paths(bindir, 'pactl'))
sd_user_service_conf.set('PA_X11_BINARY', join_paths(bindir, 'start-pulseaudio-x11'))
sd_user_service_file = configure_file(
input : 'systemd/user/pulseaudio.service.in',
@ -153,6 +159,14 @@ if systemd_dep.found()
install_dir : systemduserunitdir,
)
sd_user_x11_service_file = configure_file(
input : 'systemd/user/pulseaudio-x11.service.in',
output : 'pulseaudio-x11.service',
configuration : sd_user_service_conf,
install : true,
install_dir : systemduserunitdir,
)
install_data('systemd/user/pulseaudio.socket',
install_dir: systemduserunitdir,
)

View file

@ -6,4 +6,5 @@ Exec=start-pulseaudio-x11
Terminal=false
Type=Application
X-GNOME-Autostart-Phase=Initialization
X-GNOME-HiddenUnderSystemd=true
X-KDE-autostart-phase=1

View file

@ -17,6 +17,25 @@
set -e
if [ -n "$1" ] ; then
case $1 in
stop)
@PACTL_BINARY@ unload-module module-x11-publish > /dev/null
@PACTL_BINARY@ unload-module module-x11-cork-request > /dev/null
@PACTL_BINARY@ unload-module module-device-manager > /dev/null
@PACTL_BINARY@ unload-module module-x11-xsmp > /dev/null
exit 0
;;
start)
# Let it continue further down
;;
*)
echo "Unknown argument $1"
exit 1
;;
esac
fi
if [ x"$DISPLAY" != x ] ; then
@PACTL_BINARY@ load-module module-x11-publish "display=$DISPLAY xauthority=$XAUTHORITY" > /dev/null

View file

@ -39,6 +39,12 @@ load-module module-coreaudio-detect
### Use the static hardware detection module (for systems that lack udev/hal support)
load-module module-detect
.endif
ifelse(@OS_IS_FREEBSD@, 1, [dnl
### FreeBSD devd is used in addition to static detection (only handles hotplug)
.ifexists module-devd-detect@PA_SOEXT@
load-module module-devd-detect
.endif
])dnl
### Load several protocols
.ifexists module-esound-protocol-unix@PA_SOEXT@
@ -60,3 +66,8 @@ load-module module-suspend-on-idle
### Enable positioned event sounds
load-module module-position-event-sounds
### Allow including a system.pa.d directory, which if present, can be used
### for additional configuration snippets.
.nofail
.include @PA_DEFAULT_CONFIG_DIR_UNQUOTED@/system.pa.d

View file

@ -0,0 +1,18 @@
[Unit]
Description=Sound Service (X11 Plugins)
Requires=pulseaudio.service
After=pulseaudio.service
ConditionUser=!root
PartOf=gnome-session-x11.target
[Service]
ExecStart=@PA_X11_BINARY@
ExecStop=@PA_X11_BINARY@ stop
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
Type=simple
UMask=0077

View file

@ -28,6 +28,7 @@ SystemCallFilter=@system-service
# Note that notify will only work if --daemonize=no
Type=notify
UMask=0077
Slice=session.slice
[Install]
Also=pulseaudio.socket

View file

@ -3,7 +3,6 @@ libpulsecommon_sources = [
'pulse/error.c',
'pulse/fork-detect.c',
'pulse/format.c',
'pulse/json.c',
'pulse/mainloop-api.c',
'pulse/xmalloc.c',
'pulse/proplist.c',
@ -31,6 +30,7 @@ libpulsecommon_sources = [
'pulsecore/iochannel.c',
'pulsecore/ioline.c',
'pulsecore/ipacl.c',
'pulsecore/json.c',
'pulsecore/lock-autospawn.c',
'pulsecore/log.c',
'pulsecore/ratelimit.c',
@ -38,7 +38,6 @@ libpulsecommon_sources = [
'pulsecore/memblock.c',
'pulsecore/memblockq.c',
'pulsecore/memchunk.c',
'pulsecore/mutex-posix.c',
'pulsecore/native-common.c',
'pulsecore/once.c',
'pulsecore/packet.c',
@ -55,7 +54,6 @@ libpulsecommon_sources = [
'pulsecore/random.c',
'pulsecore/srbchannel.c',
'pulsecore/sample-util.c',
'pulsecore/semaphore-posix.c',
'pulsecore/shm.c',
'pulsecore/bitset.c',
'pulsecore/socket-client.c',
@ -64,7 +62,6 @@ libpulsecommon_sources = [
'pulsecore/strbuf.c',
'pulsecore/strlist.c',
'pulsecore/tagstruct.c',
'pulsecore/thread-posix.c',
'pulsecore/time-smoother.c',
'pulsecore/tokenizer.c',
'pulsecore/usergroup.c',
@ -76,7 +73,6 @@ libpulsecommon_headers = [
'pulse/error.h',
'pulse/fork-detect.h',
'pulse/format.h',
'pulse/json.h',
'pulse/mainloop-api.h',
'pulse/xmalloc.h',
'pulse/proplist.h',
@ -107,6 +103,7 @@ libpulsecommon_headers = [
'pulsecore/iochannel.h',
'pulsecore/ioline.h',
'pulsecore/ipacl.h',
'pulsecore/json.h',
'pulsecore/llist.h',
'pulsecore/lock-autospawn.h',
'pulsecore/log.h',
@ -175,6 +172,20 @@ if x11_dep.found()
endif
# FIXME: Do non-POSIX thread things
if host_machine.system() == 'windows'
libpulsecommon_sources += [
'pulsecore/mutex-win32.c',
'pulsecore/poll-win32.c',
'pulsecore/semaphore-win32.c',
'pulsecore/thread-win32.c',
]
else
libpulsecommon_sources += [
'pulsecore/mutex-posix.c',
'pulsecore/semaphore-posix.c',
'pulsecore/thread-posix.c'
]
endif
# FIXME: Do SIMD things
libpulsecommon = shared_library('pulsecommon-' + pa_version_major_minor,
@ -187,16 +198,20 @@ libpulsecommon = shared_library('pulsecommon-' + pa_version_major_minor,
install_dir : privlibdir,
dependencies : [
libm_dep, thread_dep, dl_dep, shm_dep, iconv_dep, sndfile_dep, dbus_dep,
x11_dep, libsystemd_dep, glib_dep, gtk_dep, asyncns_dep, libintl_dep,
x11_dep, libsystemd_dep, glib_dep.partial_dependency(compile_args: true),
gtk_dep.partial_dependency(compile_args: true), asyncns_dep, libintl_dep,
platform_dep, tcpwrap_dep, platform_socket_dep, execinfo_dep,
],
implicit_include_directories : false)
libpulsecommon_dep = declare_dependency(link_with: libpulsecommon)
subdir('pulse')
subdir('pulsecore')
subdir('daemon')
subdir('modules')
if get_option('daemon')
subdir('pulsecore')
subdir('daemon')
subdir('modules')
endif
if get_option('tests')
subdir('tests')
endif

View file

@ -19,6 +19,7 @@ SUBSYSTEM!="sound", GOTO="pulseaudio_end"
ACTION!="change", GOTO="pulseaudio_end"
KERNEL!="card*", GOTO="pulseaudio_end"
SUBSYSTEMS=="usb", GOTO="pulseaudio_check_usb"
SUBSYSTEMS=="pci", GOTO="pulseaudio_check_pci"
SUBSYSTEMS=="firewire", GOTO="pulseaudio_firewire_quirk"
SUBSYSTEMS=="platform", DRIVERS=="thinkpad_acpi", ENV{PULSE_IGNORE}="1"
@ -104,12 +105,17 @@ ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="041d", ENV{PULSE_PROFILE_SET}="nativ
ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1010", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf"
ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio6.conf"
ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1001", ENV{PULSE_PROFILE_SET}="native-instruments-komplete-audio6.conf"
ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="native-instruments-traktor-audio10.conf"
ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf"
ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="402e", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{PULSE_PROFILE_SET}="behringer-umc22.conf"
ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0269", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-120w-g2.conf"
ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0567", ENV{PULSE_PROFILE_SET}="hp-tbt-dock-audio-module.conf"
# ID 1038:12ad is for the 2018 refresh of the Arctis 7.
# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration).
@ -117,8 +123,15 @@ ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="usb-g
ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
# ID 1038:12c4 is for Arctis 9
ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12c4", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
# Lucidsound LS31
ATTRS{idVendor}=="2f12", ATTRS{idProduct}=="0109", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
# ID 9886:002c is for the Astro A50 Gen4
ATTRS{idVendor}=="9886", ATTRS{idProduct}=="002c", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
# ID 1532:0520 is for the Razer Kraken Tournament Edition
ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0520", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf"
# ID 1038:1250 is for the Arctis 5
# ID 1037:12aa is for the Arctis 5 2019
@ -129,17 +142,51 @@ ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{PULSE_PROFILE_SET}="steel
ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{PULSE_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf"
# HyperX Cloud Orbit S has three modes. Each mode has a separate product ID.
# ID_SERIAL for this device is the device name + mode repeated three times.
# ID_SERIAL is used for the ID_ID property, and the ID_ID property is used in
# the card name in PulseAudio. The resulting card name is too long for the name
# length limit, so we set a more sensible ID_ID here (the same as the default
# ID_ID, but without repetition in the serial part).
ATTRS{idVendor}=="0951", ATTRS{idProduct}=="16ff", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_2Ch-$env{ID_USB_INTERFACE_NUM}"
ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1702", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_Hi-Res_2Ch-$env{ID_USB_INTERFACE_NUM}"
ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1703", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_3D_8Ch-$env{ID_USB_INTERFACE_NUM}"
# OnePlus Type-C Bullets (ED117)
ATTRS{idVendor}=="2a70", ATTRS{idProduct}=="1881", ENV{PULSE_PROFILE_SET}="simple-headphones-mic.conf"
# ID 1395:005e is for Sennheiser GSX 1000
# ID 1395:00a0 is for Sennheiser GSX 1000
# ID 1395:005f is for Sennheiser GSX 1200
# ID 1395:00a1 is for Sennheiser GSX 1200
ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005e", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00a0", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005f", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00a1", ENV{PULSE_PROFILE_SET}="sennheiser-gsx.conf"
GOTO="pulseaudio_end"
LABEL="pulseaudio_check_pci"
# Creative SoundBlaster Audigy-based cards
# EMU10k2/CA0100/CA0102/CA10200
ATTRS{vendor}=="0x1102", ATTRS{device}=="0x0004", ENV{PULSE_PROFILE_SET}="audigy.conf"
# CA0108/CA10300
ATTRS{vendor}=="0x1102", ATTRS{device}=="0x0008", ENV{PULSE_PROFILE_SET}="audigy.conf"
GOTO="pulseaudio_end"
LABEL="pulseaudio_firewire_quirk"
# Focusrite Saffire Pro 10/26 i/o has a quirk to disappear from IEEE 1394 bus when losing connections.
# https://bugzilla.kernel.org/show_bug.cgi?id=199365
ENV{ID_VENDOR_ID}=="0x00130e", ENV{ID_MODEL_ID}=="0x000003", ENV{PULSE_IGNORE}="1"
# Both of Saffire Pro 10 i/o and Liquid Saffire 56 have the same ID_MODEL_ID
# (0x000006), but Liquid Saffire 56 doesn't suffer from the problem, so we
# can't use ID_MODEL_ID to identify the problematic card. ID_MODEL works
# better here.
ENV{ID_VENDOR_ID}=="0x00130e", ENV{ID_MODEL}=="Pro10IO" ENV{PULSE_IGNORE}="1"
# Focusrite Saffire Pro 10 i/o and Pro 26 i/o have a quirk to disappear from
# IEEE 1394 bus when breaking connections. This brings an issue for PulseAudio
# to continue the routine for ever that:
# - detecting sound card
# - starting PCM substream
# - stopping the PCM substream
# - the card disappears
# - the card appears
# In detail, see: https://bugzilla.kernel.org/show_bug.cgi?id=199365
ATTRS{vendor}=="0x00130e", ATTRS{model}=="0x00000[36]", ATTRS{units}=="0x00a02d:0x010001", ENV{PULSE_IGNORE}="1"
LABEL="pulseaudio_end"

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,8 @@ typedef struct pa_alsa_port_data pa_alsa_port_data;
#include "alsa-util.h"
#include "alsa-ucm.h"
#define POSITION_MASK_CHANNELS 8
typedef enum pa_alsa_switch_use {
PA_ALSA_SWITCH_IGNORE,
PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */
@ -113,6 +115,8 @@ struct pa_alsa_mixer_id {
int index;
};
char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id);
/* An option belongs to an element and refers to one enumeration item
* of the element is an enumeration item, or a switch status if the
* element is a switch item. */
@ -152,7 +156,7 @@ struct pa_alsa_element {
long constant_volume;
bool override_map:1;
unsigned int override_map;
bool direction_try_other:1;
bool has_dB:1;
@ -160,7 +164,7 @@ struct pa_alsa_element {
long volume_limit; /* -1 for no configured limit */
double min_dB, max_dB;
pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][2];
pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
unsigned n_channels;
pa_channel_position_mask_t merged_mask;
@ -177,8 +181,8 @@ struct pa_alsa_jack {
snd_mixer_t *mixer_handle;
char *mixer_device_name;
struct pa_alsa_mixer_id alsa_id;
char *name; /* E g "Headphone" */
char *alsa_name; /* E g "Headphone Jack" */
bool has_control; /* is the jack itself present? */
bool plugged_in; /* is this jack currently plugged in? */
snd_mixer_elem_t *melem; /* Jack detection handle */
@ -194,7 +198,7 @@ struct pa_alsa_jack {
bool append_pcm_to_name;
};
pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name);
pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index);
void pa_alsa_jack_free(pa_alsa_jack *jack);
void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
@ -211,7 +215,7 @@ struct pa_alsa_path {
char *name;
char *description_key;
char *description;
char *available_group;
char *availability_group;
pa_device_port_type_t device_port_type;
unsigned priority;
bool autodetect_eld_device;
@ -281,6 +285,7 @@ struct pa_alsa_mapping {
char *name;
char *description;
char *description_key;
unsigned priority;
pa_alsa_direction_t direction;
/* These are copied over to the resultant sink/source */
@ -322,6 +327,7 @@ struct pa_alsa_profile {
char *name;
char *description;
char *description_key;
unsigned priority;
char *input_name;

View file

@ -1494,6 +1494,7 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_cvolume r;
char volume_buf[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
bool deferred_volume = !!(s->flags & PA_SINK_DEFERRED_VOLUME);
bool write_to_hw = !deferred_volume;
pa_assert(u);
pa_assert(u->mixer_path);
@ -1502,7 +1503,14 @@ static void sink_set_volume_cb(pa_sink *s) {
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, deferred_volume, !deferred_volume) < 0)
/* If the set_volume() is called because of ucm active_port changing, the
* volume should be written to hw immediately, otherwise this volume will be
* overridden by calling get_volume_cb() which is called by
* _disdev/_enadev() -> io_mixer_callback() */
if (u->ucm_context && s->port_changing)
write_to_hw = true;
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, deferred_volume, write_to_hw) < 0)
return;
/* Shift down by the base volume, so that 0dB becomes maximum volume */
@ -1825,6 +1833,9 @@ static int process_rewind(struct userdata *u) {
pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
if (rewind_nbytes == 0)
goto rewind_done;
if (PA_UNLIKELY((unused = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
if ((err = try_recover(u, "snd_pcm_avail", (int) unused)) < 0) {
pa_log_warn("Trying to recover from underrun failed during rewind");
@ -1877,8 +1888,11 @@ static int process_rewind(struct userdata *u) {
u->after_rewind = true;
return 0;
}
} else
} else {
pa_log_debug("Mhmm, actually there is nothing to rewind.");
if (u->use_tsched)
increase_watermark(u);
}
rewind_done:
pa_sink_process_rewind(u->sink, 0);
@ -2107,7 +2121,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
NULL, (pa_free_cb_t) pa_alsa_mixer_free);
mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device");
mdev = mapping ? pa_proplist_gets(mapping->proplist, "alsa.mixer_device") : NULL;
if (mdev) {
u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, true);
} else {
@ -2267,7 +2281,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
bool volume_is_set;
bool mute_is_set;
pa_alsa_profile_set *profile_set = NULL;
void *state = NULL;
void *state;
pa_assert(m);
pa_assert(ma);
@ -2563,6 +2577,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);
state = NULL;
while ((key = pa_proplist_iterate(mapping->proplist, &state)))
pa_proplist_sets(data.proplist, key, pa_proplist_gets(mapping->proplist, key));
}
@ -2600,7 +2615,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
if (u->ucm_context) {
pa_device_port *port;
void *state;
unsigned h_prio = 0;
PA_HASHMAP_FOREACH(port, u->sink->ports, state) {
if (!h_prio || port->priority > h_prio)

View file

@ -1365,6 +1365,7 @@ static void source_set_volume_cb(pa_source *s) {
pa_cvolume r;
char volume_buf[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
bool deferred_volume = !!(s->flags & PA_SOURCE_DEFERRED_VOLUME);
bool write_to_hw = !deferred_volume;
pa_assert(u);
pa_assert(u->mixer_path);
@ -1373,7 +1374,14 @@ static void source_set_volume_cb(pa_source *s) {
/* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, deferred_volume, !deferred_volume) < 0)
/* If the set_volume() is called because of ucm active_port changing, the
* volume should be written to hw immediately, otherwise this volume will be
* overridden by calling get_volume_cb() which is called by
* _disdev/_enadev() -> io_mixer_callback() */
if (u->ucm_context && s->port_changing)
write_to_hw = true;
if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, deferred_volume, write_to_hw) < 0)
return;
/* Shift down by the base volume, so that 0dB becomes maximum volume */
@ -1813,7 +1821,7 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
NULL, (pa_free_cb_t) pa_alsa_mixer_free);
mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device");
mdev = mapping ? pa_proplist_gets(mapping->proplist, "alsa.mixer_device") : NULL;
if (mdev) {
u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, false);
} else {
@ -1972,7 +1980,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
bool volume_is_set;
bool mute_is_set;
pa_alsa_profile_set *profile_set = NULL;
void *state = NULL;
void *state;
pa_assert(m);
pa_assert(ma);
@ -2250,6 +2258,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);
state = NULL;
while ((key = pa_proplist_iterate(mapping->proplist, &state)))
pa_proplist_sets(data.proplist, key, pa_proplist_gets(mapping->proplist, key));
}
@ -2286,7 +2295,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
if (u->ucm_context) {
pa_device_port *port;
void *state;
unsigned h_prio = 0;
PA_HASHMAP_FOREACH(port, u->source->ports, state) {
if (!h_prio || port->priority > h_prio)

View file

@ -691,7 +691,7 @@ static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
if (!sub || !*sub) {
pa_xfree(sub);
pa_log_warn("Can't match media roles for modifer %s", mod_name);
pa_log_warn("Can't match media roles for modifier %s", mod_name);
return NULL;
}
@ -757,13 +757,11 @@ static void append_lost_relationship(pa_alsa_ucm_device *dev) {
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
char *card_name;
const char **verb_list;
const char **verb_list, *value;
int num_verbs, i, err = 0;
/* support multiple card instances, address card directly by index */
card_name = pa_sprintf_malloc("hw:%i", card_index);
if (card_name == NULL)
return -ENOMEM;
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
/* fallback longname: is UCM available for this card ? */
@ -771,22 +769,36 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
err = snd_card_get_name(card_index, &card_name);
if (err < 0) {
pa_log("Card can't get card_name from card_index %d", card_index);
err = -PA_ALSA_ERR_UNSPECIFIED;
goto name_fail;
}
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
pa_log_info("UCM not available for card %s", card_name);
err = -PA_ALSA_ERR_UCM_OPEN;
goto ucm_mgr_fail;
}
}
err = snd_use_case_get(ucm->ucm_mgr, "=Linked", &value);
if (err >= 0) {
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
free((void *)value);
pa_log_info("Empty (linked) UCM for card %s", card_name);
err = -PA_ALSA_ERR_UCM_LINKED;
goto ucm_verb_fail;
}
free((void *)value);
}
pa_log_info("UCM available for card %s", card_name);
/* get a list of all UCM verbs (profiles) for this card */
num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list);
if (num_verbs < 0) {
pa_log("UCM verb list not found for %s", card_name);
err = -PA_ALSA_ERR_UNSPECIFIED;
goto ucm_verb_fail;
}
@ -806,7 +818,7 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
if (!ucm->verbs) {
pa_log("No UCM verb is valid for %s", card_name);
err = -1;
err = -PA_ALSA_ERR_UCM_NO_VERB;
}
snd_use_case_free_list(verb_list, num_verbs);
@ -950,10 +962,10 @@ static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle,
PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) {
if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) {
pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name);
pa_log_warn("Could not probe path: %s, using s/w volume", path->name);
pa_hashmap_remove(data->paths, profile);
} else if (!path->has_volume) {
pa_log_warn("Path %s is not a volume control", data->path->name);
pa_log_warn("Path %s is not a volume control", path->name);
pa_hashmap_remove(data->paths, profile);
} else
pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name);
@ -1065,7 +1077,7 @@ static void ucm_add_port_combination(
pa_device_port_new_data_set_type(&port_data, type);
pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
if (jack)
pa_device_port_new_data_set_available_group(&port_data, jack->name);
pa_device_port_new_data_set_availability_group(&port_data, jack->name);
port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
pa_device_port_new_data_done(&port_data);
@ -1526,6 +1538,32 @@ static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifi
pa_channel_map_init(&m->channel_map);
}
static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *device_str, bool is_sink) {
pa_alsa_mapping *m;
char *mapping_name;
size_t ucm_alibpref_len = 0;
const char *value;
/* find private alsa-lib's configuration device prefix */
if (snd_use_case_get(ucm->ucm_mgr, "_alibpref", &value) == 0) {
if (value[0] && pa_startswith(device_str, value))
ucm_alibpref_len = strlen(value);
free((void *)value);
}
mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str + ucm_alibpref_len, is_sink ? "sink" : "source");
m = pa_alsa_mapping_get(ps, mapping_name);
if (!m)
pa_log("No mapping for %s", mapping_name);
pa_xfree(mapping_name);
return m;
}
static int ucm_create_mapping_direction(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
@ -1537,19 +1575,14 @@ static int ucm_create_mapping_direction(
bool is_sink) {
pa_alsa_mapping *m;
char *mapping_name;
unsigned priority, rate, channels;
mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink);
m = pa_alsa_mapping_get(ps, mapping_name);
if (!m) {
pa_log("No mapping for %s", mapping_name);
pa_xfree(mapping_name);
if (!m)
return -1;
}
pa_log_debug("UCM mapping: %s dev %s", mapping_name, device_name);
pa_xfree(mapping_name);
pa_log_debug("UCM mapping: %s dev %s", m->name, device_name);
priority = is_sink ? device->playback_priority : device->capture_priority;
rate = is_sink ? device->playback_rate : device->capture_rate;
@ -1594,18 +1627,13 @@ static int ucm_create_mapping_for_modifier(
bool is_sink) {
pa_alsa_mapping *m;
char *mapping_name;
mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source");
m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink);
m = pa_alsa_mapping_get(ps, mapping_name);
if (!m) {
pa_log("no mapping for %s", mapping_name);
pa_xfree(mapping_name);
if (!m)
return -1;
}
pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name);
pa_xfree(mapping_name);
pa_log_info("UCM mapping: %s modifier %s", m->name, mod_name);
if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */
m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
@ -1707,7 +1735,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control);
return NULL;
}
j = pa_alsa_jack_new(NULL, mixer_device_name, name);
j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0);
PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
finish:
@ -1941,7 +1969,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) {
continue;
}
has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL;
has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL;
pa_alsa_jack_set_has_control(dev->jack, has_control);
pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control);
}

View file

@ -731,7 +731,7 @@ snd_pcm_t *pa_alsa_open_by_device_string(
if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) {
char *t;
t = pa_sprintf_malloc("plug:%s", d);
t = pa_sprintf_malloc("plug:SLAVE='%s'", d);
pa_xfree(d);
d = t;
@ -1180,7 +1180,7 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
pa_log_debug(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
@ -1189,7 +1189,7 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa
(unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
pa_alsa_dump(PA_LOG_DEBUG, pcm);
} PA_ONCE_END;
/* Mhmm, let's try not to fail completely */
@ -1246,7 +1246,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(ngettext("snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n"
pa_log_debug(ngettext("snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
@ -1256,7 +1256,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes
(unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
pa_alsa_dump(PA_LOG_DEBUG, pcm);
} PA_ONCE_END;
/* Mhmm, let's try not to fail completely */
@ -1274,7 +1274,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
pa_log_debug(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
@ -1283,7 +1283,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes
(unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
pa_alsa_dump(PA_LOG_DEBUG, pcm);
} PA_ONCE_END;
/* Mhmm, let's try not to fail completely */
@ -1336,7 +1336,7 @@ int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas
k >= pa_bytes_per_second(ss)*10))
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(ngettext("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
pa_log_debug(ngettext("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.",
@ -1345,7 +1345,7 @@ int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas
(unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
pa_alsa_dump(PA_LOG_DEBUG, pcm);
} PA_ONCE_END;
return r;
@ -1635,8 +1635,8 @@ static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer,
return NULL;
}
snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) {
return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device);
snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) {
return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device);
}
snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) {
@ -1752,7 +1752,7 @@ snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, boo
if (!pm && pa_strneq(dev, "hw:", 3)) {
const char *s = dev + 3;
int card_index;
while (*s && *s >= 0 && *s <= '9') s++;
while (*s && *s >= '0' && *s <= '9') s++;
if (*s == '\0' && pa_atoi(dev + 3, &card_index) >= 0) {
PA_HASHMAP_FOREACH_KV(dev2, pm, mixers, state) {
if (pm->card_index == card_index) {

View file

@ -33,6 +33,13 @@
#include "alsa-mixer.h"
enum {
PA_ALSA_ERR_UNSPECIFIED = 1,
PA_ALSA_ERR_UCM_OPEN = 1000,
PA_ALSA_ERR_UCM_NO_VERB = 1001,
PA_ALSA_ERR_UCM_LINKED = 1002
};
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss, /* modified at return */
@ -141,7 +148,7 @@ const char* pa_alsa_strerror(int errnum);
bool pa_alsa_may_tsched(bool want);
snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device);
snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device);
snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device);
snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe);

View file

@ -28,6 +28,9 @@ required-any = any
state.plugged = unknown
state.unplugged = unknown
[Jack Line - Input]
required-any = any
[Element Capture]
switch = mute
volume = merge

View file

@ -29,6 +29,9 @@ required-any = any
state.plugged = unknown
state.unplugged = unknown
[Jack Mic - Input]
required-any = any
[Element Capture]
switch = mute
volume = merge

View file

@ -0,0 +1,5 @@
; Some gaming devices have a separate "chat" device, this is for voice chat
; while playing games. This device is just a fairly standard analog mono
; device, but it's nicer to make it clear that this is the "chat" device
; as is mentioned in the marketing info and manual.
.include analog-output.conf.common

View file

@ -13,17 +13,24 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; Path for mixers that have a 'Headphone2' control
; Path for the second headphone output on dual-headphone machines.
;
; See analog-output.conf.common for an explanation on the directives
[General]
priority = 98
description-key = analog-output-headphones
[Properties]
device.icon_name = audio-headphones
; HP EliteDesk 800 SFF Headphone
[Jack Front Headphone,1]
required-any = any
; HP EliteDesk 800 DM Headphone
[Jack Front Headphone Surround]
required-any = any
[Element Hardware Master]
switch = mute
volume = merge
@ -47,6 +54,13 @@ volume = off
switch = mute
volume = zero
[Element Headphone,1]
required-any = any
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
[Element Headphone+LO]
switch = mute
volume = zero
@ -56,7 +70,7 @@ switch = off
volume = off
[Element Headphone2]
required = any
required-any = any
switch = mute
volume = merge
override-map.1 = all
@ -70,9 +84,11 @@ volume = off
switch = off
volume = off
; On some machines Front is actually a part of the Headphone path
; On some machines, the Front Volume Control is shared by Headphone and Lineout,
; or Headphone and Speaker, but they have independent Volume Switch. Here only
; use switch to mute Lineout or Speaker.
[Element Front]
switch = mute
switch = off
volume = zero
[Element Rear]

View file

@ -35,6 +35,10 @@ state.unplugged = unknown
[Jack Front Headphone]
required-any = any
; HP EliteDesk 800 DM Headset
[Jack Front Headphone Front]
required-any = any
[Jack Front Headphone Phantom]
required-any = any
state.plugged = unknown
@ -52,6 +56,9 @@ state.unplugged = unknown
[Jack Headphone Mic]
required-any = any
[Jack Headphone - Output]
required-any = any
[Element Hardware Master]
switch = mute
volume = merge
@ -86,6 +93,13 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
; This path is intended to control the first headphones, not
; the second headphones. But it should not hurt if we leave the second
; headphone jack enabled nonetheless.
[Element Headphone,1]
switch = mute
volume = zero
[Element Headset]
required-any = any
switch = mute
@ -112,9 +126,11 @@ volume = off
switch = off
volume = off
; On some machines Front is actually a part of the Headphone path
; On some machines, the Front Volume Control is shared by Headphone and Lineout,
; or Headphone and Speaker, but they have independent Volume Switch. Here only
; use switch to mute Lineout or Speaker.
[Element Front]
switch = mute
switch = off
volume = zero
[Element Rear]
@ -157,4 +173,8 @@ volume = off
switch = off
volume = off
[Element Speaker Center/LFE]
switch = off
volume = off
.include analog-output.conf.common

View file

@ -127,6 +127,10 @@ required-any = any
switch = off
volume = off
[Element Headphone,1]
switch = off
volume = off
[Element Headphone2]
switch = off
volume = off
@ -181,6 +185,12 @@ volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Center/LFE]
switch = mute
volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Bass Speaker]
switch = off
volume = off

View file

@ -44,6 +44,10 @@ override-map.2 = all-left,all-right
switch = mute
volume = zero
[Element Headphone,1]
switch = mute
volume = zero
[Element Headphone+LO]
switch = mute
volume = zero

View file

@ -76,6 +76,10 @@ volume = off
switch = mute
volume = zero
[Element Headphone,1]
switch = mute
volume = zero
[Element Headphone2]
switch = mute
volume = zero
@ -174,4 +178,10 @@ volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Center/LFE]
switch = mute
volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
.include analog-output.conf.common

View file

@ -69,6 +69,9 @@ required-any = any
state.plugged = unknown
state.unplugged = unknown
[Jack Speaker - Output]
required-any = any
[Element Hardware Master]
switch = mute
volume = merge
@ -85,12 +88,23 @@ override-map.2 = all-left,all-right
switch = off
volume = off
; Make sure the internal speakers are not auto-muted once the system has speakers
[Element Auto-Mute Mode]
enumeration = select
[Option Auto-Mute Mode:Disabled]
name = analog-output-speaker
; This profile path is intended to control the speaker, let's mute headphones
; else there will be a spike when plugging in headphones
[Element Headphone]
switch = off
volume = off
[Element Headphone,1]
switch = off
volume = off
[Element Headphone2]
switch = off
volume = off
@ -217,6 +231,12 @@ volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Center/LFE]
switch = mute
volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Speaker CLFE]
switch = mute
volume = merge

View file

@ -79,4 +79,10 @@ volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
[Element Center/LFE]
switch = mute
volume = merge
override-map.1 = all-center
override-map.2 = all-center,lfe
.include analog-output.conf.common

View file

@ -53,6 +53,8 @@
; [General]
; type = ... # The device type. It's highly recommended to set a type for every path.
; # See parse_type() in alsa-mixer.c for supported values.
; priority = ... # Priority for this path
; description-key = ... # The path description is looked up from a table in path_verify() in
; # src/modules/alsa/alsa-mixer.c. By default the path name (i.e. the file name

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort
type = hdmi
priority = 59
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 2
type = hdmi
priority = 58
eld-device = auto

View file

@ -0,0 +1,12 @@
[General]
description = HDMI / DisplayPort 11
type = hdmi
priority = 49
eld-device = auto
[Properties]
device.icon_name = video-display
[Jack HDMI/DP]
append-pcm-to-name = yes
required = ignore

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 3
type = hdmi
priority = 57
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 4
type = hdmi
priority = 56
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 5
type = hdmi
priority = 55
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 6
type = hdmi
priority = 54
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 7
type = hdmi
priority = 53
eld-device = auto

View file

@ -1,5 +1,6 @@
[General]
description = HDMI / DisplayPort 8
type = hdmi
priority = 52
eld-device = auto

View file

@ -0,0 +1,12 @@
[General]
description = HDMI / DisplayPort 9
type = hdmi
priority = 51
eld-device = auto
[Properties]
device.icon_name = video-display
[Jack HDMI/DP]
append-pcm-to-name = yes
required = ignore

View file

@ -0,0 +1,12 @@
[General]
description = HDMI / DisplayPort 10
type = hdmi
priority = 50
eld-device = auto
[Properties]
device.icon_name = video-display
[Jack HDMI/DP]
append-pcm-to-name = yes
required = ignore

View file

@ -23,10 +23,12 @@
; Steelseries Arctis 7
; Steelseries Arctis Pro Wireless.
; Lucidsound LS31
;
; This path doesn't provide hardware volume control, because the stereo
; output is controlled by the PCM element with index 1, and currently
; PulseAudio only supports elements with index 0.
[General]
description-key = analog-output-headphones
[Element PCM,1]
volume = merge
switch = mute
override-map.1 = all
override-map.2 = all-left,all-right

View file

@ -0,0 +1,5 @@
[Element PCM,1]
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right

View file

@ -0,0 +1,94 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; Creative Sound Blaster Audigy product line
;
; These are just copies of the mappings we find in default.conf, with the
; small change of making analog-stereo and analog-mono non-fallback mappings.
; This is needed because these cards only support duplex profiles with mono
; inputs, and in the default configuration, with stereo being a fallback
; mapping, the mono mapping is never tried.
;
; See default.conf for an explanation on the directives used here.
[General]
auto-profiles = yes
# Based on stereo-fallback
[Mapping analog-stereo]
device-strings = hw:%f
channel-map = front-left,front-right
paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
priority = 1
# Based on mono-fallback
[Mapping analog-mono]
device-strings = hw:%f
channel-map = mono
paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono
paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic
priority = 1
# The rest of these are identical to what's in default.conf
[Mapping analog-surround-21]
device-strings = surround21:%f
channel-map = front-left,front-right,lfe
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 13
direction = output
[Mapping analog-surround-40]
device-strings = surround40:%f
channel-map = front-left,front-right,rear-left,rear-right
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 12
direction = output
[Mapping analog-surround-41]
device-strings = surround41:%f
channel-map = front-left,front-right,rear-left,rear-right,lfe
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 13
direction = output
[Mapping analog-surround-50]
device-strings = surround50:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 12
direction = output
[Mapping analog-surround-51]
device-strings = surround51:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 13
direction = output
[Mapping analog-surround-71]
device-strings = surround71:%f
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
description = Analog Surround 7.1
paths-output = analog-output analog-output-lineout analog-output-speaker
priority = 12
direction = output
[Mapping iec958-stereo]
device-strings = iec958:%f
channel-map = left,right
paths-input = iec958-stereo-input
paths-output = iec958-stereo-output
priority = 5

View file

@ -0,0 +1,68 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; Behringer U-Phoria UMC22
;
; Default mapping only allows to use stereo input and sound card has two
; physical input channels.
;
; However in case of only using a single input channel (like condenser
; microphone) only one channel will have any sound, which is often
; inconvenient for casual use.
;
; This config includes mono input options which makes it much more
; friendly in single input configuration.
;
; This config also removes default digital input/output mappings that do
; not physically exist on this card.
;
; Added by Nazar Mokrynskyi <nazar@mokrynskyi.com>
[General]
auto-profiles = yes
[Mapping analog-stereo-input]
device-strings = hw:%f
channel-map = left,right
paths-input = analog-input-mic
direction = input
priority = 4
[Mapping analog-mono]
device-strings = hw:%f
channel-map = mono,mono
paths-input = analog-input-mic
direction = input
priority = 3
[Mapping analog-mono-left]
device-strings = hw:%f
channel-map = mono,aux1
paths-input = analog-input-mic
direction = input
priority = 2
[Mapping analog-mono-right]
device-strings = hw:%f
channel-map = aux1,mono
paths-input = analog-input-mic
direction = input
priority = 1
[Mapping analog-stereo-output]
device-strings = front:%f
channel-map = left,right
paths-output = analog-output
direction = output

View file

@ -44,7 +44,11 @@
; [Mapping id]
; device-strings = ... # ALSA device string. %f will be replaced by the card identifier.
; channel-map = ... # Channel mapping to use for this device
; description = ...
; description = ... # Description for the mapping. Note that it's better to set the description
; # in the well_known_descriptions table in alsa-mixer.c than with this
; # option, because the descriptions in alsa-mixer.c are translatable.
; description-key = ... # A custom key for the well_known_descriptions table (by default the mapping
; # name is used).
; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed.
; # If multiple are found to be working they will be available as device ports
; paths-output = ...
@ -58,6 +62,8 @@
; exact-channels = yes | no # If no, and the exact number of channels is not supported,
; # allow device to be opened with another channel count
; fallback = no | yes # This mapping will only be considered if all non-fallback mappings fail
; intended-roles = ... # Set the device.intended_roles property for the sink/source.
;
; [Profile id]
; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be
; # defined in this file too
@ -455,6 +461,102 @@ channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-stereo-extra8]
description = Digital Stereo (HDMI 9)
device-strings = hdmi:%f,8
paths-output = hdmi-output-8
channel-map = left,right
priority = 7
direction = output
[Mapping hdmi-surround-extra8]
description = Digital Surround 5.1 (HDMI 9)
device-strings = hdmi:%f,8
paths-output = hdmi-output-8
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-surround71-extra8]
description = Digital Surround 7.1 (HDMI 9)
device-strings = hdmi:%f,8
paths-output = hdmi-output-8
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
priority = 6
direction = output
[Mapping hdmi-dts-surround-extra8]
description = Digital Surround 5.1 (HDMI 9/DTS)
device-strings = dcahdmi:%f,8
paths-output = hdmi-output-8
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-stereo-extra9]
description = Digital Stereo (HDMI 10)
device-strings = hdmi:%f,9
paths-output = hdmi-output-9
channel-map = left,right
priority = 7
direction = output
[Mapping hdmi-surround-extra9]
description = Digital Surround 5.1 (HDMI 10)
device-strings = hdmi:%f,9
paths-output = hdmi-output-9
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-surround71-extra9]
description = Digital Surround 7.1 (HDMI 10)
device-strings = hdmi:%f,9
paths-output = hdmi-output-9
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
priority = 6
direction = output
[Mapping hdmi-dts-surround-extra9]
description = Digital Surround 5.1 (HDMI 10/DTS)
device-strings = dcahdmi:%f,9
paths-output = hdmi-output-9
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-stereo-extra10]
description = Digital Stereo (HDMI 11)
device-strings = hdmi:%f,10
paths-output = hdmi-output-10
channel-map = left,right
priority = 7
direction = output
[Mapping hdmi-surround-extra10]
description = Digital Surround 5.1 (HDMI 11)
device-strings = hdmi:%f,10
paths-output = hdmi-output-10
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping hdmi-surround71-extra10]
description = Digital Surround 7.1 (HDMI 11)
device-strings = hdmi:%f,10
paths-output = hdmi-output-10
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
priority = 6
direction = output
[Mapping hdmi-dts-surround-extra10]
description = Digital Surround 5.1 (HDMI 11/DTS)
device-strings = dcahdmi:%f,10
paths-output = hdmi-output-10
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe
priority = 6
direction = output
[Mapping multichannel-output]
device-strings = hw:%f
channel-map = left,right,rear-left,rear-right
@ -462,7 +564,6 @@ exact-channels = false
fallback = yes
priority = 1
direction = output
paths-output = multichannel-output
[Mapping multichannel-input]
device-strings = hw:%f
@ -471,7 +572,6 @@ exact-channels = false
fallback = yes
priority = 1
direction = input
paths-input = multichannel-input
; An example for defining multiple-sink profiles
#[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo]

View file

@ -0,0 +1,35 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; HP Thunderbolt Dock 120W G2
;
; This dock has a 3.5mm headset connector. Both input and output are stereo.
;
; There's a separate speakerphone module called "HP Thunderbolt Dock Audio
; Module", which can be attached to this dock. The module will appear in ALSA
; as a separate USB sound card, configuration for it is in
; hp-tbt-dock-audio-module.conf.
[General]
auto-profiles = no
[Mapping analog-stereo-headset]
device-strings = hw:%f,0,0
channel-map = left,right
[Profile output:analog-stereo-headset+input:analog-stereo-headset]
output-mappings = analog-stereo-headset
input-mappings = analog-stereo-headset
skip-probe = yes

View file

@ -0,0 +1,36 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; HP Thunderbolt Dock Audio Module
;
; This device attaches to the "HP Thunderbolt Dock 120W G2" dock. The audio
; module provides a speakerphone with echo cancellation and appears in ALSA as
; a USB sound card with stereo input and output.
;
; The dock itself has a 3.5mm headset connector and appears as a separate USB
; sound card, configuration for it is in hp-tbt-dock-120w-g2.conf.
[General]
auto-profiles = no
[Mapping analog-stereo-speakerphone]
device-strings = hw:%f,0,0
channel-map = left,right
intended-roles = phone
[Profile output:analog-stereo-speakerphone+input:analog-stereo-speakerphone]
output-mappings = analog-stereo-speakerphone
input-mappings = analog-stereo-speakerphone
skip-probe = yes

View file

@ -0,0 +1,111 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; Native Instruments Komplete Audio 6
;
; This card has three stereo pairs of input and three stereo pairs of
; output.
;
; We knowingly only define a subset of the theoretically possible
; mapping combinations as profiles here.
;
; See default.conf for an explanation on the directives used here.
[General]
auto-profiles = no
[Mapping analog-stereo-out-ab]
description = Analog Stereo 1/2
device-strings = hw:%f,0,0
channel-map = left,right,aux0,aux1,aux2,aux3
direction = output
[Mapping analog-stereo-out-cd]
description = Analog Stereo 3/4
device-strings = hw:%f,0,0
channel-map = aux0,aux1,left,right,aux2,aux3
direction = output
[Mapping stereo-out-ef]
description = Analog Stereo 5/6
device-strings = hw:%f,0,0
channel-map = aux0,aux1,aux2,aux3,left,right
direction = output
[Mapping analog-mono-in-a]
description = Analog Mono Input 1
device-strings = hw:%f,0,0
channel-map = mono,aux0,aux1,aux2,aux3,aux4
direction = input
[Mapping analog-mono-in-b]
description = Anlog Mono Input 2
device-strings = hw:%f,0,0
channel-map = aux0,mono,aux1,aux2,aux3,aux4
direction = input
[Mapping analog-stereo-in-ab]
description = Analog Stereo Input 1/2
device-strings = hw:%f,0,0
channel-map = left,right,aux0,aux1,aux2,aux3
direction = input
[Mapping analog-stereo-in-cd]
description = Analog Stereo Input 3/4
device-strings = hw:%f,0,0
channel-map = aux0,aux1,left,right,aux2,aux3
direction = input
[Mapping stereo-in-ef]
description = Stereo Input 5/6
device-strings = hw:%f,0,0
channel-map = aux0,aux1,aux2,aux3,left,right
direction = input
[Profile output:analog-stereo-out-ab+input:analog-stereo-in-ab]
description = Analog Stereo Output 1/2, Analog Stereo Input 1/2
output-mappings = analog-stereo-out-ab
input-mappings = analog-stereo-in-ab
priority = 100
skip-probe = yes
[Profile output:analog-stereo-out-ab+input:analog-mono-in-a]
description = Analog Stereo Output 1/2, Analog Mono Input 1
output-mappings = analog-stereo-out-ab
input-mappings = analog-mono-in-a
priority = 95
skip-probe = yes
[Profile output:analog-stereo-out-ab+input:analog-mono-in-b]
description = Analog Stereo Output 1/2, Analog Mono Input 2
output-mappings = analog-stereo-out-ab
input-mappings = analog-mono-in-b
priority = 90
skip-probe = yes
[Profile output:analog-stereo-out-cd+input:analog-stereo-in-cd]
description = Analog Stereo Output 3/4, Analog Stereo Input 3/4
output-mappings = analog-stereo-out-cd
input-mappings = analog-stereo-in-cd
priority = 80
skip-probe = yes
[Profile output:stereo-out-ef+input:stereo-in-ef]
description = Stereo Output 5/6 (S/PDIF), Stereo Input 5/6 (S/PDIF)
output-mappings = stereo-out-ef
input-mappings = stereo-in-ef
priority = 70
skip-probe = yes

View file

@ -0,0 +1,58 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; USB Gaming DAC.
; These devices have two output devices. The first one is mono, meant for
; voice audio, and the second one is 7.1 surround, meant for everything
; else. The 7.1 surround is mapped to headphones within the device.
; The purpose of the mono/7.1 design is to provide separate volume
; controls for voice and other audio, which can be useful in gaming.
;
; Works with:
; Sennheiser GSX 1000
; Sennheiser GSX 1200
;
; See default.conf for an explanation on the directives used here.
[General]
auto-profiles = no
[Mapping analog-chat-output]
device-strings = hw:%f,0
channel-map = mono
paths-output = analog-chat-output
direction = output
priority = 4000
intended-roles = phone
[Mapping analog-output-surround71]
device-strings = hw:%f,1
channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right
paths-output = virtual-surround-7.1
priority = 4100
direction = output
[Mapping analog-chat-input]
device-strings = hw:%f,0
channel-map = mono
paths-input = analog-chat-input
priority = 4100
direction = input
[Profile output:analog-output-surround71+output:analog-output-chat+input:analog-input]
output-mappings = analog-output-surround71 analog-chat-output
input-mappings = analog-chat-input
priority = 5100
skip-probe = yes

View file

@ -0,0 +1,42 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; This is a profile meant for simple (stereo + mic) headphones.
; default.conf also works but using this one will hide some profiles
; that don't make sense like IEC958 and multichannel inputs.
[General]
auto-profiles = yes
[Mapping analog-stereo]
device-strings = front:%f
channel-map = left,right
paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
priority = 15
# If everything else fails, try to use hw:0 as a stereo device...
[Mapping stereo-fallback]
device-strings = hw:%f
fallback = yes
channel-map = front-left,front-right
paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2
paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic
priority = 1
[Mapping analog-mono]
device-strings = hw:%f,0,0
channel-map = mono
direction = input

View file

@ -2,7 +2,7 @@
auto-profiles = yes
[Mapping analog-chat]
description = Chat
description-key = gaming-headset-chat
device-strings = hw:%f,0,0
channel-map = left,right
paths-input = analog-input-mic
@ -10,7 +10,7 @@ paths-output = steelseries-arctis-output-chat-common
intended-roles = phone
[Mapping analog-game]
description = Game
description-key = gaming-headset-game
device-strings = hw:%f,1,0
channel-map = left,right
paths-output = steelseries-arctis-output-game-common

View file

@ -14,36 +14,51 @@
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
; USB gaming headset.
; These headsets usually have two output devices. The first one is mono,
; meant for voice audio, and the second one is stereo, meant for everything
; else. The purpose of this unusual design is to provide separate volume
; These headsets usually have two output devices. The first one is meant
; for voice audio, and the second one is meant for everything else.
; The purpose of this unusual design is to provide separate volume
; controls for voice and other audio, which can be useful in gaming.
;
; Works with:
; Steelseries Arctis 7
; Steelseries Arctis Pro Wireless.
; Lucidsound LS31
; Astro A50
;
; See default.conf for an explanation on the directives used here.
[General]
auto-profiles = yes
[Mapping analog-mono]
[Mapping mono-chat]
description-key = gaming-headset-chat
device-strings = hw:%f,0,0
channel-map = mono
paths-output = usb-gaming-headset-output-mono
paths-input = usb-gaming-headset-input
intended-roles = phone
[Mapping analog-stereo]
[Mapping stereo-chat]
description-key = gaming-headset-chat
device-strings = hw:%f,0,0
channel-map = left,right
paths-output = usb-gaming-headset-output-stereo
paths-input = usb-gaming-headset-input
intended-roles = phone
[Mapping stereo-game]
description-key = gaming-headset-game
device-strings = hw:%f,1,0
channel-map = left,right
paths-output = usb-gaming-headset-output-stereo
direction = output
[Profile output:analog-mono+output:analog-stereo+input:analog-mono]
output-mappings = analog-mono analog-stereo
input-mappings = analog-mono
[Profile output:mono-chat+output:stereo-game+input:mono-chat]
output-mappings = mono-chat stereo-game
input-mappings = mono-chat
priority = 5100
[Profile output:stereo-game+output:stereo-chat+input:mono-chat]
output-mappings = stereo-game stereo-chat
input-mappings = mono-chat
priority = 5100
skip-probe = yes

View file

@ -104,6 +104,15 @@ static const char* const valid_modargs[] = {
#define DEFAULT_DEVICE_ID "0"
#define PULSE_MODARGS "PULSE_MODARGS"
/* dynamic profile priority bonus, for all alsa profiles, the original priority
needs to be less than 0x7fff (32767), then could apply the rule of priority
bonus. So far there are 2 kinds of alsa profiles, one is from alsa ucm, the
other is from mixer profile-sets, their priorities are all far less than 0x7fff
*/
#define PROFILE_PRIO_BONUS 0x8000
struct userdata {
pa_core *core;
pa_module *module;
@ -144,7 +153,7 @@ static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) {
uint32_t idx;
cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data));
cp->priority = ap->priority;
cp->priority = ap->priority ? ap->priority : 1;
cp->input_name = pa_xstrdup(ap->input_name);
cp->output_name = pa_xstrdup(ap->output_name);
@ -459,9 +468,19 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
* as available (well, "unknown" to be precise, but there's little
* practical difference).
*
* When all output ports are unavailable, we know that all sinks are
* unavailable, and therefore the profile is marked unavailable as well.
* The same applies to input ports as well, of course.
* A profile will be marked unavailable:
* only contains output ports and all ports are unavailable
* only contains input ports and all ports are unavailable
* contains both input and output ports and all ports are unavailable
*
* A profile will be awarded priority bonus:
* only contains output ports and at least one port is available
* only contains input ports and at least one port is available
* contains both output and input ports and at least one output port
* and one input port are available
*
* The rest profiles will not be marked unavailable and will not be
* awarded priority bonus
*
* If there are no output ports at all, but the profile contains at least
* one sink, then the output is considered to be available. */
@ -476,6 +495,7 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
bool found_available_output_port = false;
pa_available_t available = PA_AVAILABLE_UNKNOWN;
profile->priority &= ~PROFILE_PRIO_BONUS;
PA_HASHMAP_FOREACH(port, u->card->ports, state2) {
if (!pa_hashmap_get(port->profiles, profile->name))
continue;
@ -493,8 +513,15 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
}
}
if ((has_input_port && !found_available_input_port) || (has_output_port && !found_available_output_port))
available = PA_AVAILABLE_NO;
if ((has_input_port && found_available_input_port && !has_output_port) ||
(has_output_port && found_available_output_port && !has_input_port) ||
(has_input_port && found_available_input_port && has_output_port && found_available_output_port))
profile->priority |= PROFILE_PRIO_BONUS;
if ((has_input_port && !found_available_input_port && has_output_port && !found_available_output_port) ||
(has_input_port && !found_available_input_port && !has_output_port) ||
(has_output_port && !found_available_output_port && !has_input_port))
available = PA_AVAILABLE_NO;
/* We want to update the active profile's status last, so logic that
* may change the active profile based on profile availability status
@ -621,6 +648,7 @@ static void init_jacks(struct userdata *u) {
void *state;
pa_alsa_path* path;
pa_alsa_jack* jack;
char buf[64];
u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
@ -663,9 +691,10 @@ static void init_jacks(struct userdata *u) {
}
}
pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop);
jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0);
jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0);
if (!jack->melem) {
pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id);
pa_log_warn("Jack %s seems to have disappeared.", buf);
pa_alsa_jack_set_has_control(jack, false);
continue;
}
@ -675,6 +704,42 @@ static void init_jacks(struct userdata *u) {
}
}
static void prune_singleton_availability_groups(pa_hashmap *ports) {
pa_device_port *p;
pa_hashmap *group_counts;
void *state, *count;
const char *group;
/* Collect groups and erase those that don't have more than 1 path */
group_counts = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
PA_HASHMAP_FOREACH(p, ports, state) {
if (p->availability_group) {
count = pa_hashmap_get(group_counts, p->availability_group);
pa_hashmap_remove(group_counts, p->availability_group);
pa_hashmap_put(group_counts, p->availability_group, PA_UINT_TO_PTR(PA_PTR_TO_UINT(count) + 1));
}
}
/* Now we have an availability_group -> count map, let's drop all groups
* that have only one member */
PA_HASHMAP_FOREACH_KV(group, count, group_counts, state) {
if (count == PA_UINT_TO_PTR(1))
pa_hashmap_remove(group_counts, group);
}
PA_HASHMAP_FOREACH(p, ports, state) {
if (p->availability_group && !pa_hashmap_get(group_counts, p->availability_group)) {
pa_log_debug("Pruned singleton availability group %s from port %s", p->availability_group, p->name);
pa_xfree(p->availability_group);
p->availability_group = NULL;
}
}
pa_hashmap_free(group_counts);
}
static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) {
char *t;
const char *n;
@ -784,7 +849,9 @@ int pa__init(pa_module *m) {
const char *description;
const char *profile_str = NULL;
char *fn = NULL;
char *udev_args = NULL;
bool namereg_fail = false;
int err = -PA_MODULE_ERR_UNSPECIFIED, rval;
pa_alsa_refcnt_inc();
@ -812,6 +879,47 @@ int pa__init(pa_module *m) {
goto fail;
}
#ifdef HAVE_UDEV
udev_args = pa_udev_get_property(u->alsa_card_index, PULSE_MODARGS);
#endif
if (udev_args) {
bool udev_modargs_success = true;
pa_modargs *temp_ma = pa_modargs_new(udev_args, valid_modargs);
if (temp_ma) {
/* do not try to replace device_id */
if (pa_modargs_remove_key(temp_ma, "device_id") == 0) {
pa_log_warn("Unexpected 'device_id' module argument override ignored from udev " PULSE_MODARGS "='%s'", udev_args);
}
/* Implement modargs override by copying original module arguments
* over udev entry arguments ignoring duplicates. */
if (pa_modargs_merge_missing(temp_ma, u->modargs, valid_modargs) == 0) {
/* swap module arguments */
pa_modargs *old_ma = u->modargs;
u->modargs = temp_ma;
temp_ma = old_ma;
pa_log_info("Applied module arguments override from udev " PULSE_MODARGS "='%s'", udev_args);
} else {
pa_log("Failed to apply module arguments override from udev " PULSE_MODARGS "='%s'", udev_args);
udev_modargs_success = false;
}
pa_modargs_free(temp_ma);
} else {
pa_log("Failed to parse module arguments from udev " PULSE_MODARGS "='%s'", udev_args);
udev_modargs_success = false;
}
pa_xfree(udev_args);
if (!udev_modargs_success)
goto fail;
}
if (pa_modargs_get_value_boolean(u->modargs, "ignore_dB", &ignore_dB) < 0) {
pa_log("Failed to parse ignore_dB argument.");
goto fail;
@ -841,7 +949,12 @@ int pa__init(pa_module *m) {
snd_config_update_free_global();
if (u->use_ucm && !pa_alsa_ucm_query_profiles(&u->ucm, u->alsa_card_index)) {
rval = u->use_ucm ? pa_alsa_ucm_query_profiles(&u->ucm, u->alsa_card_index) : -1;
if (rval == -PA_ALSA_ERR_UCM_LINKED) {
err = -PA_MODULE_ERR_SKIP;
goto fail;
}
if (rval == 0) {
pa_log_info("Found UCM profiles");
u->profile_set = pa_alsa_ucm_add_profile_set(&u->ucm, &u->core->default_channel_map);
@ -918,6 +1031,7 @@ int pa__init(pa_module *m) {
}
add_disabled_profile(data.profiles);
prune_singleton_availability_groups(data.ports);
if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
@ -1009,7 +1123,7 @@ fail:
pa__done(m);
return -1;
return err;
}
int pa__get_n_used(pa_module *m) {

View file

@ -22,7 +22,10 @@
#include <pulsecore/core.h>
#include "bt-codec-api.h"
#define MAX_A2DP_CAPS_SIZE 254
#define DEFAULT_OUTPUT_RATE_REFRESH_INTERVAL_MS 500
typedef struct pa_a2dp_codec_capabilities {
uint8_t size;
@ -35,19 +38,16 @@ typedef struct pa_a2dp_codec_id {
uint16_t vendor_codec_id;
} pa_a2dp_codec_id;
typedef struct pa_a2dp_codec {
/* Unique name of the codec, lowercase and without whitespaces, used for
* constructing identifier, D-Bus paths, ... */
const char *name;
/* Human readable codec description */
const char *description;
typedef struct pa_a2dp_endpoint_conf {
/* A2DP codec id */
pa_a2dp_codec_id id;
/* True if codec is bi-directional and supports backchannel */
bool support_backchannel;
/* Returns true if the codec can be supported on the system */
bool (*can_be_supported)(bool for_encoding);
/* Returns true if codec accepts capabilities, for_encoding is true when
* capabilities are used for encoding */
bool (*can_accept_capabilities)(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding);
@ -63,36 +63,8 @@ typedef struct pa_a2dp_codec {
/* Fill preferred codec configuration, returns size of filled buffer or 0 on failure */
uint8_t (*fill_preferred_configuration)(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]);
/* Initialize codec, returns codec info data and set sample_spec,
* for_encoding is true when codec_info is used for encoding,
* for_backchannel is true when codec_info is used for backchannel */
void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec);
/* Deinitialize and release codec info data in codec_info */
void (*deinit)(void *codec_info);
/* Reset internal state of codec info data in codec_info, returns
* a negative value on failure */
int (*reset)(void *codec_info);
/* Get read block size for codec, it is minimal size of buffer
* needed to decode read_link_mtu bytes of encoded data */
size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
/* Get write block size for codec, it is maximal size of buffer
* which can produce at most write_link_mtu bytes of encoded data */
size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
/* Reduce encoder bitrate for codec, returns new write block size or zero
* if not changed, called when socket is not accepting encoded data fast
* enough */
size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
/* Encode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*encode_buffer)(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
/* Decode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*decode_buffer)(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
} pa_a2dp_codec;
/* Bluetooth codec */
pa_bt_codec bt_codec;
} pa_a2dp_endpoint_conf;
#endif

View file

@ -0,0 +1,603 @@
/***
This file is part of PulseAudio.
Copyright 2020 Sanchayan Maity <sanchayan@asymptotic.io>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/once.h>
#include <pulse/sample.h>
#include <arpa/inet.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-api.h"
#include "a2dp-codec-gst.h"
#include "rtp.h"
static bool can_be_supported(bool for_encoding) {
GstElementFactory *element_factory;
if (for_encoding) {
element_factory = gst_element_factory_find("openaptxenc");
if (element_factory == NULL) {
pa_log_info("aptX encoder element `openaptxenc` not found");
return false;
}
gst_object_unref(element_factory);
} else {
element_factory = gst_element_factory_find("openaptxdec");
if (element_factory == NULL) {
pa_log_info("aptX decoder element `openaptxdec` not found");
return false;
}
gst_object_unref(element_factory);
}
return true;
}
static bool can_accept_capabilities_common(const a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
return false;
if (!(capabilities->frequency & (APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 |
APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000)))
return false;
if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO))
return false;
return true;
}
static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities))
return false;
return can_accept_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);
}
static bool can_accept_capabilities_hd(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities))
return false;
return can_accept_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
}
static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static const char *choose_remote_endpoint_hd(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities_hd(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static void fill_capabilities_common(a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
capabilities->channel_mode = APTX_CHANNEL_MODE_STEREO;
capabilities->frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 |
APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000;
}
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aptx_t *capabilities = (a2dp_aptx_t *) capabilities_buffer;
pa_zero(*capabilities);
fill_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);
return sizeof(*capabilities);
}
static uint8_t fill_capabilities_hd(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aptx_hd_t *capabilities = (a2dp_aptx_hd_t *) capabilities_buffer;
pa_zero(*capabilities);
fill_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
return sizeof(*capabilities);
}
static bool is_configuration_valid_common(const a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {
if (A2DP_GET_VENDOR_ID(config->info) != vendor_id || A2DP_GET_CODEC_ID(config->info) != codec_id) {
pa_log_error("Invalid vendor codec information in configuration");
return false;
}
if (config->frequency != APTX_SAMPLING_FREQ_16000 && config->frequency != APTX_SAMPLING_FREQ_32000 &&
config->frequency != APTX_SAMPLING_FREQ_44100 && config->frequency != APTX_SAMPLING_FREQ_48000) {
pa_log_error("Invalid sampling frequency in configuration");
return false;
}
if (config->channel_mode != APTX_CHANNEL_MODE_STEREO) {
pa_log_error("Invalid channel mode in configuration");
return false;
}
return true;
}
static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_aptx_t *config = (const a2dp_aptx_t *) config_buffer;
if (config_size != sizeof(*config)) {
pa_log_error("Invalid size of config buffer");
return false;
}
return is_configuration_valid_common(config, APTX_VENDOR_ID, APTX_CODEC_ID);
}
static bool is_configuration_valid_hd(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_aptx_hd_t *config = (const a2dp_aptx_hd_t *) config_buffer;
if (config_size != sizeof(*config)) {
pa_log_error("Invalid size of config buffer");
return false;
}
return is_configuration_valid_common(&config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);
}
static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_aptx_t *capabilities, a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {
int i;
static const struct {
uint32_t rate;
uint8_t cap;
} freq_table[] = {
{ 16000U, APTX_SAMPLING_FREQ_16000 },
{ 32000U, APTX_SAMPLING_FREQ_32000 },
{ 44100U, APTX_SAMPLING_FREQ_44100 },
{ 48000U, APTX_SAMPLING_FREQ_48000 }
};
if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id) {
pa_log_error("No supported vendor codec information");
return -1;
}
config->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO)) {
pa_log_error("No supported channel modes");
return -1;
}
config->channel_mode = APTX_CHANNEL_MODE_STEREO;
/* Find the lowest freq that is at least as high as the requested sampling rate */
for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) {
if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
config->frequency = freq_table[i].cap;
break;
}
}
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
for (--i; i >= 0; i--) {
if (capabilities->frequency & freq_table[i].cap) {
config->frequency = freq_table[i].cap;
break;
}
}
if (i < 0) {
pa_log_error("Not suitable sample rate");
return false;
}
}
return 0;
}
static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer;
const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
pa_zero(*config);
if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, APTX_VENDOR_ID, APTX_CODEC_ID) < 0)
return 0;
return sizeof(*config);
}
static uint8_t fill_preferred_configuration_hd(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_aptx_hd_t *config = (a2dp_aptx_hd_t *) config_buffer;
const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
pa_zero(*config);
if (fill_preferred_configuration_common(default_sample_spec, &capabilities->aptx, &config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID) < 0)
return 0;
return sizeof(*config);
}
GstElement *gst_init_aptx(struct gst_info *info, pa_sample_spec *ss, bool for_encoding) {
GstElement *bin, *sink, *src, *capsf;
GstCaps *caps;
GstPad *pad;
const char *aptx_codec_media_type;
ss->format = PA_SAMPLE_S24LE;
if (info->codec_type == APTX_HD) {
switch (info->a2dp_codec_t.aptx_hd_config->aptx.frequency) {
case APTX_SAMPLING_FREQ_16000:
ss->rate = 16000u;
break;
case APTX_SAMPLING_FREQ_32000:
ss->rate = 32000u;
break;
case APTX_SAMPLING_FREQ_44100:
ss->rate = 44100u;
break;
case APTX_SAMPLING_FREQ_48000:
ss->rate = 48000u;
break;
default:
pa_log_error("aptX HD invalid frequency %d", info->a2dp_codec_t.aptx_hd_config->aptx.frequency);
goto fail;
}
switch (info->a2dp_codec_t.aptx_hd_config->aptx.channel_mode) {
case APTX_CHANNEL_MODE_STEREO:
ss->channels = 2;
break;
default:
pa_log_error("aptX HD invalid channel mode %d", info->a2dp_codec_t.aptx_hd_config->aptx.frequency);
goto fail;
}
} else {
switch (info->a2dp_codec_t.aptx_config->frequency) {
case APTX_SAMPLING_FREQ_16000:
ss->rate = 16000u;
break;
case APTX_SAMPLING_FREQ_32000:
ss->rate = 32000u;
break;
case APTX_SAMPLING_FREQ_44100:
ss->rate = 44100u;
break;
case APTX_SAMPLING_FREQ_48000:
ss->rate = 48000u;
break;
default:
pa_log_error("aptX invalid frequency %d", info->a2dp_codec_t.aptx_config->frequency);
goto fail;
}
switch (info->a2dp_codec_t.aptx_config->channel_mode) {
case APTX_CHANNEL_MODE_STEREO:
ss->channels = 2;
break;
default:
pa_log_error("aptX invalid channel mode %d", info->a2dp_codec_t.aptx_config->frequency);
goto fail;
}
}
aptx_codec_media_type = info->codec_type == APTX_HD ? "audio/aptx-hd" : "audio/aptx";
capsf = gst_element_factory_make("capsfilter", "aptx_capsfilter");
if (!capsf) {
pa_log_error("Could not create aptX capsfilter element");
goto fail;
}
caps = gst_caps_new_simple(aptx_codec_media_type,
"rate", G_TYPE_INT, (int) ss->rate,
"channels", G_TYPE_INT, (int) ss->channels,
NULL);
g_object_set(capsf, "caps", caps, NULL);
gst_caps_unref(caps);
if (for_encoding) {
sink = gst_element_factory_make("openaptxenc", "aptx_encoder");
src = capsf;
if (sink == NULL) {
pa_log_error("Could not create aptX encoder element");
goto fail_enc_dec;
}
bin = gst_bin_new("aptx_enc_bin");
} else {
sink = capsf;
src = gst_element_factory_make("openaptxdec", "aptx_decoder");
if (src == NULL) {
pa_log_error("Could not create aptX decoder element");
goto fail_enc_dec;
}
bin = gst_bin_new("aptx_dec_bin");
}
pa_assert(bin);
gst_bin_add_many(GST_BIN(bin), sink, src, NULL);
pa_assert_se(gst_element_link_many(sink, src, NULL));
pad = gst_element_get_static_pad(sink, "sink");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("sink", pad)));
gst_object_unref(GST_OBJECT(pad));
pad = gst_element_get_static_pad(src, "src");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("src", pad)));
gst_object_unref(GST_OBJECT(pad));
return bin;
fail_enc_dec:
gst_object_unref(GST_OBJECT(capsf));
fail:
pa_log_error("aptX initialisation failed");
return NULL;
}
static void *init_common(enum a2dp_codec_type codec_type, bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
GstElement *bin;
struct gst_info *info = NULL;
info = pa_xnew0(struct gst_info, 1);
pa_assert(info);
info->core = core;
info->ss = sample_spec;
if (codec_type == APTX) {
info->codec_type = APTX;
info->a2dp_codec_t.aptx_config = (const a2dp_aptx_t *) config_buffer;
pa_assert(config_size == sizeof(*(info->a2dp_codec_t.aptx_config)));
} else if (codec_type == APTX_HD) {
info->codec_type = APTX_HD;
info->a2dp_codec_t.aptx_hd_config = (const a2dp_aptx_hd_t *) config_buffer;
pa_assert(config_size == sizeof(*(info->a2dp_codec_t.aptx_hd_config)));
} else
pa_assert_not_reached();
if (!(bin = gst_init_aptx(info, sample_spec, for_encoding)))
goto fail;
if (!gst_codec_init(info, for_encoding, bin))
goto fail;
return info;
fail:
if (info)
pa_xfree(info);
return NULL;
}
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
return init_common(APTX, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
}
static void *init_hd(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
return init_common(APTX_HD, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
}
static void deinit(void *codec_info) {
return gst_codec_deinit(codec_info);
}
static int reset(void *codec_info) {
return 0;
}
static int reset_hd(void *codec_info) {
struct gst_info *info = (struct gst_info *) codec_info;
info->seq_num = 0;
return 0;
}
static size_t get_block_size(void *codec_info, size_t link_mtu) {
/* aptX compression ratio is 6:1 and we need to process one aptX frame (4 bytes) at once */
size_t frame_count = (link_mtu / 4);
return frame_count * 4 * 6;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % (4 * 6) == 0);
return (input_size / (4 * 6)) * 4;
}
static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
/* aptX HD compression ratio is 4:1 and we need to process one aptX HD frame (6 bytes) at once, plus aptX HD frames are encapsulated in RTP */
size_t rtp_size = sizeof(struct rtp_header);
size_t frame_count = (link_mtu - rtp_size) / 6;
return frame_count * 6 * 4;
}
static size_t get_encoded_block_size_hd(void *codec_info, size_t input_size) {
size_t rtp_size = sizeof(struct rtp_header);
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % (4 * 6) == 0);
return (input_size / (4 * 6)) * 6 + rtp_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
size_t written;
written = gst_transcode_buffer(codec_info, input_buffer, input_size, output_buffer, output_size, processed);
if (PA_UNLIKELY(*processed == 0 || *processed != input_size))
pa_log_error("aptX encoding error");
return written;
}
static size_t encode_buffer_hd(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct gst_info *info = (struct gst_info *) codec_info;
struct rtp_header *header;
size_t written;
if (PA_UNLIKELY(output_size < sizeof(*header))) {
*processed = 0;
return 0;
}
written = encode_buffer(codec_info, timestamp, input_buffer, input_size, output_buffer + sizeof(*header), output_size - sizeof(*header), processed);
if (PA_LIKELY(written > 0)) {
header = (struct rtp_header *) output_buffer;
pa_zero(*header);
header->v = 2;
header->pt = 96;
header->sequence_number = htons(info->seq_num++);
header->timestamp = htonl(timestamp);
header->ssrc = htonl(1);
written += sizeof(*header);
}
return written;
}
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
size_t written;
written = gst_transcode_buffer(codec_info, input_buffer, input_size, output_buffer, output_size, processed);
/* Due to aptX latency, aptx_decode starts filling output buffer after 90 input samples.
* If input buffer contains less than 90 samples, aptx_decode returns zero (=no output)
* but set *processed to non zero as input samples were processed. So do not check for
* return value of aptx_decode, zero is valid. Decoding error is indicating by fact that
* not all input samples were processed. */
if (PA_UNLIKELY(*processed != input_size))
pa_log_error("aptX decoding error");
return written;
}
static size_t decode_buffer_hd(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct rtp_header *header;
size_t written;
if (PA_UNLIKELY(input_size < sizeof(*header))) {
*processed = 0;
return 0;
}
header = (struct rtp_header *) input_buffer;
written = decode_buffer(codec_info, input_buffer + sizeof(*header), input_size - sizeof(*header), output_buffer, output_size, processed);
*processed += sizeof(*header);
return written;
}
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx = {
.id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.bt_codec = {
.name = "aptx",
.description = "aptX",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd = {
.id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_hd,
.choose_remote_endpoint = choose_remote_endpoint_hd,
.fill_capabilities = fill_capabilities_hd,
.is_configuration_valid = is_configuration_valid_hd,
.fill_preferred_configuration = fill_preferred_configuration_hd,
.bt_codec = {
.name = "aptx_hd",
.description = "aptX HD",
.init = init_hd,
.deinit = deinit,
.reset = reset_hd,
.get_read_block_size = get_block_size_hd,
.get_write_block_size = get_block_size_hd,
.get_encoded_block_size = get_encoded_block_size_hd,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer_hd,
.decode_buffer = decode_buffer_hd,
},
};

View file

@ -0,0 +1,359 @@
/***
This file is part of PulseAudio.
Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <arpa/inet.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/once.h>
#include <pulsecore/core-util.h>
#include <pulse/sample.h>
#include <pulse/util.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-api.h"
#include "a2dp-codec-gst.h"
/* Called from the GStreamer streaming thread */
static void app_sink_eos(GstAppSink *appsink, gpointer userdata) {
pa_log_debug("Sink got EOS");
}
/* Called from the GStreamer streaming thread */
static GstFlowReturn app_sink_new_sample(GstAppSink *appsink, gpointer userdata) {
struct gst_info *info = (struct gst_info *) userdata;
GstSample *sample = NULL;
GstBuffer *buf;
sample = gst_app_sink_pull_sample(GST_APP_SINK(info->app_sink));
if (!sample)
return GST_FLOW_OK;
buf = gst_sample_get_buffer(sample);
gst_buffer_ref(buf);
gst_adapter_push(info->sink_adapter, buf);
gst_sample_unref(sample);
pa_fdsem_post(info->sample_ready_fdsem);
return GST_FLOW_OK;
}
static void gst_deinit_common(struct gst_info *info) {
if (!info)
return;
if (info->sample_ready_fdsem)
pa_fdsem_free(info->sample_ready_fdsem);
if (info->app_src)
gst_object_unref(info->app_src);
if (info->app_sink)
gst_object_unref(info->app_sink);
if (info->sink_adapter)
g_object_unref(info->sink_adapter);
if (info->pipeline)
gst_object_unref(info->pipeline);
}
static GstBusSyncReply sync_bus_handler (GstBus *bus, GstMessage *message, struct gst_info *info) {
GstStreamStatusType type;
GstElement *owner;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_STREAM_STATUS:
gst_message_parse_stream_status (message, &type, &owner);
switch (type) {
case GST_STREAM_STATUS_TYPE_ENTER:
pa_log_debug("GStreamer pipeline thread starting up");
if (info->core->realtime_scheduling)
pa_thread_make_realtime(info->core->realtime_priority);
break;
case GST_STREAM_STATUS_TYPE_LEAVE:
pa_log_debug("GStreamer pipeline thread shutting down");
break;
default:
break;
}
break;
default:
break;
}
/* pass all messages on the async queue */
return GST_BUS_PASS;
}
bool gst_init_common(struct gst_info *info) {
GstElement *pipeline = NULL;
GstElement *appsrc = NULL, *appsink = NULL;
GstAdapter *adapter;
GstAppSinkCallbacks callbacks = { 0, };
GstBus *bus;
appsrc = gst_element_factory_make("appsrc", "app_source");
if (!appsrc) {
pa_log_error("Could not create appsrc element");
goto fail;
}
g_object_set(appsrc, "is-live", FALSE, "format", GST_FORMAT_TIME, "stream-type", 0, "max-bytes", 0, NULL);
appsink = gst_element_factory_make("appsink", "app_sink");
if (!appsink) {
pa_log_error("Could not create appsink element");
goto fail;
}
g_object_set(appsink, "sync", FALSE, "async", FALSE, "enable-last-sample", FALSE, NULL);
callbacks.eos = app_sink_eos;
callbacks.new_sample = app_sink_new_sample;
gst_app_sink_set_callbacks(GST_APP_SINK(appsink), &callbacks, info, NULL);
adapter = gst_adapter_new();
pa_assert(adapter);
pipeline = gst_pipeline_new(NULL);
pa_assert(pipeline);
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) sync_bus_handler, info, NULL);
gst_object_unref (bus);
info->app_src = appsrc;
info->app_sink = appsink;
info->sink_adapter = adapter;
info->pipeline = pipeline;
info->sample_ready_fdsem = pa_fdsem_new();
return true;
fail:
if (appsrc)
gst_object_unref(appsrc);
if (appsink)
gst_object_unref(appsink);
return false;
}
/*
* The idea of using buffer probes is as follows. We set a buffer probe on the
* encoder sink pad. In the buffer probe, we set an idle probe on the upstream
* source pad. In encode_buffer, we wait on the fdsem. The fdsem gets posted
* when either new_sample or idle probe gets called. We do this, to make the
* appsink behave synchronously.
*
* For buffer probes, see
* https://gstreamer.freedesktop.org/documentation/additional/design/probes.html?gi-language=c
*/
static GstPadProbeReturn gst_sink_buffer_idle_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
{
struct gst_info *info = (struct gst_info *)userdata;
pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_IDLE);
pa_fdsem_post(info->sample_ready_fdsem);
return GST_PAD_PROBE_REMOVE;
}
static GstPadProbeReturn gst_sink_buffer_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer userdata)
{
struct gst_info *info = (struct gst_info *)userdata;
GstPad *peer_pad;
pa_assert(probe_info->type & GST_PAD_PROBE_TYPE_BUFFER);
peer_pad = gst_pad_get_peer(pad);
gst_pad_add_probe(peer_pad, GST_PAD_PROBE_TYPE_IDLE, gst_sink_buffer_idle_probe, info, NULL);
gst_object_unref(peer_pad);
return GST_PAD_PROBE_OK;
}
static GstCaps *gst_create_caps_from_sample_spec(const pa_sample_spec *ss) {
gchar *sample_format;
GstCaps *caps;
int channel_mask;
switch (ss->format) {
case PA_SAMPLE_S16LE:
sample_format = "S16LE";
break;
case PA_SAMPLE_S24LE:
sample_format = "S24LE";
break;
case PA_SAMPLE_S32LE:
sample_format = "S32LE";
break;
case PA_SAMPLE_FLOAT32LE:
sample_format = "F32LE";
break;
default:
pa_assert_not_reached();
break;
}
switch (ss->channels) {
case 1:
channel_mask = 0x1;
break;
case 2:
channel_mask = 0x3;
break;
default:
pa_assert_not_reached();
break;
}
caps = gst_caps_new_simple("audio/x-raw",
"format", G_TYPE_STRING, sample_format,
"rate", G_TYPE_INT, (int) ss->rate,
"channels", G_TYPE_INT, (int) ss->channels,
"channel-mask", GST_TYPE_BITMASK, channel_mask,
"layout", G_TYPE_STRING, "interleaved",
NULL);
pa_assert(caps);
return caps;
}
bool gst_codec_init(struct gst_info *info, bool for_encoding, GstElement *transcoder) {
GstPad *pad;
GstCaps *caps;
pa_assert(transcoder);
info->seq_num = 0;
if (!gst_init_common(info))
goto common_fail;
caps = gst_create_caps_from_sample_spec(info->ss);
if (for_encoding)
g_object_set(info->app_src, "caps", caps, NULL);
else
g_object_set(info->app_sink, "caps", caps, NULL);
gst_caps_unref(caps);
gst_bin_add_many(GST_BIN(info->pipeline), info->app_src, transcoder, info->app_sink, NULL);
if (!gst_element_link_many(info->app_src, transcoder, info->app_sink, NULL)) {
pa_log_error("Failed to link codec elements into pipeline");
goto pipeline_fail;
}
if (gst_element_set_state(info->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
pa_log_error("Could not start pipeline");
goto pipeline_fail;
}
/* See the comment on buffer probe functions */
pad = gst_element_get_static_pad(transcoder, "sink");
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, gst_sink_buffer_probe, info, NULL);
gst_object_unref(pad);
pa_log_info("GStreamer pipeline initialisation succeeded");
return true;
pipeline_fail:
gst_deinit_common(info);
pa_log_error("GStreamer pipeline initialisation failed");
return false;
common_fail:
/* If common initialization fails the bin has not yet had its ownership
* transferred to the pipeline yet.
*/
gst_object_unref(transcoder);
pa_log_error("GStreamer pipeline creation failed");
return false;
}
size_t gst_transcode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct gst_info *info = (struct gst_info *) codec_info;
gsize available, transcoded;
GstBuffer *in_buf;
GstMapInfo map_info;
GstFlowReturn ret;
size_t written = 0;
in_buf = gst_buffer_new_allocate(NULL, input_size, NULL);
pa_assert(in_buf);
pa_assert_se(gst_buffer_map(in_buf, &map_info, GST_MAP_WRITE));
memcpy(map_info.data, input_buffer, input_size);
gst_buffer_unmap(in_buf, &map_info);
ret = gst_app_src_push_buffer(GST_APP_SRC(info->app_src), in_buf);
if (ret != GST_FLOW_OK) {
pa_log_error("failed to push buffer for transcoding %d", ret);
goto fail;
}
pa_fdsem_wait(info->sample_ready_fdsem);
available = gst_adapter_available(info->sink_adapter);
if (available) {
transcoded = PA_MIN(available, output_size);
gst_adapter_copy(info->sink_adapter, output_buffer, 0, transcoded);
gst_adapter_flush(info->sink_adapter, transcoded);
written += transcoded;
} else
pa_log_debug("No transcoded data available in adapter");
*processed = input_size;
return written;
fail:
*processed = 0;
return written;
}
void gst_codec_deinit(void *codec_info) {
struct gst_info *info = (struct gst_info *) codec_info;
if (info->sample_ready_fdsem)
pa_fdsem_free(info->sample_ready_fdsem);
if (info->pipeline) {
gst_element_set_state(info->pipeline, GST_STATE_NULL);
gst_object_unref(info->pipeline);
}
if (info->sink_adapter)
g_object_unref(info->sink_adapter);
pa_xfree(info);
}

View file

@ -0,0 +1,57 @@
/***
This file is part of PulseAudio.
Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>
#include <gst/base/gstadapter.h>
#include <pulsecore/fdsem.h>
enum a2dp_codec_type {
AAC = 0,
APTX,
APTX_HD,
LDAC_EQMID_HQ,
LDAC_EQMID_SQ,
LDAC_EQMID_MQ
};
struct gst_info {
pa_core *core;
pa_sample_spec *ss;
enum a2dp_codec_type codec_type;
union {
const a2dp_aac_t *aac_config;
const a2dp_aptx_t *aptx_config;
const a2dp_aptx_hd_t *aptx_hd_config;
const a2dp_ldac_t *ldac_config;
} a2dp_codec_t;
GstElement *app_src, *app_sink;
GstElement *pipeline;
GstAdapter *sink_adapter;
pa_fdsem *sample_ready_fdsem;
uint16_t seq_num;
};
bool gst_codec_init(struct gst_info *info, bool for_encoding, GstElement *transcoder);
size_t gst_transcode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
void gst_codec_deinit(void *codec_info);

View file

@ -0,0 +1,500 @@
/***
This file is part of PulseAudio.
Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io>
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/once.h>
#include <pulse/sample.h>
#include <arpa/inet.h>
#include "a2dp-codecs.h"
#include "a2dp-codec-api.h"
#include "a2dp-codec-gst.h"
#include "rtp.h"
static bool can_be_supported(bool for_encoding) {
GstElementFactory *element_factory;
if (!for_encoding)
return false;
element_factory = gst_element_factory_find("ldacenc");
if (element_factory == NULL) {
pa_log_info("LDAC encoder element `ldacenc` not found");
return false;
}
gst_object_unref(element_factory);
element_factory = gst_element_factory_find("rtpldacpay");
if (element_factory == NULL) {
pa_log_info("LDAC RTP payloader element `rtpldacpay` not found");
return false;
}
gst_object_unref(element_factory);
return true;
}
static bool can_accept_capabilities_common(const a2dp_ldac_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)
return false;
if (!(capabilities->frequency & (LDAC_SAMPLING_FREQ_44100 | LDAC_SAMPLING_FREQ_48000 |
LDAC_SAMPLING_FREQ_88200 | LDAC_SAMPLING_FREQ_96000)))
return false;
if (!(capabilities->channel_mode & LDAC_CHANNEL_MODE_STEREO))
return false;
return true;
}
static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_ldac_t *capabilities = (const a2dp_ldac_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities))
return false;
return can_accept_capabilities_common(capabilities, LDAC_VENDOR_ID, LDAC_CODEC_ID);
}
static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static void fill_capabilities_common(a2dp_ldac_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {
capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
capabilities->channel_mode = LDAC_CHANNEL_MODE_STEREO;
capabilities->frequency = LDAC_SAMPLING_FREQ_44100 | LDAC_SAMPLING_FREQ_48000 |
LDAC_SAMPLING_FREQ_88200 | LDAC_SAMPLING_FREQ_96000;
}
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_ldac_t *capabilities = (a2dp_ldac_t *) capabilities_buffer;
pa_zero(*capabilities);
fill_capabilities_common(capabilities, LDAC_VENDOR_ID, LDAC_CODEC_ID);
return sizeof(*capabilities);
}
static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_ldac_t *config = (const a2dp_ldac_t *) config_buffer;
if (config_size != sizeof(*config)) {
pa_log_error("Invalid size of config buffer");
return false;
}
if (A2DP_GET_VENDOR_ID(config->info) != LDAC_VENDOR_ID || A2DP_GET_CODEC_ID(config->info) != LDAC_CODEC_ID) {
pa_log_error("Invalid vendor codec information in configuration");
return false;
}
if (config->frequency != LDAC_SAMPLING_FREQ_44100 && config->frequency != LDAC_SAMPLING_FREQ_48000 &&
config->frequency != LDAC_SAMPLING_FREQ_88200 && config->frequency != LDAC_SAMPLING_FREQ_96000) {
pa_log_error("Invalid sampling frequency in configuration");
return false;
}
if (config->channel_mode != LDAC_CHANNEL_MODE_STEREO) {
pa_log_error("Invalid channel mode in configuration");
return false;
}
return true;
}
static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_ldac_t *capabilities, a2dp_ldac_t *config, uint32_t vendor_id, uint16_t codec_id) {
int i;
static const struct {
uint32_t rate;
uint8_t cap;
} freq_table[] = {
{ 44100U, LDAC_SAMPLING_FREQ_44100 },
{ 48000U, LDAC_SAMPLING_FREQ_48000 },
{ 88200U, LDAC_SAMPLING_FREQ_88200 },
{ 96000U, LDAC_SAMPLING_FREQ_96000 }
};
if (A2DP_GET_VENDOR_ID(capabilities->info) != LDAC_VENDOR_ID || A2DP_GET_CODEC_ID(capabilities->info) != LDAC_CODEC_ID) {
pa_log_error("No supported vendor codec information");
return -1;
}
config->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);
if (!(capabilities->channel_mode & LDAC_CHANNEL_MODE_STEREO)) {
pa_log_error("No supported channel modes");
return -1;
}
config->channel_mode = LDAC_CHANNEL_MODE_STEREO;
/* Find the lowest freq that is at least as high as the requested sampling rate */
for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) {
if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
config->frequency = freq_table[i].cap;
break;
}
}
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
for (--i; i >= 0; i--) {
if (capabilities->frequency & freq_table[i].cap) {
config->frequency = freq_table[i].cap;
break;
}
}
if (i < 0) {
pa_log_error("Not suitable sample rate");
return false;
}
}
return 0;
}
static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_ldac_t *config = (a2dp_ldac_t *) config_buffer;
const a2dp_ldac_t *capabilities = (const a2dp_ldac_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
pa_zero(*config);
if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, LDAC_VENDOR_ID, LDAC_CODEC_ID) < 0)
return 0;
return sizeof(*config);
}
GstElement *gst_init_ldac(struct gst_info *info, pa_sample_spec *ss, bool for_encoding) {
GstElement *bin;
GstElement *rtpldacpay;
GstElement *enc;
GstPad *pad;
if (!for_encoding) {
pa_log_error("LDAC does not support decoding");
return NULL;
}
ss->format = PA_SAMPLE_FLOAT32LE;
switch (info->a2dp_codec_t.ldac_config->frequency) {
case LDAC_SAMPLING_FREQ_44100:
ss->rate = 44100u;
break;
case LDAC_SAMPLING_FREQ_48000:
ss->rate = 48000u;
break;
case LDAC_SAMPLING_FREQ_88200:
ss->rate = 88200;
break;
case LDAC_SAMPLING_FREQ_96000:
ss->rate = 96000;
break;
default:
pa_log_error("LDAC invalid frequency %d", info->a2dp_codec_t.ldac_config->frequency);
goto fail;
}
switch (info->a2dp_codec_t.ldac_config->channel_mode) {
case LDAC_CHANNEL_MODE_STEREO:
ss->channels = 2;
break;
case LDAC_CHANNEL_MODE_MONO:
ss->channels = 1;
break;
case LDAC_CHANNEL_MODE_DUAL:
ss->channels = 1;
break;
default:
pa_log_error("LDAC invalid channel mode %d", info->a2dp_codec_t.ldac_config->channel_mode);
goto fail;
}
enc = gst_element_factory_make("ldacenc", "ldac_enc");
if (!enc) {
pa_log_error("Could not create LDAC encoder element");
goto fail;
}
switch (info->codec_type) {
case LDAC_EQMID_HQ:
g_object_set(enc, "eqmid", 0, NULL);
break;
case LDAC_EQMID_SQ:
g_object_set(enc, "eqmid", 1, NULL);
break;
case LDAC_EQMID_MQ:
g_object_set(enc, "eqmid", 2, NULL);
break;
default:
goto fail;
}
rtpldacpay = gst_element_factory_make("rtpldacpay", "rtp_ldac_pay");
if (!rtpldacpay) {
pa_log_error("Could not create RTP LDAC payloader element");
goto fail;
}
bin = gst_bin_new("ldac_enc_bin");
pa_assert(bin);
gst_bin_add_many(GST_BIN(bin), enc, rtpldacpay, NULL);
if (!gst_element_link(enc, rtpldacpay)) {
pa_log_error("Failed to link LDAC encoder to LDAC RTP payloader");
gst_object_unref(bin);
return NULL;
}
pad = gst_element_get_static_pad(enc, "sink");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("sink", pad)));
gst_object_unref(GST_OBJECT(pad));
pad = gst_element_get_static_pad(rtpldacpay, "src");
pa_assert_se(gst_element_add_pad(bin, gst_ghost_pad_new("src", pad)));
gst_object_unref(GST_OBJECT(pad));
return bin;
fail:
pa_log_error("LDAC encoder initialisation failed");
return NULL;
}
static void *init_common(enum a2dp_codec_type codec_type, bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
GstElement *bin;
struct gst_info *info = NULL;
if (!for_encoding) {
pa_log_error("LDAC decoder not supported");
return NULL;
}
info = pa_xnew0(struct gst_info, 1);
pa_assert(info);
info->core = core;
info->ss = sample_spec;
info->codec_type = codec_type;
info->a2dp_codec_t.ldac_config = (const a2dp_ldac_t *) config_buffer;
pa_assert(config_size == sizeof(*(info->a2dp_codec_t.ldac_config)));
if (!(bin = gst_init_ldac(info, sample_spec, for_encoding)))
goto fail;
if (!gst_codec_init(info, for_encoding, bin))
goto fail;
return info;
fail:
if (info)
pa_xfree(info);
return NULL;
}
static void *init_hq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
return init_common(LDAC_EQMID_HQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
}
static void *init_sq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
return init_common(LDAC_EQMID_SQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
}
static void *init_mq(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
return init_common(LDAC_EQMID_MQ, for_encoding, for_backchannel, config_buffer, config_size, sample_spec, core);
}
static void deinit(void *codec_info) {
return gst_codec_deinit(codec_info);
}
static int reset(void *codec_info) {
return 0;
}
static uint32_t get_ldac_num_samples(void *codec_info) {
struct gst_info *info = (struct gst_info *) codec_info;
switch (info->a2dp_codec_t.ldac_config->frequency) {
case LDAC_SAMPLING_FREQ_44100:
case LDAC_SAMPLING_FREQ_48000:
return 128;
break;
case LDAC_SAMPLING_FREQ_88200:
case LDAC_SAMPLING_FREQ_96000:
return 256;
break;
default:
break;
}
return 128;
}
static uint8_t get_ldac_num_frames(void *codec_info, enum a2dp_codec_type codec_type) {
struct gst_info *info = (struct gst_info *) codec_info;
uint8_t channels;
switch (info->a2dp_codec_t.ldac_config->channel_mode) {
case LDAC_CHANNEL_MODE_STEREO:
channels = 2;
break;
case LDAC_CHANNEL_MODE_MONO:
case LDAC_CHANNEL_MODE_DUAL:
channels = 1;
break;
default:
break;
}
switch (codec_type) {
case LDAC_EQMID_HQ:
return 4 / channels;
case LDAC_EQMID_SQ:
return 6 / channels;
case LDAC_EQMID_MQ:
return 12 / channels;
default:
break;
}
return 6 / channels;
}
static size_t get_block_size(void *codec_info, size_t link_mtu) {
struct gst_info *info = (struct gst_info *) codec_info;
return get_ldac_num_samples(codec_info) * get_ldac_num_frames(codec_info, info->codec_type) * pa_frame_size(info->ss);
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
/* encoded block size is not exactly known, report input_size */
return input_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
size_t written;
written = gst_transcode_buffer(codec_info, input_buffer, input_size, output_buffer, output_size, processed);
if (PA_UNLIKELY(*processed != input_size))
pa_log_error("LDAC encoding error");
return written;
}
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.bt_codec = {
.name = "ldac_hq",
.description = "LDAC (High Quality)",
.init = init_hq,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
},
};
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.bt_codec = {
.name = "ldac_sq",
.description = "LDAC (Standard Quality)",
.init = init_sq,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
},
};
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq = {
.id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.bt_codec = {
.name = "ldac_mq",
.description = "LDAC (Mobile Quality)",
.init = init_mq,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
},
};

View file

@ -36,8 +36,8 @@
#include "a2dp-codec-api.h"
#include "rtp.h"
#define SBC_BITPOOL_DEC_LIMIT 32
#define SBC_BITPOOL_DEC_STEP 5
#define SBC_BITPOOL_INC_STEP 1
struct sbc_info {
sbc_t sbc; /* Codec data */
@ -51,8 +51,15 @@ struct sbc_info {
uint8_t initial_bitpool;
uint8_t min_bitpool;
uint8_t max_bitpool;
uint8_t nr_blocks;
uint8_t nr_subbands;
};
static bool can_be_supported(bool for_encoding) {
return true;
}
static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
@ -77,6 +84,30 @@ static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t
return true;
}
static bool can_accept_capabilities_xq(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {
const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
if (capabilities_size != sizeof(*capabilities))
return false;
if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000)))
return false;
if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_DUAL_CHANNEL)))
return false;
if (!(capabilities->allocation_method & (SBC_ALLOCATION_LOUDNESS)))
return false;
if (!(capabilities->subbands & (SBC_SUBBANDS_8)))
return false;
if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_16)))
return false;
return true;
}
static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
@ -91,6 +122,20 @@ static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap
return NULL;
}
static const char *choose_remote_endpoint_xq(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {
const pa_a2dp_codec_capabilities *a2dp_capabilities;
const char *key;
void *state;
/* There is no preference, just choose random valid entry */
PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {
if (can_accept_capabilities_xq(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))
return key;
}
return NULL;
}
static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
@ -109,6 +154,29 @@ static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]
return sizeof(*capabilities);
}
/* SBC XQ
*
* References:
* https://habr.com/en/post/456476/
* http://soundexpert.org/articles/-/blogs/audio-quality-of-sbc-xq-bluetooth-audio-codec
*
*/
static uint8_t fill_capabilities_xq(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {
a2dp_sbc_t *capabilities = (a2dp_sbc_t *) capabilities_buffer;
pa_zero(*capabilities);
capabilities->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
capabilities->frequency = SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000;
capabilities->allocation_method = SBC_ALLOCATION_LOUDNESS;
capabilities->subbands = SBC_SUBBANDS_8;
capabilities->block_length = SBC_BLOCK_LENGTH_16;
capabilities->min_bitpool = SBC_MIN_BITPOOL;
capabilities->max_bitpool = SBC_MAX_BITPOOL;
return sizeof(*capabilities);
}
static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {
const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
@ -310,38 +378,7 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample
return sizeof(*config);
}
static void set_params(struct sbc_info *sbc_info) {
sbc_info->sbc.frequency = sbc_info->frequency;
sbc_info->sbc.blocks = sbc_info->blocks;
sbc_info->sbc.subbands = sbc_info->subbands;
sbc_info->sbc.mode = sbc_info->mode;
sbc_info->sbc.allocation = sbc_info->allocation;
sbc_info->sbc.bitpool = sbc_info->initial_bitpool;
sbc_info->sbc.endian = SBC_LE;
sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
}
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {
struct sbc_info *sbc_info;
const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
int ret;
pa_assert(config_size == sizeof(*config));
pa_assert(!for_backchannel);
sbc_info = pa_xnew0(struct sbc_info, 1);
ret = sbc_init(&sbc_info->sbc, 0);
if (ret != 0) {
pa_xfree(sbc_info);
pa_log_error("SBC initialization failed: %d", ret);
return NULL;
}
sample_spec->format = PA_SAMPLE_S16LE;
static void set_info_and_sample_spec_from_sbc_config(struct sbc_info *sbc_info, pa_sample_spec *sample_spec, const a2dp_sbc_t *config) {
switch (config->frequency) {
case SBC_SAMPLING_FREQ_16000:
sbc_info->frequency = SBC_FREQ_16000;
@ -398,9 +435,11 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
switch (config->subbands) {
case SBC_SUBBANDS_4:
sbc_info->subbands = SBC_SB_4;
sbc_info->nr_subbands = 4;
break;
case SBC_SUBBANDS_8:
sbc_info->subbands = SBC_SB_8;
sbc_info->nr_subbands = 8;
break;
default:
pa_assert_not_reached();
@ -409,15 +448,19 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
switch (config->block_length) {
case SBC_BLOCK_LENGTH_4:
sbc_info->blocks = SBC_BLK_4;
sbc_info->nr_blocks = 4;
break;
case SBC_BLOCK_LENGTH_8:
sbc_info->blocks = SBC_BLK_8;
sbc_info->nr_blocks = 8;
break;
case SBC_BLOCK_LENGTH_12:
sbc_info->blocks = SBC_BLK_12;
sbc_info->nr_blocks = 12;
break;
case SBC_BLOCK_LENGTH_16:
sbc_info->blocks = SBC_BLK_16;
sbc_info->nr_blocks = 16;
break;
default:
pa_assert_not_reached();
@ -425,6 +468,182 @@ static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config
sbc_info->min_bitpool = config->min_bitpool;
sbc_info->max_bitpool = config->max_bitpool;
}
static void set_params(struct sbc_info *sbc_info) {
sbc_info->sbc.frequency = sbc_info->frequency;
sbc_info->sbc.blocks = sbc_info->blocks;
sbc_info->sbc.subbands = sbc_info->subbands;
sbc_info->sbc.mode = sbc_info->mode;
sbc_info->sbc.allocation = sbc_info->allocation;
sbc_info->sbc.bitpool = sbc_info->initial_bitpool;
sbc_info->sbc.endian = SBC_LE;
sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
}
uint8_t sbc_get_max_bitpool_below_rate(a2dp_sbc_t *config, uint8_t lower_bound, uint8_t upper_bound, uint32_t bitrate_cap) {
pa_sample_spec sample_spec;
struct sbc_info sbc_info;
int ret;
pa_assert(config);
ret = sbc_init(&sbc_info.sbc, 0);
if (ret != 0) {
pa_log_error("SBC initialization failed: %d", ret);
return lower_bound;
}
set_info_and_sample_spec_from_sbc_config(&sbc_info, &sample_spec, config);
while (upper_bound - lower_bound > 1) {
size_t midpoint = (upper_bound + lower_bound) / 2;
sbc_info.initial_bitpool = midpoint;
set_params(&sbc_info);
size_t bitrate = sbc_info.frame_length * 8 * sample_spec.rate / (sbc_info.nr_subbands * sbc_info.nr_blocks);
if (bitrate > bitrate_cap)
upper_bound = midpoint;
else
lower_bound = midpoint;
}
sbc_finish(&sbc_info.sbc);
pa_log_debug("SBC target bitrate %u bitpool %u sample rate %u", bitrate_cap, lower_bound, sample_spec.rate);
return lower_bound;
}
static uint8_t fill_preferred_configuration_xq(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE], uint32_t bitrate_cap) {
a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer;
const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer;
int i;
static const struct {
uint32_t rate;
uint8_t cap;
} freq_table[] = {
{ 16000U, SBC_SAMPLING_FREQ_16000 },
{ 32000U, SBC_SAMPLING_FREQ_32000 },
{ 44100U, SBC_SAMPLING_FREQ_44100 },
{ 48000U, SBC_SAMPLING_FREQ_48000 }
};
if (capabilities_size != sizeof(*capabilities)) {
pa_log_error("Invalid size of capabilities buffer");
return 0;
}
pa_zero(*config);
/* Find the lowest freq that is at least as high as the requested sampling rate */
for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {
config->frequency = freq_table[i].cap;
break;
}
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
for (--i; i >= 0; i--) {
if (capabilities->frequency & freq_table[i].cap) {
config->frequency = freq_table[i].cap;
break;
}
}
if (i < 0) {
pa_log_error("Not suitable sample rate");
return 0;
}
}
pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
if (default_sample_spec->channels <= 1) {
if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
else {
pa_log_error("No supported channel modes");
return 0;
}
} else {
if (capabilities->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
else {
pa_log_error("No supported channel modes");
return 0;
}
}
if (capabilities->block_length & SBC_BLOCK_LENGTH_16)
config->block_length = SBC_BLOCK_LENGTH_16;
else {
pa_log_error("No supported block lengths");
return 0;
}
if (capabilities->subbands & SBC_SUBBANDS_8)
config->subbands = SBC_SUBBANDS_8;
else {
pa_log_error("No supported subbands");
return 0;
}
if (capabilities->allocation_method & SBC_ALLOCATION_LOUDNESS)
config->allocation_method = SBC_ALLOCATION_LOUDNESS;
else {
pa_log_error("No supported allocation method");
return 0;
}
config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, capabilities->min_bitpool);
config->max_bitpool = sbc_get_max_bitpool_below_rate(config, config->min_bitpool, capabilities->max_bitpool, bitrate_cap);
if (config->min_bitpool > config->max_bitpool) {
pa_log_error("No supported bitpool");
return 0;
}
return sizeof(*config);
}
static uint8_t fill_preferred_configuration_xq_453kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 453000);
}
static uint8_t fill_preferred_configuration_xq_512kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 512000);
}
static uint8_t fill_preferred_configuration_xq_552kbps(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {
return fill_preferred_configuration_xq(default_sample_spec, capabilities_buffer, capabilities_size, config_buffer, 552000);
}
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
struct sbc_info *sbc_info;
const a2dp_sbc_t *config = (const a2dp_sbc_t *) config_buffer;
int ret;
pa_assert(config_size == sizeof(*config));
pa_assert(!for_backchannel);
sbc_info = pa_xnew0(struct sbc_info, 1);
ret = sbc_init(&sbc_info->sbc, 0);
if (ret != 0) {
pa_xfree(sbc_info);
pa_log_error("SBC initialization failed: %d", ret);
return NULL;
}
sample_spec->format = PA_SAMPLE_S16LE;
set_info_and_sample_spec_from_sbc_config(sbc_info, sample_spec, config);
/* Set minimum bitpool for source to get the maximum possible block_size
* in get_block_size() function. This block_size is length of buffer used
@ -495,18 +714,34 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
return frame_count * sbc_info->codesize;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_sbc_payload);
/* input size should be aligned to codec input block size */
pa_assert_fp(input_size % sbc_info->codesize == 0);
return (input_size / sbc_info->codesize) * sbc_info->frame_length + rtp_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
uint8_t bitpool;
/* Check if bitpool is already at its limit */
if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT)
bitpool = PA_MAX(sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP, sbc_info->min_bitpool);
if (sbc_info->sbc.bitpool == bitpool)
return 0;
bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP;
set_bitpool(sbc_info, bitpool);
return get_block_size(codec_info, write_link_mtu);
}
if (bitpool < SBC_BITPOOL_DEC_LIMIT)
bitpool = SBC_BITPOOL_DEC_LIMIT;
static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
uint8_t bitpool;
bitpool = PA_MIN(sbc_info->sbc.bitpool + SBC_BITPOOL_INC_STEP, sbc_info->max_bitpool);
if (sbc_info->sbc.bitpool == bitpool)
return 0;
@ -661,22 +896,115 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
return d - output_buffer;
}
const pa_a2dp_codec pa_a2dp_codec_sbc = {
.name = "sbc",
.description = "SBC",
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc = {
.id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities,
.choose_remote_endpoint = choose_remote_endpoint,
.fill_capabilities = fill_capabilities,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration,
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
.bt_codec = {
.name = "sbc",
.description = "SBC",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};
/* There are multiple definitions of SBC XQ, but in all cases this is
* SBC codec in Dual Channel mode, 8 bands, block length 16, allocation method Loudness,
* with bitpool adjusted to match target bitrates.
*
* Most commonly choosen bitrates and reasons are:
* 453000 - this yields most efficient packing of frames on Android for bluetooth EDR 2mbps
* 512000 - this looks to be old limit stated in bluetooth documents
* 552000 - this yields most efficient packing of frames on Android for bluetooth EDR 3mbps
*
* Efficient packing considerations do not apply on Linux (yet?) but still
* we can gain from increased bitrate.
*/
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453 = {
.id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_xq,
.choose_remote_endpoint = choose_remote_endpoint_xq,
.fill_capabilities = fill_capabilities_xq,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration_xq_453kbps,
.bt_codec = {
.name = "sbc_xq_453",
.description = "SBC XQ 453kbps",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512 = {
.id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_xq,
.choose_remote_endpoint = choose_remote_endpoint_xq,
.fill_capabilities = fill_capabilities_xq,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration_xq_512kbps,
.bt_codec = {
.name = "sbc_xq_512",
.description = "SBC XQ 512kbps",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};
const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552 = {
.id = { A2DP_CODEC_SBC, 0, 0 },
.support_backchannel = false,
.can_be_supported = can_be_supported,
.can_accept_capabilities = can_accept_capabilities_xq,
.choose_remote_endpoint = choose_remote_endpoint_xq,
.fill_capabilities = fill_capabilities_xq,
.is_configuration_valid = is_configuration_valid,
.fill_preferred_configuration = fill_preferred_configuration_xq_552kbps,
.bt_codec = {
.name = "sbc_xq_552",
.description = "SBC XQ 552kbps",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
},
};

View file

@ -23,34 +23,119 @@
#include <pulsecore/core.h>
#include <pulsecore/core-util.h>
#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC)
#include <gst/gst.h>
#endif
#include "a2dp-codec-util.h"
extern const pa_a2dp_codec pa_a2dp_codec_sbc;
extern const pa_bt_codec pa_bt_codec_msbc;
extern const pa_bt_codec pa_bt_codec_cvsd;
/* This is list of supported codecs. Their order is important.
* Codec with higher index has higher priority. */
const pa_a2dp_codec *pa_a2dp_codecs[] = {
&pa_a2dp_codec_sbc,
/* List of HSP/HFP codecs.
*/
static const pa_bt_codec *pa_hf_codecs[] = {
&pa_bt_codec_cvsd,
&pa_bt_codec_msbc,
};
unsigned int pa_bluetooth_a2dp_codec_count(void) {
return PA_ELEMENTSOF(pa_a2dp_codecs);
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552;
#ifdef HAVE_GSTAPTX
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd;
#endif
#ifdef HAVE_GSTLDAC
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq;
extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq;
#endif
/* This is list of supported codecs. Their order is important.
* Codec with lower index has higher priority. */
static const pa_a2dp_endpoint_conf *pa_a2dp_endpoint_configurations[] = {
#ifdef HAVE_GSTLDAC
&pa_a2dp_endpoint_conf_ldac_eqmid_hq,
&pa_a2dp_endpoint_conf_ldac_eqmid_sq,
&pa_a2dp_endpoint_conf_ldac_eqmid_mq,
#endif
#ifdef HAVE_GSTAPTX
&pa_a2dp_endpoint_conf_aptx_hd,
&pa_a2dp_endpoint_conf_aptx,
#endif
&pa_a2dp_endpoint_conf_sbc,
&pa_a2dp_endpoint_conf_sbc_xq_453,
&pa_a2dp_endpoint_conf_sbc_xq_512,
&pa_a2dp_endpoint_conf_sbc_xq_552,
};
unsigned int pa_bluetooth_a2dp_endpoint_conf_count(void) {
return PA_ELEMENTSOF(pa_a2dp_endpoint_configurations);
}
const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i) {
pa_assert(i < pa_bluetooth_a2dp_codec_count());
return pa_a2dp_codecs[i];
const pa_a2dp_endpoint_conf *pa_bluetooth_a2dp_endpoint_conf_iter(unsigned int i) {
pa_assert(i < pa_bluetooth_a2dp_endpoint_conf_count());
return pa_a2dp_endpoint_configurations[i];
}
const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name) {
unsigned int pa_bluetooth_hf_codec_count(void) {
return PA_ELEMENTSOF(pa_hf_codecs);
}
const pa_bt_codec *pa_bluetooth_hf_codec_iter(unsigned int i) {
pa_assert(i < pa_bluetooth_hf_codec_count());
return pa_hf_codecs[i];
}
const pa_bt_codec *pa_bluetooth_get_hf_codec(const char *name) {
unsigned int i;
unsigned int count = pa_bluetooth_a2dp_codec_count();
for (i = 0; i < count; i++) {
if (pa_streq(pa_a2dp_codecs[i]->name, name))
return pa_a2dp_codecs[i];
for (i = 0; i < PA_ELEMENTSOF(pa_hf_codecs); ++i) {
if (pa_streq(pa_hf_codecs[i]->name, name))
return pa_hf_codecs[i];
}
return NULL;
}
const pa_a2dp_endpoint_conf *pa_bluetooth_get_a2dp_endpoint_conf(const char *name) {
unsigned int i;
unsigned int count = pa_bluetooth_a2dp_endpoint_conf_count();
for (i = 0; i < count; i++) {
if (pa_streq(pa_a2dp_endpoint_configurations[i]->bt_codec.name, name))
return pa_a2dp_endpoint_configurations[i];
}
return NULL;
}
void pa_bluetooth_a2dp_codec_gst_init(void) {
#if defined(HAVE_GSTAPTX) || defined(HAVE_GSTLDAC)
GError *error = NULL;
if (!gst_init_check(NULL, NULL, &error)) {
pa_log_error("Could not initialise GStreamer: %s", error->message);
g_error_free(error);
return;
}
pa_log_info("GStreamer initialisation done");
#endif
}
bool pa_bluetooth_a2dp_codec_is_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink) {
unsigned int i;
unsigned int count = pa_bluetooth_a2dp_endpoint_conf_count();
const pa_a2dp_endpoint_conf *conf;
for (i = 0; i < count; i++) {
conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
if (memcmp(id, &conf->id, sizeof(pa_a2dp_codec_id)) == 0
&& conf->can_be_supported(is_a2dp_sink))
return true;
}
return false;
}

View file

@ -23,12 +23,27 @@
#include "a2dp-codec-api.h"
/* Get number of supported A2DP codecs */
unsigned int pa_bluetooth_a2dp_codec_count(void);
unsigned int pa_bluetooth_a2dp_endpoint_conf_count(void);
/* Get i-th codec. Codec with higher number has higher priority */
const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
const pa_a2dp_endpoint_conf *pa_bluetooth_a2dp_endpoint_conf_iter(unsigned int i);
/* Get codec by name */
const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name);
const pa_a2dp_endpoint_conf *pa_bluetooth_get_a2dp_endpoint_conf(const char *name);
/* Check if the given codec can be supported in A2DP_SINK or A2DP_SOURCE */
bool pa_bluetooth_a2dp_codec_is_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink);
/* Initialise GStreamer */
void pa_bluetooth_a2dp_codec_gst_init(void);
/* Get number of supported HSP/HFP codecs */
unsigned int pa_bluetooth_hf_codec_count(void);
/* Get i-th codec. Codec with higher number has higher priority */
const pa_bt_codec *pa_bluetooth_hf_codec_iter(unsigned int i);
/* Get HSP/HFP codec by name */
const pa_bt_codec *pa_bluetooth_get_hf_codec(const char *name);
#endif

View file

@ -35,12 +35,16 @@
#include <bluetooth/sco.h>
#include "bluez5-util.h"
#include "bt-codec-msbc.h"
struct pa_bluetooth_backend {
pa_core *core;
pa_dbus_connection *connection;
pa_bluetooth_discovery *discovery;
bool enable_hs_role;
pa_hook_slot *adapter_uuids_changed_slot;
bool enable_shared_profiles;
bool enable_hsp_hs;
bool enable_hfp_hf;
PA_LLIST_HEAD(pa_dbus_pending, pending);
};
@ -53,15 +57,51 @@ struct transport_data {
pa_mainloop_api *mainloop;
};
#define BLUEZ_SERVICE "org.bluez"
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
struct hfp_config {
uint32_t capabilities;
int state;
bool support_codec_negotiation;
bool support_msbc;
bool supports_indicators;
int selected_codec;
};
#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
/*
* the separate hansfree headset (HF) and Audio Gateway (AG) features
*/
enum hfp_hf_features {
HFP_HF_EC_NR = 0,
HFP_HF_CALL_WAITING = 1,
HFP_HF_CLI = 2,
HFP_HF_VR = 3,
HFP_HF_RVOL = 4,
HFP_HF_ESTATUS = 5,
HFP_HF_ECALL = 6,
HFP_HF_CODECS = 7,
HFP_HF_INDICATORS = 8,
};
#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1"
#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1"
enum hfp_ag_features {
HFP_AG_THREE_WAY = 0,
HFP_AG_EC_NR = 1,
HFP_AG_VR = 2,
HFP_AG_RING = 3,
HFP_AG_NUM_TAG = 4,
HFP_AG_REJECT = 5,
HFP_AG_ESTATUS = 6,
HFP_AG_ECALL = 7,
HFP_AG_EERR = 8,
HFP_AG_CODECS = 9,
HFP_AG_INDICATORS = 10,
};
/* gateway features we support, which is as little as we can get away with */
static uint32_t hfp_features =
/* HFP 1.6 requires this */
(1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS);
#define HSP_AG_PROFILE "/Profile/HSPAGProfile"
#define HFP_AG_PROFILE "/Profile/HFPAGProfile"
#define HSP_HS_PROFILE "/Profile/HSPHSProfile"
/* RFCOMM channel for HSP headset role
@ -83,13 +123,52 @@ struct transport_data {
" <arg name=\"opts\" direction=\"in\" type=\"a{sv}\"/>" \
" </method>" \
" </interface>" \
" <interface name=\"org.freedesktop.DBus.Introspectable\">" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">" \
" <method name=\"Introspect\">" \
" <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
" </method>" \
" </interface>" \
"</node>"
static pa_volume_t hsp_gain_to_volume(uint16_t gain) {
pa_volume_t volume = (pa_volume_t) ((
gain * PA_VOLUME_NORM
/* Round to closest by adding half the denominator */
+ HSP_MAX_GAIN / 2
) / HSP_MAX_GAIN);
if (volume > PA_VOLUME_NORM)
volume = PA_VOLUME_NORM;
return volume;
}
static uint16_t volume_to_hsp_gain(pa_volume_t volume) {
uint16_t gain = volume * HSP_MAX_GAIN / PA_VOLUME_NORM;
if (gain > HSP_MAX_GAIN)
gain = HSP_MAX_GAIN;
return gain;
}
static bool is_peer_audio_gateway(pa_bluetooth_profile_t peer_profile) {
switch(peer_profile) {
case PA_BLUETOOTH_PROFILE_HFP_HF:
case PA_BLUETOOTH_PROFILE_HSP_HS:
return false;
case PA_BLUETOOTH_PROFILE_HFP_AG:
case PA_BLUETOOTH_PROFILE_HSP_AG:
return true;
default:
pa_assert_not_reached();
}
}
static bool is_pulseaudio_audio_gateway(pa_bluetooth_profile_t peer_profile) {
return !is_peer_audio_gateway(peer_profile);
}
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
DBusPendingCallNotifyFunction func, void *call_data) {
@ -108,6 +187,59 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, D
return p;
}
static void rfcomm_fmt_write(int fd, const char* fmt_line, const char *fmt_command, va_list ap)
{
size_t len;
char buf[512];
char command[512];
pa_vsnprintf(command, sizeof(command), fmt_command, ap);
pa_log_debug("RFCOMM >> %s", command);
len = pa_snprintf(buf, sizeof(buf), fmt_line, command);
/* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */
if ((size_t)write(fd, buf, len) != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
/* The format of COMMAND line sent from HS to AG is COMMAND<cr> */
static void rfcomm_write_command(int fd, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
rfcomm_fmt_write(fd, "%s\r", fmt, ap);
va_end(ap);
}
/* The format of RESPONSE line sent from AG to HS is <cr><lf>RESPONSE<cr><lf> */
static void rfcomm_write_response(int fd, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
rfcomm_fmt_write(fd, "\r\n%s\r\n", fmt, ap);
va_end(ap);
}
static int sco_setsockopt_enable_bt_voice(pa_bluetooth_transport *t, int fd) {
/* the mSBC codec requires a special transparent eSCO connection */
struct bt_voice voice;
memset(&voice, 0, sizeof(voice));
voice.setting = BT_VOICE_TRANSPARENT;
if (setsockopt(fd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) < 0) {
pa_log_error("sockopt(): %s", pa_cstrerror(errno));
return -1;
}
pa_log_info("Enabled BT_VOICE_TRANSPARENT connection for mSBC");
return 0;
}
static int sco_do_connect(pa_bluetooth_transport *t) {
pa_bluetooth_device *d = t->device;
struct sockaddr_sco addr;
@ -143,6 +275,9 @@ static int sco_do_connect(pa_bluetooth_transport *t) {
goto fail_close;
}
if (t->setsockopt && t->setsockopt(t, sock) < 0)
goto fail_close;
memset(&addr, 0, len);
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, &dst);
@ -194,8 +329,8 @@ static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu
if (sock < 0)
goto fail;
if (imtu) *imtu = 48;
if (omtu) *omtu = 48;
if (imtu) *imtu = 60;
if (omtu) *omtu = 60;
if (t->device->autodetect_mtu) {
struct sco_options sco_opt;
@ -212,6 +347,11 @@ static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu
}
}
/* read/decode machinery only works if we get at most one MSBC encoded packet at a time
* when it is fixed to process stream of packets, lift this assertion */
pa_assert(*imtu <= MSBC_PACKET_SIZE);
pa_assert(*omtu <= MSBC_PACKET_SIZE);
return sock;
fail:
@ -223,6 +363,61 @@ static void sco_release_cb(pa_bluetooth_transport *t) {
/* device will close the SCO socket for us */
}
static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
size_t write_size;
pa_assert(t);
/* since SCO setup is symmetric, fix write MTU to be size of last read packet */
if (t->last_read_size)
write_mtu = PA_MIN(t->last_read_size, write_mtu);
/* if encoder buffer has less data than required to make complete packet */
if (size < write_mtu)
return 0;
/* write out MTU sized chunks only */
while (written < size) {
write_size = PA_MIN(size - written, write_mtu);
if (write_size < write_mtu)
break;
l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
if (l < 0)
break;
written += l;
}
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
/* Drain write buffer */
written = size;
} else if (errno == EINVAL && t->last_read_size == 0) {
/* Likely write_link_mtu is still wrong, retry after next successful read */
pa_log_debug("got write EINVAL, next successful read should fix MTU");
/* Drain write buffer */
written = size;
} else {
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
/* Report error from write call */
return -1;
}
}
/* if too much data left discard it all */
if (size - written >= write_mtu) {
pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
written, size, write_mtu);
/* Drain write buffer */
written = size;
}
return written;
}
static void sco_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
@ -293,49 +488,57 @@ static void register_profile_reply(DBusPendingCall *pending, void *userdata) {
DBusMessage *r;
pa_dbus_pending *p;
pa_bluetooth_backend *b;
char *profile;
pa_bluetooth_profile_t profile;
pa_assert(pending);
pa_assert_se(p = userdata);
pa_assert_se(b = p->context_data);
pa_assert_se(profile = p->call_data);
pa_assert_se(profile = (pa_bluetooth_profile_t)p->call_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
pa_log_info("Couldn't register profile %s because it is disabled in BlueZ", profile);
pa_log_info("Couldn't register profile %s because it is disabled in BlueZ", pa_bluetooth_profile_to_string(profile));
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
goto finish;
}
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log_error(BLUEZ_PROFILE_MANAGER_INTERFACE ".RegisterProfile() failed: %s: %s", dbus_message_get_error_name(r),
pa_dbus_get_error_message(r));
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
goto finish;
}
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_REGISTERED);
finish:
dbus_message_unref(r);
PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
pa_dbus_pending_free(p);
pa_xfree(profile);
}
static void register_profile(pa_bluetooth_backend *b, const char *profile, const char *uuid) {
static void register_profile(pa_bluetooth_backend *b, const char *object, const char *uuid, pa_bluetooth_profile_t profile) {
DBusMessage *m;
DBusMessageIter i, d;
dbus_bool_t autoconnect;
dbus_uint16_t version, chan;
pa_log_debug("Registering Profile %s %s", profile, uuid);
pa_assert(profile_status_get(b->discovery, profile) == PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
pa_log_debug("Registering Profile %s %s", pa_bluetooth_profile_to_string(profile), uuid);
pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"));
dbus_message_iter_init_append(m, &i);
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &profile));
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object));
pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid));
dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&d);
if (pa_bluetooth_uuid_is_hsp_hs(uuid)) {
/* In the headset role, the connection will only be initiated from the remote side */
autoconnect = 0;
@ -348,7 +551,170 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const
}
dbus_message_iter_close_container(&i, &d);
send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile));
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_REGISTERING);
send_and_add_to_pending(b, m, register_profile_reply, (void *)profile);
}
static void transport_put(pa_bluetooth_transport *t)
{
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
}
static pa_volume_t set_sink_volume(pa_bluetooth_transport *t, pa_volume_t volume);
static pa_volume_t set_source_volume(pa_bluetooth_transport *t, pa_volume_t volume);
static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf)
{
struct hfp_config *c = t->config;
int indicator, val;
char str[5];
const char *r;
size_t len;
const char *state;
/* first-time initialize selected codec to CVSD */
if (c->selected_codec == 0)
c->selected_codec = 1;
/* stateful negotiation */
if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
c->capabilities = val;
pa_log_info("HFP capabilities returns 0x%x", val);
rfcomm_write_response(fd, "+BRSF: %d", hfp_features);
c->supports_indicators = !!(1 << HFP_HF_INDICATORS);
c->state = 1;
return true;
} else if (sscanf(buf, "AT+BAC=%3s", str) == 1) {
c->support_msbc = false;
state = NULL;
/* check if codec id 2 (mSBC) is in the list of supported codecs */
while ((r = pa_split_in_place(str, ",", &len, &state))) {
if (len == 1 && r[0] == '2') {
c->support_msbc = true;
break;
}
}
c->support_codec_negotiation = true;
if (c->state == 1) {
/* initial list of codecs supported by HF */
} else {
/* HF sent updated list of codecs */
}
/* no state change */
return true;
} else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) {
/* we declare minimal no indicators */
rfcomm_write_response(fd, "+CIND: "
/* many indicators can be supported, only call and
* callheld are mandatory, so that's all we reply */
"(\"service\",(0-1)),"
"(\"call\",(0-1)),"
"(\"callsetup\",(0-3)),"
"(\"callheld\",(0-2))");
c->state = 2;
return true;
} else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) {
rfcomm_write_response(fd, "+CIND: 0,0,0,0");
c->state = 3;
return true;
} else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) {
rfcomm_write_response(fd, "OK");
if (c->support_codec_negotiation) {
if (c->support_msbc && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
rfcomm_write_response(fd, "+BCS:2");
c->state = 4;
} else {
rfcomm_write_response(fd, "+BCS:1");
c->state = 4;
}
} else {
c->state = 5;
pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
transport_put(t);
}
return false;
} else if (sscanf(buf, "AT+BCS=%d", &val)) {
if (val == 1) {
pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
} else if (val == 2 && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, sco_setsockopt_enable_bt_voice);
} else {
pa_assert_fp(val != 1 && val != 2);
rfcomm_write_response(fd, "ERROR");
return false;
}
c->selected_codec = val;
if (c->state == 4) {
c->state = 5;
pa_log_info("HFP negotiated codec %s", t->bt_codec->name);
transport_put(t);
}
return true;
} else if (c->supports_indicators && pa_startswith(buf, "AT+BIND=?")) {
// Support battery indication
rfcomm_write_response(fd, "+BIND: (2)");
return true;
} else if (c->supports_indicators && pa_startswith(buf, "AT+BIND?")) {
// Battery indication is enabled
rfcomm_write_response(fd, "+BIND: 2,1");
return true;
} else if (c->supports_indicators && pa_startswith(buf, "AT+BIND=")) {
// If this comma-separated list contains `2`, the HF is
// able to report values for the battery indicator.
return true;
} else if (c->supports_indicators && sscanf(buf, "AT+BIEV=%u,%u", &indicator, &val)) {
switch (indicator) {
case 2:
pa_log_notice("Battery Level: %d%%", val);
if (val < 0 || val > 100) {
pa_log_error("Battery HF indicator %d out of [0, 100] range", val);
rfcomm_write_response(fd, "ERROR");
return false;
}
pa_bluetooth_device_report_battery_level(t->device, val, "HFP 1.7 HF indicator");
break;
default:
pa_log_error("Unknown HF indicator %u", indicator);
rfcomm_write_response(fd, "ERROR");
return false;
}
return true;
} if (c->state == 4) {
/* the ack for the codec setting may take a while. we need
* to reply OK to everything else until then */
return true;
}
/* if we get here, negotiation should be complete */
if (c->state != 5) {
pa_log_error("HFP negotiation failed in state %d with inbound %s\n",
c->state, buf);
rfcomm_write_response(fd, "ERROR");
return false;
}
/*
* once we're fully connected, just reply OK to everything
* it will just be the headset sending the occasional status
* update, but we process only the ones we care about
*/
return true;
}
static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
@ -359,6 +725,11 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
pa_log_info("Lost RFCOMM connection.");
// TODO: Keep track of which profile is the current battery provider,
// only deregister if it is us currently providing these levels.
// (Also helpful to fill the 'Source' property)
// We might also move this to Profile1::RequestDisconnection
pa_bluetooth_device_deregister_battery(t->device);
goto fail;
}
@ -366,7 +737,9 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
char buf[512];
ssize_t len;
int gain, dummy;
bool do_reply = false;
bool do_reply = false;
int vendor, product, version, features;
int num;
len = pa_read(fd, buf, 511, NULL);
if (len < 0) {
@ -386,31 +759,81 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
* AT+CKPD=200: Sent by HS when headset button is pressed.
* RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because
* it does not expect a reply. */
if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) {
t->speaker_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t);
if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM%*[=:]%d\r\n", &gain) == 1) {
if (!t->set_sink_volume) {
pa_log_debug("HS/HF peer supports speaker gain control");
t->set_sink_volume = set_sink_volume;
}
t->sink_volume = hsp_gain_to_volume(gain);
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED), t);
do_reply = true;
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) {
t->microphone_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS%*[=:]%d\r\n", &gain) == 1) {
if (!t->set_source_volume) {
pa_log_debug("HS/HF peer supports microphone gain control");
t->set_source_volume = set_source_volume;
}
t->source_volume = hsp_gain_to_volume(gain);
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED), t);
do_reply = true;
} else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
do_reply = true;
} else if (sscanf(buf, "AT+XAPL=%04x-%04x-%04x,%d", &vendor, &product, &version, &features) == 4) {
if (features & 0x2)
/* claim, that we support battery status reports */
rfcomm_write_response(fd, "+XAPL=iPhone,6");
do_reply = true;
} else if (sscanf(buf, "AT+IPHONEACCEV=%d", &num) == 1) {
char *substr = buf, *keystr;
int key, val, i;
do_reply = true;
for (i = 0; i < num; ++i) {
keystr = strchr(substr, ',');
if (!keystr) {
pa_log_warn("%s misses key for argument #%d", buf, i);
do_reply = false;
break;
}
keystr++;
substr = strchr(keystr, ',');
if (!substr) {
pa_log_warn("%s misses value for argument #%d", buf, i);
do_reply = false;
break;
}
substr++;
key = atoi(keystr);
val = atoi(substr);
switch (key) {
case 1:
pa_log_notice("Battery Level: %d0%%", val + 1);
pa_bluetooth_device_report_battery_level(t->device, (val + 1) * 10, "Apple accessory indication");
break;
case 2:
pa_log_notice("Dock Status: %s", val ? "docked" : "undocked");
break;
default:
pa_log_debug("Unexpected IPHONEACCEV key %#x", key);
break;
}
}
if (!do_reply)
rfcomm_write_response(fd, "ERROR");
} else if (t->config) { /* t->config is only non-null for hfp profile */
do_reply = hfp_rfcomm_handle(fd, t, buf);
} else {
rfcomm_write_response(fd, "ERROR");
do_reply = false;
}
if (do_reply) {
pa_log_debug("RFCOMM >> OK");
len = write(fd, "\r\nOK\r\n", 6);
/* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */
if (len < 0)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
if (do_reply)
rfcomm_write_response(fd, "OK");
}
return;
@ -436,58 +859,54 @@ static void transport_destroy(pa_bluetooth_transport *t) {
pa_xfree(trd);
}
static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) {
static pa_volume_t set_sink_volume(pa_bluetooth_transport *t, pa_volume_t volume) {
struct transport_data *trd = t->userdata;
char buf[512];
ssize_t len, written;
uint16_t gain = volume_to_hsp_gain(volume);
if (t->speaker_gain == gain)
return;
/* Propagate rounding and bound checks */
volume = hsp_gain_to_volume(gain);
t->speaker_gain = gain;
if (t->sink_volume == volume)
return volume;
/* If we are in the AG role, we send a command to the head set to change
* the speaker gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the microphone gain has changed */
if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
len = sprintf(buf, "\r\n+VGS=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGS=%d", gain);
t->sink_volume = volume;
/* If we are in the AG role, we send an unsolicited result-code to the headset
* to change the speaker gain. In the HS role, source and sink are swapped,
* so in this case we notify the AG that the microphone gain has changed
* by sending a command. */
if (is_pulseaudio_audio_gateway(t->profile)) {
rfcomm_write_response(trd->rfcomm_fd, "+VGS=%d", gain);
} else {
len = sprintf(buf, "\r\nAT+VGM=%d\r\n", gain);
pa_log_debug("RFCOMM >> AT+VGM=%d", gain);
rfcomm_write_command(trd->rfcomm_fd, "AT+VGM=%d", gain);
}
written = write(trd->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
return volume;
}
static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) {
static pa_volume_t set_source_volume(pa_bluetooth_transport *t, pa_volume_t volume) {
struct transport_data *trd = t->userdata;
char buf[512];
ssize_t len, written;
uint16_t gain = volume_to_hsp_gain(volume);
if (t->microphone_gain == gain)
return;
/* Propagate rounding and bound checks */
volume = hsp_gain_to_volume(gain);
t->microphone_gain = gain;
if (t->source_volume == volume)
return volume;
/* If we are in the AG role, we send a command to the head set to change
* the microphone gain. In the HS role, source and sink are swapped, so
* in this case we notify the AG that the speaker gain has changed */
if (t->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT) {
len = sprintf(buf, "\r\n+VGM=%d\r\n", gain);
pa_log_debug("RFCOMM >> +VGM=%d", gain);
t->source_volume = volume;
/* If we are in the AG role, we send an unsolicited result-code to the headset
* to change the microphone gain. In the HS role, source and sink are swapped,
* so in this case we notify the AG that the speaker gain has changed
* by sending a command. */
if (is_pulseaudio_audio_gateway(t->profile)) {
rfcomm_write_response(trd->rfcomm_fd, "+VGM=%d", gain);
} else {
len = sprintf(buf, "\r\nAT+VGS=%d\r\n", gain);
pa_log_debug("RFCOMM >> AT+VGS=%d", gain);
rfcomm_write_command(trd->rfcomm_fd, "AT+VGS=%d", gain);
}
written = write (trd->rfcomm_fd, buf, len);
if (written != len)
pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
return volume;
}
static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) {
@ -509,9 +928,11 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
handler = dbus_message_get_path(m);
if (pa_streq(handler, HSP_AG_PROFILE)) {
p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
p = PA_BLUETOOTH_PROFILE_HSP_HS;
} else if (pa_streq(handler, HSP_HS_PROFILE)) {
p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
p = PA_BLUETOOTH_PROFILE_HSP_AG;
} else if (pa_streq(handler, HFP_AG_PROFILE)) {
p = PA_BLUETOOTH_PROFILE_HFP_HF;
} else {
pa_log_error("Invalid handler");
goto fail;
@ -522,10 +943,20 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
d = pa_bluetooth_discovery_get_device_by_path(b->discovery, path);
if (d == NULL) {
pa_log_error("Device doesnt exist for %s", path);
pa_log_error("Device doesn't exist for %s", path);
goto fail;
}
if (d->enable_hfp_hf) {
if (p == PA_BLUETOOTH_PROFILE_HSP_HS && pa_hashmap_get(d->uuids, PA_BLUETOOTH_UUID_HFP_HF)) {
/* If peer connecting to HSP Audio Gateway supports HFP HF profile
* reject this connection to force it to connect to HSP Audio Gateway instead.
*/
pa_log_info("HFP HF enabled in native backend and is supported by peer, rejecting HSP HS peer connection");
goto fail;
}
}
pa_assert_se(dbus_message_iter_next(&arg_i));
pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_UNIX_FD);
@ -537,14 +968,35 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
sender = dbus_message_get_sender(m);
pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL,
p == PA_BLUETOOTH_PROFILE_HFP_HF ?
sizeof(struct hfp_config) : 0);
pa_xfree(pathfd);
t->acquire = sco_acquire_cb;
t->release = sco_release_cb;
t->destroy = transport_destroy;
t->set_speaker_gain = set_speaker_gain;
t->set_microphone_gain = set_microphone_gain;
/* If PA is the HF/HS we are in control of volume attenuation and
* can always send volume commands (notifications) to keep the peer
* updated on actual volume value.
*
* If the peer is the HF/HS it is responsible for attenuation of both
* speaker and microphone gain.
* On HFP speaker/microphone gain support is reported by bit 4 in the
* `AT+BRSF=` command. Since it isn't explicitly documented whether this
* applies to speaker or microphone gain but the peer is required to send
* an initial value with `AT+VG[MS]=` either callback is hooked
* independently as soon as this command is received.
* On HSP this is not specified and is assumed to be dynamic for both
* speaker and microphone.
*/
if (is_peer_audio_gateway(p)) {
t->set_sink_volume = set_sink_volume;
t->set_source_volume = set_source_volume;
}
pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
trd = pa_xnew0(struct transport_data, 1);
trd->rfcomm_fd = fd;
@ -555,16 +1007,15 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
sco_listen(t);
pa_bluetooth_transport_put(t);
pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
if (p != PA_BLUETOOTH_PROFILE_HFP_HF)
transport_put(t);
pa_assert_se(r = dbus_message_new_method_return(m));
return r;
fail:
pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to handle new connection"));
pa_assert_se(r = dbus_message_new_error(m, BLUEZ_ERROR_INVALID_ARGUMENTS, "Unable to handle new connection"));
return r;
}
@ -589,10 +1040,11 @@ static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
if (!pa_streq(path, HSP_AG_PROFILE) && !pa_streq(path, HSP_HS_PROFILE))
if (!pa_streq(path, HSP_AG_PROFILE) && !pa_streq(path, HSP_HS_PROFILE)
&& !pa_streq(path, HFP_AG_PROFILE))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
const char *xml = PROFILE_INTROSPECT_XML;
pa_assert_se(r = dbus_message_new_method_return(m));
@ -616,6 +1068,26 @@ static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void
return DBUS_HANDLER_RESULT_HANDLED;
}
static pa_hook_result_t adapter_uuids_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_adapter *a, pa_bluetooth_backend *b) {
pa_assert(y);
pa_assert(a);
pa_assert(b);
if (profile_status_get(y, PA_BLUETOOTH_PROFILE_HSP_HS) == PA_BLUETOOTH_PROFILE_STATUS_ACTIVE &&
!pa_hashmap_get(a->uuids, PA_BLUETOOTH_UUID_HSP_AG))
register_profile(b, HSP_AG_PROFILE, PA_BLUETOOTH_UUID_HSP_AG, PA_BLUETOOTH_PROFILE_HSP_HS);
if (profile_status_get(y, PA_BLUETOOTH_PROFILE_HSP_AG) == PA_BLUETOOTH_PROFILE_STATUS_ACTIVE &&
!pa_hashmap_get(a->uuids, PA_BLUETOOTH_UUID_HSP_HS))
register_profile(b, HSP_HS_PROFILE, PA_BLUETOOTH_UUID_HSP_HS, PA_BLUETOOTH_PROFILE_HSP_AG);
if (profile_status_get(y, PA_BLUETOOTH_PROFILE_HFP_HF) == PA_BLUETOOTH_PROFILE_STATUS_ACTIVE &&
!pa_hashmap_get(a->uuids, PA_BLUETOOTH_UUID_HFP_AG))
register_profile(b, HFP_AG_PROFILE, PA_BLUETOOTH_UUID_HFP_AG, PA_BLUETOOTH_PROFILE_HFP_HF);
return PA_HOOK_OK;
}
static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
static const DBusObjectPathVTable vtable_profile = {
.message_function = profile_handler,
@ -626,53 +1098,73 @@ static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile
pa_assert(b);
switch (profile) {
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
case PA_BLUETOOTH_PROFILE_HSP_HS:
object_name = HSP_AG_PROFILE;
uuid = PA_BLUETOOTH_UUID_HSP_AG;
break;
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
case PA_BLUETOOTH_PROFILE_HSP_AG:
object_name = HSP_HS_PROFILE;
uuid = PA_BLUETOOTH_UUID_HSP_HS;
break;
case PA_BLUETOOTH_PROFILE_HFP_HF:
object_name = HFP_AG_PROFILE;
uuid = PA_BLUETOOTH_UUID_HFP_AG;
break;
default:
pa_assert_not_reached();
break;
}
pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(b->connection), object_name, &vtable_profile, b));
register_profile(b, object_name, uuid);
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
register_profile(b, object_name, uuid, profile);
}
static void profile_done(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
pa_assert(b);
profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_INACTIVE);
switch (profile) {
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
case PA_BLUETOOTH_PROFILE_HSP_HS:
dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE);
break;
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
case PA_BLUETOOTH_PROFILE_HSP_AG:
dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_HS_PROFILE);
break;
case PA_BLUETOOTH_PROFILE_HFP_HF:
dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HFP_AG_PROFILE);
break;
default:
pa_assert_not_reached();
break;
}
}
void pa_bluetooth_native_backend_enable_hs_role(pa_bluetooth_backend *native_backend, bool enable_hs_role) {
if (enable_hs_role == native_backend->enable_hs_role)
return;
if (enable_hs_role)
profile_init(native_backend, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
else
profile_done(native_backend, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
native_backend->enable_hs_role = enable_hs_role;
static void native_backend_apply_profile_registration_change(pa_bluetooth_backend *native_backend, bool enable_shared_profiles) {
if (enable_shared_profiles) {
profile_init(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
if (native_backend->enable_hfp_hf)
profile_init(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
} else {
profile_done(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
if (native_backend->enable_hfp_hf)
profile_done(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
}
}
pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role) {
void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *native_backend, bool enable) {
if (enable == native_backend->enable_shared_profiles)
return;
native_backend_apply_profile_registration_change(native_backend, enable);
native_backend->enable_shared_profiles = enable;
}
pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) {
pa_bluetooth_backend *backend;
DBusError err;
@ -690,11 +1182,22 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
}
backend->discovery = y;
backend->enable_hs_role = enable_hs_role;
backend->enable_shared_profiles = enable_shared_profiles;
backend->enable_hfp_hf = pa_bluetooth_discovery_get_enable_native_hfp_hf(y);
backend->enable_hsp_hs = pa_bluetooth_discovery_get_enable_native_hsp_hs(y);
if (enable_hs_role)
profile_init(backend, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
profile_init(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
backend->adapter_uuids_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED), PA_HOOK_NORMAL,
(pa_hook_cb_t) adapter_uuids_changed_cb, backend);
if (!backend->enable_hsp_hs && !backend->enable_hfp_hf)
pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections.");
if (backend->enable_hsp_hs)
profile_init(backend, PA_BLUETOOTH_PROFILE_HSP_HS);
if (backend->enable_shared_profiles)
native_backend_apply_profile_registration_change(backend, true);
return backend;
}
@ -704,9 +1207,14 @@ void pa_bluetooth_native_backend_free(pa_bluetooth_backend *backend) {
pa_dbus_free_pending_list(&backend->pending);
if (backend->enable_hs_role)
profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
if (backend->adapter_uuids_changed_slot)
pa_hook_slot_free(backend->adapter_uuids_changed_slot);
if (backend->enable_shared_profiles)
native_backend_apply_profile_registration_change(backend, false);
if (backend->enable_hsp_hs)
profile_done(backend, PA_BLUETOOTH_PROFILE_HSP_HS);
pa_dbus_connection_unref(backend->connection);

View file

@ -43,12 +43,12 @@
#define HF_AUDIO_AGENT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
"<node>" \
" <interface name=\"org.freedesktop.DBus.Introspectable\">" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">" \
" <method name=\"Introspect\">" \
" <arg direction=\"out\" type=\"s\" />" \
" </method>" \
" </interface>" \
" <interface name=\"org.ofono.HandsfreeAudioAgent\">" \
" <interface name=\"" HF_AUDIO_AGENT_INTERFACE "\">" \
" <method name=\"Release\">" \
" </method>" \
" <method name=\"NewConnection\">" \
@ -83,6 +83,61 @@ struct pa_bluetooth_backend {
PA_LLIST_HEAD(pa_dbus_pending, pending);
};
static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
ssize_t l = 0;
size_t written = 0;
size_t write_size;
pa_assert(t);
/* since SCO setup is symmetric, fix write MTU to be size of last read packet */
if (t->last_read_size)
write_mtu = PA_MIN(t->last_read_size, write_mtu);
/* if encoder buffer has less data than required to make complete packet */
if (size < write_mtu)
return 0;
/* write out MTU sized chunks only */
while (written < size) {
write_size = PA_MIN(size - written, write_mtu);
if (write_size < write_mtu)
break;
l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
if (l < 0)
break;
written += l;
}
if (l < 0) {
if (errno == EAGAIN) {
/* Hmm, apparently the socket was not writable, give up for now */
pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
/* Drain write buffer */
written = size;
} else if (errno == EINVAL && t->last_read_size == 0) {
/* Likely write_link_mtu is still wrong, retry after next successful read */
pa_log_debug("got write EINVAL, next successful read should fix MTU");
/* Drain write buffer */
written = size;
} else {
pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
/* Report error from write call */
return -1;
}
}
/* if too much data left discard it all */
if (size - written >= write_mtu) {
pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
written, size, write_mtu);
/* Drain write buffer */
written = size;
}
return written;
}
static pa_dbus_pending* hf_dbus_send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
DBusPendingCallNotifyFunction func, void *call_data) {
pa_dbus_pending *p;
@ -165,14 +220,21 @@ static int card_acquire(struct hf_audio_card *card) {
DBUS_TYPE_BYTE, &codec,
DBUS_TYPE_INVALID) == true)) {
dbus_message_unref(r);
if (codec != HFP_AUDIO_CODEC_CVSD) {
if (codec == HFP_AUDIO_CODEC_CVSD) {
pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
} else if (codec == HFP_AUDIO_CODEC_MSBC) {
/* oFono is expected to set up socket BT_VOICE_TRANSPARENT option */
pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, NULL);
} else {
pa_assert_fp(codec != HFP_AUDIO_CODEC_CVSD && codec != HFP_AUDIO_CODEC_MSBC);
pa_log_error("Invalid codec: %u", codec);
/* shutdown to make sure connection is dropped immediately */
shutdown(fd, SHUT_RDWR);
close(fd);
return -1;
}
card->transport->codec = codec;
card->fd = fd;
return 0;
}
@ -267,11 +329,14 @@ static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool opti
* the Bluetooth adapter and (for adapters in the USB bus) the MxPS
* value from the Isoc USB endpoint in use by btusb and should be
* made available to userspace by the Bluetooth kernel subsystem.
* Meanwhile the empiric value 48 will be used. */
*
* Set initial MTU to max size which is reported to be working (60 bytes)
* See also pa_bluetooth_transport::last_read_size handling.
*/
if (imtu)
*imtu = 48;
*imtu = 60;
if (omtu)
*omtu = 48;
*omtu = 60;
err = socket_accept(card->fd);
if (err < 0) {
@ -303,7 +368,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
const char *key, *value;
struct hf_audio_card *card;
pa_bluetooth_device *d;
pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
pa_bluetooth_profile_t p = PA_BLUETOOTH_PROFILE_HFP_AG;
pa_assert(backend);
pa_assert(path);
@ -337,7 +402,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
card->local_address = pa_xstrdup(value);
} else if (pa_streq(key, "Type")) {
if (pa_streq(value, "gateway"))
p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
p = PA_BLUETOOTH_PROFILE_HFP_HF;
}
pa_log_debug("%s: %s", key, value);
@ -347,7 +412,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
d = pa_bluetooth_discovery_get_device_by_address(backend->discovery, card->remote_address, card->local_address);
if (!d) {
pa_log_error("Device doesnt exist for %s", path);
pa_log_error("Device doesn't exist for %s", path);
goto fail;
}
@ -355,6 +420,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
card->transport->acquire = hf_audio_agent_transport_acquire;
card->transport->release = hf_audio_agent_transport_release;
card->transport->userdata = card;
pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
pa_bluetooth_transport_put(card->transport);
pa_hashmap_put(backend->cards, card->path, card);
@ -482,6 +548,8 @@ static void hf_audio_agent_register(pa_bluetooth_backend *hf) {
pa_assert_se(m = dbus_message_new_method_call(OFONO_SERVICE, "/", HF_AUDIO_MANAGER_INTERFACE, "Register"));
codecs[ncodecs++] = HFP_AUDIO_CODEC_CVSD;
if (pa_bluetooth_discovery_get_enable_msbc(hf->discovery))
codecs[ncodecs++] = HFP_AUDIO_CODEC_MSBC;
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pcodecs, ncodecs,
DBUS_TYPE_INVALID));
@ -515,12 +583,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
pa_assert(backend);
sender = dbus_message_get_sender(m);
if (!pa_safe_streq(backend->ofono_bus_id, sender) && !pa_streq("org.freedesktop.DBus", sender))
if (!pa_safe_streq(backend->ofono_bus_id, sender) && !pa_streq(DBUS_SERVICE_DBUS, sender))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
dbus_error_init(&err);
if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
const char *name, *old_owner, *new_owner;
if (!dbus_message_get_args(m, &err,
@ -528,7 +596,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *da
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID)) {
pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
pa_log_error("Failed to parse " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message);
goto fail;
}
@ -627,9 +695,7 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
card = pa_hashmap_get(backend->cards, path);
card->connecting = false;
if (!card || codec != HFP_AUDIO_CODEC_CVSD || card->fd >= 0) {
if (!card || (codec != HFP_AUDIO_CODEC_CVSD && codec != HFP_AUDIO_CODEC_MSBC) || card->fd >= 0) {
pa_log_warn("New audio connection invalid arguments (path=%s fd=%d, codec=%d)", path, fd, codec);
pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call"));
shutdown(fd, SHUT_RDWR);
@ -639,8 +705,14 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
pa_log_debug("New audio connection on card %s (fd=%d, codec=%d)", path, fd, codec);
card->connecting = false;
card->fd = fd;
card->transport->codec = codec;
if (codec == HFP_AUDIO_CODEC_CVSD) {
pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
} else if (codec == HFP_AUDIO_CODEC_MSBC) {
/* oFono is expected to set up socket BT_VOICE_TRANSPARENT option */
pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, NULL);
}
pa_bluetooth_transport_set_state(card->transport, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING);
@ -665,7 +737,7 @@ static DBusHandlerResult hf_audio_agent_handler(DBusConnection *c, DBusMessage *
pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
const char *xml = HF_AUDIO_AGENT_XML;
pa_assert_se(r = dbus_message_new_method_return(m));
@ -719,7 +791,7 @@ pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_di
}
if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err,
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
"type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',"
"arg0='" OFONO_SERVICE "'",
"type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardAdded'",
"type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardRemoved'",
@ -749,7 +821,7 @@ void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *backend) {
dbus_connection_unregister_object_path(pa_dbus_connection_get(backend->connection), HF_AUDIO_AGENT_PATH);
pa_dbus_remove_matches(pa_dbus_connection_get(backend->connection),
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
"type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',"
"arg0='" OFONO_SERVICE "'",
"type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardAdded'",
"type='signal',sender='" OFONO_SERVICE "',interface='" HF_AUDIO_MANAGER_INTERFACE "',member='CardRemoved'",

File diff suppressed because it is too large Load diff

View file

@ -25,6 +25,21 @@
#include "a2dp-codec-util.h"
#define BLUEZ_SERVICE "org.bluez"
#define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
#define BLUEZ_BATTERY_PROVIDER_INTERFACE BLUEZ_SERVICE ".BatteryProvider1"
#define BLUEZ_BATTERY_PROVIDER_MANAGER_INTERFACE BLUEZ_SERVICE ".BatteryProviderManager1"
#define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
#define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
#define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1"
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1"
#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1"
#define BLUEZ_ERROR_INVALID_ARGUMENTS BLUEZ_SERVICE ".Error.InvalidArguments"
#define BLUEZ_ERROR_NOT_AVAILABLE BLUEZ_SERVICE ".Error.NotAvailable"
#define BLUEZ_ERROR_NOT_SUPPORTED BLUEZ_SERVICE ".Error.NotSupported"
#define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb"
@ -39,6 +54,9 @@
#define PA_BLUETOOTH_UUID_HFP_HF "0000111e-0000-1000-8000-00805f9b34fb"
#define PA_BLUETOOTH_UUID_HFP_AG "0000111f-0000-1000-8000-00805f9b34fb"
#define A2DP_MAX_GAIN 127
#define HSP_MAX_GAIN 15
typedef struct pa_bluetooth_transport pa_bluetooth_transport;
typedef struct pa_bluetooth_device pa_bluetooth_device;
typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
@ -46,23 +64,34 @@ typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
typedef struct pa_bluetooth_backend pa_bluetooth_backend;
typedef enum pa_bluetooth_hook {
PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED, /* Call data: pa_bluetooth_adapter */
PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_MAX
} pa_bluetooth_hook_t;
typedef enum profile {
PA_BLUETOOTH_PROFILE_A2DP_SINK,
PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
PA_BLUETOOTH_PROFILE_HSP_HS,
PA_BLUETOOTH_PROFILE_HSP_AG,
PA_BLUETOOTH_PROFILE_HFP_HF,
PA_BLUETOOTH_PROFILE_HFP_AG,
PA_BLUETOOTH_PROFILE_OFF
} pa_bluetooth_profile_t;
#define PA_BLUETOOTH_PROFILE_COUNT PA_BLUETOOTH_PROFILE_OFF
typedef enum pa_bluetooth_profile_status {
PA_BLUETOOTH_PROFILE_STATUS_INACTIVE,
PA_BLUETOOTH_PROFILE_STATUS_ACTIVE,
PA_BLUETOOTH_PROFILE_STATUS_REGISTERING,
PA_BLUETOOTH_PROFILE_STATUS_REGISTERED
} pa_bluetooth_profile_status_t;
typedef enum pa_bluetooth_transport_state {
PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED,
PA_BLUETOOTH_TRANSPORT_STATE_IDLE,
@ -72,8 +101,9 @@ typedef enum pa_bluetooth_transport_state {
typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu);
typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
typedef pa_volume_t (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, pa_volume_t volume);
typedef ssize_t (*pa_bluetooth_transport_write_cb)(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu);
typedef int (*pa_bluetooth_transport_setsockopt_cb)(pa_bluetooth_transport *t, int fd);
struct pa_bluetooth_transport {
pa_bluetooth_device *device;
@ -82,22 +112,25 @@ struct pa_bluetooth_transport {
char *path;
pa_bluetooth_profile_t profile;
uint8_t codec;
uint8_t *config;
void *config;
size_t config_size;
const pa_a2dp_codec *a2dp_codec;
const pa_bt_codec *bt_codec;
int stream_write_type;
size_t last_read_size;
uint16_t microphone_gain;
uint16_t speaker_gain;
pa_volume_t source_volume;
pa_volume_t sink_volume;
pa_bluetooth_transport_state_t state;
pa_bluetooth_transport_acquire_cb acquire;
pa_bluetooth_transport_release_cb release;
pa_bluetooth_transport_write_cb write;
pa_bluetooth_transport_setsockopt_cb setsockopt;
pa_bluetooth_transport_destroy_cb destroy;
pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
pa_bluetooth_transport_set_volume_cb set_sink_volume;
pa_bluetooth_transport_set_volume_cb set_source_volume;
void *userdata;
};
@ -105,10 +138,14 @@ struct pa_bluetooth_device {
pa_bluetooth_discovery *discovery;
pa_bluetooth_adapter *adapter;
bool enable_hfp_hf;
bool properties_received;
bool tried_to_link_with_adapter;
bool valid;
bool autodetect_mtu;
bool codec_switching_in_progress;
bool avrcp_absolute_volume;
uint32_t output_rate_refresh_interval_ms;
/* Device information */
char *path;
@ -117,18 +154,28 @@ struct pa_bluetooth_device {
char *address;
uint32_t class_of_device;
pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
/* pa_a2dp_codec_id* -> pa_hashmap ( char* (remote endpoint) -> struct a2dp_codec_capabilities* ) */
pa_hashmap *a2dp_sink_endpoints;
pa_hashmap *a2dp_source_endpoints;
pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT];
pa_time_event *wait_for_profiles_timer;
bool has_battery_level;
uint8_t battery_level;
const char *battery_source;
};
struct pa_bluetooth_adapter {
pa_bluetooth_discovery *discovery;
char *path;
char *address;
pa_hashmap *uuids; /* char* -> char* (hashmap-as-a-set) */
bool valid;
bool application_registered;
bool battery_provider_registered;
};
#ifdef HAVE_BLUEZ_5_OFONO_HEADSET
@ -142,26 +189,36 @@ static inline void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b) {}
#endif
#ifdef HAVE_BLUEZ_5_NATIVE_HEADSET
pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role);
pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles);
void pa_bluetooth_native_backend_free(pa_bluetooth_backend *b);
void pa_bluetooth_native_backend_enable_hs_role(pa_bluetooth_backend *b, bool enable_hs_role);
void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *b, bool enable);
#else
static inline pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role) {
static inline pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) {
return NULL;
}
static inline void pa_bluetooth_native_backend_free(pa_bluetooth_backend *b) {}
static inline void pa_bluetooth_native_backend_enable_hs_role(pa_bluetooth_backend *b, bool enable_hs_role) {}
static inline void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetooth_backend *b, bool enable) {}
#endif
pa_bluetooth_profile_status_t profile_status_get(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile);
void profile_status_set(pa_bluetooth_discovery *y, pa_bluetooth_profile_t profile, pa_bluetooth_profile_status_t status);
pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
pa_bluetooth_profile_t p, const uint8_t *config, size_t size);
void pa_bluetooth_transport_reconfigure(pa_bluetooth_transport *t, const pa_bt_codec *bt_codec,
pa_bluetooth_transport_write_cb write_cb, pa_bluetooth_transport_setsockopt_cb setsockopt_cb);
void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state);
void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t);
void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
void pa_bluetooth_transport_load_a2dp_sink_volume(pa_bluetooth_transport *t);
bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_endpoint_conf *endpoint_conf, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
void pa_bluetooth_device_report_battery_level(pa_bluetooth_device *d, uint8_t level, const char *reporting_source);
void pa_bluetooth_device_deregister_battery(pa_bluetooth_device *d);
pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path);
pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local);
@ -169,6 +226,8 @@ pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_d
pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook);
const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
bool pa_bluetooth_profile_should_attenuate_volume(pa_bluetooth_profile_t profile);
bool pa_bluetooth_profile_is_a2dp(pa_bluetooth_profile_t profile);
static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
@ -178,8 +237,11 @@ static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
#define HEADSET_BACKEND_NATIVE 1
#define HEADSET_BACKEND_AUTO 2
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend);
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend, bool enable_native_hsp_hs, bool enable_native_hfp_hf, bool enable_msbc);
pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y);
void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is_running);
bool pa_bluetooth_discovery_get_enable_native_hsp_hs(pa_bluetooth_discovery *y);
bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y);
bool pa_bluetooth_discovery_get_enable_msbc(pa_bluetooth_discovery *y);
#endif

View file

@ -0,0 +1,67 @@
#pragma once
/***
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#include <pulsecore/core.h>
typedef struct pa_bt_codec {
/* Unique name of the codec, lowercase and without whitespaces, used for
* constructing identifier, D-Bus paths, ... */
const char *name;
/* Human readable codec description */
const char *description;
/* Initialize codec, returns codec info data and set sample_spec,
* for_encoding is true when codec_info is used for encoding,
* for_backchannel is true when codec_info is used for backchannel */
void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core);
/* Deinitialize and release codec info data in codec_info */
void (*deinit)(void *codec_info);
/* Reset internal state of codec info data in codec_info, returns
* a negative value on failure */
int (*reset)(void *codec_info);
/* Get read block size for codec, it is minimal size of buffer
* needed to decode read_link_mtu bytes of encoded data */
size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
/* Get write block size for codec, it is maximal size of buffer
* which can produce at most write_link_mtu bytes of encoded data */
size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
/* Get encoded block size for codec to hold one encoded frame.
* Note HFP mSBC codec encoded block may not fit into one MTU and is sent out in chunks. */
size_t (*get_encoded_block_size)(void *codec_info, size_t input_size);
/* Reduce encoder bitrate for codec, returns new write block size or zero
* if not changed, called when socket is not accepting encoded data fast
* enough */
size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
/* Increase encoder bitrate for codec, returns new write block size or zero
* if not changed, called periodically when socket is keeping up with
* encoded data */
size_t (*increase_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
/* Encode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*encode_buffer)(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
/* Decode input_buffer of input_size to output_buffer of output_size,
* returns size of filled ouput_buffer and set processed to size of
* processed input_buffer */
size_t (*decode_buffer)(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed);
} pa_bt_codec;

View file

@ -0,0 +1,123 @@
/***
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/core.h>
#include "bt-codec-api.h"
typedef struct codec_info {
pa_sample_spec sample_spec;
} codec_info_t;
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
codec_info_t *info;
info = pa_xnew0(codec_info_t, 1);
info->sample_spec.format = PA_SAMPLE_S16LE;
info->sample_spec.channels = 1;
info->sample_spec.rate = 8000;
*sample_spec = info->sample_spec;
return info;
}
static void deinit(void *codec_info) {
pa_xfree(codec_info);
}
static int reset(void *codec_info) {
return 0;
}
static size_t get_block_size(void *codec_info, size_t link_mtu) {
codec_info_t *info = (codec_info_t *) codec_info;
size_t block_size = link_mtu;
if (!pa_frame_aligned(block_size, &info->sample_spec)) {
pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
block_size = pa_frame_align(block_size, &info->sample_spec);
}
return block_size;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
codec_info_t *info = (codec_info_t *) codec_info;
/* input size should be aligned to sample spec */
pa_assert_fp(pa_frame_aligned(input_size, &info->sample_spec));
return input_size;
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
pa_assert(input_size <= output_size);
memcpy(output_buffer, input_buffer, input_size);
*processed = input_size;
return input_size;
}
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
codec_info_t *info = (codec_info_t *) codec_info;
*processed = input_size;
/* In some rare occasions, we might receive packets of a very strange
* size. This could potentially be possible if the SCO packet was
* received partially over-the-air, or more probably due to hardware
* issues in our Bluetooth adapter. In these cases, in order to avoid
* an assertion failure due to unaligned data, just discard the whole
* packet */
if (!pa_frame_aligned(input_size, &info->sample_spec)) {
pa_log_warn("SCO packet received of unaligned size: %zu", input_size);
return 0;
}
memcpy(output_buffer, input_buffer, input_size);
return input_size;
}
/* dummy passthrough codec used with HSP/HFP CVSD */
const pa_bt_codec pa_bt_codec_cvsd = {
.name = "CVSD",
.description = "CVSD",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_block_size,
.get_write_block_size = get_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
};

View file

@ -0,0 +1,317 @@
/***
This file is part of PulseAudio.
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulsecore/core.h>
#include "bt-codec-api.h"
#include "bt-codec-msbc.h"
#include <sbc/sbc.h>
typedef struct sbc_info {
sbc_t sbc; /* Codec data */
size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
uint8_t msbc_seq:2; /* mSBC packet sequence number, 2 bits only */
uint16_t msbc_push_offset;
uint8_t input_buffer[MSBC_PACKET_SIZE]; /* Codec transfer buffer */
pa_sample_spec sample_spec;
} sbc_info_t;
static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
struct sbc_info *info;
int ret;
info = pa_xnew0(struct sbc_info, 1);
ret = sbc_init_msbc(&info->sbc, 0);
if (ret != 0) {
pa_xfree(info);
pa_log_error("mSBC initialization failed: %d", ret);
return NULL;
}
info->sbc.endian = SBC_LE;
info->codesize = sbc_get_codesize(&info->sbc);
info->frame_length = sbc_get_frame_length(&info->sbc);
pa_log_info("mSBC codesize=%d, frame_length=%d",
(int)info->codesize,
(int)info->frame_length);
info->sample_spec.format = PA_SAMPLE_S16LE;
info->sample_spec.channels = 1;
info->sample_spec.rate = 16000;
pa_assert(pa_frame_aligned(info->codesize, &info->sample_spec));
*sample_spec = info->sample_spec;
return info;
}
static void deinit(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
sbc_finish(&sbc_info->sbc);
pa_xfree(sbc_info);
}
static int reset(void *codec_info) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
int ret;
/* SBC library release 1.5 has a bug in sbc_reinit_msbc:
* it forgets to restore priv->msbc flag after clearing priv content.
* This causes decoder assertion on first call since codesize would be
* different from expected for mSBC configuration.
*
* Do not use sbc_reinit_msbc until it is fixed.
*/
sbc_finish(&sbc_info->sbc);
ret = sbc_init_msbc(&sbc_info->sbc, 0);
if (ret != 0) {
pa_xfree(sbc_info);
pa_log_error("mSBC initialization failed: %d", ret);
return -1;
}
sbc_info->sbc.endian = SBC_LE;
sbc_info->msbc_seq = 0;
sbc_info->msbc_push_offset = 0;
return 0;
}
static size_t get_read_block_size(void *codec_info, size_t link_mtu) {
struct sbc_info *info = (struct sbc_info *) codec_info;
size_t block_size = info->codesize;
/* this never happens as sbc_info->codesize is always frame-aligned */
if (!pa_frame_aligned(block_size, &info->sample_spec)) {
pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
block_size = pa_frame_align(block_size, &info->sample_spec);
}
return block_size;
}
static size_t get_write_block_size(void *codec_info, size_t link_mtu) {
struct sbc_info *info = (struct sbc_info *) codec_info;
return info->codesize;
}
static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
struct sbc_info *info = (struct sbc_info *) codec_info;
size_t encoded_size = MSBC_PACKET_SIZE;
/* input size should be aligned to write block size */
pa_assert_fp(input_size % info->codesize == 0);
return encoded_size * (input_size / info->codesize);
}
static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
return 0;
}
static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
struct msbc_frame *frame;
uint8_t seq;
ssize_t encoded;
ssize_t written;
pa_assert(input_size == sbc_info->codesize);
/* must be room to render packet */
pa_assert(output_size >= MSBC_PACKET_SIZE);
frame = (struct msbc_frame *)output_buffer;
seq = sbc_info->msbc_seq++;
frame->hdr.id0 = MSBC_H2_ID0;
frame->hdr.id1.s.id1 = MSBC_H2_ID1;
if (seq & 0x02)
frame->hdr.id1.s.sn1 = 3;
else
frame->hdr.id1.s.sn1 = 0;
if (seq & 0x01)
frame->hdr.id1.s.sn0 = 3;
else
frame->hdr.id1.s.sn0 = 0;
encoded = sbc_encode(&sbc_info->sbc,
input_buffer, input_size,
frame->payload, MSBC_FRAME_SIZE,
&written);
frame->padding = 0x00;
if (PA_UNLIKELY(encoded <= 0)) {
pa_log_error("SBC encoding error (%li) for input size %lu, SBC codesize %lu",
(long) encoded, input_size, sbc_get_codesize(&sbc_info->sbc));
if (encoded < 0) {
*processed = 0;
return -1;
} else {
*processed = input_size;
return 0;
}
}
pa_assert_fp((size_t) encoded == sbc_info->codesize);
pa_assert_fp((size_t) written == sbc_info->frame_length);
*processed = encoded;
return MSBC_PACKET_SIZE;
}
static inline bool is_all_zero(const uint8_t *ptr, size_t len) {
size_t i;
for (i = 0; i < len; ++i)
if (ptr[i] != 0)
return false;
return true;
}
/*
* We build a msbc frame up in the sbc_info buffer until we have a whole one
*/
static struct msbc_frame *msbc_find_frame(struct sbc_info *si, ssize_t *len,
const uint8_t *buf, int *pseq)
{
int i;
uint8_t *p = si->input_buffer;
/* skip input if it has all zero bytes
* this could happen with older kernels inserting all-zero blocks
* inside otherwise valid mSBC stream */
if (*len > 0 && is_all_zero(buf, *len))
*len = 0;
for (i = 0; i < *len; i++) {
union msbc_h2_id1 id1;
if (si->msbc_push_offset == 0) {
if (buf[i] != MSBC_H2_ID0)
continue;
} else if (si->msbc_push_offset == 1) {
id1.b = buf[i];
if (id1.s.id1 != MSBC_H2_ID1)
goto error;
if (id1.s.sn0 != 3 && id1.s.sn0 != 0)
goto error;
if (id1.s.sn1 != 3 && id1.s.sn1 != 0)
goto error;
} else if (si->msbc_push_offset == 2) {
if (buf[i] != MSBC_SYNC_BYTE)
goto error;
}
p[si->msbc_push_offset++] = buf[i];
if (si->msbc_push_offset == MSBC_PACKET_SIZE) {
id1.b = p[1];
*pseq = (id1.s.sn0 & 0x1) | (id1.s.sn1 & 0x2);
si->msbc_push_offset = 0;
*len = *len - i;
return (struct msbc_frame *)p;
}
continue;
error:
si->msbc_push_offset = 0;
}
*len = 0;
return NULL;
}
static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) {
struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
ssize_t remaining;
ssize_t decoded;
size_t written = 0;
struct msbc_frame *frame;
int seq;
remaining = input_size;
frame = msbc_find_frame(sbc_info, &remaining, input_buffer, &seq);
/* only process when we have a full frame */
if (!frame) {
*processed = input_size - remaining;
return 0;
}
uint8_t lost_packets = (4 + seq - sbc_info->msbc_seq++) % 4;
if (lost_packets) {
pa_log_debug("Lost %d input audio packet(s)", lost_packets);
sbc_info->msbc_seq = seq + 1;
}
decoded = sbc_decode(&sbc_info->sbc, frame->payload, MSBC_FRAME_SIZE, output_buffer, output_size, &written);
/* now we've consumed the sbc_info buffer, start a new one with
* the partial frame we have */
if (remaining > 0)
msbc_find_frame(sbc_info, &remaining, input_buffer + input_size - remaining, &seq);
pa_assert_fp(remaining == 0);
if (PA_UNLIKELY(decoded <= 0)) {
pa_log_error("mSBC decoding error (%li)", (long) decoded);
pa_silence_memory(output_buffer, sbc_info->codesize, &sbc_info->sample_spec);
decoded = sbc_info->frame_length;
written = sbc_info->codesize;
}
pa_assert_fp((size_t)decoded == sbc_info->frame_length);
pa_assert_fp((size_t)written == sbc_info->codesize);
*processed = input_size - remaining;
return written;
}
/* Modified SBC codec for HFP Wideband Speech*/
const pa_bt_codec pa_bt_codec_msbc = {
.name = "mSBC",
.description = "mSBC",
.init = init,
.deinit = deinit,
.reset = reset,
.get_read_block_size = get_read_block_size,
.get_write_block_size = get_write_block_size,
.get_encoded_block_size = get_encoded_block_size,
.reduce_encoder_bitrate = reduce_encoder_bitrate,
.increase_encoder_bitrate = increase_encoder_bitrate,
.encode_buffer = encode_buffer,
.decode_buffer = decode_buffer,
};

View file

@ -0,0 +1,35 @@
#pragma once
/*
* Parameters for use with mSBC over eSCO link
*/
#define MSBC_H2_ID0 0x01
#define MSBC_H2_ID1 0x08
#define MSBC_FRAME_SIZE 57
#define MSBC_SYNC_BYTE 0xad
struct msbc_h2_id1_s {
uint8_t id1:4;
uint8_t sn0:2;
uint8_t sn1:2;
} __attribute__ ((packed));
union msbc_h2_id1 {
struct msbc_h2_id1_s s;
uint8_t b;
};
struct msbc_h2_header {
uint8_t id0;
union msbc_h2_id1 id1;
} __attribute__ ((packed));
struct msbc_frame {
struct msbc_h2_header hdr;
uint8_t payload[MSBC_FRAME_SIZE];
uint8_t padding; /* must be zero */
} __attribute__ ((packed));
#define MSBC_PACKET_SIZE sizeof(struct msbc_frame)

View file

@ -2,6 +2,8 @@ libbluez5_util_sources = [
'a2dp-codec-sbc.c',
'a2dp-codec-util.c',
'bluez5-util.c',
'bt-codec-cvsd.c',
'bt-codec-msbc.c',
]
libbluez5_util_headers = [
@ -20,13 +22,20 @@ if get_option('bluez5-ofono-headset')
libbluez5_util_sources += [ 'backend-ofono.c' ]
endif
if have_bluez5_gstreamer
libbluez5_util_headers += [ 'a2dp-codec-gst.h' ]
libbluez5_util_sources += [ 'a2dp-codec-gst.c' ]
libbluez5_util_sources += [ 'a2dp-codec-ldac-gst.c' ]
libbluez5_util_sources += [ 'a2dp-codec-aptx-gst.c' ]
endif
libbluez5_util = shared_library('bluez5-util',
libbluez5_util_sources,
libbluez5_util_headers,
c_args : [pa_c_args, server_c_args],
link_args : [nodelete_link_args],
include_directories : [configinc, topinc],
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, dbus_dep, sbc_dep, libintl_dep],
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep],
install : true,
install_rpath : privlibdir,
install_dir : modlibexecdir,

View file

@ -38,7 +38,7 @@ PA_MODULE_LOAD_ONCE(true);
PA_MODULE_USAGE(
"auto_switch=<Switch between hsp and a2dp profile? (0 - never, 1 - media.role=phone, 2 - heuristic> "
"a2dp_source=<Handle a2dp_source card profile (sink role)?> "
"ag=<Handle headset_audio_gateway card profile (headset role)?> ");
"ag=<Handle headset_audio_gateway or handsfree_audio_gateway card profile (headset role)?> ");
static const char* const valid_modargs[] = {
"auto_switch",
@ -86,7 +86,7 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
if (u->enable_a2dp_source && pa_streq(s, "a2dp_source"))
role = "music";
else if (u->enable_ag && pa_streq(s, "headset_audio_gateway"))
else if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
role = "phone";
else {
pa_log_debug("Profile %s cannot be selected for loopback", s);
@ -125,7 +125,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *
if (!s)
return PA_HOOK_OK;
if (u->enable_ag && pa_streq(s, "headset_audio_gateway"))
if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway")))
role = "phone";
else {
pa_log_debug("Profile %s cannot be selected for loopback", s);
@ -156,7 +156,7 @@ static void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a
if (!pa_streq(profile->name, "a2dp_sink"))
continue;
} else {
if (!pa_streq(profile->name, "headset_head_unit"))
if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "handsfree_head_unit"))
continue;
}
@ -190,8 +190,8 @@ static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) {
if (!pa_hashmap_remove(u->will_need_revert_card_map, card))
return;
/* Skip card if does not have active hsp profile */
if (!pa_streq(card->active_profile->name, "headset_head_unit"))
/* Skip card if does not have active headset profile */
if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "handsfree_head_unit"))
return;
/* Skip card if already has active a2dp profile */
@ -202,8 +202,8 @@ static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) {
if (!pa_streq(card->active_profile->name, "a2dp_sink"))
return;
/* Skip card if already has active hsp profile */
if (pa_streq(card->active_profile->name, "headset_head_unit"))
/* Skip card if already has active headset profile */
if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "handsfree_head_unit"))
return;
}
@ -358,7 +358,9 @@ static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_prof
return PA_HOOK_OK;
/* Do not automatically switch profiles for headsets, just in case */
if (pa_streq(profile->name, "a2dp_sink") || pa_streq(profile->name, "headset_head_unit"))
if (pa_streq(profile->name, "a2dp_sink") ||
pa_streq(profile->name, "headset_head_unit") ||
pa_streq(profile->name, "handsfree_head_unit"))
return PA_HOOK_OK;
is_active_profile = card->active_profile == profile;

File diff suppressed because it is too large Load diff

View file

@ -37,11 +37,21 @@ PA_MODULE_LOAD_ONCE(true);
PA_MODULE_USAGE(
"headset=ofono|native|auto"
"autodetect_mtu=<boolean>"
"enable_msbc=<boolean, enable mSBC support in native and oFono backends, default is true>"
"output_rate_refresh_interval_ms=<interval between attempts to improve output rate in milliseconds>"
"enable_native_hsp_hs=<boolean, enable HSP support in native backend>"
"enable_native_hfp_hf=<boolean, enable HFP support in native backend>"
"avrcp_absolute_volume=<synchronize volume with peer, true by default>"
);
static const char* const valid_modargs[] = {
"headset",
"autodetect_mtu",
"enable_msbc",
"output_rate_refresh_interval_ms",
"enable_native_hsp_hs",
"enable_native_hfp_hf",
"avrcp_absolute_volume",
NULL
};
@ -52,6 +62,8 @@ struct userdata {
pa_hook_slot *device_connection_changed_slot;
pa_bluetooth_discovery *discovery;
bool autodetect_mtu;
bool avrcp_absolute_volume;
uint32_t output_rate_refresh_interval_ms;
};
static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
@ -62,7 +74,9 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
module_loaded = pa_hashmap_get(u->loaded_device_paths, d->path) ? true : false;
if (module_loaded && !pa_bluetooth_device_any_transport_connected(d)) {
/* When changing A2DP codec there is no transport connected, ensure that no module is unloaded */
if (module_loaded && !pa_bluetooth_device_any_transport_connected(d) &&
!d->codec_switching_in_progress) {
/* disconnection, the module unloads itself */
pa_log_debug("Unregistering module for %s", d->path);
pa_hashmap_remove(u->loaded_device_paths, d->path);
@ -72,7 +86,12 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
if (!module_loaded && pa_bluetooth_device_any_transport_connected(d)) {
/* a new device has been connected */
pa_module *m;
char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i", d->path, (int)u->autodetect_mtu);
char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i output_rate_refresh_interval_ms=%u"
" avrcp_absolute_volume=%i",
d->path,
(int)u->autodetect_mtu,
u->output_rate_refresh_interval_ms,
(int)u->avrcp_absolute_volume);
pa_log_debug("Loading module-bluez5-device %s", args);
pa_module_load(&m, u->module->core, "module-bluez5-device", args);
@ -92,7 +111,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
}
#ifdef HAVE_BLUEZ_5_NATIVE_HEADSET
const char *default_headset_backend = "auto";
const char *default_headset_backend = "native";
#else
const char *default_headset_backend = "ofono";
#endif
@ -103,6 +122,11 @@ int pa__init(pa_module *m) {
const char *headset_str;
int headset_backend;
bool autodetect_mtu;
bool enable_msbc;
bool avrcp_absolute_volume;
uint32_t output_rate_refresh_interval_ms;
bool enable_native_hsp_hs;
bool enable_native_hfp_hf;
pa_assert(m);
@ -123,9 +147,37 @@ int pa__init(pa_module *m) {
goto fail;
}
/* default value if no module parameter */
enable_native_hfp_hf = (headset_backend == HEADSET_BACKEND_NATIVE);
autodetect_mtu = false;
if (pa_modargs_get_value_boolean(ma, "autodetect_mtu", &autodetect_mtu) < 0) {
pa_log("Invalid boolean value for autodetect_mtu parameter");
}
enable_msbc = true;
if (pa_modargs_get_value_boolean(ma, "enable_msbc", &enable_msbc) < 0) {
pa_log("Invalid boolean value for enable_msbc parameter");
}
enable_native_hfp_hf = true;
if (pa_modargs_get_value_boolean(ma, "enable_native_hfp_hf", &enable_native_hfp_hf) < 0) {
pa_log("enable_native_hfp_hf must be true or false");
goto fail;
}
enable_native_hsp_hs = !enable_native_hfp_hf;
if (pa_modargs_get_value_boolean(ma, "enable_native_hsp_hs", &enable_native_hsp_hs) < 0) {
pa_log("enable_native_hsp_hs must be true or false");
goto fail;
}
avrcp_absolute_volume = true;
if (pa_modargs_get_value_boolean(ma, "avrcp_absolute_volume", &avrcp_absolute_volume) < 0) {
pa_log("avrcp_absolute_volume must be true or false");
goto fail;
}
output_rate_refresh_interval_ms = DEFAULT_OUTPUT_RATE_REFRESH_INTERVAL_MS;
if (pa_modargs_get_value_u32(ma, "output_rate_refresh_interval_ms", &output_rate_refresh_interval_ms) < 0) {
pa_log("Invalid value for output_rate_refresh_interval parameter.");
goto fail;
}
@ -133,9 +185,11 @@ int pa__init(pa_module *m) {
u->module = m;
u->core = m->core;
u->autodetect_mtu = autodetect_mtu;
u->avrcp_absolute_volume = avrcp_absolute_volume;
u->output_rate_refresh_interval_ms = output_rate_refresh_interval_ms;
u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend)))
if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend, enable_native_hsp_hs, enable_native_hfp_hf, enable_msbc)))
goto fail;
u->device_connection_changed_slot =
@ -163,11 +217,11 @@ void pa__done(pa_module *m) {
if (u->device_connection_changed_slot)
pa_hook_slot_free(u->device_connection_changed_slot);
if (u->discovery)
pa_bluetooth_discovery_unref(u->discovery);
if (u->loaded_device_paths)
pa_hashmap_free(u->loaded_device_paths);
if (u->discovery)
pa_bluetooth_discovery_unref(u->discovery);
pa_xfree(u);
}

View file

@ -158,7 +158,7 @@ static DBusHandlerResult disconnection_filter_cb(DBusConnection *connection, DBu
pa_assert(message);
pa_assert(c);
if (dbus_message_is_signal(message, "org.freedesktop.DBus.Local", "Disconnected")) {
if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
/* The connection died. Now we want to free the connection object, but
* let's wait until this message is fully processed, in case someone
* else is interested in this signal too. */

View file

@ -98,13 +98,13 @@ class PaWebrtcTraceCallback : public webrtc::TraceCallback {
void Print(webrtc::TraceLevel level, const char *message, int length)
{
if (level & webrtc::kTraceError || level & webrtc::kTraceCritical)
pa_log(message);
pa_log("%s", message);
else if (level & webrtc::kTraceWarning)
pa_log_warn(message);
pa_log_warn("%s", message);
else if (level & webrtc::kTraceInfo)
pa_log_info(message);
pa_log_info("%s", message);
else
pa_log_debug(message);
pa_log_debug("%s", message);
}
};

View file

@ -85,6 +85,9 @@ int main(int argc, char *argv[]) {
#if !GLIB_CHECK_VERSION(2,36,0)
g_type_init();
#endif
#if GLIB_CHECK_VERSION(2,68,0)
g_log_writer_default_set_use_stderr(true);
#endif
/* gsettings-data-convert copies data from GConf to GSettings. The
* conversion is defined in the pulseaudio.convert file. The conversion is

View file

@ -52,7 +52,11 @@ int pa__init(pa_module*m) {
if ((u->fd = pa_start_child_for_read(
#if defined(__linux__) && defined(HAVE_RUNNING_FROM_BUILD_TREE)
#ifdef MESON_BUILD
pa_run_from_build_tree() ? PA_BUILDDIR PA_PATH_SEP "src" PA_PATH_SEP "modules" PA_PATH_SEP "gsettings" PA_PATH_SEP "gsettings-helper" :
#else
pa_run_from_build_tree() ? PA_BUILDDIR "/gsettings-helper" :
#endif
#endif
PA_GSETTINGS_HELPER, NULL, &u->pid)) < 0)
goto fail;
@ -60,7 +64,7 @@ int pa__init(pa_module*m) {
u->io_event = m->core->mainloop->io_new(
m->core->mainloop,
u->fd,
PA_IO_EVENT_INPUT,
PA_IO_EVENT_INPUT | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR,
io_event_cb,
u);

View file

@ -28,6 +28,8 @@
#include <unistd.h>
#include <jack/jack.h>
#include <jack/metadata.h>
#include <jack/uuid.h>
#include <pulse/util.h>
#include <pulse/xmalloc.h>
@ -69,6 +71,8 @@ PA_MODULE_USAGE(
"connect=<connect ports?>");
#define DEFAULT_SINK_NAME "jack_out"
#define METADATA_TYPE_INT "http://www.w3.org/2001/XMLSchema#int"
#define METADATA_KEY_ORDER "http://jackaudio.org/metadata/order"
struct userdata {
pa_core *core;
@ -301,6 +305,8 @@ int pa__init(pa_module*m) {
const char **ports = NULL, **p;
pa_sink_new_data data;
jack_latency_range_t r;
jack_uuid_t port_uuid;
char port_order[4];
size_t n;
pa_assert(m);
@ -389,6 +395,17 @@ int pa__init(pa_module*m) {
pa_log("jack_port_register() failed.");
goto fail;
}
/* Set order of ports as JACK metadata, if possible. */
/* See: https://jackaudio.org/api/group__Metadata.html */
port_uuid = jack_port_uuid(u->port[i]);
if (!jack_uuid_empty(port_uuid)) {
if (snprintf(port_order, 4, "%d", i+1) >= 4)
pa_log("Port order metadata value > 999 truncated.");
if (jack_set_property(u->client, port_uuid, METADATA_KEY_ORDER, port_order, METADATA_TYPE_INT) != 0)
pa_log("jack_set_property() failed.");
}
}
pa_sink_new_data_init(&data);
@ -400,7 +417,7 @@ int pa__init(pa_module*m) {
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
if (server_name)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "JACK sink (%s)", jack_get_client_name(u->client));
pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client));
if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {

View file

@ -28,6 +28,8 @@
#include <unistd.h>
#include <jack/jack.h>
#include <jack/metadata.h>
#include <jack/uuid.h>
#include <pulse/util.h>
#include <pulse/xmalloc.h>
@ -59,6 +61,8 @@ PA_MODULE_USAGE(
"connect=<connect ports?>");
#define DEFAULT_SOURCE_NAME "jack_in"
#define METADATA_TYPE_INT "http://www.w3.org/2001/XMLSchema#int"
#define METADATA_KEY_ORDER "http://jackaudio.org/metadata/order"
struct userdata {
pa_core *core;
@ -249,6 +253,8 @@ int pa__init(pa_module*m) {
const char **ports = NULL, **p;
pa_source_new_data data;
jack_latency_range_t r;
jack_uuid_t port_uuid;
char port_order[4];
size_t n;
pa_assert(m);
@ -331,6 +337,17 @@ int pa__init(pa_module*m) {
pa_log("jack_port_register() failed.");
goto fail;
}
/* Set order of ports as JACK metadata, if possible. */
/* See: https://jackaudio.org/api/group__Metadata.html */
port_uuid = jack_port_uuid(u->port[i]);
if (!jack_uuid_empty(port_uuid)) {
if (snprintf(port_order, 4, "%d", i+1) >= 4)
pa_log("Port order metadata value > 999 truncated.");
if (jack_set_property(u->client, port_uuid, METADATA_KEY_ORDER, port_order, METADATA_TYPE_INT) != 0)
pa_log("jack_set_property() failed.");
}
}
pa_source_new_data_init(&data);
@ -342,7 +359,7 @@ int pa__init(pa_module*m) {
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack");
if (server_name)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client));
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "JACK source (%s)", jack_get_client_name(u->client));
pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client));
if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {

View file

@ -25,12 +25,14 @@
#include <config.h>
#endif
#include <pulse/proplist.h>
#include <pulse/xmalloc.h>
#include <pulsecore/log.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-util.h>
#include <pulsecore/dbus-shared.h>
#include <pulsecore/strbuf.h>
PA_MODULE_AUTHOR("David Henningsson");
PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started");
@ -38,8 +40,16 @@ PA_MODULE_LOAD_ONCE(true);
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_USAGE(
"channels=<number of channels> "
"source_channels=<number of channels> "
"sink_name=<name for the sink> "
"sink_properties=<properties for the card> "
"sink_client_name=<jack client name> "
"sink_channels=<number of channels> "
"sink_channel_map=<channel map> "
"source_name=<name for the source> "
"source_properties=<properties for the source> "
"source_client_name=<jack client name> "
"source_channels=<number of channels> "
"source_channel_map=<channel map> "
"connect=<connect ports?>");
#define JACK_SERVICE_NAME "org.jackaudio.service"
@ -61,8 +71,16 @@ PA_MODULE_USAGE(
static const char* const valid_modargs[] = {
"channels",
"source_channels",
"sink_name",
"sink_properties",
"sink_client_name",
"sink_channels",
"sink_channel_map",
"source_name",
"source_properties",
"source_client_name",
"source_channels",
"source_channel_map",
"connect",
NULL
};
@ -76,6 +94,19 @@ static const char* const modnames[JACK_SS_COUNT] = {
"module-jack-source"
};
static const char* const modtypes[JACK_SS_COUNT] = {
"sink",
"source"
};
struct moddata {
char *name;
pa_proplist *proplist;
char *client_name;
uint32_t channels;
pa_channel_map channel_map;
};
struct userdata {
pa_module *module;
pa_core *core;
@ -83,13 +114,13 @@ struct userdata {
bool filter_added, match_added;
bool is_service_started;
bool autoconnect_ports;
uint32_t channels[JACK_SS_COUNT];
struct moddata mod_args[JACK_SS_COUNT];
/* Using index here protects us from module unloading without us knowing */
int jack_module_index[JACK_SS_COUNT];
};
static void ensure_ports_stopped(struct userdata* u) {
int i;
unsigned i;
pa_assert(u);
for (i = 0; i < JACK_SS_COUNT; i++)
@ -100,19 +131,81 @@ static void ensure_ports_stopped(struct userdata* u) {
}
}
static char* proplist_to_arg(pa_proplist *p) {
const char *key;
void *state = NULL;
pa_strbuf *buf;
pa_assert(p);
buf = pa_strbuf_new();
while ((key = pa_proplist_iterate(p, &state))) {
const char *v;
char *escaped;
if (!pa_strbuf_isempty(buf))
pa_strbuf_puts(buf, " ");
if ((v = pa_proplist_gets(p, key))) {
pa_strbuf_printf(buf, "%s=\"", key);
escaped = pa_escape(v, "\"'");
pa_strbuf_puts(buf, escaped);
pa_xfree(escaped);
pa_strbuf_puts(buf, "\"");
} else {
const void *value;
size_t nbytes;
char *c;
pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0);
c = pa_xmalloc(nbytes*2+1);
pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1);
pa_strbuf_printf(buf, "%s=hex:%s", key, c);
pa_xfree(c);
}
}
return pa_strbuf_to_string_free(buf);
}
static void ensure_ports_started(struct userdata* u) {
int i;
unsigned i;
char *escaped;
pa_assert(u);
for (i = 0; i < JACK_SS_COUNT; i++)
if (!u->jack_module_index[i]) {
char* args;
pa_module* m;
if (u->channels[i] > 0) {
args = pa_sprintf_malloc("connect=%s channels=%" PRIu32, pa_yes_no(u->autoconnect_ports), u->channels[i]);
} else {
args = pa_sprintf_malloc("connect=%s", pa_yes_no(u->autoconnect_ports));
pa_strbuf *args_buf = pa_strbuf_new();
char *args;
pa_module *m;
pa_strbuf_printf(args_buf, "connect=%s", pa_yes_no(u->autoconnect_ports));
if (u->mod_args[i].name) {
escaped = pa_escape(u->mod_args[i].name, "'");
pa_strbuf_printf(args_buf, " %s_name='%s'", modtypes[i], escaped);
pa_xfree(escaped);
}
if (!pa_proplist_isempty(u->mod_args[i].proplist)) {
escaped = proplist_to_arg(u->mod_args[i].proplist);
pa_strbuf_printf(args_buf, " %s_properties='%s'", modtypes[i], escaped);
pa_xfree(escaped);
}
if (u->mod_args[i].client_name) {
escaped = pa_escape(u->mod_args[i].client_name, "'");
pa_strbuf_printf(args_buf, " client_name='%s'", escaped);
pa_xfree(escaped);
}
if (u->mod_args[i].channels > 0)
pa_strbuf_printf(args_buf, " channels=%" PRIu32, u->mod_args[i].channels);
if (u->mod_args[i].channel_map.channels > 0) {
char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
pa_channel_map_snprint(cm, sizeof(cm), &u->mod_args[i].channel_map);
pa_strbuf_printf(args_buf, " channel_map='%s'", cm);
}
args = pa_strbuf_to_string_free(args_buf);
pa_module_load(&m, u->core, modnames[i], args);
pa_xfree(args);
@ -218,7 +311,9 @@ int pa__init(pa_module *m) {
struct userdata *u = NULL;
pa_modargs *ma;
uint32_t channels = 0;
int i;
unsigned i;
char argname[32];
const char *name;
pa_assert(m);
@ -243,18 +338,40 @@ int pa__init(pa_module *m) {
pa_log("Failed to parse channels= argument.");
goto fail;
}
for (i = 0; i < JACK_SS_COUNT; i++) {
u->channels[i] = channels;
}
pa_snprintf(argname, sizeof(argname), "%s_name", modtypes[i]);
name = pa_modargs_get_value(ma, argname, NULL);
u->mod_args[i].name = pa_xstrdup(name);
if (pa_modargs_get_value_u32(ma, "source_channels", &u->channels[JACK_SS_SOURCE]) < 0 || (u->channels[JACK_SS_SOURCE] > 0 && !pa_channels_valid(u->channels[JACK_SS_SOURCE]))) {
pa_log("Failed to parse source_channels= argument.");
goto fail;
}
u->mod_args[i].proplist = pa_proplist_new();
pa_snprintf(argname, sizeof(argname), "%s_properties", modtypes[i]);
if (pa_modargs_get_proplist(ma, argname, u->mod_args[i].proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid %s properties", modtypes[i]);
goto fail;
}
if (pa_modargs_get_value_u32(ma, "sink_channels", &u->channels[JACK_SS_SINK]) < 0 || (u->channels[JACK_SS_SINK] > 0 && !pa_channels_valid(u->channels[JACK_SS_SINK]))) {
pa_log("Failed to parse sink_channels= argument.");
goto fail;
pa_snprintf(argname, sizeof(argname), "%s_client_name", modtypes[i]);
name = pa_modargs_get_value(ma, argname, NULL);
u->mod_args[i].client_name = pa_xstrdup(name);
u->mod_args[i].channels = channels;
pa_snprintf(argname, sizeof(argname), "%s_channels", modtypes[i]);
if (pa_modargs_get_value_u32(ma, argname, &u->mod_args[i].channels) < 0
|| (u->mod_args[i].channels > 0 && !pa_channels_valid(u->mod_args[i].channels))) {
pa_log("Failed to parse %s= argument.", argname);
goto fail;
}
pa_channel_map_init(&u->mod_args[i].channel_map);
pa_snprintf(argname, sizeof(argname), "%s_channel_map", modtypes[i]);
if (pa_modargs_get_value(ma, argname, NULL)) {
if (pa_modargs_get_channel_map(ma, argname, &u->mod_args[i].channel_map) < 0
|| (u->mod_args[i].channels > 0 && u->mod_args[i].channel_map.channels != u->mod_args[i].channels)) {
pa_log("Failed to parse %s= argument.", argname);
goto fail;
}
}
}
if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
@ -298,6 +415,7 @@ fail:
void pa__done(pa_module *m) {
struct userdata *u;
unsigned i;
pa_assert(m);
@ -320,5 +438,14 @@ void pa__done(pa_module *m) {
pa_dbus_connection_unref(u->connection);
}
for (i = 0; i < JACK_SS_COUNT; i++) {
pa_xfree(u->mod_args[i].name);
if (u->mod_args[i].proplist)
pa_proplist_free(u->mod_args[i].proplist);
pa_xfree(u->mod_args[i].client_name);
}
pa_xfree(u);
}

View file

@ -27,6 +27,7 @@
#include <config.h>
#endif
#include <pulse/util.h>
#include <pulse/xmalloc.h>
#include <pulsecore/sink.h>

View file

@ -1,4 +1,6 @@
subdir('rtp')
if host_machine.system() != 'windows'
subdir('rtp')
endif
# module name, sources, [headers, extra flags, extra deps, extra libs]
all_modules = [
@ -26,7 +28,6 @@ all_modules = [
# [ 'module-esound-sink', 'module-esound-sink.c' ],
[ 'module-filter-apply', 'module-filter-apply.c' ],
[ 'module-filter-heuristics', 'module-filter-heuristics.c' ],
# [ 'module-gconf', 'gconf/module-gconf.c' ],
[ 'module-http-protocol-tcp', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_TCP_SOCKETS'], [], libprotocol_http ],
[ 'module-http-protocol-unix', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_HTTP', '-DUSE_UNIX_SOCKETS'], [], libprotocol_http ],
[ 'module-intended-roles', 'module-intended-roles.c' ],
@ -44,8 +45,6 @@ all_modules = [
[ 'module-rescue-streams', 'module-rescue-streams.c' ],
[ 'module-role-cork', ['module-role-cork.c', 'stream-interaction.c'], 'stream-interaction.h' ],
[ 'module-role-ducking', ['module-role-ducking.c', 'stream-interaction.c'], 'stream-interaction.h' ],
[ 'module-rtp-recv', 'rtp/module-rtp-recv.c', [], [], [], librtp ],
[ 'module-rtp-send', 'rtp/module-rtp-send.c' , [], [], [], librtp ],
[ 'module-simple-protocol-tcp', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_SIMPLE', '-DUSE_TCP_SOCKETS'], [], libprotocol_simple ],
[ 'module-simple-protocol-unix', 'module-protocol-stub.c', [], ['-DUSE_PROTOCOL_SIMPLE', '-DUSE_UNIX_SOCKETS'], [], libprotocol_simple ],
[ 'module-sine', 'module-sine.c' ],
@ -61,11 +60,24 @@ all_modules = [
[ 'module-tunnel-source-new', 'module-tunnel-source-new.c' ],
[ 'module-virtual-sink', 'module-virtual-sink.c' ],
[ 'module-virtual-source', 'module-virtual-source.c' ],
[ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c' ],
[ 'module-volume-restore', 'module-volume-restore.c' ],
# [ 'module-waveout', 'module-waveout.c' ],
]
if host_machine.system() == 'windows'
winmm_dep = meson.get_compiler('c').find_library('winmm')
ksuser_dep = meson.get_compiler('c').find_library('ksuser')
all_modules += [
[ 'module-waveout', 'module-waveout.c', [], [], [winmm_dep, ksuser_dep] ],
]
endif
if host_machine.system() != 'windows'
all_modules += [
[ 'module-rtp-recv', 'rtp/module-rtp-recv.c', [], [], [], librtp ],
[ 'module-rtp-send', 'rtp/module-rtp-send.c' , [], [], [], librtp ],
]
endif
# Modules enabled by headers
if cc.has_header('linux/input.h')
@ -74,7 +86,7 @@ if cc.has_header('linux/input.h')
]
endif
if cc.has_header('sys/soundcard.h')
if cdata.has('HAVE_OSS_OUTPUT')
subdir('oss')
all_modules += [
[ 'module-oss', 'oss/module-oss.c', [], [], [], liboss_util ],
@ -106,7 +118,7 @@ if avahi_dep.found()
]
endif
if get_option('bluez5')
if cdata.has('HAVE_BLUEZ_5')
subdir('bluetooth')
all_modules += [
[ 'module-bluetooth-discover', 'bluetooth/module-bluetooth-discover.c' ],
@ -137,13 +149,19 @@ if dbus_dep.found()
]
endif
if fftw_dep.found()
all_modules += [
[ 'module-virtual-surround-sink', 'module-virtual-surround-sink.c', [], [], [fftw_dep, libm_dep] ],
]
endif
if dbus_dep.found() and fftw_dep.found()
all_modules += [
[ 'module-equalizer-sink', 'module-equalizer-sink.c', [], [], [dbus_dep, fftw_dep, libm_dep] ],
]
endif
if gio_dep.found()
if get_option('gsettings').enabled() and gio_dep.found()
subdir('gsettings')
all_modules += [
[ 'module-gsettings',
@ -171,10 +189,13 @@ if lirc_dep.found()
endif
if openssl_dep.found()
subdir('raop')
all_modules += [
[ 'module-raop-sink', 'raop/module-raop-sink.c', [], [], [], libraop ],
]
if host_machine.system() != 'windows'
subdir('raop')
all_modules += [
[ 'module-raop-sink', 'raop/module-raop-sink.c', [], [], [], libraop ],
]
endif
if avahi_dep.found()
all_modules += [
[ 'module-raop-discover', 'raop/module-raop-discover.c', [], [], [avahi_dep], libavahi_wrap ],
@ -182,9 +203,9 @@ if openssl_dep.found()
endif
endif
if libsystemd_dep.found()
if libsystemd_dep.found() or libelogind_dep.found()
all_modules += [
[ 'module-systemd-login', 'module-systemd-login.c', [], [], [libsystemd_dep] ],
[ 'module-systemd-login', 'module-systemd-login.c', [], [], [libsystemd_dep, libelogind_dep] ],
]
endif
@ -195,6 +216,10 @@ if udev_dep.found()
endif
endif
if host_machine.system() == 'freebsd'
all_modules += [ [ 'module-devd-detect', 'module-devd-detect.c', [], [], [] ] ]
endif
if x11_dep.found()
all_modules += [
[ 'module-x11-bell', 'x11/module-x11-bell.c', [], [], [x11_dep] ],
@ -279,12 +304,12 @@ foreach m : all_modules
mod = shared_module(name,
sources,
headers,
include_directories : [configinc, topinc],
include_directories : [configinc, topinc, include_directories('.')],
c_args : [pa_c_args, server_c_args, '-DPA_MODULE_NAME=' + name.underscorify()] + extra_flags,
install : true,
install_rpath : rpath_dirs,
install_dir : modlibexecdir,
dependencies : [thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep] + extra_deps,
dependencies : [thread_dep, libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libintl_dep, platform_dep, platform_socket_dep] + extra_deps,
link_args : [nodelete_link_args, '-Wl,--no-undefined' ],
link_with : extra_libs,
name_prefix : '',

View file

@ -67,7 +67,7 @@ struct userdata {
bool restore_bluetooth_profile;
};
#define ENTRY_VERSION 4
#define ENTRY_VERSION 5
struct port_info {
char *name;
@ -80,6 +80,7 @@ struct entry {
pa_hashmap *ports; /* Port name -> struct port_info */
char *preferred_input_port;
char *preferred_output_port;
bool profile_is_sticky; /* since version 5; must be restored together with profile name */
};
static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
@ -153,7 +154,8 @@ static struct entry *entry_from_card(pa_card *card) {
pa_assert(card);
entry = entry_new();
if (card->save_profile)
entry->profile_is_sticky = card->profile_is_sticky;
if (card->save_profile || entry->profile_is_sticky)
entry->profile = pa_xstrdup(card->active_profile->name);
PA_HASHMAP_FOREACH(port, card->ports, state) {
@ -189,6 +191,9 @@ static bool entrys_equal(struct entry *a, struct entry *b) {
if (!pa_safe_streq(a->preferred_output_port, b->preferred_output_port))
return false;
if (a->profile_is_sticky != b->profile_is_sticky)
return false;
return true;
}
@ -217,6 +222,8 @@ static bool entry_write(struct userdata *u, const char *name, const struct entry
pa_tagstruct_puts(t, e->preferred_input_port);
pa_tagstruct_puts(t, e->preferred_output_port);
pa_tagstruct_put_boolean(t, e->profile_is_sticky);
key.data = (char *) name;
key.size = strlen(name);
@ -342,6 +349,14 @@ static struct entry* entry_read(struct userdata *u, const char *name) {
e->preferred_output_port = pa_xstrdup(preferred_output_port);
}
if (version >= 5) {
bool profile_is_sticky;
if (pa_tagstruct_get_boolean(t, &profile_is_sticky) < 0)
goto fail;
e->profile_is_sticky = profile_is_sticky;
}
if (!pa_tagstruct_eof(t))
goto fail;
@ -437,11 +452,12 @@ static pa_hook_result_t card_profile_changed_callback(pa_core *c, pa_card *card,
pa_assert(card);
if (!card->save_profile)
if (!card->save_profile && !card->profile_is_sticky)
return PA_HOOK_OK;
if ((entry = entry_read(u, card->name))) {
pa_xfree(entry->profile);
entry->profile_is_sticky = card->profile_is_sticky;
entry->profile = pa_xstrdup(card->active_profile->name);
pa_log_info("Storing card profile for card %s.", card->name);
} else {
@ -565,12 +581,18 @@ static pa_hook_result_t card_choose_initial_profile_callback(pa_core *core, pa_c
goto finish;
}
card->profile_is_sticky = e->profile_is_sticky;
pa_log_info("Profile '%s' was previously %s for card %s.",
e->profile,
card->profile_is_sticky ? "sticky" : "automatically selected",
card->name);
if (e->profile[0]) {
pa_card_profile *profile;
profile = pa_hashmap_get(card->profiles, e->profile);
if (profile) {
if (profile->available != PA_AVAILABLE_NO) {
if (profile->available != PA_AVAILABLE_NO || card->profile_is_sticky) {
pa_log_info("Restoring profile '%s' for card %s.", profile->name, card->name);
pa_card_set_profile(card, profile, true);
} else
@ -618,7 +640,7 @@ static pa_hook_result_t card_preferred_port_changed_callback(pa_core *core, pa_c
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
char *fname;
char *state_path;
bool restore_bluetooth_profile;
pa_assert(m);
@ -648,17 +670,15 @@ int pa__init(pa_module*m) {
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL, (pa_hook_cb_t) card_profile_added_callback, u);
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) port_offset_change_callback, u);
if (!(fname = pa_state_path("card-database", true)))
if (!(state_path = pa_state_path(NULL, true)))
goto fail;
if (!(u->database = pa_database_open(fname, true))) {
pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
pa_xfree(fname);
if (!(u->database = pa_database_open(state_path, "card-database", true, true))) {
pa_xfree(state_path);
goto fail;
}
pa_log_info("Successfully opened database file '%s'.", fname);
pa_xfree(fname);
pa_xfree(state_path);
pa_modargs_free(ma);
return 0;

View file

@ -160,11 +160,40 @@ static int detect_oss(pa_core *c, int just_one) {
continue;
} else if (sscanf(line, "pcm%u: ", &device) == 1) {
/* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */
pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device);
pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
if (pa_module_load(&m, c, "module-oss", args) < 0)
continue;
if (!pa_endswith(line, "default"))
continue;
const char *p = strrchr(line, '(');
if (!p)
continue;
if (!c->configured_default_sink && (strstr(p, "play") || (strstr(p, "p:") && !strstr(p, "(0p:")))) {
uint32_t idx = PA_IDXSET_INVALID;
pa_sink *s;
PA_IDXSET_FOREACH(s, c->sinks, idx) {
if (s->module == m) {
pa_core_set_configured_default_sink(c, s->name);
break;
}
}
}
if (!c->configured_default_source && (strstr(p, "rec") || (strstr(p, "r:") && !strstr(p, "/0r:")))) {
uint32_t idx = PA_IDXSET_INVALID;
pa_source *s;
PA_IDXSET_FOREACH(s, c->sources, idx) {
if (s->module == m) {
pa_core_set_configured_default_source(c, s->name);
break;
}
}
}
}
n++;

View file

@ -0,0 +1,135 @@
/***
This file is part of PulseAudio.
Copyright 2020 Greg V
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pulsecore/core-util.h>
#include <pulsecore/module.h>
#include <pulsecore/hashmap.h>
#include <pulsecore/iochannel.h>
#include <pulsecore/ioline.h>
#include <pulsecore/log.h>
PA_MODULE_AUTHOR("Greg V");
PA_MODULE_DESCRIPTION("Detect hotplugged audio hardware and load matching drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(true);
PA_MODULE_USAGE("");
struct userdata {
pa_core *core;
pa_hashmap *devices;
pa_iochannel *io;
pa_ioline *line;
};
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
struct userdata *u = userdata;
pa_module *m = NULL;
unsigned devnum;
uint32_t modidx;
char args[64];
pa_assert(line);
pa_assert(u);
if (sscanf(s, "+pcm%u", &devnum) == 1) {
pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", devnum);
pa_module_load(&m, u->core, "module-oss", args);
if (m) {
pa_hashmap_put(u->devices, (void *)(uintptr_t)devnum, (void *)(uintptr_t)m->index);
pa_log_info("Card %u module loaded (%u).", devnum, m->index);
} else {
pa_log_info("Card %u failed to load module.", devnum);
}
} else if (sscanf(s, "-pcm%u", &devnum) == 1) {
if (!(modidx = (uint32_t)pa_hashmap_remove(u->devices, (void *)(uintptr_t)devnum)))
return;
pa_log_info("Card %u (module %u) removed.", devnum, modidx);
if (modidx != PA_INVALID_INDEX)
pa_module_unload_request_by_index(u->core, modidx, true);
}
}
static void device_free(void *a) {
}
int pa__init(pa_module *m) {
struct userdata *u = NULL;
struct sockaddr_un addr = { .sun_family = AF_UNIX };
int fd;
pa_assert(m);
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->devices = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) device_free);
if ((fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) {
pa_log("Failed to open socket for devd.");
return -1;
}
strncpy(addr.sun_path, "/var/run/devd.seqpacket.pipe", sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
pa_log("Failed to connect to devd.");
close(fd);
return -1;
}
pa_assert_se(u->io = pa_iochannel_new(m->core->mainloop, fd, -1));
pa_assert_se(u->line = pa_ioline_new(u->io));
pa_ioline_set_callback(u->line, line_callback, m->userdata);
return 0;
}
void pa__done(pa_module *m) {
struct userdata *u;
pa_assert(m);
if (!(u = m->userdata))
return;
if (u->devices)
pa_hashmap_free(u->devices);
if (u->line)
pa_ioline_close(u->line);
if (u->io)
pa_iochannel_free(u->io);
pa_xfree(u);
}

View file

@ -1544,7 +1544,7 @@ struct prioritised_indexes {
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
char *fname;
char *state_path;
pa_sink *sink;
pa_source *source;
uint32_t idx;
@ -1601,17 +1601,15 @@ int pa__init(pa_module*m) {
u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u);
}
if (!(fname = pa_state_path("device-manager", true)))
if (!(state_path = pa_state_path(NULL, true)))
goto fail;
if (!(u->database = pa_database_open(fname, true))) {
pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
pa_xfree(fname);
if (!(u->database = pa_database_open(state_path, "device-manager", true, true))) {
pa_xfree(state_path);
goto fail;
}
pa_log_info("Successfully opened database file '%s'.", fname);
pa_xfree(fname);
pa_xfree(state_path);
/* Attempt to inject the devices into the list in priority order */
total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources));

View file

@ -528,6 +528,8 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry *
char port[PA_NAME_MAX];
} PA_GCC_PACKED;
struct legacy_entry *le;
pa_channel_map channel_map;
pa_cvolume volume;
pa_assert(u);
pa_assert(data);
@ -551,12 +553,17 @@ static bool legacy_entry_read(struct userdata *u, pa_datum *data, struct entry *
return false;
}
if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) {
/* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these
* members to be accessed directly */
channel_map = le->channel_map;
volume = le->volume;
if (le->volume_valid && !pa_channel_map_valid(&channel_map)) {
pa_log_warn("Invalid channel map.");
return false;
}
if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) {
if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) {
pa_log_warn("Volume and channel map don't match.");
return false;
}
@ -1195,7 +1202,7 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
char *fname;
char *state_path;
pa_sink *sink;
pa_source *source;
uint32_t idx;
@ -1252,17 +1259,15 @@ int pa__init(pa_module*m) {
if (restore_formats)
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_put_hook_callback, u);
if (!(fname = pa_state_path("device-volumes", true)))
if (!(state_path = pa_state_path(NULL, true)))
goto fail;
if (!(u->database = pa_database_open(fname, true))) {
pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
pa_xfree(fname);
if (!(u->database = pa_database_open(state_path, "device-volumes", true, true))) {
pa_xfree(state_path);
goto fail;
}
pa_log_info("Successfully opened database file '%s'.", fname);
pa_xfree(fname);
pa_xfree(state_path);
PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);

View file

@ -946,7 +946,7 @@ static void save_state(struct userdata *u) {
float *H;
pa_datum key, data;
pa_database *database;
char *dbname;
char *state_path;
char *packed;
size_t packed_length;
@ -969,9 +969,9 @@ static void save_state(struct userdata *u) {
data.data = state;
data.size = filter_state_size + packed_length;
//thread safety for 0.9.17?
pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false));
pa_assert_se(database = pa_database_open(dbname, true));
pa_xfree(dbname);
pa_assert_se(state_path = pa_state_path(NULL, false));
pa_assert_se(database = pa_database_open(state_path, EQ_STATE_DB, false, true));
pa_xfree(state_path);
pa_database_set(database, &key, &data, true);
pa_database_sync(database);
@ -1020,10 +1020,10 @@ static void load_state(struct userdata *u) {
float *H;
pa_datum key, value;
pa_database *database;
char *dbname;
pa_assert_se(dbname = pa_state_path(EQ_STATE_DB, false));
database = pa_database_open(dbname, false);
pa_xfree(dbname);
char *state_path;
pa_assert_se(state_path = pa_state_path(NULL, false));
database = pa_database_open(state_path, EQ_STATE_DB, false, false);
pa_xfree(state_path);
if (!database) {
pa_log("No resume state");
return;
@ -1626,12 +1626,12 @@ void dbus_init(struct userdata *u) {
sink_list = pa_shared_get(u->sink->core, SINKLIST);
u->database = pa_shared_get(u->sink->core, EQDB);
if (sink_list == NULL) {
char *dbname;
char *state_path;
sink_list=pa_idxset_new(&pa_idxset_trivial_hash_func, &pa_idxset_trivial_compare_func);
pa_shared_set(u->sink->core, SINKLIST, sink_list);
pa_assert_se(dbname = pa_state_path("equalizer-presets", false));
pa_assert_se(u->database = pa_database_open(dbname, true));
pa_xfree(dbname);
pa_assert_se(state_path = pa_state_path(NULL, false));
pa_assert_se(u->database = pa_database_open(state_path, "equalizer-presets", false, true));
pa_xfree(state_path);
pa_shared_set(u->sink->core, EQDB, u->database);
pa_dbus_protocol_add_interface(u->dbus_protocol, MANAGER_PATH, &manager_info, u->sink->core);
pa_dbus_protocol_register_extension(u->dbus_protocol, EXTNAME);

View file

@ -249,9 +249,7 @@ static void thread_func(void *userdata) {
if (l < 0) {
if (errno == EINTR)
continue;
else if (errno == EAGAIN) {
if (errno == EAGAIN) {
/* OK, we filled all socket buffers up
* now. */

View file

@ -146,15 +146,20 @@ static const char* get_filter_name(pa_object *o, bool is_sink_input) {
static const char* get_filter_parameters(pa_object *o, const char *want, bool is_sink_input) {
const char *parameters;
char *prop_parameters;
pa_proplist *pl;
pa_proplist *pl, *device_pl;
if (is_sink_input)
if (is_sink_input) {
pl = PA_SINK_INPUT(o)->proplist;
else
device_pl = PA_SINK_INPUT(o)->sink->proplist;
} else {
pl = PA_SOURCE_OUTPUT(o)->proplist;
device_pl = PA_SOURCE_OUTPUT(o)->source->proplist;
}
prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, want);
parameters = pa_proplist_gets(pl, prop_parameters);
if (!parameters)
parameters = pa_proplist_gets(device_pl, prop_parameters);
pa_xfree(prop_parameters);
return parameters;

View file

@ -714,6 +714,9 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, p
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
if (!PA_SINK_IS_LINKED(u->sink->state))
return;
if (i->sink->state != PA_SINK_SUSPENDED || i->sink->suspend_cause == PA_SUSPEND_IDLE)
pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE);
else

View file

@ -152,6 +152,7 @@ struct userdata {
struct loopback_msg {
pa_msgobject parent;
struct userdata *userdata;
bool dead;
};
PA_DEFINE_PRIVATE_CLASS(loopback_msg, pa_msgobject);
@ -206,6 +207,9 @@ static void teardown(struct userdata *u) {
u->adjust_time = 0;
enable_adjust_timer(u, false);
if (u->msg)
u->msg->dead = true;
/* Handling the asyncmsgq between the source output and the sink input
* requires some care. When the source output is unlinked, nothing needs
* to be done for the asyncmsgq, because the source output is the sending
@ -401,7 +405,7 @@ static void adjust_rates(struct userdata *u) {
/* Drop or insert samples if fast_adjust_threshold_msec was specified and the latency difference is too large. */
if (u->fast_adjust_threshold > 0 && abs(latency_difference) > u->fast_adjust_threshold) {
pa_log_debug ("Latency difference larger than %lu msec, skipping or inserting samples.", u->fast_adjust_threshold / PA_USEC_PER_MSEC);
pa_log_debug ("Latency difference larger than %" PRIu64 " msec, skipping or inserting samples.", u->fast_adjust_threshold / PA_USEC_PER_MSEC);
pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_FAST_ADJUST, NULL, current_source_sink_latency, NULL);
@ -539,13 +543,13 @@ static void memblockq_adjust(struct userdata *u, int64_t latency_offset_usec, bo
if (current_memblockq_length > requested_memblockq_length) {
/* Drop audio from queue */
buffer_correction = current_memblockq_length - requested_memblockq_length;
pa_log_info("Dropping %lu usec of audio from queue", pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec));
pa_log_info("Dropping %" PRIu64 " usec of audio from queue", pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec));
pa_memblockq_drop(u->memblockq, buffer_correction);
} else if (current_memblockq_length < requested_memblockq_length && allow_push) {
/* Add silence to queue */
buffer_correction = requested_memblockq_length - current_memblockq_length;
pa_log_info("Adding %lu usec of silence to queue", pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec));
pa_log_info("Adding %" PRIu64 " usec of silence to queue", pa_bytes_to_usec(buffer_correction, &u->sink_input->sample_spec));
pa_memblockq_seek(u->memblockq, (int64_t)buffer_correction, PA_SEEK_RELATIVE, true);
}
}
@ -1016,6 +1020,14 @@ static void set_sink_input_latency(struct userdata *u, pa_sink *sink) {
if (u->min_source_latency > requested_latency) {
latency = PA_MAX(u->latency, u->minimum_latency);
requested_latency = (latency - u->min_source_latency) / 2;
/* In the case of a fixed alsa source, u->minimum_latency is calculated from
* the default fragment size while u->min_source_latency is the reported minimum
* of the source latency (nr_of_fragments * fragment_size). This can lead to a
* situation where u->minimum_latency < u->min_source_latency. We only fall
* back to use the fragment size instead of min_source_latency if the calculation
* above does not deliver a usable result. */
if (u->fixed_alsa_source && u->min_source_latency >= latency)
requested_latency = (latency - u->core->default_fragment_size_msec * PA_USEC_PER_MSEC) / 2;
}
latency = PA_CLAMP(requested_latency , u->min_sink_latency, u->max_sink_latency);
@ -1219,6 +1231,12 @@ static int loopback_process_msg_cb(pa_msgobject *o, int code, void *userdata, in
pa_assert_ctl_context();
msg = LOOPBACK_MSG(o);
/* If messages are processed after a module unload request, they
* must be ignored. */
if (msg->dead)
return 0;
pa_assert_se(u = msg->userdata);
switch (code) {
@ -1605,6 +1623,7 @@ int pa__init(pa_module *m) {
u->msg = pa_msgobject_new(loopback_msg);
u->msg->parent.process_msg = loopback_process_msg_cb;
u->msg->userdata = u;
u->msg->dead = false;
/* The output thread is not yet running, set effective_source_latency directly */
update_effective_source_latency(u, u->source_output->source, NULL);
@ -1648,5 +1667,8 @@ void pa__done(pa_module*m) {
if (u->asyncmsgq)
pa_asyncmsgq_unref(u->asyncmsgq);
if (u->msg)
loopback_msg_unref(u->msg);
pa_xfree(u);
}

View file

@ -47,7 +47,7 @@
PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Playback stream expression matching module");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(true);
PA_MODULE_LOAD_ONCE(false);
PA_MODULE_USAGE("table=<filename> "
"key=<property_key>");

View file

@ -57,8 +57,8 @@ PA_MODULE_USAGE(
"norewinds=<disable rewinds>");
#define DEFAULT_SINK_NAME "null"
#define BLOCK_USEC (PA_USEC_PER_SEC * 2)
#define NOREWINDS_MAX_LATENCY_USEC (50*PA_USEC_PER_MSEC)
#define BLOCK_USEC (2 * PA_USEC_PER_SEC)
#define BLOCK_USEC_NOREWINDS (50 * PA_USEC_PER_MSEC)
struct userdata {
pa_core *core;
@ -223,8 +223,12 @@ static void process_render(struct userdata *u, pa_usec_t now) {
/* Fill the buffer up the latency size */
while (u->timestamp < now + u->block_usec) {
pa_memchunk chunk;
size_t request_size;
request_size = pa_usec_to_bytes(now + u->block_usec - u->timestamp, &u->sink->sample_spec);
request_size = PA_MIN(request_size, u->sink->thread_info.max_request);
pa_sink_render(u->sink, request_size, &chunk);
pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk);
pa_memblock_unref(chunk.memblock);
/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */
@ -318,6 +322,7 @@ int pa__init(pa_module*m) {
u->core = m->core;
u->module = m;
u->rtpoll = pa_rtpoll_new();
u->block_usec = BLOCK_USEC;
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
pa_log("pa_thread_mq_init() failed.");
@ -381,13 +386,15 @@ int pa__init(pa_module*m) {
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
u->block_usec = BLOCK_USEC;
nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
if(pa_modargs_get_value_boolean(ma, "norewinds", &u->norewinds) < 0){
pa_log("Invalid argument, norewinds expects a boolean value.");
}
if (u->norewinds)
u->block_usec = BLOCK_USEC_NOREWINDS;
nbytes = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec);
if(u->norewinds){
pa_sink_set_max_rewind(u->sink, 0);
} else {
@ -401,11 +408,7 @@ int pa__init(pa_module*m) {
goto fail;
}
if(u->norewinds){
pa_sink_set_latency_range(u->sink, 0, NOREWINDS_MAX_LATENCY_USEC);
} else {
pa_sink_set_latency_range(u->sink, 0, BLOCK_USEC);
}
pa_sink_set_latency_range(u->sink, 0, u->block_usec);
pa_sink_put(u->sink);

View file

@ -52,6 +52,7 @@ PA_MODULE_USAGE(
"rate=<sample rate> "
"source_name=<name of source> "
"channel_map=<channel map> "
"max_latency_msec=<maximum latency in ms> "
"description=<description for the source> ");
#define DEFAULT_SOURCE_NAME "source.null"
@ -79,6 +80,7 @@ static const char* const valid_modargs[] = {
"channels",
"source_name",
"channel_map",
"max_latency_msec",
"description",
NULL
};
@ -123,8 +125,11 @@ static void source_update_requested_latency_cb(pa_source *s) {
pa_assert(u);
u->block_usec = pa_source_get_requested_latency_within_thread(s);
if (u->block_usec == (pa_usec_t)-1)
u->block_usec = u->source->thread_info.max_latency;
pa_source_set_max_rewind_within_thread(s, pa_usec_to_bytes(u->block_usec, &u->source->sample_spec));
}
static void thread_func(void *userdata) {
@ -197,6 +202,7 @@ int pa__init(pa_module*m) {
pa_channel_map map;
pa_modargs *ma = NULL;
pa_source_new_data data;
uint32_t max_latency_msec;
pa_assert(m);
@ -247,7 +253,14 @@ int pa__init(pa_module*m) {
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
pa_source_set_latency_range(u->source, MIN_LATENCY_USEC, MAX_LATENCY_USEC);
max_latency_msec = MAX_LATENCY_USEC / PA_USEC_PER_MSEC;
if (pa_modargs_get_value_u32(ma, "max_latency_msec", &max_latency_msec) < 0) {
pa_log("Failed to get max_latency_msec.");
goto fail;
}
pa_source_set_latency_range(u->source, MIN_LATENCY_USEC, max_latency_msec * PA_USEC_PER_MSEC);
u->block_usec = u->source->thread_info.max_latency;
u->source->thread_info.max_rewind =

View file

@ -199,14 +199,13 @@ static ssize_t pipe_sink_write(struct userdata *u, pa_memchunk *pchunk) {
if (l < 0) {
if (errno == EAGAIN)
break;
else if (errno != EINTR) {
if (!u->fifo_error) {
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
u->fifo_error = true;
}
count = -1 - count;
break;
if (!u->fifo_error) {
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
u->fifo_error = true;
}
count = -1 - count;
break;
} else {
if (u->fifo_error) {
pa_log_debug("Recovered from FIFO error");
@ -288,9 +287,7 @@ static int process_render(struct userdata *u) {
if (l < 0) {
if (errno == EINTR)
continue;
else if (errno == EAGAIN)
if (errno == EAGAIN)
return 0;
else {
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));

View file

@ -155,9 +155,7 @@ static void thread_func(void *userdata) {
if (l < 0) {
if (errno == EINTR)
continue;
else if (errno != EAGAIN) {
if (errno != EAGAIN) {
pa_log("Failed to read data from FIFO: %s", pa_cstrerror(errno));
goto fail;
}

View file

@ -300,7 +300,9 @@ int pa__init(pa_module*m) {
# if defined(USE_PROTOCOL_ESOUND)
# if defined(USE_PER_USER_ESOUND_SOCKET)
/* Windows doesn't support getuid(), so we ignore the per-user Esound socket compile flag.
* Moreover, Esound Unix sockets haven't been supported on Windows historically. */
# if defined(USE_PER_USER_ESOUND_SOCKET) && !defined(OS_IS_WIN32)
u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid());
# else
u->socket_path = pa_xstrdup("/tmp/.esd/socket");

View file

@ -96,7 +96,7 @@ PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
" <property name=\"Path\" type=\"s\" access=\"read\"/>" \
" <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \
" </interface>" \
" <interface name=\"org.freedesktop.DBus.Properties\">" \
" <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">" \
" <method name=\"Get\">" \
" <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
" <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
@ -107,7 +107,7 @@ PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
" <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
" </method>" \
" </interface>" \
" <interface name=\"org.freedesktop.DBus.Introspectable\">" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">" \
" <method name=\"Introspect\">" \
" <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
" </method>" \
@ -130,7 +130,7 @@ PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
" <interface name=\"org.gnome.UPnP.MediaItem2\">" \
" <property name=\"URLs\" type=\"as\" access=\"read\"/>" \
" <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \
" <property name=\"DLNAProfile\" type=\"s\" access=\"read\"/>" \
" <property name=\"DLNAProfile\" type=\"s\" access=\"read\"/>" \
" </interface>" \
" <interface name=\"org.gnome.UPnP.MediaObject2\">" \
" <property name=\"Parent\" type=\"s\" access=\"read\"/>" \
@ -138,7 +138,7 @@ PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
" <property name=\"Path\" type=\"s\" access=\"read\"/>" \
" <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \
" </interface>" \
" <interface name=\"org.freedesktop.DBus.Properties\">" \
" <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">" \
" <method name=\"Get\">" \
" <arg name=\"interface\" direction=\"in\" type=\"s\"/>" \
" <arg name=\"property\" direction=\"in\" type=\"s\"/>" \
@ -149,7 +149,7 @@ PA_MODULE_USAGE("display_name=<UPnP Media Server name>");
" <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
" </method>" \
" </interface>" \
" <interface name=\"org.freedesktop.DBus.Introspectable\">" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">" \
" <method name=\"Introspect\">" \
" <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
" </method>" \
@ -215,7 +215,7 @@ static bool message_is_property_get(DBusMessage *m, const char *interface, const
pa_assert(m);
if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get"))
if (!dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get"))
return false;
if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
@ -234,7 +234,7 @@ static bool message_is_property_get_all(DBusMessage *m, const char *interface) {
pa_assert(m);
if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll"))
if (!dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll"))
return false;
if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
@ -721,7 +721,7 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u
append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name);
pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
} else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
} else if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
const char *xml = ROOT_INTROSPECT_XML;
pa_assert_se(r = dbus_message_new_method_return(m));
@ -913,7 +913,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag
append_sink_or_source_container_mediaobject2_properties(r, &sub, path);
pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
} else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
} else if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
pa_strbuf *sb;
char *xml;
uint32_t idx;
@ -1010,7 +1010,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag
pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
} else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
} else if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
const char *xml =
ITEM_INTROSPECT_XML;

View file

@ -714,9 +714,7 @@ static void thread_func(void *userdata) {
pa_memblock_release(u->memchunk.memblock);
if (w <= 0) {
if (errno == EINTR) {
continue;
} else if (errno == EAGAIN) {
if (errno == EAGAIN) {
/* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */
pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes);
break;

Some files were not shown because too many files have changed in this diff Show more