[Jack-Devel] RFC: jackd portnames

PrevNext  Index
DateThu, 03 Nov 2011 11:53:41 +0100
From Adrian Knoth <[hidden] at drcomp dot erfurt dot thur dot de>
To[hidden] at lists dot jackaudio dot org
Follow-UpDavid Nielson Re: [Jack-Devel] RFC: jackd portnames
Hi!

I've forward-ported faberman's portname patch from jackd2-1.9.4 to
1.9.7.

It is supposed to provide stable port names across cards, no matter of
the underlying physical connection.

I see mainly two use cases:

   1.) Human-readable portnames on larger cards where "playback_56" is
       simply to cumbersome to figure out what's actually connected

   2.) People who travel with ADAT-ADCs/DACs, but connect them via
       different interfaces in different locations, e.g. a Multiface
       when on the road and a RayDat when in the studio. Despite the
       different cards, the port names and hence any ardour session
       would remain intact.

We would probably want a generic approach including firewire, either by
duplicating the code (bad) or somewhere higher in the higher layers, but
let's get started, first.

Here is how it looks like:

   http://adi.loris.tv/jackd2-portnames.png

Right now, I've added a bit of verbosity to point the user to the
correct file location:

Using port names patch v0.1 (07.04.2010)
Trying to load portnames from /home/racl/adi/.config/jack/cards/RME
RayDAT_f1cd85.ss.ports.in
Trying to load portnames from /home/racl/adi/.config/jack/cards/RME
RayDAT_f1cd85.ports.in
Trying to load portnames from /etc/jack/cards/RME RayDAT_f1cd85.ss.ports.in
Trying to load portnames from /etc/jack/cards/RME RayDAT_f1cd85.ports.in
Trying to load portnames from /home/racl/adi/.config/jack/cards/RME
RayDAT_f1cd85.ss.ports.out
Trying to load portnames from /home/racl/adi/.config/jack/cards/RME
RayDAT_f1cd85.ports.out
Trying to load portnames from /etc/jack/cards/RME RayDAT_f1cd85.ss.ports.out
Trying to load portnames from /etc/jack/cards/RME RayDAT_f1cd85.ports.out


The beginning of my ports.in file:

# generated by hdspm
1=Korg-L
2=Korg-R
3=IN-ADAT1.3
4=IN-ADAT1.4
5=IN-ADAT1.5
6=IN-ADAT1.6
7=Guitar
8=Sindy

While the generic portnames were defined by the driver, I had to prepend
an "IN-" to have different portnames for input and output.

How do you feel about system:in:portname? This is easier for the user,
but a little less freedom.

OTOH, I can also image cardname:portname in case users have multiple
cards.

That's the main purpose of this RFC: what's a useful naming convention?
Any requirements from the pro-audio/broadcasting camp?

Note that all the previous names are still available:

$ jack_lsp -A
system:Korg-L
   alsa_pcm:hw:1,0:out1
   system:capture_1
system:Korg-R
   alsa_pcm:hw:1,0:out2
   system:capture_2
[...]

This hopefully remains backward compatibility with existing sessions.

Of course, if we agree on something, I'd forward-port faberman's jackd
version, too, but since jackd2 was installed on my workstation, I
started with jackd2.


Note that nobody has to use portnames. If the user does nothing, he'd
end up with his ordinary channel names. The patch hence targets more
advanced users.



Cheers

And here's the patch:


--- /dev/null
+++ b/linux/alsa/port_names.c
@@ -0,0 +1,179 @@
+/* -*- mode: c; c-file-style: "linux"; -*- */
+/*
+    Copyright (C) 2010 Florian Faber, [hidden]
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+
+#include <math.h>
+#include <stdio.h>
+#include <memory.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <string.h>
+
+#include "alsa_driver.h"
+
+
+static int port_names_load_portfile(alsa_driver_t *driver, const char
*filename, char **buf, const unsigned int offset, const unsigned int num) {
+	int fh, i, ret, lineno, id, res=0;
+	char line[256];
+
+	printf("Trying to load portnames from %s\n", filename);
+	fh = open(filename, O_RDONLY);
+	if (-1!=fh) {
+		res = 1;
+		i = 0;
+		lineno = 1;
+		for (;;) {
+			ret = read(fh, &line[i], 1);
+			if (0==ret) {
+				break;
+			} else if (-1==ret) {
+				sprintf(stderr, "Error while reading \"%s\": %s", filename,
strerror(errno));
+				break;
+			}
+			if (0x0A==line[i]) {
+				/* new line, parse input */
+				line[i] = 0;
+
+				if ('#' != line[0]) {
+					i=0;
+					while ((i<255) && (line[i]!='=')) i++;
+					if (255==i) {
+						sprintf(stderr, "Error while reading \"%s\": Line %d has no
key=value syntax!", filename, lineno);
+					} else {
+						line[i] = 0;
+						id = atoi(line);
+						if ((id>=1) && (id<=num)) {
+							if (NULL==buf[id-1+offset]) {
+								/* don't overwrite existing names */
+								buf[id-1+offset] = strdup(&line[i+1]);
+							}
+						} else {
+							sprintf(stderr, "Error while reading \"%s\": Key %d out of range
in line %d (1..%d)", filename, id, lineno, num);
+						}
+					}
+				}
+
+				i = 0;
+				lineno++;
+			} else {
+				i++;
+				if (i==255) {
+					sprintf(stderr, "Error while reading \"%s\": Line %d is too long",
filename, lineno);
+					break;
+				}
+			}
+		}
+
+		(void) close(fh);
+	}
+
+	return res;
+}
+
+
+static void port_names_default_portnames(char **buf, const unsigned int
offset, const unsigned int num, const char *defaultname) {
+	unsigned int i;
+	char line[256];
+
+	/* Fill in default names */
+	for (i=0; i<num; i++) {
+		if (NULL==buf[i+offset]) {
+			snprintf(line, 255, defaultname, i+1);
+			buf[i+offset] = strdup(line);
+		}
+	}
+}
+
+
+char** port_names_get_portnames(alsa_driver_t *driver) {
+	snd_ctl_card_info_t *card_info;
+	int err;
+	const char *card_name = NULL;
+	char filename[256], *speed;
+	char **buf;
+
+	printf("Using port names patch v0.1 (07.04.2010)\n");
+
+	if (driver->frame_rate > 96000) {
+		speed="qs";
+	} else if (driver->frame_rate > 48000) {
+		speed="ds";
+	} else {
+		speed="ss";
+	}
+
+	snd_ctl_card_info_alloca(&card_info);
+	err = snd_ctl_card_info(driver->ctl_handle, card_info);
+	if (err >= 0) {
+		card_name = snd_ctl_card_info_get_name(card_info);
+	} else {
+		card_name = "noname";
+	}
+
+	buf = malloc(sizeof(char *)*(driver->capture_nchannels +
driver->playback_nchannels));
+	if (NULL==buf) {
+		sprintf(stderr, "ALSA: Not enough memory for %d port names",
driver->capture_nchannels + driver->playback_nchannels);
+		return NULL;
+	}
+	bzero(buf, sizeof(char *)*(driver->capture_nchannels +
driver->playback_nchannels));
+
+	/* Read port names from special to general:
+	 * Begin with user and speed specific port names */
+	snprintf(filename, 255, "%s/.config/jack/cards/%s.%s.ports.in",
getenv("HOME"), card_name, speed);
+	(void) port_names_load_portfile(driver, filename, buf, 0,
driver->capture_nchannels);
+
+	/* Now user general */
+	snprintf(filename, 255, "%s/.config/jack/cards/%s.ports.in",
getenv("HOME"), card_name);
+	(void) port_names_load_portfile(driver, filename, buf, 0,
driver->capture_nchannels);
+
+	/* System speed specific */
+	snprintf(filename, 255, "/etc/jack/cards/%s.%s.ports.in", card_name,
speed);
+	(void) port_names_load_portfile(driver, filename, buf, 0,
driver->capture_nchannels);
+
+	/* System general */
+	snprintf(filename, 255, "/etc/jack/cards/%s.ports.in", card_name);
+	(void) port_names_load_portfile(driver, filename, buf, 0,
driver->capture_nchannels);
+
+	/* Fill all still unnamed ports with default names */
+	port_names_default_portnames(buf, 0, driver->capture_nchannels,
"capture_%lu");
+
+
+	/* Same procedure for the playback channels */
+	snprintf(filename, 255, "%s/.config/jack/cards/%s.%s.ports.out",
getenv("HOME"), card_name, speed);
+	(void) port_names_load_portfile(driver, filename, buf,
driver->capture_nchannels, driver->playback_nchannels);
+
+	snprintf(filename, 255, "%s/.config/jack/cards/%s.ports.out",
getenv("HOME"), card_name);
+	(void) port_names_load_portfile(driver, filename, buf,
driver->capture_nchannels, driver->playback_nchannels);
+
+	snprintf(filename, 255, "/etc/jack/cards/%s.%s.ports.out", card_name,
speed);
+	(void) port_names_load_portfile(driver, filename, buf,
driver->capture_nchannels, driver->playback_nchannels);
+
+	snprintf(filename, 255, "/etc/jack/cards/%s.ports.out", card_name);
+	(void) port_names_load_portfile(driver, filename, buf,
driver->capture_nchannels, driver->playback_nchannels);
+
+	port_names_default_portnames(buf, driver->capture_nchannels,
driver->playback_nchannels, "playback_%lu");
+
+	return buf;
+}
--- /dev/null
+++ b/linux/alsa/port_names.h
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2010 Florian Faber, [hidden]
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __jack_port_names_h__
+#define __jack_port_names_h__
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+char** port_names_get_portnames(alsa_driver_t *driver);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __jack_port_names_h__ */
--- a/linux/alsa/JackAlsaDriver.cpp
+++ b/linux/alsa/JackAlsaDriver.cpp
@@ -43,6 +43,7 @@
 #include "JackPosixThread.h"
 #include "JackCompilerDeps.h"
 #include "JackServerGlobals.h"
+#include "port_names.h"

 namespace Jack
 {
@@ -72,6 +73,8 @@
     unsigned long port_flags = (unsigned long)CaptureDriverFlags;
     char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
     char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
+    char old_name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
+    char **portnames;
     jack_latency_range_t range;

     assert(fCaptureChannels < DRIVER_PORT_NUM);
@@ -88,15 +91,20 @@

     jack_log("JackAlsaDriver::Attach fBufferSize %ld fSampleRate %ld",
fEngineControl->fBufferSize, fEngineControl->fSampleRate);

+    portnames = port_names_get_portnames(alsa_driver);
+
     for (int i = 0; i < fCaptureChannels; i++) {
         snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName,
fCaptureDriverName, i + 1);
-        snprintf(name, sizeof(name) - 1, "%s:capture_%d",
fClientControl.fName, i + 1);
+        snprintf(old_name, sizeof(old_name) - 1, "%s:capture_%d",
fClientControl.fName, i + 1);
+        snprintf(name, sizeof(name) - 1, "%s:%s", fClientControl.fName,
portnames[i]);
         if ((port_index =
fGraphManager->AllocatePort(fClientControl.fRefNum, name,
JACK_DEFAULT_AUDIO_TYPE, (JackPortFlags)port_flags,
fEngineControl->fBufferSize)) == NO_PORT) {
             jack_error("driver: cannot register port for %s", name);
             return -1;
         }
+        free(portnames[i]);
         port = fGraphManager->GetPort(port_index);
         port->SetAlias(alias);
+        port->SetAlias(old_name);
         range.min = range.max = alsa_driver->frames_per_cycle +
alsa_driver->capture_frame_latency;
         port->SetLatencyRange(JackCaptureLatency, &range);
         fCapturePortList[i] = port_index;
@@ -107,13 +115,16 @@

     for (int i = 0; i < fPlaybackChannels; i++) {
         snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName,
fPlaybackDriverName, i + 1);
-        snprintf(name, sizeof(name) - 1, "%s:playback_%d",
fClientControl.fName, i + 1);
+        snprintf(old_name, sizeof(old_name) - 1, "%s:playback_%d",
fClientControl.fName, i + 1);
+        snprintf(name, sizeof(name) - 1, "%s:%s", fClientControl.fName,
portnames[i+fCaptureChannels]);
         if ((port_index =
fGraphManager->AllocatePort(fClientControl.fRefNum, name,
JACK_DEFAULT_AUDIO_TYPE, (JackPortFlags)port_flags,
fEngineControl->fBufferSize)) == NO_PORT) {
             jack_error("driver: cannot register port for %s", name);
             return -1;
         }
+        free(portnames[i+fCaptureChannels]);
         port = fGraphManager->GetPort(port_index);
         port->SetAlias(alias);
+        port->SetAlias(old_name);
         // Add one buffer more latency if "async" mode is used...
         range.min = range.max = (alsa_driver->frames_per_cycle *
(alsa_driver->user_nperiods - 1)) +
                          ((fEngineControl->fSyncMode) ? 0 :
fEngineControl->fBufferSize) + alsa_driver->playback_frame_latency;
@@ -137,6 +148,8 @@
         }
     }

+    free(portnames);
+
     if (alsa_driver->midi) {
         int err = (alsa_driver->midi->attach)(alsa_driver->midi);
         if (err)
--- a/linux/wscript
+++ b/linux/wscript
@@ -54,6 +54,7 @@
                        'alsa/hdsp.c',
 		       'alsa/alsa_driver.c',
                        'alsa/hammerfall.c',
+                       'alsa/port_names.c',
                        'alsa/ice1712.c'
                        ]

system:Korg-L
   alsa_pcm:hw:1,0:out1
   system:capture_1
system:Korg-R
   alsa_pcm:hw:1,0:out2
   system:capture_2
system:IN-ADAT1.3
   alsa_pcm:hw:1,0:out3
   system:capture_3
system:IN-ADAT1.4
   alsa_pcm:hw:1,0:out4
   system:capture_4
system:IN-ADAT1.5
   alsa_pcm:hw:1,0:out5
   system:capture_5
system:IN-ADAT1.6
   alsa_pcm:hw:1,0:out6
   system:capture_6
system:Guitar
   alsa_pcm:hw:1,0:out7
   system:capture_7
system:Sindy
   alsa_pcm:hw:1,0:out8
   system:capture_8
system:IN-ADAT2.1
   alsa_pcm:hw:1,0:out9
   system:capture_9
system:IN-ADAT2.2
   alsa_pcm:hw:1,0:out10
   system:capture_10
system:IN-ADAT2.3
   alsa_pcm:hw:1,0:out11
   system:capture_11
system:IN-ADAT2.4
   alsa_pcm:hw:1,0:out12
   system:capture_12
system:IN-ADAT2.5
   alsa_pcm:hw:1,0:out13
   system:capture_13
system:IN-ADAT2.6
   alsa_pcm:hw:1,0:out14
   system:capture_14
system:IN-ADAT2.7
   alsa_pcm:hw:1,0:out15
   system:capture_15
system:IN-ADAT2.8
   alsa_pcm:hw:1,0:out16
   system:capture_16
system:IN-ADAT3.1
   alsa_pcm:hw:1,0:out17
   system:capture_17
system:IN-ADAT3.2
   alsa_pcm:hw:1,0:out18
   system:capture_18
system:IN-ADAT3.3
   alsa_pcm:hw:1,0:out19
   system:capture_19
system:IN-ADAT3.4
   alsa_pcm:hw:1,0:out20
   system:capture_20
system:IN-ADAT3.5
   alsa_pcm:hw:1,0:out21
   system:capture_21
system:IN-ADAT3.6
   alsa_pcm:hw:1,0:out22
   system:capture_22
system:IN-ADAT3.7
   alsa_pcm:hw:1,0:out23
   system:capture_23
system:IN-ADAT3.8
   alsa_pcm:hw:1,0:out24
   system:capture_24
system:IN-ADAT4.1
   alsa_pcm:hw:1,0:out25
   system:capture_25
system:IN-ADAT4.2
   alsa_pcm:hw:1,0:out26
   system:capture_26
system:IN-ADAT4.3
   alsa_pcm:hw:1,0:out27
   system:capture_27
system:IN-ADAT4.4
   alsa_pcm:hw:1,0:out28
   system:capture_28
system:IN-ADAT4.5
   alsa_pcm:hw:1,0:out29
   system:capture_29
system:IN-ADAT4.6
   alsa_pcm:hw:1,0:out30
   system:capture_30
system:IN-ADAT4.7
   alsa_pcm:hw:1,0:out31
   system:capture_31
system:IN-ADAT4.8
   alsa_pcm:hw:1,0:out32
   system:capture_32
system:IN-AES.L
   alsa_pcm:hw:1,0:out33
   system:capture_33
system:IN-AES.R
   alsa_pcm:hw:1,0:out34
   system:capture_34
system:IN-SPDIF.L
   alsa_pcm:hw:1,0:out35
   system:capture_35
system:IN-SPDIF.R
   alsa_pcm:hw:1,0:out36
   system:capture_36
system:ADAT1.1
   alsa_pcm:hw:1,0:in1
   system:playback_1
system:ADAT1.2
   alsa_pcm:hw:1,0:in2
   system:playback_2
system:ADAT1.3
   alsa_pcm:hw:1,0:in3
   system:playback_3
system:ADAT1.4
   alsa_pcm:hw:1,0:in4
   system:playback_4
system:ADAT1.5
   alsa_pcm:hw:1,0:in5
   system:playback_5
system:ADAT1.6
   alsa_pcm:hw:1,0:in6
   system:playback_6
system:ADAT1.7
   alsa_pcm:hw:1,0:in7
   system:playback_7
system:ADAT1.8
   alsa_pcm:hw:1,0:in8
   system:playback_8
system:ADAT2.1
   alsa_pcm:hw:1,0:in9
   system:playback_9
system:ADAT2.2
   alsa_pcm:hw:1,0:in10
   system:playback_10
system:ADAT2.3
   alsa_pcm:hw:1,0:in11
   system:playback_11
system:ADAT2.4
   alsa_pcm:hw:1,0:in12
   system:playback_12
system:ADAT2.5
   alsa_pcm:hw:1,0:in13
   system:playback_13
system:ADAT2.6
   alsa_pcm:hw:1,0:in14
   system:playback_14
system:ADAT2.7
   alsa_pcm:hw:1,0:in15
   system:playback_15
system:ADAT2.8
   alsa_pcm:hw:1,0:in16
   system:playback_16
system:ADAT3.1
   alsa_pcm:hw:1,0:in17
   system:playback_17
system:ADAT3.2
   alsa_pcm:hw:1,0:in18
   system:playback_18
system:ADAT3.3
   alsa_pcm:hw:1,0:in19
   system:playback_19
system:ADAT3.4
   alsa_pcm:hw:1,0:in20
   system:playback_20
system:ADAT3.5
   alsa_pcm:hw:1,0:in21
   system:playback_21
system:ADAT3.6
   alsa_pcm:hw:1,0:in22
   system:playback_22
system:ADAT3.7
   alsa_pcm:hw:1,0:in23
   system:playback_23
system:ADAT3.8
   alsa_pcm:hw:1,0:in24
   system:playback_24
system:ADAT4.1
   alsa_pcm:hw:1,0:in25
   system:playback_25
system:ADAT4.2
   alsa_pcm:hw:1,0:in26
   system:playback_26
system:ADAT4.3
   alsa_pcm:hw:1,0:in27
   system:playback_27
system:ADAT4.4
   alsa_pcm:hw:1,0:in28
   system:playback_28
system:ADAT4.5
   alsa_pcm:hw:1,0:in29
   system:playback_29
system:ADAT4.6
   alsa_pcm:hw:1,0:in30
   system:playback_30
system:ADAT4.7
   alsa_pcm:hw:1,0:in31
   system:playback_31
system:ADAT4.8
   alsa_pcm:hw:1,0:in32
   system:playback_32
system:AES.L
   alsa_pcm:hw:1,0:in33
   system:playback_33
system:AES.R
   alsa_pcm:hw:1,0:in34
   system:playback_34
system:SPDIF.L
   alsa_pcm:hw:1,0:in35
   system:playback_35
system:SPDIF.R
   alsa_pcm:hw:1,0:in36
   system:playback_36
PrevNext  Index

1320317666.13694_0.ltw:2,a <4EB272B5.9020108 at drcomp dot erfurt dot thur dot de>