Software-Experimente mit Linux

Buschel
Aktiver Hörer
Beiträge: 989
Registriert: 12.12.2013, 20:12
Wohnort: Raum Karlsruhe

Beitrag von Buschel »

Hallo zusammen,

der Urlaub geht dem Ende entgegen und, wie ich mir vorgenommen hatte, habe ich einige Änderungen an den reduzierten Versionen von playhrt/resample_soxr vorgenommen:
  • Parameter Kurzform
  • Asukommentieren doppelter memory refresh
  • Kommentare gefixt
Bei playhrt sind die Parameter jetzt mehr an die üblichen und auch bei resample_soxr benutzten angepasst. "-b" stellt jetzt die Puffergröße, "-c" die Kanalanzahl und "-r" die Samplingrate ein. Wegen aus meiner Sicht leichter fallender Zuordnung zu den langen Parameternamen wird "--host" jetzt mit "-H" und "--port" mit "-P" abgekürzt. Außerdem habe ich den doppelten memory refresh auskommentiert, und nur noch den refresh direkt vor der Datenübergabe an den Soundkartentreiber belassen. Das kann je nach Bedarf natürlich wieder einkommentiert werden.

Code: Alles auswählen

/*
playhrt.c                Copyright frankl 2013-2016
                         Copyright Andree Buschmann 2020

This file is part of frankl's stereo utilities and was reworked by Andree Buschmann.
See the file License.txt of the distribution and http://www.gnu.org/licenses/gpl.txt 
for license details.
*/

#include "version.h"
#include "net.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <alsa/asoundlib.h>
#include "cprefresh.h"

/* help page */
/* vim hint to remove resp. add quotes:
      s/^"\(.*\)\\n"$/\1/
      s/.*$/"\0\\n"/
*/
void usage( ) {
    fprintf(stderr, "playhrt (version %s of frankl's stereo utilities", VERSION);
#ifdef ALSANC
    fprintf(stderr, ", with alsa-lib patch");
#endif
    fprintf(stderr, ", reworked by Andree Buschmann");
    fprintf(stderr, ", with PI control to compensate clock deviation");
    fprintf(stderr, ")\n\nUSAGE:\n");
    fprintf(stderr,
"\n"
"  playhrt [options] \n"
"\n"
"  This program reads raw(!) stereo audio data from stdin, a file or the \n"
"  network and plays it on a local (ALSA) sound device. \n"
"\n"
"  The program repeats in a given number of loops per second: reading\n"
"  a chunk of input data, preparing data for the audio driver, then it\n"
"  sleeps until a specific instant of time and after wakeup it hands data\n"
"  to the audio driver. In contrast to other player programs this is done\n"
"  with a very precise timing such that no buffers underrun or overrun and\n"
"  no reading or writing of data is blocking. Furthermore, the data is\n"
"  refreshed in RAM directly before copying it to the audio driver.\n"
"\n"
"  The Linux kernel needs the highres-timer functionality enabled (on most\n"
"  systems this is the case).\n"
"\n"
"  This reworked version only writes input data directly to the memory\n"
"  of the audio driver (mmap mode).\n"
"\n"
"  USAGE HINTS\n"
"  \n"
"  It is recommended to give this program a high priority and not to run\n"
"  too many other things on the same computer during playback. A high\n"
"  priority can be specified with the 'chrt' command:\n"
"\n"
"  chrt -f 70 playhrt .....\n"
"\n"
"  (Depending on the configuration of your computer you may need root\n"
"  privileges for this, in that case use 'sudo chrt -f 99 playhrt ....' \n"
"  or give 'chrt' setuid permissions.)\n"
"\n"
"  While running this program the computer should run as few other things\n"
"  as possible. In particular we recommend to generate the input data\n"
"  on a different computer and to send them via the network to 'playhrt'\n"
"  using the program 'bufhrt' which is also contained in this package. \n"
"  \n"
"  OPTIONS\n"
"\n"
"  --host=hostname, -H hostname\n"
"      the host from which to receive the data , given by name or\n"
"      ip-address.\n"
"\n"
"  --port=portnumber, -P portnumber\n"
"      the port number on the remote host from which to receive data.\n"
"\n"
"  --stdin, -S\n"
"      read data from stdin (instead of --host and --port).\n"
"\n"
"  --device=alsaname, -d alsaname\n"
"      the name of the sound device. A typical name is 'hw:0,0', maybe\n"
"      use 'aplay -l' to find out the correct numbers. It is recommended\n"
"      to use the hardware devices 'hw:...' if possible.\n"
"\n"
"  --sample-rate=intval, -r intval\n"
"      the sample rate of the audio data. Default is 44100 as on CDs.\n"
"\n"
"  --sample-format=formatstring, -f formatstring\n"
"      the format of the samples in the audio data. Currently recognised\n"
"      are 'S16_LE' (the sample format on CDs), 'S24_LE' \n"
"      (signed integer data with 24 bits packed into 32 bit words, used by\n"
"      many DACs), 'S24_3LE' (also 24 bit integers but only using 3 bytes\n"
"      per sample), 'S32_LE' (true 32 bit signed integer samples).\n"
"      Default is 'S16_LE'.\n"
"\n"
"  --number-channels=intval, -c intval\n"
"      the number of channels in the (interleaved) audio stream. The \n"
"      default is 2 (stereo).\n"
"\n"
"  --loops-per-second=intval, -n intval\n"
"      the number of loops per second in which 'playhrt' reads some\n"
"      data from the network into a buffer, sleeps until a precise\n"
"      moment and then writes a chunk of data to the sound device. \n"
"      Typical values would be 1000 or 2000. Default is 1000.\n"
"\n"
"  --non-blocking-write, -N\n"
"      write data to sound device in a non-blocking fashion. This can\n"
"      improve sound quality, but the timing must be very precise.\n"
"\n"
"  --hw-buffer=intval, -b intval\n"
"      the buffer size (number of frames) used on the sound device.\n"
"      It may be worth to experiment a bit with this,\n"
"      in particular to try some smaller values. When 'playhrt' is\n"
"      called with --verbose it should report on the range allowed by\n"
"      the device. Default is 16384 (but there are devices where this\n"
"      is not valid).\n"
" \n"
"  --in-net-buffer-size=intval, -K intval\n"
"      when reading from the network this allows to set the buffer\n"
"      size for the incoming data. This is for finetuning only, normally\n"
"      the operating system chooses sizes to guarantee constant data\n"
"      flow. The actual fill of the buffer during playback can be checked\n"
"      with 'netstat -tpn', it can be up to twice as big as the given\n"
"      intval.\n"
"\n"
"  --sleep=intval, -D intval\n"
"      causes playhrt to sleep for intval microseconds (1/1000000 sec)\n"
"      after opening the sound device and before starting playback.\n"
"      This may sometimes be useful to give other programs time to \n"
"      fill the input buffer of playhrt. Default is no sleep.\n"
"\n"
"  --verbose, -v\n"
"      print some information during startup and operation.\n"
"      This option can be given twice for more output about the auto-\n"
"      matic speed control and availability of the audio buffer.\n"
"\n"
"  --version, -V\n"
"      print information about the version of the program and abort.\n"
"\n"
"  --help, -h\n"
"      print this help page and abort.\n"
"\n"
"  EXAMPLES\n"
"\n"
"  We read from myserver on port 5123 stereo data in 32-bit integer\n"
"  format with a sample rate of 192000. We want to run 1000 loops per \n"
"  second (this is in particular a good choice for USB devices), our sound\n"
"  device is 'hw:0,0' and we want to write non-blocking to the device:\n"
"\n"
"  playhrt --host=myserver --port=5123 \\\n"
"      --loops-per-second=1000 \\\n"
"      --device=hw:0,0 --sample-rate=192000 --sample-format=S32_LE \\\n"
"      --non-blocking --verbose \n"
"\n"
"  To play a local CD quality flac file 'music.flac' you need another \n"
"  program to unpack the raw audio data. In this example we use 'sox':\n"
"\n"
"  sox musik.flac -t raw - | playhrt --stdin \\\n"
"          --loops-per-second=1000 --device=hw:0,0 --sample-rate=44100 \\\n"
"          --sample-format=S16_LE --non-blocking --verbose \n"
"\n"
"  ADJUSTING SPEED\n"
"\n"
"  This version of playhrt is automatically adjusting the speed of\n"
"  writing the data to the hardware buffer. This is done via measuring\n"
"  the space left in the hardware buffer and tuning the interval time\n"
"  until the next data write occurs. The targeted value is hw-buffer/2.\n"
"  \n"
"  The automatic adjustment is implemented as PI-control which allows\n"
"  playhrt to adjust to fixed and variable deviation of the local clock\n"
"  against the consuming clock (typically a DAC).\n"
"\n"
);
}

int main(int argc, char *argv[])
{
    int sfd, readbytes, verbose, nrchannels, startcount, sumavg, innetbufsize;
    long loopspersec, sleep, nsec, extransec, count, avgav;
    long long bytecount;
    void *iptr;
    struct timespec mtime;
    struct timespec mtimecheck;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    snd_pcm_format_t format;
    char *host, *port, *pcm_name;
    int optc, nonblock, rate, bytespersample, bytesperframe;
    snd_pcm_uframes_t hwbufsize, offset, frames;
    snd_pcm_sframes_t avail;
    const snd_pcm_channel_area_t *areas;

    /* define variables and default for PI control */
    #define LOOPS_AVG 16        /* amount of averaged buffer measurement */
    #define LOOPS_CADENCE 4000  /* measure each LOOPS_CADENCE loops */
    double bufavg = 0;
    double buferr = 0;
    double buferr_i = 0;
    double Ta = 0.0;	/* will be calculated later */
    double Kp = 1.0;	/* value based on tests */
    double Ki = 0.05;	/* value based on tests */

    /**********************************************************************/
    /* read and set parameters                                            */
    /**********************************************************************/

    /* no parameter given */
    if (argc == 1) {
        usage();
        exit(0);
    }

    /* read command line options */
    static struct option longoptions[] = {
        {"host",               required_argument, 0, 'H' },
        {"port",               required_argument, 0, 'P' },
        {"loops-per-second",   required_argument, 0, 'n' },
        {"sample-rate",        required_argument, 0, 'r' },
        {"sample-format",      required_argument, 0, 'f' },
        {"number-channels",    required_argument, 0, 'c' },
        {"hw-buffer",          required_argument, 0, 'b' },
        {"device",             required_argument, 0, 'd' },
        {"sleep",              required_argument, 0, 'D' },
        {"in-net-buffer-size", required_argument, 0, 'K' },
        {"mmap",               no_argument,       0, 'M' },
        {"stdin",              no_argument,       0, 'S' },
        {"non-blocking-write", no_argument,       0, 'N' },
        {"verbose",            no_argument,       0, 'v' },
        {"version",            no_argument,       0, 'V' },
        {"help",               no_argument,       0, 'h' },
        {0,                    0,                 0,  0  }
    };

    /* set defaults */
    host = NULL;
    port = NULL;
    loopspersec = 1000;
    rate = 44100;
    format = SND_PCM_FORMAT_S16_LE;
    bytespersample = 2;
    hwbufsize = 16384;
    pcm_name = NULL;
    sfd = -1;
    nrchannels = 2;
    extransec = 0;
    sleep = 0;
    nonblock = 0;
    innetbufsize = 0;
    verbose = 0;
    sumavg = 0;
    avgav = 0;
    buferr_i = 0;
    bytecount = 0;

    /* read parameters */
    while ((optc = getopt_long(argc, argv, "H:P:n:r:f:c:b:d:D:K:MSNvVh", longoptions, &optind)) != -1) {
        switch (optc) {
        case 'H':
            host = optarg;
          	break;
        case 'P':
          	port = optarg;
          	break;
        case 'S':
          	sfd = 0;
          	break;
        case 'n':
          	loopspersec = atoi(optarg);
          	break;
        case 'r':
          	rate = atoi(optarg);
          	break;
        case 'f':
            if        (strcmp(optarg, "S16_LE" )==0) {
                format = SND_PCM_FORMAT_S16_LE;
                bytespersample = 2;
            } else if (strcmp(optarg, "S24_LE" )==0) {
                format = SND_PCM_FORMAT_S24_LE;
                bytespersample = 4;
            } else if (strcmp(optarg, "S24_3LE")==0) {
                format = SND_PCM_FORMAT_S24_3LE;
                bytespersample = 3;
            } else if (strcmp(optarg, "S32_LE" )==0) {
                format = SND_PCM_FORMAT_S32_LE;
                bytespersample = 4;
            } else {
                fprintf(stderr, "playhrt: Error. Sample format %s not recognized.\n", optarg);
                exit(1);
            }
            break;
        case 'c':
            nrchannels = atoi(optarg);
            break;
        case 'b':
            hwbufsize = atoi(optarg);
            break;
        case 'M':
            /* ignore, just kept for compatibility */
            break;
        case 'd':
            pcm_name = optarg;
            break;
        case 'D':
            sleep = atoi(optarg);
            break;
        case 'K':
            innetbufsize = atoi(optarg);
            if (innetbufsize != 0 && innetbufsize < 128)
                innetbufsize = 128;
            break;
        case 'N':
            nonblock = 1;
            break;
        case 'v':
            verbose += 1;
            break;
        case 'V':
            fprintf(stderr, "playhrt (version %s of frankl's stereo utilities", VERSION);
#ifdef ALSANC
            fprintf(stderr, ", with alsa-lib patch");
#endif
            fprintf(stderr, ", reworked by Andree Buschmann");
            fprintf(stderr, ", with PI control to compensate clock deviation)\n");
            exit(0);
	    default:
            usage();
            exit(2);
        }
    }

    /**********************************************************************/
    /* calculate and check values from given parameters                   */
    /**********************************************************************/

    /* calculate some values from the parameters */
    bytesperframe = bytespersample*nrchannels;  /* bytes per frame */
    frames = rate/loopspersec;                  /* frames per loop */
    nsec = (int) (1000000000/loopspersec);      /* compute nanoseconds per loop (wrt local clock) */
    Ta = (1.0*LOOPS_CADENCE)/loopspersec;       /* delta T seconds */
    Ta = (Ki*Ta>0.2) ? 0.2/Ki : Ta;             /* limit Ta to avoid oscallation */

    /* set hwbuffer to a multiple of frames per loop (needed for mmap!) */
    hwbufsize = hwbufsize - (hwbufsize % frames);

    /* amount of loops to fill half buffer */
    startcount = hwbufsize/(2*frames);
		
    /* check some arguments and set some parameters */
    if ((host == NULL || port == NULL) && sfd < 0) {
        fprintf(stderr, "playhrt: Error. Must specify --host and --port or --stdin.\n");
        exit(3);
    }

    /**********************************************************************/
    /* show playhrt configuration                                         */
    /**********************************************************************/

    /* show configuration */
    if (verbose) {
        fprintf(stderr, "playhrt: Version %s\n", VERSION);
        fprintf(stderr, "playhrt: Using mmap access.\n");
        fprintf(stderr, "playhrt: Step size is %ld nsec.\n", nsec);
        fprintf(stderr, "playhrt: %d channels with %d bytes per sample at %d Hz\n", nrchannels, bytespersample, rate);
    }

    /**********************************************************************/
    /* setup network connection                                           */
    /**********************************************************************/

    /* setup network connection */
    if (host != NULL && port != NULL) {
        sfd = fd_net(host, port);
        if (innetbufsize != 0) {
            if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void*)&innetbufsize, sizeof(int)) < 0) {
                fprintf(stderr, "playhrt: Error setting buffer size for network socket to %d.\n", innetbufsize);
                exit(4);
            }
        }
    }

    /**********************************************************************/
    /* setup sound device                                                 */
    /**********************************************************************/

    /* setup sound device */
    snd_pcm_hw_params_malloc(&hwparams);
    if (snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
        fprintf(stderr, "playhrt: Error opening PCM device %s\n", pcm_name);
        exit(5);
    }
    if (nonblock) {
        if (snd_pcm_nonblock(pcm_handle, 1) < 0) {
            fprintf(stderr, "playhrt: Error setting non-block mode.\n");
            exit(6);
        } else if (verbose) {
            fprintf(stderr, "playhrt: Using card in non-block mode.\n");
        }
    }
    if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
        fprintf(stderr, "playhrt: Error configuring this PCM device.\n");
        exit(7);
    }
    if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
        fprintf(stderr, "playhrt: Error setting MMAP access.\n");
        exit(8);
    }
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) {
        fprintf(stderr, "playhrt: Error setting format.\n");
        exit(9);
    }
    if (snd_pcm_hw_params_set_rate(pcm_handle, hwparams, rate, 0) < 0) {
        fprintf(stderr, "playhrt: Error setting rate.\n");
        exit(10);
    }
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, nrchannels) < 0) {
        fprintf(stderr, "playhrt: Error setting channels to %d.\n", nrchannels);
        exit(11);
    }
    if (verbose) {
        snd_pcm_uframes_t min=1, max=100000000;
        snd_pcm_hw_params_set_buffer_size_minmax(pcm_handle, hwparams, &min, &max);
        fprintf(stderr, "playhrt: Min and max buffer size of device %ld .. %ld - ", min, max);
    }
    if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, hwbufsize) < 0) {
        fprintf(stderr, "\nplayhrt: Error setting buffersize to %ld.\n", hwbufsize);
        exit(12);
    }
    snd_pcm_hw_params_get_buffer_size(hwparams, &hwbufsize);
    if (verbose) {
        fprintf(stderr, "using %ld.\n", hwbufsize);
    }
    if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
        fprintf(stderr, "playhrt: Error setting HW params.\n");
        exit(13);
    }
    snd_pcm_hw_params_free(hwparams);
    if (snd_pcm_sw_params_malloc (&swparams) < 0) {
        fprintf(stderr, "playhrt: Error allocating SW params.\n");
        exit(14);
    }
    if (snd_pcm_sw_params_current(pcm_handle, swparams) < 0) {
        fprintf(stderr, "playhrt: Error getting current SW params.\n");
        exit(15);
    }
    if (snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, hwbufsize/2) < 0) {
        fprintf(stderr, "playhrt: Error setting start threshold.\n");
        exit(16);
    }
    if (snd_pcm_sw_params(pcm_handle, swparams) < 0) {
        fprintf(stderr, "playhrt: Error applying SW params.\n");
        exit(17);
    }
    snd_pcm_sw_params_free (swparams);

    /**********************************************************************/
    /* sleep for defined amount of time to allow source to fill buffer    */
    /**********************************************************************/

    /* short sleep to allow input to fill buffer */
    if (sleep > 0) {
        mtime.tv_sec = sleep/1000000;
        mtime.tv_nsec = 1000*(sleep - mtime.tv_sec*1000000);
        nanosleep(&mtime, NULL);
    }
	
    /* get time */
    if (clock_gettime(CLOCK_MONOTONIC, &mtime) < 0) {
        fprintf(stderr, "playhrt: Error getting monotonic clock.\n");
       	exit(18);
    }
    if (verbose)
        fprintf(stderr, "playhrt: Start process  (%ld sec %ld nsec).\n", mtime.tv_sec, mtime.tv_nsec);

    /**********************************************************************/
    /* main loop                                                          */
    /**********************************************************************/

    for (count=1; 1; count++) {

        /* start playing when half of hwbuffer is filled */
        if (count == startcount) { 
            snd_pcm_start(pcm_handle);
            if (verbose)
                if (count == startcount) {
                    clock_gettime(CLOCK_MONOTONIC, &mtimecheck);
                    fprintf(stderr, "playhrt: Start playback (%ld sec %ld nsec).\n", 
                            mtimecheck.tv_sec, mtimecheck.tv_nsec);
                }
        }

        /* read amount of frames which can be written to hardware buffer */
        avail = snd_pcm_avail(pcm_handle);
        if (avail < 0) {
            fprintf(stderr, "playhrt: Error on snd_pcm_avail(): %ld.\n", avail);
            exit(19);
       	}

        /* get address for mmap access */
        if (snd_pcm_mmap_begin(pcm_handle, &areas, &offset, &frames) < 0) {
            fprintf(stderr, "playhrt: Error getting mmap address.\n");
            exit(20);
        }

        /**********************************************************************/
        /* automatic rate adaption                                            */
        /**********************************************************************/

       	/* start measurement/adaption in time to finish when LOOPS_CADENCE loops were done */
        if (count > startcount && (count+LOOPS_AVG) % LOOPS_CADENCE == 0) {
            sumavg = LOOPS_AVG;
            avgav = 0;
       	}

        /* add up buffer level for an amount of LOOPS_AVG measurements */
        if (sumavg) {
            avgav += avail;
            if (sumavg == 1) {
                bufavg = (double)avgav/LOOPS_AVG;   /* average buffer level */
                buferr = bufavg - hwbufsize/2;      /* error against target (hwbufsize/2) */
                buferr_i = buferr_i + buferr;       /* integrated error */

                /* calculate amount of time to be added to default step time */
                /* to overall match the local clock to the outgoing clock */
                extransec = (long)(-(Kp * buferr + Ki * Ta * buferr_i) + 0.5);
                nsec = (int)(1000000000/loopspersec + extransec);

                if (verbose > 1) {
                    /* deviation: >0 local clock it too fast, <0 local clock too slow */
                    double deviation = nsec / (1000000000.0/loopspersec);
                    deviation = (deviation > 1) ? (deviation-1) : (deviation-1);
                    fprintf(stderr, "playhrt: (%ld sec) buf: %5.1f e: %4.1f ei: %4.1f dt: %3ld ns (%1.4f%%)\n", 
                            mtime.tv_sec, bufavg, buferr, buferr_i, extransec, deviation*100);
                }
            }
            sumavg--;
        }

        /**********************************************************************/
        /* read data directly into mmap¥ed area                               */
        /**********************************************************************/

        iptr = areas[0].addr + offset * bytesperframe;
        /* memclean(iptr, frames * bytesperframe);  commented out to save some CPU-time */
        /* in --mmap mode we read directly into mmaped space without internal buffer */
        readbytes = read(sfd, iptr, frames * bytesperframe);

        /**********************************************************************/
        /* calculate next wakeup                                              */
        /**********************************************************************/

        /* compute time for next wakeup */
        mtime.tv_nsec += nsec;
        if (mtime.tv_nsec > 999999999) {
            mtime.tv_nsec -= 1000000000;
            mtime.tv_sec++;
        }

        /**********************************************************************/
        /* sleep until defined wakeup, refresh data, commit data              */
        /**********************************************************************/

        /* refreshmem(iptr, readbytes); commented out as called again before commit */
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &mtime, NULL);
        refreshmem(iptr, readbytes); /* refresh memory */
        snd_pcm_mmap_commit(pcm_handle, offset, frames);
        bytecount += readbytes;
        if (readbytes == 0) /* done */
            break;
    }

    /**********************************************************************/
    /* playhrt end, cleanup                                               */
    /**********************************************************************/

    /* cleanup network connection and sound device */
    close(sfd);
    snd_pcm_drain(pcm_handle);
    snd_pcm_close(pcm_handle);
    if (verbose) {
        fprintf(stderr, "playhrt: Loops: %ld, bytes: %lld. \n", count, bytecount);
    }
    return 0;
}

Die spannenderen Änderungen gibt es in resample_soxr. Hier gibt es zwei neue Parameter, um mehr Funktionalität der zu Grunde liegenden soxr Bibliothek ansprechen zu können:
  • "--rolloff" ("-R")
  • "--aliasing" ("-A")
Mit dem "rolloff"-Parameter kann man die von soxr zur Verfügung gestellten Filter-rolloffs auswählen: SOXR_ROLLOFF_SMALL (<= 0.01 dB), SOXR_ROLLOFF_MEDIUM (<=0.35 dB) und SOXR_ROLLOFF_NONE (Chebyshev Bandwidth). Der Defaultwert bleibt unverändert auf dem kleinen Rollloff.
Mit dem "aliasing"-Parameter kann man dem Resamplingfilter Aliasing erlauben, vergleichbar dem Parameter "-a" bei sox. Allerdings ist die Aktivierung des Aliasings recht stumpf umgesetzt (Stopband = 2.0 - Passband). Das Feature ist in der Grundeinstellung nicht aktiviert. Eventuell lege ich hier nochmal nach und erlaube das explizite Setzen des Werts für das Stopband.

Auf den Änderungen basierend habe ich den Hilfe-Text und die "-verbose"-Ausgaben entsprechend angepasst. Dabei habe ich jetzt auch die Erläuterung zum hier so oft diskutierten "--precision"-Wert ergänzt. Er beschreibt nämlich nicht die Genauigkeit, mit der berechnet wird, sondern die Sperrdämpfung des Resamplingfilters (hatte ich hier mal gemessen). Im Code von sox kann man übrigens sehr schön sehen, dass die Parameter "precision" und "rejection" gleichwertig sind und ineinander umgerechnet werden.

Code: Alles auswählen

/*
resample_soxr.c                    Copyright frankl 2018
                         Copyright Andree Buschmann 2020

This file is part of frankl's stereo utilities.
See the file License.txt of the distribution and
http://www.gnu.org/licenses/gpl.txt for license details.
Compile with
    gcc -o resample_soxr -O2 resample_soxr.c -lsoxr -lsndfile -lrt
*/

#include "version.h"
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <soxr.h>

/* help page */
/* vim hint to remove resp. add quotes:
      s/^"\(.*\)\\n"$/\1/
      s/.*$/"\0\\n"/
*/
void usage( ) {
    fprintf(stderr, "resample_soxr (version %s of frankl's stereo utilities", VERSION);
    fprintf(stderr, ", reworked by Andree Buschmann");
    fprintf(stderr, ")\n\nUSAGE:\n");
    fprintf(stderr,
"\n"
"  resample_soxr [options] \n"
"\n"
"  By default this program works as a resampling filter for stereo audio \n"
"  streams in raw double format (in some programs denoted FLOAT64_LE).\n"
"  Here 'filter' means that input comes from stdin and output is\n"
"  written to stdout. Use pipes and 'sox' or other programs to deal with \n"
"  other stream formats. The main options are the input sample rate,\n"
"  the output sample rate and parameters for the resmapler itself.\n"
"\n"
"  This program uses the 'soxr' standalone resampling library (see\n"
"  https://sourceforge.net/projects/soxr/) with the highest quality \n"
"  settings, all computations are done with 64-bit floating point \n"
"  numbers.\n"
"\n"
"  The computation is similar to using 'sox' with effect 'rate -v'.\n"
"  But 'sox' applies all effects internally to 32-bit signed integer\n"
"  samples (that is, the 64-bit input precision is lost).\n"
"\n"
"  OPTIONS\n"
"  \n"
"  --inrate=floatval, -i floatval\n"
"      the input sample rate as floating point number (must not be an \n"
"      integer). Default is 44100. In case of file input this value is\n"
"      overwritten by the sampling rate specified in the file (so, this\n"
"      option is not needed).\n"
"\n"
"  --outrate=floatval, -o floatval\n"
"      the output sample rate as floating point number (must not be an \n"
"      integer). Default is 192000.\n"
"\n"
"  --channels=intval, -c intval\n"
"      number of interleaved channels in the input. Default is 2 (stereo).\n"
"      In case of input from a file this number is overwritten by the \n"
"      the number of channels in the file.\n"
"\n"
"  --buffer-length=intval, -b intval\n"
"      the size of the input buffer in number of frames. The default\n"
"      (and minimal value) is 8192 and should usually be fine.\n"
"\n"
"  --phase=floatval, -P floatval\n"
"      the phase response of the filter used during resampling; see the \n"
"      documentation of the 'rate' effect in 'sox' for more details. This\n"
"      is a number from 0 (minimum phase) to 100 (maximal phase), with \n"
"      50 (linear phase) and 25 (intermediate phase). The default is 25,\n"
"      and should usually be fine.\n"
"\n"
"  --band-width=floatval, -B floatval\n"
"      the band-width of the filter used during resampling; see the \n"
"      documentation of the rate effect in 'sox' for more details. The value\n"
"      is given as percentage (of the Nyquist frequency of the smaller \n"
"      sampling rate). The allowed range is 74.0..99.7, the default is 91.09\n"
"      (this filter is flat up to about 20kHz at 44.1kHz sampling rate).\n"
"\n"
"  --precision=floatval, -e floatval\n"
"      the bit precision for resampling, which is equivalent to the rejection\n"
"      of the resampling filter; higher values cause higher CPU usage. The\n"
"      valid range is 16.0..33.0, the default is 33.0 and should usually be\n"
"      fine (except lower CPU usage is essential).\n"
"\n"
"  --rolloff=intval, -R intval\n"
"      the rolloff for the resampling filter; Valid range is 0..2, default 0.\n"
"      According to soxr documentation:\n"
"      0 = SOXR_ROLLOFF_SMALL  (<= 0.01 dB)\n"
"      1 = SOXR_ROLLOFF_MEDIUM (<= 0.35 dB)\n"
"      2 = SOXR_ROLLOFF_NONE   (For Chebyshev bandwidth).\n"
"\n"
"  --aliasing, -A\n"
"      allow aliasing in the resampling filter; This is switched off by default\n"
"      and not recommended, but it can be enabled to create short filters.\n"
"      Enabling aliasing sets stopband begin to fs x (2 - bandwidth), example:\n"
"      bandwidth = 0.91 results in stoppand = 1.09.\n"
"\n"
"  --help, -h\n"
"      show this help.\n"
"\n"
"  --verbose, -v\n"
"      shows some information during startup and operation.\n"
"\n"
"  --version, -V\n"
"      show the version of this program and exit.\n"
"\n"
"   EXAMPLES\n"
"\n"
"   Convert a file 'musicfile' that can be read by 'sox' to a 96/32\n"
"   wav-file using a pipe:\n"
"      ORIGRATE=`sox --i musicfile | grep \"Sample Rate\" | \\\n"
"                cut -d: -f2 | sed -e \"s/ //g\"`\n"
"      sox musicfile -t raw -e float -b 64 - | \\\n"
"          resample_soxr --inrate=$ORIGRATE --outrate=96000 --precision=28 | \\\n"
"          sox -t raw -e float -b 64 -c 2 -r 96000 - -e signed -b 32 out.wav\n"
"\n"
);
}

int main(int argc, char *argv[])
{
    /* variables for the resampler */
    double inrate, outrate, phase, bwidth, prec;
    double *iptr, *optr;
    int verbose, optc, rolloff, aliasing;
    long itotal, ototal, blen, mlen, check, nch, olen;
    size_t indone, outdone;
    /* variables for soxr */
    int qspecflags, qspecrecipe;
    soxr_quality_spec_t q_spec;
    soxr_io_spec_t io_spec;
    soxr_runtime_spec_t runtime_spec;
    soxr_t soxr;
    soxr_error_t error;

    /**********************************************************************/
    /* read and set parameters                                            */
    /**********************************************************************/
    
    /* no parameter given */
    if (argc == 1) {
        usage();
        exit(1);
    }

    /* read command line options */
    static struct option longoptions[] = {
        {"inrate",        required_argument, 0, 'i' },
        {"outrate",       required_argument, 0, 'o' },
        {"phase",         required_argument, 0, 'P' },
        {"band-width",    required_argument, 0, 'B' },
        {"precision",     required_argument, 0, 'e' },
        {"channels",      required_argument, 0, 'c' },
        {"buffer-length", required_argument, 0, 'b' },
        {"rolloff",       required_argument, 0, 'R' },
        {"aliasing",      no_argument,       0, 'A' },
        {"verbose",       no_argument,       0, 'v' },
        {"version",       no_argument,       0, 'V' },
        {"help",          no_argument,       0, 'h' },
        {0,               0,                 0,  0  }
    };

    /* defaults */
    inrate = 44100.0;
    outrate = 192000.0;
    phase = 25.0; 
    bwidth = 0.0;
    rolloff = SOXR_ROLLOFF_SMALL;
    aliasing = 0;
    prec = 33.0;
    nch = 2;
    blen = 8192;
    verbose = 0;
    itotal = 0;
    ototal = 0;
    
    /* read parameters */
    while ((optc = getopt_long(argc, argv, "i:o:P:B:e:c:b:R:AvVh", longoptions, &optind)) != -1) {
        switch (optc) {
        case 'i':
            inrate = atof(optarg);
            break;
        case 'o':
            outrate = atof(optarg);
            break;
        case 'P':
            phase = atof(optarg);
            if (phase < 0.0 || phase > 100.0)
                phase = 25.0;
            break;
        case 'B':
            bwidth = atof(optarg);
            if (bwidth < 74.0 || bwidth > 99.7)
                bwidth = 0.0;
            break;
        case 'e':
            prec = atof(optarg);
            if (prec < 16.0 || prec > 33.0)
                prec = 33.0;
            break;
        case 'c':
            nch = atoi(optarg);
            break;
        case 'b':
            blen = atoi(optarg);
            if (blen < 1024)
                blen = 8192;
            break;
        case 'R':
            rolloff = atoi(optarg);
            if (rolloff < 0 || rolloff > 2)
                rolloff = SOXR_ROLLOFF_SMALL;
            break;
        case 'A':
            aliasing = 1;
            break;        
        case 'v':
            verbose = 1;
            break;
        case 'V':
            fprintf(stderr, "resample_soxr (version %s of frankl's stereo utilities", VERSION);
            fprintf(stderr, ", reworked by Andree Buschmann)\n");
            exit(2);
        default:
            usage();
            exit(3);
        }
    }

    /**********************************************************************/
    /* allocate buffers                                                   */
    /**********************************************************************/

    /* allocate input buffer */
    iptr = (double*) malloc(nch*blen*sizeof(double));

    /* allocate output buffer */
    olen = (long)(blen*(outrate/inrate+1.0));
    optr = (double*) malloc(nch*olen*sizeof(double));

    /**********************************************************************/
    /* create soxr resampler, for parameters see                          */
    /* https://sourceforge.net/p/soxr/code/ci/master/tree/src/soxr.h      */
    /**********************************************************************/

    /* use chosen rolloff and high precision for irrational ratios */
    qspecflags = rolloff | SOXR_HI_PREC_CLOCK; 

    /* intermediate phase (will later be overwritten by chosen value for phase) */
    /* precision 32 (will later be overwritten by chosen value for precision)   */ 
    qspecrecipe = SOXR_INTERMEDIATE_PHASE | SOXR_32_BITQ;

    /* create qspec, overwrite phase/prec/passband/stopband with chosen values */
    q_spec = soxr_quality_spec(qspecrecipe, qspecflags);
    q_spec.phase_response = phase;
    q_spec.precision = prec;
    q_spec.passband_end = (bwidth != 0.0) ? bwidth/100.0 : q_spec.passband_end;
    q_spec.stopband_begin = (aliasing == 1) ? (2.0-q_spec.passband_end) : 1.0;

    /* set io_spec for FLOAT64 precision */
    io_spec = soxr_io_spec(SOXR_FLOAT64_I,SOXR_FLOAT64_I);

    /* set runtime spec for 1 thread */
    runtime_spec = soxr_runtime_spec(1);

    /* now we can create the resampler */
    soxr = soxr_create(inrate, outrate, nch, &error, &io_spec, &q_spec, &runtime_spec);
    if (error) {
        fprintf(stderr, "resample_soxr: Error initializing soxr resampler.\n");
        fflush(stderr);
        exit(4);
    }

    /**********************************************************************/
    /* show resample_soxr configuration                                   */
    /**********************************************************************/
  
    if (verbose) {
        fprintf(stderr, "resample_soxr: Version %s\n", VERSION);
        fprintf(stderr, "resample_soxr: input rate %.1f Hz, output rate %.1f Hz\n", 
                inrate, outrate);
        fprintf(stderr, "resample_soxr: phase %.0f, precision %.0f (rejection %.0f dB)\n", 
                q_spec.phase_response, q_spec.precision, q_spec.precision*6.02);
        fprintf(stderr, "resample_soxr: passband %.4f, stopband %.4f %s\n", 
                q_spec.passband_end, q_spec.stopband_begin, (aliasing)?"(Aliasing!)":"");
        fprintf(stderr, "resample_soxr: rolloff 0x%x, SOXR_HI_PREC_CLOCK\n",
                rolloff);
    }

    /**********************************************************************/
    /* main loop                                                          */
    /**********************************************************************/
     
    /* we read from stdin until eof and write to stdout */
    while (1) {

        /* clean buffers */
        memset(iptr, 0, nch*blen*sizeof(double));
        memset(optr, 0, nch*olen*sizeof(double));

        /**********************************************************************/
        /* read data                                                          */
        /**********************************************************************/

        /* read input block */
        mlen = fread((void*)iptr, nch*sizeof(double), blen, stdin);

        /**********************************************************************/
        /* resample data                                                      */
        /**********************************************************************/
    
        /* call resampler */
        error = soxr_process(soxr, iptr, mlen, &indone, optr, olen, &outdone);
        if (mlen > indone) {
            fprintf(stderr, "resample_soxr: only %ld/%ld processed.\n",(long)indone,(long)mlen);
            fflush(stderr);
        }
        if (error) {
            fprintf(stderr, "resample_soxr: Error calling soxr_process: (%s).\n", soxr_strerror(error));
            fflush(stderr);
            exit(5);
        }

        /**********************************************************************/
        /* write data to output                                               */
        /**********************************************************************/
 
        /* write output */
        check = fwrite((void*)optr, nch*sizeof(double), outdone, stdout);
        fflush(stdout);
    
        /* this should not happen, the whole block should be written */
        if (check < outdone) {
            fprintf(stderr, "resample_soxr: Error writing to output..\n");
            fflush(stderr);
            exit(6);
        }
    
        itotal += mlen;
        ototal += outdone;
        if (mlen == 0) /* done */
            break;
    }

    /**********************************************************************/
    /* resample_soxr end, cleanup                                         */
    /**********************************************************************/

    soxr_delete(soxr);
    free(iptr);
    free(optr);
    if (verbose) {
        fprintf(stderr, "resample_soxr: %ld input and %ld output samples\n", (long)itotal, (long)ototal);
    }
    return(0);
}

Grüße,
Andree
Bild
Buschel
Aktiver Hörer
Beiträge: 989
Registriert: 12.12.2013, 20:12
Wohnort: Raum Karlsruhe

Beitrag von Buschel »

Moin,

nach ein paar Wochen kommt hier das nächste Update zu playhrt. Simon hat mich auf einen Umstand gestoßen, der mich interessiert hat. Die bisherige playhrt-Implementierung brauchte nahezu zwingend eine manuell ermittelte Startverzögerung, die mit "--sleep" übergeben wurde. Diese Verzögerung diente dazu, dass die pipeline zu playhrt aufgebaut und befüllt wird bevor playhrt darauf zugreift. Bei zu kurzen Verzögerungen kam es entweder zum Beenden mit Fehlercode oder zu Audioaussetzern.

In der neuen Version kann entweder weiterhin eine solche Verzögerung direkt mit "-sleep" gesetzt werden, oder es wird automatisch gewartet bis die pipeline verfügbar und befüllt ist. Dabei wir zunächst über select() gewartet bis die pipeline verfügbar ist. In einem zweiten Schritt wird dann ein nanosleep() ausgeführt, der von der ermittelten Puffergröße der pipeline und den Eigenschaften des Audiostreams abhängt. Diese zweite Verzögerung dauert so lange wie der Audiostream braucht, um den Puffer zu füllen.

Während des Umbaus ist mir dabei aufgefallen, dass in playhrt ein Fehler vorlag, der potentiell zu unvollständigen Lesevorgängen führte. Bei zu kurz gewählten Verzögerungszeiten konnte nicht immer die gewünschte Anzahl von Bytes von der pipeline gelesen werden. Je nach Verzögerungszeit traten bis zu mehreren Fehlern pro Sekunde und hörbare Aussetzern auf -- leider ohne eine Fehlermeldung von playhrt.
Diese Lesefehler lassen sich durch ein Nachlesen der noch fehlenden Bytes lösen, das jetzt in die neue Version eingeflossen ist. Falls dieser Fall eintritt, gibt playhrt eine Meldung aus. Damit lässt sich die manuelle Verzögerung, falls diese weiterhin gewünscht ist, so trimmen, dass das Nachlesen vermieden wird. Die neue automatische Verzögerung zeigt bei meinen Tests keine solche Fehlersituationen mehr.

Änderungen:
  • Automatisches Warten auf befüllte pipeline
  • Mehrfaches Lesen von pipeline, falls nötig
  • HW-Buffer Default auf 4096
  • Verbesserte Zeitdarstellung und Formatierung bei verbose-Ausgaben
  • Kleine Korrekturen am Hilfetext
Source Code:

Code: Alles auswählen

/*
playhrt.c                Copyright frankl 2013-2016
                         Copyright Andree Buschmann 2020

This file is part of frankl's stereo utilities and was reworked by Andree Buschmann.
See the file License.txt of the distribution and http://www.gnu.org/licenses/gpl.txt 
for license details.
*/

#define _GNU_SOURCE
#include "version.h"
#include "net.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <alsa/asoundlib.h>
#include "cprefresh.h"

/* help page */
/* vim hint to remove resp. add quotes:
      s/^"\(.*\)\\n"$/\1/
      s/.*$/"\0\\n"/
*/
void usage( ) {
    fprintf(stderr, "playhrt (version %s of frankl's stereo utilities", VERSION);
#ifdef ALSANC
    fprintf(stderr, ", with alsa-lib patch");
#endif
    fprintf(stderr, ", reworked by Andree Buschmann");
    fprintf(stderr, ", with PI control to compensate clock deviation");
    fprintf(stderr, ")\n\nUSAGE:\n");
    fprintf(stderr,
"\n"
"  playhrt [options] \n"
"\n"
"  This program reads raw(!) stereo audio data from stdin, a file or the \n"
"  network and plays it on a local (ALSA) sound device. \n"
"\n"
"  The program repeats in a given number of loops per second: reading\n"
"  a chunk of input data, preparing data for the audio driver, then it\n"
"  sleeps until a specific instant of time and after wakeup it hands data\n"
"  to the audio driver. In contrast to other player programs this is done\n"
"  with a very precise timing such that no buffers underrun or overrun and\n"
"  no reading or writing of data is blocking. Furthermore, the data is\n"
"  refreshed in RAM directly before copying it to the audio driver.\n"
"\n"
"  The Linux kernel needs the highres-timer functionality enabled (on most\n"
"  systems this is the case).\n"
"\n"
"  This reworked version only writes input data directly to the memory\n"
"  of the audio driver (mmap mode).\n"
"\n"
"  USAGE HINTS\n"
"  \n"
"  It is recommended to give this program a high priority and not to run\n"
"  too many other things on the same computer during playback. A high\n"
"  priority can be specified with the 'chrt' command:\n"
"\n"
"  'chrt -f 70 playhrt .....'\n"
"\n"
"  (Depending on the configuration of your computer you may need root\n"
"  privileges for this, in that case use 'sudo chrt -f 99 playhrt ....' \n"
"  or give 'chrt' setuid permissions.)\n"
"\n"
"  While running this program the computer should run as few other things\n"
"  as possible. In particular we recommend to generate the input data\n"
"  on a different computer and to send them via the network to 'playhrt'\n"
"  using the program 'bufhrt' which is also contained in this package. \n"
"  \n"
"  OPTIONS\n"
"\n"
"  --host=hostname, -H hostname\n"
"      the host from which to receive the data , given by name or\n"
"      ip-address.\n"
"\n"
"  --port=portnumber, -P portnumber\n"
"      the port number on the remote host from which to receive data.\n"
"\n"
"  --stdin, -S\n"
"      read data from stdin (instead of --host and --port).\n"
"\n"
"  --device=alsaname, -d alsaname\n"
"      the name of the sound device. A typical name is 'hw:0,0', maybe\n"
"      use 'aplay -l' to find out the correct numbers. It is recommended\n"
"      to use the hardware devices 'hw:...' if possible.\n"
"\n"
"  --rate=intval, -r intval\n"
"      the sample rate of the audio data. Default is 44100 as on CDs.\n"
"\n"
"  --format=formatstring, -f formatstring\n"
"      the format of the samples in the audio data. Currently recognised are\n"
"      'S16_LE'  (signed integer 16 bits, the sample format on CDs),\n"
"      'S24_LE'  (signed integer 24 bits, packed to 4 bytes, used by many DACs)\n" 
"      'S24_3LE' (signed integer 24 bits, using 3 bytes per sample), \n"
"      'S32_LE'  (signed integer 32 bits, true 32 bit samples).\n"
"      Default is 'S16_LE'.\n"
"\n"
"  --channels=intval, -c intval\n"
"      the number of channels in the (interleaved) audio stream. The \n"
"      default is 2 (stereo).\n"
"\n"
"  --loops-per-second=intval, -n intval\n"
"      the number of loops per second in which 'playhrt' reads some\n"
"      data from the network into a buffer, sleeps until a precise\n"
"      moment and then writes a chunk of data to the sound device. \n"
"      Typical values would be 1000 or 2000. Default is 1000.\n"
"\n"
"  --non-blocking-write, -N\n"
"      write data to sound device in a non-blocking fashion. This can\n"
"      improve sound quality, but the timing must be very precise.\n"
"\n"
"  --hw-buffer-size=intval, -B intval\n"
"      the buffer size (number of frames) used on the sound device.\n"
"      It may be worth to experiment a bit with this, in particular\n"
"      to try some smaller values. When 'playhrt' is called with\n"
"      '--verbose' it will report on the range allowed by the device.\n"
"      Default is 4096.\n"
" \n"
"  --in-net-buffer-size=intval, -I intval\n"
"      when reading from the network this allows to set the buffer\n"
"      size for the incoming data. This is for finetuning only, normally\n"
"      the operating system chooses sizes to guarantee constant data\n"
"      flow. The actual fill of the buffer during playback can be checked\n"
"      with 'netstat -tpn', it can be up to twice as big as the given\n"
"      intval.\n"
"\n"
"  --sleep=intval, -D intval\n"
"      causes 'playhrt' to sleep for intval microseconds (1/1000000 sec)\n"
"      after opening the sound device and before starting playback.\n"
"      This may sometimes be useful to give other programs time to fill\n"
"      the input buffer of 'playhrt'. Default is no sleep, in this case\n"
"      'playhrt' waits for the input pipeline to provide data.\n"
"\n"
"  --verbose, -v\n"
"      print some information during startup and operation.\n"
"      This option can be given twice for more output about the auto-\n"
"      matic speed control and availability of the audio buffer.\n"
"\n"
"  --version, -V\n"
"      print information about the version of the program and abort.\n"
"\n"
"  --help, -h\n"
"      print this help page and abort.\n"
"\n"
"  EXAMPLES\n"
"\n"
"  We read from myserver on port 5123 stereo data in 32-bit integer\n"
"  format with a sample rate of 192000. We want to run 1000 loops per \n"
"  second (this is in particular a good choice for USB devices), our sound\n"
"  device is 'hw:0,0' and we want to write non-blocking to the device:\n"
"\n"
"  playhrt --host=myserver --port=5123 \\\n"
"      --loops-per-second=1000 \\\n"
"      --device=hw:0,0 --sample-rate=192000 --sample-format=S32_LE \\\n"
"      --non-blocking --verbose \n"
"\n"
"  To play a local CD quality flac file 'music.flac' you need another \n"
"  program to unpack the raw audio data. In this example we use 'sox':\n"
"\n"
"  sox musik.flac -t raw - | playhrt --stdin \\\n"
"          --loops-per-second=1000 --device=hw:0,0 --sample-rate=44100 \\\n"
"          --sample-format=S16_LE --non-blocking --verbose \n"
"\n"
"  ADJUSTING SPEED\n"
"\n"
"  This version of 'playhrt' is automatically adjusting the speed of\n"
"  writing the data to the hardware buffer. This is done via measuring\n"
"  the space left in the hardware buffer and tuning the interval time\n"
"  until the next data write occurs. The targeted value is hw-buffer/2.\n"
"  \n"
"  The automatic adjustment is implemented as PI-control which allows\n"
"  'playhrt' to adjust to fixed and variable deviation of the local clock\n"
"  against the consuming clock (typically a DAC).\n"
"\n"
);
}

const char *formattime(char* text, struct timespec mtime)
{
    int hrs  = mtime.tv_sec/3600;
    int min  = (mtime.tv_sec-3600*hrs)/60;
    int sec  = mtime.tv_sec%60;
    int msec = mtime.tv_nsec/1000000;
    int usec = (mtime.tv_nsec-1000000*msec)/1000;
    int nsec = mtime.tv_nsec%1000;

    /* formatting into <h:m:s.ms us ns> for better readability */
    sprintf(text, "%02d:%02d:%02d.%03d %03d %03d", hrs, min, sec, msec, usec, nsec);
    return text;
}

const char *hms(char* text, struct timespec mtime)
{
    int hrs  = mtime.tv_sec/3600;
    int min  = (mtime.tv_sec-3600*hrs)/60;
    int sec  = mtime.tv_sec%60;

    /* formatting into <h:m:s> for better readability */
    sprintf(text, "%02d:%02d:%02d", hrs, min, sec);
    return text;
}

int main(int argc, char *argv[])
{
    int sfd, readbytes, readthis, verbose, nrchannels, startcount, sumavg, innetbufsize;
    long loopspersec, sleep, nsec, extransec, count, avgav;
    long long bytecount;
    void *iptr;
    struct timespec mtime;
    struct timespec mtimecheck;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    snd_pcm_format_t format;
    char *host, *port, *pcm_name;
    int optc, nonblock, rate, bytespersample, bytesperframe;
    snd_pcm_uframes_t hwbufsize, offset, frames;
    snd_pcm_sframes_t avail;
    const snd_pcm_channel_area_t *areas;
    char text[32];

    /* define variables and default for PI control */
    #define LOOPS_AVG 16        /* amount of averaged buffer measurement */
    #define LOOPS_CADENCE 4000  /* measure each LOOPS_CADENCE loops */
    double bufavg = 0;
    double buferr = 0;
    double buferr_i = 0;
    double Ta = 0.0;	/* will be calculated later */
    double Kp = 1.0;	/* value based on tests */
    double Ki = 0.05;	/* value based on tests */

    /**********************************************************************/
    /* read and set parameters                                            */
    /**********************************************************************/

    /* no parameter given */
    if (argc == 1) {
        usage();
        exit(0);
    }

    /* read command line options */
    static struct option longoptions[] = {
        {"host",               required_argument, 0, 'H' },
        {"port",               required_argument, 0, 'P' },
        {"loops-per-second",   required_argument, 0, 'n' },
        {"rate",               required_argument, 0, 'r' },
        {"format",             required_argument, 0, 'f' },
        {"channels",           required_argument, 0, 'c' },
        {"hw-buffer-size",     required_argument, 0, 'B' },
        {"device",             required_argument, 0, 'd' },
        {"sleep",              required_argument, 0, 'D' },
        {"in-net-buffer-size", required_argument, 0, 'I' },
        {"mmap",               no_argument,       0, 'M' },
        {"stdin",              no_argument,       0, 'S' },
        {"non-blocking-write", no_argument,       0, 'N' },
        {"verbose",            no_argument,       0, 'v' },
        {"version",            no_argument,       0, 'V' },
        {"help",               no_argument,       0, 'h' },
        {0,                    0,                 0,  0  }
    };

    /* set defaults */
    host = NULL;
    port = NULL;
    loopspersec = 1000;
    rate = 44100;
    format = SND_PCM_FORMAT_S16_LE;
    bytespersample = 2;
    hwbufsize = 4096;
    pcm_name = NULL;
    sfd = -1;
    nrchannels = 2;
    extransec = 0;
    sleep = 0;
    nonblock = 0;
    innetbufsize = 0;
    verbose = 0;
    sumavg = 0;
    avgav = 0;
    buferr_i = 0;
    bytecount = 0;

    /* read parameters */
    while ((optc = getopt_long(argc, argv, "H:P:n:r:f:c:B:d:D:I:MSNvVh", longoptions, &optind)) != -1) {
        switch (optc) {
        case 'H':
            host = optarg;
          	break;
        case 'P':
          	port = optarg;
          	break;
        case 'S':
          	sfd = 0;
          	break;
        case 'n':
          	loopspersec = atoi(optarg);
          	break;
        case 'r':
          	rate = atoi(optarg);
          	break;
        case 'f':
            if        (strcmp(optarg, "S16_LE" )==0) {
                format = SND_PCM_FORMAT_S16_LE;
                bytespersample = 2;
            } else if (strcmp(optarg, "S24_LE" )==0) {
                format = SND_PCM_FORMAT_S24_LE;
                bytespersample = 4;
            } else if (strcmp(optarg, "S24_3LE")==0) {
                format = SND_PCM_FORMAT_S24_3LE;
                bytespersample = 3;
            } else if (strcmp(optarg, "S32_LE" )==0) {
                format = SND_PCM_FORMAT_S32_LE;
                bytespersample = 4;
            } else {
                fprintf(stderr, "playhrt: Error. Sample format %s not recognized.\n", optarg);
                exit(1);
            }
            break;
        case 'c':
            nrchannels = atoi(optarg);
            break;
        case 'B':
            hwbufsize = atoi(optarg);
            break;
        case 'M':
            /* ignore, just kept for compatibility */
            break;
        case 'd':
            pcm_name = optarg;
            break;
        case 'D':
            sleep = atoi(optarg);
            break;
        case 'I':
            innetbufsize = atoi(optarg);
            if (innetbufsize != 0 && innetbufsize < 128)
                innetbufsize = 128;
            break;
        case 'N':
            nonblock = 1;
            break;
        case 'v':
            verbose += 1;
            break;
        case 'V':
            fprintf(stderr, "playhrt (version %s of frankl's stereo utilities", VERSION);
#ifdef ALSANC
            fprintf(stderr, ", with alsa-lib patch");
#endif
            fprintf(stderr, ", reworked by Andree Buschmann");
            fprintf(stderr, ", with PI control to compensate clock deviation)\n");
            exit(2);
	    default:
            usage();
            exit(3);
        }
    }

    /**********************************************************************/
    /* calculate and check values from given parameters                   */
    /**********************************************************************/

    /* calculate some values from the parameters */
    bytesperframe = bytespersample*nrchannels;  /* bytes per frame */
    frames = rate/loopspersec;                  /* frames per loop */
    nsec = (int) (1000000000/loopspersec);      /* compute nanoseconds per loop (wrt local clock) */
    Ta = (1.0*LOOPS_CADENCE)/loopspersec;       /* delta T seconds */
    Ta = (Ki*Ta>0.2) ? 0.2/Ki : Ta;             /* limit Ta to avoid oscallation */

    /* set hwbuffer to a multiple of frames per loop (needed for mmap!) */
    hwbufsize = hwbufsize - (hwbufsize % frames);

    /* amount of loops to fill half buffer */
    startcount = hwbufsize/(2*frames);
		
    /* check some arguments and set some parameters */
    if ((host == NULL || port == NULL) && sfd < 0) {
        fprintf(stderr, "playhrt: Error. Must specify --host and --port or --stdin.\n");
        exit(4);
    }

    /**********************************************************************/
    /* show playhrt configuration                                         */
    /**********************************************************************/

    /* show configuration */
    if (verbose) {
        fprintf(stderr, "playhrt: Version %s\n", VERSION);
        fprintf(stderr, "playhrt: Using mmap access.\n");
        fprintf(stderr, "playhrt: Step size is %ld nsec.\n", nsec);
        fprintf(stderr, "playhrt: %d channels with %d bytes per sample at %d Hz\n", nrchannels, bytespersample, rate);
    }

    /**********************************************************************/
    /* setup network connection                                           */
    /**********************************************************************/

    /* setup network connection */
    if (host != NULL && port != NULL) {
        sfd = fd_net(host, port);
        if (innetbufsize != 0) {
            if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (void*)&innetbufsize, sizeof(int)) < 0) {
                fprintf(stderr, "playhrt: Error setting buffer size for network socket to %d.\n", innetbufsize);
                exit(5);
            }
        }
    }

    /**********************************************************************/
    /* setup sound device                                                 */
    /**********************************************************************/

    /* setup sound device */
    snd_pcm_hw_params_malloc(&hwparams);
    if (snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
        fprintf(stderr, "playhrt: Error opening PCM device %s\n", pcm_name);
        exit(6);
    }
    if (nonblock) {
        if (snd_pcm_nonblock(pcm_handle, 1) < 0) {
            fprintf(stderr, "playhrt: Error setting non-block mode.\n");
            exit(7);
        } else if (verbose) {
            fprintf(stderr, "playhrt: Using card in non-block mode.\n");
        }
    }
    if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
        fprintf(stderr, "playhrt: Error configuring this PCM device.\n");
        exit(8);
    }
    if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
        fprintf(stderr, "playhrt: Error setting MMAP access.\n");
        exit(9);
    }
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) {
        fprintf(stderr, "playhrt: Error setting format.\n");
        exit(10);
    }
    if (snd_pcm_hw_params_set_rate(pcm_handle, hwparams, rate, 0) < 0) {
        fprintf(stderr, "playhrt: Error setting rate.\n");
        exit(11);
    }
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, nrchannels) < 0) {
        fprintf(stderr, "playhrt: Error setting channels to %d.\n", nrchannels);
        exit(12);
    }
    if (verbose) {
        snd_pcm_uframes_t min=1, max=100000000;
        snd_pcm_hw_params_set_buffer_size_minmax(pcm_handle, hwparams, &min, &max);
        fprintf(stderr, "playhrt: Min and max buffer size of device %ld .. %ld - ", min, max);
    }
    if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, hwbufsize) < 0) {
        fprintf(stderr, "\nplayhrt: Error setting buffersize to %ld.\n", hwbufsize);
        exit(13);
    }
    snd_pcm_hw_params_get_buffer_size(hwparams, &hwbufsize);
    if (verbose) {
        fprintf(stderr, "using %ld.\n", hwbufsize);
    }
    if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
        fprintf(stderr, "playhrt: Error setting HW params.\n");
        exit(14);
    }
    snd_pcm_hw_params_free(hwparams);
    if (snd_pcm_sw_params_malloc (&swparams) < 0) {
        fprintf(stderr, "playhrt: Error allocating SW params.\n");
        exit(15);
    }
    if (snd_pcm_sw_params_current(pcm_handle, swparams) < 0) {
        fprintf(stderr, "playhrt: Error getting current SW params.\n");
        exit(16);
    }
    if (snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, hwbufsize/2) < 0) {
        fprintf(stderr, "playhrt: Error setting start threshold.\n");
        exit(17);
    }
    if (snd_pcm_sw_params(pcm_handle, swparams) < 0) {
        fprintf(stderr, "playhrt: Error applying SW params.\n");
        exit(18);
    }
    snd_pcm_sw_params_free (swparams);

    /**********************************************************************/
    /* wait to allow filling input pipeline                               */
    /**********************************************************************/

    /* get time */
    if (clock_gettime(CLOCK_MONOTONIC, &mtime) < 0) {
        fprintf(stderr, "playhrt: Error getting monotonic clock.\n");
       	exit(19);
    }
    if (verbose)
        fprintf(stderr, "playhrt: Wait for pipeline (%s).\n", formattime(text, mtime));
    
    /* use defined sleep to allow input process to fill pipeline */
    if (sleep > 0) {
        mtime.tv_sec = sleep/1000000;
        mtime.tv_nsec = 1000*(sleep - mtime.tv_sec*1000000);
        nanosleep(&mtime, NULL);
    /* waits until pipeline is filled */
    } else {
        fd_set rdfs;
        FD_ZERO(&rdfs);
        FD_SET(sfd, &rdfs);

        /* select() waits until pipeline is ready */
        if (select(sfd+1, &rdfs, NULL, NULL, NULL) <=0 ) {
            fprintf(stderr, "playhrt: Error waiting for pipeline data.\n");
            exit(20);
        };

        /* now sleep until the pipeline is filled */
        sleep = (long)((fcntl(sfd, F_GETPIPE_SZ)/bytesperframe)*1000000.0/rate); /* us */
        mtime.tv_sec = 0;
        mtime.tv_nsec = sleep*1000;
        nanosleep(&mtime, NULL);
    }
	
    /* get time */
    if (clock_gettime(CLOCK_MONOTONIC, &mtime) < 0) {
        fprintf(stderr, "playhrt: Error getting monotonic clock.\n");
       	exit(21);
    }
    if (verbose)
        fprintf(stderr, "playhrt: Pipeline ready    (%s).\n", formattime(text, mtime));

    /**********************************************************************/
    /* main loop                                                          */
    /**********************************************************************/

    for (count=1; 1; count++) {

        /* start playing when half of hwbuffer is filled */
        if (count == startcount) {
            snd_pcm_start(pcm_handle);
            if (verbose) {
                clock_gettime(CLOCK_MONOTONIC, &mtimecheck);
                fprintf(stderr, "playhrt: Start playback    (%s).\n", formattime(text, mtimecheck));
            }
        }

        /* read amount of frames which can be written to hardware buffer */
        avail = snd_pcm_avail(pcm_handle);
        if (avail < 0) {
            fprintf(stderr, "playhrt: Error on snd_pcm_avail(): %ld.\n", avail);
            exit(22);
       	}

        /* get address for mmap access, we will write to iptr */
        if (snd_pcm_mmap_begin(pcm_handle, &areas, &offset, &frames) < 0) {
            fprintf(stderr, "playhrt: Error getting mmap address.\n");
            exit(23);
        }
        iptr = areas[0].addr + offset * bytesperframe;

        /**********************************************************************/
        /* automatic rate adaption                                            */
        /**********************************************************************/

       	/* start measurement/adaption in time to finish when LOOPS_CADENCE loops were done */
        if (count > startcount && (count+LOOPS_AVG) % LOOPS_CADENCE == 0) {
            sumavg = LOOPS_AVG;
            avgav = 0;
       	}

        /* add up buffer level for an amount of LOOPS_AVG measurements */
        if (sumavg) {
            avgav += avail;
            if (sumavg == 1) {
                bufavg = (double)avgav/LOOPS_AVG;   /* average buffer level */
                buferr = bufavg - hwbufsize/2;      /* error against target (hwbufsize/2) */
                buferr_i = buferr_i + buferr;       /* integrated error */

                /* calculate amount of time to be added to default step time */
                /* to overall match the local clock to the outgoing clock */
                extransec = (long)(-(Kp * buferr + Ki * Ta * buferr_i) + 0.5);
                nsec = (int)(1000000000/loopspersec + extransec);

                if (verbose > 1) {
                    /* deviation: >0 local clock it too fast, <0 local clock too slow */
                    double deviation = nsec / (1000000000.0/loopspersec) - 1.0;
                    fprintf(stderr, "playhrt: (%s) buf: %6.1f e: % 6.1f ei: % 6.1f dt: % 4ld ns (%+6.4f%%)\n", 
                            hms(text,mtime), bufavg, buferr, buferr_i, extransec, deviation*100);
                }
            }
            sumavg--;
        }

        /**********************************************************************/
        /* read data directly from pipeline into mmap´ed area (iptr)          */
        /**********************************************************************/

        /* memset(iptr, 0, frames * bytesperframe); commented out to save time */
//AB        readbytes = read(sfd, iptr, frames * bytesperframe);

        /* important: we might need to read several times to get targeted amount of data. */
        readbytes = 0;
        int nloops = 0;
        do {
            readthis = read(sfd, iptr+readbytes, (frames * bytesperframe)-readbytes);
            readbytes += readthis;
            nloops++;
        } while (readbytes < frames * bytesperframe && readthis > 0);

        if (verbose && readbytes != frames * bytesperframe)
            fprintf(stderr, "playhrt: Incomplete read (pipe end): read=%d targeted=%ld\n", 
                    readbytes, frames * bytesperframe);
        if (nloops>1)
            fprintf(stderr, "playhrt: Multiple reads required (nloops=%d).\n", nloops);

        /**********************************************************************/
        /* calculate next wakeup                                              */
        /**********************************************************************/

        /* compute time for next wakeup */
        mtime.tv_nsec += nsec;
        if (mtime.tv_nsec > 999999999) {
            mtime.tv_nsec -= 1000000000;
            mtime.tv_sec++;
        }

        /**********************************************************************/
        /* sleep until defined wakeup, refresh data, commit data              */
        /**********************************************************************/

        /* refreshmem(iptr, readbytes); commented out as called again before commit */
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &mtime, NULL);
        refreshmem(iptr, readbytes); /* refresh memory */
        snd_pcm_mmap_commit(pcm_handle, offset, frames);
        bytecount += readbytes;
        if (readthis == 0) /* done */
            break;
    }

    /**********************************************************************/
    /* playhrt end, cleanup                                               */
    /**********************************************************************/

    /* cleanup network connection and sound device */
    close(sfd);
    snd_pcm_drain(pcm_handle);
    snd_pcm_close(pcm_handle);
    if (verbose) {
        fprintf(stderr, "playhrt: Loops: %ld, bytes: %lld. \n", count, bytecount);
    }
    return 0;
}


Grüße,
Andree
Bild
h0e
Administrator
Beiträge: 3864
Registriert: 11.11.2013, 09:40
Wohnort: München

Beitrag von h0e »

Hallo zusammen,

ich wollte mir eine Lautstärkereglung mit einem Microsoft Surface Dial basteln.
Das Surface wird vom Kernel (Ubuntu 20.04) unterstüzt, so ist es mir auch gelungen ein Python Script so abzuwandeln,
um damit die Lautstärke zu regeln.
Dazu habe ich die Drehgewschwindigkeit in einer Schleife gezählt, da das Surface Dail zu emfindlich ist.
Das klappt jetzt auch recht gut.
Aber,
leider spart vermutlich das Surface Strom und unterbricht die Bluetoothverbindung.
Dadurch stürzt dann das Python Script ab, dass sich vorher das Iput Device gesucht hatte.
Jetzt ist es dann auch noch so, dass das Input Device wechselt,
so dass man nicht fix auf ein InputDevice stellen kann.
Leider ist mein Latein äh Linux damit schon am Ende, ich bin mit Windoof groß geworden.
Hat jemand von den Linux Cracks eine Idee?

Grüsse Jürgen
Bild
Daihedz
Aktiver Hörer
Beiträge: 793
Registriert: 25.06.2010, 15:09

Beitrag von Daihedz »

Buschel hat geschrieben: 25.10.2020, 17:45 ... nach ein paar Wochen kommt hier das nächste Update zu playhrt. ...
... und nach einer weiteren Woche kommt von mir folgendes, anwenderbezogene Feedback:

Playhrt war für mich der bevorzugte Player, seit ihn Frank veröffentlichte. Ich verwende playhrt, weil playhrt ein Player ist, der nichts anderes macht, als das abzuspielen, womit er gefüttert wird. Und das erwarte ich von einem Player, und nichts mehr.

Aber playhrt es war ein störrischer, zickiger Player, welcher einiges an Konfigurationsaufwand abverlangte, und zum Teil auch einige Workarounds mittels erweitertem scripting notwendig machte. Ich musste zum Beispiel, wie schon mal beschrieben, die clocks meiner RME Soundkarten nachtunen, damit playhrt trotz händisch sorgfältig eingestellter --extra-bytes-per-second weder über-, noch unterlief. Und die Geschichte mit dem handsärmeligen --sleep half auch nicht immer weiter, weswegen ich, doppelt gemoppelt, playhrt noch einen writeloop-catloop-Puffer vorschaltete. Und trotz all diesem Unbill blieb playhrt in meinen pipes. Weil es ein präziser Player ist, der nichts anderes macht, als das, was er sollte. Ceterum censeo ...

Wenn ein Setup mit playhrt mal nicht lief, was durchaus des öfteren vorkam, dann musste wieder aplay auf die Bühne. Denn aplay frisst fast alles und frickelt im Stillen, vom User meist unbemerkt, mit grosser Wahrscheinlichkeit etwas daraus, was dann auch der Soundkarte bekommt ... Aplay ist wesentlich permissiver als playhrt, ist dafür jedoch in Bezug auf das dem Audiostream aufgebrachte DSP eine verschwiegene blackbox. Und aplay spuckte nebenbei auch wertvolle infos zum Debugging aus, welche es unter Anweisung mittels --verbose und --dump-hw-params preisgab. Aber als standard-player? Nein danke. Zu viel stilles, unkontollierbares Eigenleben.

Und nun, nach diesem Exkurs zu aplay - was ist nun mit playhrt nach einer Woche? Sagen wir es mal so: Ich habe seither aplay nicht mehr gebraucht.

Playhrt war, wie schon erwähnt, der prinzipiell schlanke und präzise Player, den ich mir wünsche. Und meines Wissens ist es der präziseste Player, den es für Linux gibt. Jetzt ist playhrt noch schlanker geworden. Und zwei der früher notwendigen, empirisch zu ermittelnden Parameter, --sleep und --extra-bytes-per-second, sind durch die Implementation interner Algorithmen hinfällig geworden. Und damit ist playhrt sogar anwenderfreundlich geworden.

Als einzig potenziell kritischer und empirisch zu ermittelnder Parameter ist --loops-per-second (LPS) geblieben. Ich hatte bislang den Eindruck, dass LPS nicht beliebig deklariert werden konnte, sondern in Abhängigkeit der Rate und der Anzahl Ausgangskanäle gesetzt werden musste. Dabei scheint es günstig zu sein, wenn in einem 2-Kanal-System der Quotient Rate/LPS Werte von 2^n ergibt. Das heisst, bei einer Rate von 44.1kHz stelle ich die LPS vorzugsweise auf 1050 ein, bei einer solchen von 48kHz stelle ich die LPS=1500 ein. Aktuell, auf einem 8-Kanal-System bei 96kHz habe ich LPS=6000 eingestellt.

Fazit: Merci viumau, Frank u Andree

Spielerische Grüsse
Simon
Bild
Koala887
Aktiver Hörer
Beiträge: 537
Registriert: 27.12.2010, 17:23
Wohnort: Eltmann, Unterfranken

Beitrag von Koala887 »

Hallo Simon,
Daihedz hat geschrieben: 01.11.2020, 22:16 Als einzig potenziell kritischer und empirisch zu ermittelnder Parameter ist --loops-per-second (LPS) geblieben. Ich hatte bislang den Eindruck, dass LPS nicht beliebig deklariert werden konnte, sondern in Abhängigkeit der Rate und der Anzahl Ausgangskanäle gesetzt werden musste. Dabei scheint es günstig zu sein, wenn in einem 2-Kanal-System der Quotient Rate/LPS Werte von 2^n ergibt. Das heisst, bei einer Rate von 44.1kHz stelle ich die LPS vorzugsweise auf 1050 ein, bei einer solchen von 48kHz stelle ich die LPS=1500 ein. Aktuell, auf einem 8-Kanal-System bei 96kHz habe ich LPS=6000 eingestellt.
das ist mir vor einiger Zeit auch schon mal aufgefallen :wink:
Dann habe ich angefangen Franks Programme zu studieren und habe herausgefunden, dass man die loops-per-second so wählen sollte, dass die Samplerate durch loops-per-second teilbar ist. Bei 44,1kHz wären das z.B.: 980, 1050, 1225, usw.
Die 1050 Loops erwiesen sich als beste Wahl, damit hatte ich dann statt ca. 800 extra-bytes nur noch 8 und der Puffer schwankte nur noch um ein paar Frames. :D
Schöne Grüße
Daniel
Bild
Daihedz
Aktiver Hörer
Beiträge: 793
Registriert: 25.06.2010, 15:09

Beitrag von Daihedz »

Hallo Jürgen
h0e hat geschrieben: 25.10.2020, 18:24 ... Lautstärkereglung mit einem Microsoft Surface Dial basteln...
Möchte ich jetzt vielleicht auch. Hast Du es zwischenzeitlich hingekriegt? Und kennst Du:
https://github.com/daniel5151/surface-dial-linux

Dialysierte Grüsse
Simon
Bild
h0e
Administrator
Beiträge: 3864
Registriert: 11.11.2013, 09:40
Wohnort: München

Beitrag von h0e »

Hallo Simon,

nein, den kannte ich noch nicht.
Ich habe bisher nur ein Python Syript gefunden, dass die Standardsinputs abfängt.
Leider stürzt dieses ab, sobald das Device aufgrund der Energiesparfunktion getrennt wird,
sonst wäre ich schon fertig.
Werde mir Deinen Link mal genauer ansehen,
ich hoffe ich komme damit zurecht,
denn Linux ist eigentlich nicht meine Welt.

Danke Jürgen
Bild
Daihedz
Aktiver Hörer
Beiträge: 793
Registriert: 25.06.2010, 15:09

Beitrag von Daihedz »

Hallo in die fakenews-permissive Runde

Erratum: Aplay ist clean!

Daihedz hat geschrieben: 01.11.2020, 22:16 ... aplay ... frickelt im Stillen, vom User meist unbemerkt, mit grosser Wahrscheinlichkeit etwas daraus, was dann auch der Soundkarte bekommt ... ... in Bezug auf das dem Audiostream aufgebrachte DSP eine verschwiegene blackbox ... viel stilles, unkontollierbares Eigenleben ...
... und damit habe ich da einen fertigen Quatsch in die Welt gesetzt! Was ich erst jetzt gemerkt habe:

Auf der aktuellen Suche nach einem schlanken Frontend, welches SPDIF von der Soundkarte liest und den Datenstrom an stdout ausgibt, habe ich mich nun doch in den Sourcecode von aplay == arecord vertieft. Alles ist in Bezug auf Bit-Perfection paletti! Kein heimliches DSP!

Denn die Anweisungen ...
--disable-resample
--disable-channels
--disable-format
--disable-softvol
... welche meinen Argwohn in Bezug auf aplay-internes, heimliches DSP entflammt hatten, werden ohne weitere Konsequenzen für den Datenstrom ganz einfach an das Hardware-Device, sprich den ALSA-Soundkartentreiber weitergegeben.

Kein DSP innerhalb von Aplay also. Aplay ist clean, aber nicht lean: Was bleibt, ist dass aplay/arecord mit allen darin implementierten Optionen einfach ein bisschen fett daherkommt. Da wäre es doch schön, eine schlanke Minimalversion fürs Recording davon zu extrahieren.

Was jedoch aus dieser Erfahrung mit meiner eigenen Falschmeldung (neudeutsch Fake-News) geblieben ist, ist ein ungutes Gefühl, dass hier solcher diffamierender Unsinn, wie ich ihn publiziert habe, unwidersprochen geblieben ist.

Rehabilitative Grüsse
Simon
Bild
h0e
Administrator
Beiträge: 3864
Registriert: 11.11.2013, 09:40
Wohnort: München

Beitrag von h0e »

Daihedz hat geschrieben: 09.12.2020, 13:59 Hallo Jürgen
h0e hat geschrieben: 25.10.2020, 18:24 ... Lautstärkereglung mit einem Microsoft Surface Dial basteln...
Möchte ich jetzt vielleicht auch. Hast Du es zwischenzeitlich hingekriegt? Und kennst Du:
https://github.com/daniel5151/surface-dial-linux

Dialysierte Grüsse
Simon
Hallo Simon,

ich hatte mir letztens mal Zeit genommen und mit dem Script von Daniel5151 gebastelt.
Man kann dort eigene Einstellungen erstellen, das ist klasse.
Schön wäre gewesen die Inputs direkt auf Sripts oder Bash Befehle umzuleiten,
ob das geht weiß ich nicht, ich kann es nicht.
Ich habe es jedenfalls hinbekommen die Eingaben des Surface auf nicht üblich gebräuchbliche Buttons umzuleiten und
die Eingaben dann mit xbindkeys entsprechend auf meine Scripte für die Lautstärkeregelung umzuleiten.
Das schöne am den o.g. Projekt ist, dass es auch haptisches Feedback am Surface Dial gibt,
das heißt, ist das Gerät verbunden und man dreht, dann vibriert das Surface Dial.
Dadurch kann man sehr feinfühlig einstellen.
Außerdem findet das Programm das Surface Dial auch nach einem Neuverbinden z.B. nach Energiespartimeout des Surface Dial.
Meine aktuelle Konfiguration ist also Danuel5151 Script -> Xbindkeys -> Bash Script -> IR Sender per http request.
Danke für den entscheidenden HInweis.

Grüsse Jürgen
Bild
frankl
Aktiver Hörer
Beiträge: 486
Registriert: 20.01.2013, 01:43
Wohnort: Aachen

Beitrag von frankl »

Hallo Forenten,

ich habe jetzt mal meine Audio-Software für Linux, über die in diesem Thread schon einiges diskutiert wurde, auf den neuesten Stand gebracht und ein neues Release gemacht (Version 0.8 ).

Die Programme sind jetzt in diesem github-Repository zu finden:

https://github.com/frankl-audio/frankl_stereo

Enthalten ist die Datei INSTALL mit deren Hilfe die Installation hoffentlich nicht so schwierig ist.

Auch die Webseite mit weiteren Informationen und Erklärungen habe ich aktualisiert. Von dort können alternativ die Programme der Version 0.8 als auch als Archiv heruntergeladen werden.

Stichpunkte zu den Änderungen in dieser Version sind in der Datei CHANGES aufgeführt.

Insbesondere gehört jetzt das Programm 'resample_soxr' zum Resamplen zur Sammlung, das hier im Forum schon verschiedentlich diskutiert wurde. Ich benutze das bei mir zum Lesen von Musikdateien, resamplen, für die Lautstärkeregelung und den RACE Effekt (die Funktionalitäten sind separat auch als 'cat64' und 'volrace' verfügbar).

Sonst hat sich im Prinzip an den vorher existierenden Programmen nicht viel geändert, es gibt kleinere Korrekturen und ein paar neue Optionen.

Klanglich am interessantesten ist vermutlich neuer Code zum 'refresh' von Daten im Arbeitsspeicher und in den CPU-Registern, den ich vor ca. 2 Jahren geschrieben habe. Hier habe ich insbesondere für zwei Architekturen etwas Assembler-Code geschrieben, um Compiler-"Optimierungen" zu umgehen: Das ist für x86_86, also PCs und andere Rechner mit Intel oder AMD CPU, und für aarch64, also neuere ARM Rechner wie Raspberry Pi 4.

Dieses 'refresh' wird in fast allen Programmen der Sammlung benutzt und hat in meinem Setup einen wichtigen Anteil am Klang beim Musik abspielen. Ich benutze auch eine veränderte Version des Convolver-Programms 'brutefir', das auf die Daten vor der Ausgabe ein 'refresh' anwendet.

Eine Anwendung des Programms 'bufhrt' ist in dem zur Sammlung gehörenden Skript 'improvefile' zu finden, mit dem Kopien von (Musik-)Dateien erstellt werden können. Ich benutze das seit Jahren, um die Musikdateien auf der SSD in meinem optimierten Audio-Rechner (zur Zeit ein Raspi 4 mit Farad-Netzteil und SSD hinter USB Regen) neu zu schreiben. Die kopierten Dateien klingen - für meine Ohren - so viel besser, dass das für mich ein wichtiger Baustein ganz vorne in der Wiedergabekette ist. Durch die erwähnten neuen Versionen der 'refresh' Routinen hat sich hier nochmal etwas getan. Aktuell gibt es hierzu auch Berichte anderer Forenten, etwa in diesen Beiträgen von Harald, Bernd Peter und Jürgen. Das Thema wurde hier im Forum schon ausführlich diskutiert, etwa in den Threads "Bit-identische Musik-Dateien auf derselben Festplatte" und "Rewrite Data".

Ach ja, die Programmsammlung enthält auch noch zwei experimentelle Programme 'clreg86' (für x86_64) bzw. 'clreg' (für aarch64), die -soweit ich das verstanden habe- etwas ähnliches wie 'MinorityClean' auf Windows-Rechnern machen. Ich starte das einmal auf jedem CPU-Core meines Audio-Rechners. Die Auswirkung ist nicht riesig, aber ich bilde mir ein, dass damit noch etwas mehr Ruhe in die Wiedergabe kommt.

Viele Grüße,
Frank
Bild
Trinnov
Aktiver Hörer
Beiträge: 971
Registriert: 13.11.2009, 19:02

Beitrag von Trinnov »

cornoalto hat geschrieben: 26.11.2023, 14:06 Hallo Bernd,
welchen Zeitaufwand erfordert die Beabeitung eines Albums etwa?

Viele Grüße
Martin
Hallo Martin,

bei mir sind das ca. 2 Minuten pro 44.1/16 Track
Ausgeführt mit dem kompakten DietPi Linux (ohne grafische Benutzeroberfläche) auf meinem optimierten Audio-PC.
Allerdings bei 2,0 GHz CPU Takt. Ich werde den Rechner noch etwas runtertakten, so dass es dann sicherlich noch länger dauern wird.
In den CPU Governor Einstellungen des DietPi ist das Einstellen möglich. Aber es greift (noch) nicht so, wie ich es möchte.

Das kleine DietPi, das ich vom Büro-PC aus per SSH Server Remote bediene, verbraucht verglichen mit einem großen grafischen Ubuntu sehr wenig Systemressourcen und dürfte die Files vermutlich noch etwas perfekter "improven".

Die Audio-Tracks werden auf den internen Datenträger improved. Das Linux ist auf einem USB Datenträger und wird nur bei Bedarf angesteckt.
Normal bootet ohne Bootstick meine Server 2016 Ramdisk. Dessen Bootblock befindet sich auf dem internen Datenträger und somit ist booten ohne den ursprünglich benötigten Ramdisk Bootstick möglich. Bei angestecktem Linux Datenträger bootet das DietPi und improved ausgewählte Tracks auf dem internen netzteilmäßig optimierten Datenträger auf dem sich auch das Windows OS (VHD) befindet. Ein praktikabler Workflow war mir sehr wichtig. Daher habe ich auch ein paar Tage beißen müssen, bis es so lief wie ich das wollte.
Eine DietPi Installation auf dem internen Datenträger geht nicht, da Grub4DOS einen Error meldet und das Ramdisk booten abbricht, sobald er die EXT4 Parttion des Linux sieht. Da ich mit einer USB-Stick Installation des DietPi dann auch Fehlermeldungen beim Linux booten hatte, habe ich kurzerhand eine kleine SLC Compact Flash Card genommen und diese über einen Cardreader per USB angesteckt. Damit funktioniert es perfekt. Eine 4 GB SLC CF Card reicht.

Der "Improve"-Effekt ist deutlich. Die Bühnentiefe / Bühnenstaffelung nimmt deutlich zu, bei Aufnahmen in denen dies aufnahmetechnisch vorhanden ist.
Obwohl ich vorher auch eine wie ich meinte gute Bühnentiefen-Darstellung hatte, ist es nach dem "improven" im Vergleich, wie wenn vorher alle Vögel nebeneinander auf einer Stange saßen. Jetzt gibt es etliche Einzelsitzplätze hintereinander / nebeneinander. :)

Viele Grüße
Horst
Bild
frankl
Aktiver Hörer
Beiträge: 486
Registriert: 20.01.2013, 01:43
Wohnort: Aachen

Beitrag von frankl »

Hallo Horst,

schön zu lesen, dass Du das Diet-Pi Setup zum Laufen gebracht hast! Und es freut mich natürlich auch, dass Du den Effekt von 'improvefile' jetzt auch bei Dir nachvollziehen kannst. Deine Beschreibung mit den Vögeln ist nett und trifft es für mich ganz gut.

Die CPU-Rechenzeit des vom Skript aufgerufenen 'bufhrt' Programms ist übrigens sehr gering. Die Dauer des Kopierens hängt nicht von der CPU-Geschwindigkeit ab. In der Standardeinstellung des Skriptes aus meinem Repository wird ein großer Puffer beim Lesen der Daten gefüllt und dann wird in sehr genauem Takt 2000 Mal pro Sekunde ein Block von 3072 Bytes in die Ausgabedatei geschrieben. Das macht ca. 6MB pro Sekunde oder 368MB pro Minute.

Wie ich schon irgendwo erwähnt hatte, könnte man jetzt mit den Parametern herumspielen. Bei mir funktioniert die Standardeinstellung aber sehr gut und besser als andere Einstellungen, die ich probiert habe (nicht sehr viele).

Ich benutze bei mir ein Skript, das neu aufgespielte Dateien auf der SSD zuerst mit improvefile in eine RAM-Disk kopiert, dann die Originaldatei umbenennt, dann die Kopie in der RAM-Disk mit improvefile auf den Original-Dateinamen kopiert und am Ende die umbenannte Originaldatei löscht. (Eine Weile lang habe ich die Originaldatei nicht gelöscht zum Vergleichshören, aber das habe ich oft genug gemacht.)

Bei Feintuning des Diet-Pi könnten vielleicht auch einige Details etwas bringen, die ich auch auf meiner Webseite erwähne. Zum Beispiel, den bufhrt Prozess mit 'isolcpus=...' Boot-Parameter und 'taskset -c ...' auf einen exklusiven CPU-Core zu legen. Oder die CPU-Frequenz mit dem CPU governor "userspace" auf eine bestimmte Frequenz fest einstellen.

Viele Grüße,
Frank
Bild
Fortepianus
Aktiver Hersteller
Beiträge: 3672
Registriert: 17.12.2008, 12:41
Wohnort: Stuttgart

Beitrag von Fortepianus »

Hallo zusammen,

auch ich habe erfolgreich mit Improvefile experimentiert. Dafür zunächst mein ganz herzlicher Dank an Frank (frankl) für seine Programme und Harald (nihil.sine.causa) für die umfangreiche Unterstützung bei der Installation. Ich habe mich da bei Harald drangehängt, deshalb läuft das jetzt bei mir ganz ähnlich wie bei ihm:

- Ein eigens zu diesem Zweck angeschaffter und aufgesetzter NUC i5 mit 16GB RAM und einer internen M.2 SSD
- Auf der internen SSD läuft DietPi
- Der NUC ist runtergetaktet
- Die Versorgung erfolgt durch ein externes ziemlich kräftiges Linearnetzteil
- Dieses G-LNT hat zusätzlich einen 5V-Ausgang, der intern mit meiner Supercap-Technik mit Stromreglern ausgestattet ist
- An einem USB-Port des NUC hängt die zu behandelnde SSD, versorgt von diesen 5V
- Der NUC hängt über ein kurzes LAN-Kabel an einem LWL-Umsetzer, der ebenfalls von einem G-LNT versorgt wird
- Gesteuert wird der NUC i5 aus der Ferne von meinem Werkstattrechner mit dem Terminalprogramm MobaXterm

Ein Bild sagt mehr als 1000 Worte, aber derzeit kann man bei abload.de keine Bilder hochladen - weiß jemand, was dort los ist?
Ich habe bei abload.de eine E-Mail-Anfrage gestartet, aber bislang keine Antwort erhalten. Gruß, Rudolf

Meine Versuche (ermöglicht durch Harald) ergaben, dass man zwar schon einen deutlichen Klangfortschritt erzielt, wenn man Files auf der 8TB-Platte improved, aber der Genuss wird dann nochmal gesteigert, wenn man das auf einer SLC-SSD macht. Danke, Bernd Peter, für den Tipp.

Hallo Horst,
Trinnov hat geschrieben: 05.12.2023, 11:08 Der "Improve"-Effekt ist deutlich. Die Bühnentiefe / Bühnenstaffelung nimmt deutlich zu, bei Aufnahmen in denen dies aufnahmetechnisch vorhanden ist.
Obwohl ich vorher auch eine wie ich meinte gute Bühnentiefen-Darstellung hatte, ist es nach dem "improven" im Vergleich, wie wenn vorher alle Vögel nebeneinander auf einer Stange saßen. Jetzt gibt es etliche Einzelsitzplätze hintereinander / nebeneinander. :)
das ist bei mir ganz ähnlich. Zusätzlich bemerke ich noch eine verbesserte Transientenwiedergabe und eine Zunahme der Körperhaftigkeit. Ein Flügel steht da vorne in seiner natürlichen Ausdehnung, wenn die Aufnahme was taugt.

Der Wermutstropfen: Abgesehen vom Material- und Zeitaufwand muss man sich genau überlegen, welche Alben (die für die Insel) man in bestmöglicher Qualität möchte, denn 60GB sind ruckzuck voll. Aber auch die behandelten Alben von der großen Platte sind schon nicht schlecht. Wollte man aber die ganze Sammlung damit behandeln, wäre der Rechner damit ewig beschäftigt. Und bei weniger guten Aufnahmen lohnt sich der Aufwand nicht.

Aber schon interessant, dass bitidentische Dateien sogar von derselben SSD so unterschiedlich klingen können.

Viele Grüße
Gert
Bild
Fortepianus
Aktiver Hersteller
Beiträge: 3672
Registriert: 17.12.2008, 12:41
Wohnort: Stuttgart

Beitrag von Fortepianus »

Hallo Rudolf,

danke für die Anfrage. Ich habe jetzt Gabriels Hinweis
StreamFidelity hat geschrieben: 07.12.2023, 12:06 Vielleicht ist es Zeit für einen Wechsel?

https://picr.de/
Gibt es seit 20 Jahren.
umgesetzt und liefere hier das Bildchen nach, auf dem man die kleine Gerätesammlung für Roon und Improvefile sieht:

Bild

Das ist das Kabuff direkt hinter der Anlage, erreichbar über eine ausklappbare Treppe. Im Bild sieht man rechts hinten meine 8TB-SSD mit eigenem SCap-Netzteil und in der Mitte unten die SLC, die ebenfalls am Roon-NUC angeschlossen ist und vom schwarzen Linear-Netzteil links versorgt wird. Dazu habe ich jeweils ein USB-Kabel gemacht, bei dem die 5V aufgetrennt sind und extern eingespeist werden können.

Will man den Improve-Vorgang starten, stöpselt man eine der beiden Platten um auf den i5, schaltet den ein und kann dann über das Terminalprogramm damit arbeiten. MS-DOS lässt grüßen, aber mit etwas Übung klappt's. Die Installation auf dem i5 ist allerdings nichts für den DAU im IT-Bereich.

Viele Grüße
Gert
Bild
Trinnov
Aktiver Hörer
Beiträge: 971
Registriert: 13.11.2009, 19:02

Beitrag von Trinnov »

Bernd Peter hat geschrieben: 06.12.2023, 17:37 Hallo Frank,

vielleicht habe ich mich etwas falsch ausgedrückt.

Mit Fehlerkorrektur verbinde ich auch das Verfahren bei schlecht lesbaren Bits - Stichwort Signal Integrity - diese einer Überprüfung zu unterziehen.

Es grüßt

Bernd Peter
Hallo,
ich habe den Beitrag obigen Beitrag aus Bernds Vorstellungsthread aufgegriffen und möchte hier endlich mal etwas zu dem Mysterium sagen, weil das Thema eigentlich eher hierher gehört.

Der Artikel ist etwas länger geworden, aber vielleicht doch komplett lesen. Könnte gut zum Verständnis beitragen.

Mich stört etwas der Begriff "Fehler". Es gibt keine Fehler und es müssen auch keine korrigiert werden.

Die Audiodaten bleiben immer bitidentisch. Man kann sich ja die Protokollierung der erfolgten ECC Korrekturen des Datenträgers anschauen.
Das habe ich vor längerer Zeit mal gemacht. Meine SLC Industriedatenträger haben das Feature ECC Korrektur. Da gab es aber nichts zu korrigieren. ECC ist also ziemlich arbeitslos.

Statt von Fehlern zu reden, würde ich das als Zeitvariation bezeichnen. Uns ist es mit üblicher Messtechnik nicht möglich diese Zeitvariationen zu messen. Dafür benötigt man eine ultrapräzise und bis in den Femtosekundenbereich auflösende Messgerätezeitbasis, die zudem nur sehr gering jittern darf. Zudem ist auch noch der richtig Mess-Algorithmus erforderlich. Wer die falschen Parameter misst, wird nichts finden. Da habe ich schon sehr genaue Vorstellungen. Mir fehlt nur die entsprechend teure Messtechnik.

Man kann sich die Zeitvariationen im Audiodatenstrom im Vergleich vorstellen als wenn man eine lange Sinuswelle am Anfang und am Ende mit jeweils einer Hand festhalten könnte um den Schwingungsverlauf dazwischen durch Zusammenschieben und Strecken nach Belieben zu dehnen und zu stauchen würde. Exakt das passiert mit einem seriellen Signal. Verwendet wurde so etwas übrigens absichtlich bei unserem FM Rundfunk. Da wird tatsächlich eine Frequenzmodulation der Trägerwelle ausgeführt.

Zurück zur Computertechnik.
Die Datenintegrität bleibt trotz Zeitvariation in jedem Fall logischerweise immer erhalten. Das kann und darf auch nicht anders sein, sonst würde jeder Admin die Hände über dem Kopf zusammenschlagen aufgrund von zufälligen Datenfehlern. Als weitere datenkritische Beispiele möchte ich da die Rechner in Banken und auch beim Militär usw. nennen. Es kommen immer alle Daten korrekt an. Aber in der Zeitachse gibt es kleine Variationen.

Die Zeitvariationen liegen beim Auslesen des Datenträgers während des Track Abspielens möglicherweise nur im Femtosekundenbereich. Aber der Effekt ist, dass die richtigen Sample Daten zur falschen Zeit beim DAC ankommen. Das sind also keine falschen Daten sondern nur ein winziger Sample Audiopegel Shift in der Zeitachse und somit nach dem DAC eine minimal andere Audiosignalform. Warum soll das auch nicht hörbar sein. Wie hören ja auch klangliche Unterschiede von Clocks, die statt hervorragenden 50 Femtosekunden Jitter eben 500 oder 1000 Femtosekunden oder mehr jittern.
1000 Femtosekunden (fs) = 1 Pikosekunde (ps) = 0,001 Nanosekunden (ns) = 0,000001 Mikrosekunden (µs) = 0,000000001 Millisekunden (ms) = 0,000000000001 Sekunden.
Wer einen Kommafehler findet darf ihn behalten …

Die zeitlichen Variationen sind bei Audio deswegen so kritisch und somit hörbar, weil wir Stereo hören. Bei einer Wiedergabe eines einzelnen Kanals über einen einzigen Lausprecher ist das alles viel unkritischer, also kaum oder gar nicht wahrnehmbar.
Aber wir wollen die perfekte Stereo-Illusion. Die digitale Konserve soll den zum Zeitpunkt der Aufnahme vorhanden Aufnahmeraum mit allen seinen durch feinen Reflektion entstandenen Rauminformationen perfekt ins Wohnzimmer projizieren, als ohne das es von der Bühnentiefe und Staffelung her flach wirkt und vielleicht auch noch verwischt und nervig spielt.

Noch ein Bespiel zum Thema Zeitvariation:
Du erwartest vom Paketzusteller DHL 10 Pakete. Diese Pakete soll DHL bitte einzeln an 10 aufeinanderfolgenden Tagen exakt jeweils um 12 Uhr mittags auf die Sekunde genau ausliefern. Jetzt hat aber der Postbote auch noch etwas anderes zu tun, weil er auch noch andere Pakete ausliefern muss, oder telefonieren muss oder eine Pause macht usw. Du wirst dein Paket also nicht täglich exakt zur gleichen Zeit bekommen.
Aber da ist die Zeitvariation egal. Du wirst nach 10 Tagen deine 10 Pakete vollständig zuhause liegen haben. Bei Computerdaten würde man sagen, dass die Datenintegrität erfüllt ist.

So etwas passiert auch in einem Betriebssystem. Es schaut so aus, als ob Windows anscheinend alles gleichzeitig erledigt, weil es so rasend schnell geht. Aber das ist wie wir wissen nicht der Fall. Das ist immer eine sequentielle Abarbeitung.
Dein PC spielt also Audio aus. Gleichzeitig macht das OS im Hintergrund noch hunderte andere Sachen. Man schaue nur mal in den Ressourcenmanager von Windows. Diese vielen anderen Aufgaben stören aber die Zeitrichtigkeit der Audio-Daten beim Abspielen eines Audio-Tracks. Bringst du also das Betriebssystem dazu im Hintergrund so gut wie nichts anderes zu machen als Audio auszuspielen, wird es weniger Zeitvariationen im Audiodatenstrom geben. Deswegen sucht man auch eine Verbesserung des Audiodatenstroms, also eine Verringerung der Zeitvariation, indem man die Aufgabenbereiche auf jeweils einzelne CPU Cores aufteilt. Denn physikalische Kerne können tatsächlich zeitgleich Aufgaben abarbeiten. Suboptimal wird es aber schon wieder, weil sie sich teileweise physikalischen Speicher teilen müssen (z.B. L3 Cache und Arbeitsspeicher). Also auch nicht des Rätsels Lösung.
Das ist so wie wenn du für die zeitrichtige Zustellung von mehreren Paketen an dich mehrere DHL Mitarbeiter gleichzeitig beauftragst, diese sich aber ein Fahrzeug teilen müssen. :)

Sehr nützlich ist natürlich die Kombination der Ramdisk für das Betriebssystem und zusätzlich noch das „Abspecken“ des Systems, also reduzieren von Diensten und Prozessen.
Sobald sich das komplette OS im RAM befindet, gibt es auch keinen den Audiodatenstrom immens störenden, weil langsamen Datenaustausch mehr zwischen RAM und der Betriebssystem-SSD. Leicht beweisbar über den Windows Ressourcenmanager in z.B. Server 2016 Ramdisk Installationen.
Nicht vergessen zu erwähnen sollte man der Vollständigkeit halber natürlich die optimierten Netzteile der Audio-PCs. Insbesondere auch an der SSD ist der Einsatz eines top Netzteils immens hörbar. Datenflanken sind niemals unendlich steil. Deren Steilheit würde man als endlich bezeichnen. Es gibt also eine sogenannte Anstiegszeit. Jede nicht unendliche steile Flanke ist für über das Netzteil eingebrachten Noise empfänglich. Die Flanke wird mit dem Noise des Netzteils überlagert und somit variiert ein klein wenig die zeitlichen Position der Flanke. Die Auswirkungen sind wie gehabt.
Bei einer unendlich steilen Flanke (die nicht möglich ist) würde nur das Impulsdach mit Noise beaufschlagt, was dann für die Zeitrichtigkeit nicht von belang ist. Es zählt nur die Flanke.

Das i-Tüpfelchen darauf ist nun Franks geniales „File Improve“ Verfahren, weil somit auch die Zeitvariationen beim Auslesen des Audiotracks aus dem Datenträger während des direkten Musik Ausspielens oder dem vorab in den Arbeitsspeicher noch kleiner gemacht werden können.
Es geht also um den Flaschenhals von ansonsten bereits sehr gut optimierten Audio-PCs.

Da ich kein Speicherchip- bzw. Datenträger-Entwickler bin, kann ich nur vage Vermutungen anstellen, was da im Datenträger bei der „improved“ Datei anders ist, nachdem sie Franks Software sehr langsam mittels eines sehr „abgespeckten“ Linux Systems in die in Cluster aufteilten Speicherzellen geschrieben hat. Das sehr langsame Schreiben und Refreshen scheint mit entscheidend zu sein. Würde man den Schreibvorgang also das Schreiben der Kopie in den C_Improved Ordner nur sehr viel schneller erledigen und alle anderen Schreibparameter gleich halten, würde vermutlich bereits ein Großteil der klanglichen Vorteile verlorengehen.

Möglicherweise liegt der Unterschied gar nicht in der physikalischen Position der Audio File Daten auf dem Datenträger, sondern im absoluten Spannungswert der 0 oder 1 Information in der jeweiligen Speicherzelle. Das Aufladen von Kondensatoren erfolgt immer nach einer e-Funktion.
Auch eine Speicherzelle besteht unter anderem aus einem oder mehreren winzig kleinen Kondensatoren. Zum Beispiel die MOS-Kondensatoren.
Somit macht es einen Unterschied wie lange und vor allen Dingen wie gleichmäßig ich in der Zeit die einzelnen Speicherzellen beschreibe. Natürlich werden alle Pegel im jeweils spannungsmäßig sicheren Spannungsbereich geschrieben, aber beim Auslesen entstehen durch die leicht variierenden Pegel der Zellen wieder minimale Zeitvariationen in der nachfolgenden seriellen Pegelauswertung.
Das heißt dass aufgrund von unterschiedlicher Spannung der Speicherzelle die Flipflop Triggerflanke der Speicherzelle mal eher und mal später ausgelöst wird. Das ist aber nur meine These. Wer da mehr weiß, darf das gerne hier kundtun.

Man kann übrigens sehr sicher sagen, dass die beiden File Versionen (ich meine Franks Verfahren) absolut bitidentisch sind. Ich habe das kritisch mit dem Tool DeltaWave ohne vorheriges Matchen der beiden zu vergleichenden Files geprüft.

Trotzdem kenne ich mittlerweile 5 Personen mit sehr guten Hörfähigkeiten, die mir glaubhaft versichern, dass die beiden zueinander bitidentischen Audiofile Varianten unterschiedlich klingen. Also muss es da etwas geben, was noch nicht ausreichend erforscht ist.

Das DeltaWave Tool kann man kostenlos hier downloaden.
https://deltaw.org/

Viele Grüße
Horst
Bild
Antworten