Software-Experimente mit Linux

Musikwiedergabe über PC und Mac
Martin
Aktiver Hörer
Beiträge: 103
Registriert: 15.03.2012, 14:23

Beitrag von Martin »

Hallo Andree,
nach einem Hinweis von Simon habe ich die Option --mmap noch einmal ausprobiert und sie funktioniert nun auch bei mir. Zunächst habe ich die Original-Version von playhrt 24 Stunden mit den Optionen --verbose --verbose durchlaufen lassen und die Bufferwerte beobachtet. Der Startwert lag bei 7700 und ging dann langsam runter auf etwa 3000 um dann seltsamerweise wieder anzusteigen auf 9000.

Heute habe ich nun Deine neue playhrt Version installiert und die Ausgabe (kurzer Ausschnitt) sieht nun so aus:

Code: Alles auswählen

playhrt: (91961 sec) buf: 8148.0 e:  0.0 ei: -7.7 dt:   1 ns (0.0000%)
playhrt: (91965 sec) buf: 8148.0 e:  0.0 ei: -7.7 dt:   1 ns (0.0000%)
playhrt: (91969 sec) buf: 8148.0 e:  0.0 ei: -7.7 dt:   1 ns (0.0000%)
playhrt: (91973 sec) buf: 8145.5 e: -2.5 ei: -10.2 dt:   4 ns (0.0003%)
playhrt: (91976 sec) buf: 8148.0 e:  0.0 ei: -10.2 dt:   2 ns (0.0001%)
playhrt: (91980 sec) buf: 8148.0 e:  0.0 ei: -10.2 dt:   2 ns (0.0001%)
playhrt: (91984 sec) buf: 8148.0 e:  0.0 ei: -10.2 dt:   2 ns (0.0001%)
playhrt: (91988 sec) buf: 8148.0 e:  0.0 ei: -10.2 dt:   2 ns (0.0001%)
playhrt: (91992 sec) buf: 8148.2 e:  0.2 ei: -9.9 dt:   2 ns (0.0001%)
playhrt: (91996 sec) buf: 8148.2 e:  0.2 ei: -9.7 dt:   2 ns (0.0001%)
playhrt: (91999 sec) buf: 8148.8 e:  0.8 ei: -8.9 dt:   1 ns (0.0000%)
playhrt: (92003 sec) buf: 8148.0 e:  0.0 ei: -8.9 dt:   2 ns (0.0001%)
playhrt: (92007 sec) buf: 8149.2 e:  1.2 ei: -7.7 dt:   0 ns (-0.0001%)
playhrt: (92011 sec) buf: 8148.2 e:  0.2 ei: -7.4 dt:   1 ns (0.0000%)
playhrt: (92015 sec) buf: 8148.0 e:  0.0 ei: -7.4 dt:   1 ns (0.0000%)
playhrt: (92018 sec) buf: 8148.2 e:  0.2 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92022 sec) buf: 8148.0 e:  0.0 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92026 sec) buf: 8148.0 e:  0.0 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92030 sec) buf: 8148.0 e:  0.0 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92034 sec) buf: 8148.0 e:  0.0 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92037 sec) buf: 8148.0 e:  0.0 ei: -7.2 dt:   1 ns (0.0000%)
playhrt: (92041 sec) buf: 8146.6 e: -1.4 ei: -8.7 dt:   3 ns (0.0002%)
playhrt: (92045 sec) buf: 8148.0 e:  0.0 ei: -8.7 dt:   2 ns (0.0001%)
playhrt: (92049 sec) buf: 8148.0 e:  0.0 ei: -8.7 dt:   2 ns (0.0001%)
playhrt: (92053 sec) buf: 8148.1 e:  0.1 ei: -8.6 dt:   2 ns (0.0001%)
playhrt: (92056 sec) buf: 8148.2 e:  0.2 ei: -8.3 dt:   1 ns (0.0000%)
playhrt: (92060 sec) buf: 8148.0 e:  0.0 ei: -8.3 dt:   2 ns (0.0001%)
playhrt: (92064 sec) buf: 8148.0 e:  0.0 ei: -8.3 dt:   2 ns (0.0001%)
playhrt: (92068 sec) buf: 8148.2 e:  0.2 ei: -8.1 dt:   1 ns (0.0000%)
playhrt: (92072 sec) buf: 8148.2 e:  0.2 ei: -7.9 dt:   1 ns (0.0000%)
playhrt: (92076 sec) buf: 8148.0 e:  0.0 ei: -7.9 dt:   2 ns (0.0001%)
playhrt: (92079 sec) buf: 8148.2 e:  0.2 ei: -7.6 dt:   1 ns (0.0000%)
playhrt: (92083 sec) buf: 8148.2 e:  0.2 ei: -7.4 dt:   1 ns (0.0000%)
playhrt: (92087 sec) buf: 8148.0 e:  0.0 ei: -7.4 dt:   1 ns (0.0000%)


Ich würde sagen, der Regler läuft wirklich gut :cheers:
Ob es nun anders klingt als vorher, kann ich noch nicht sagen.
Jedenfalls vielen Dank und viele Grüße

Martin
Bild
Daihedz
Aktiver Hörer
Beiträge: 747
Registriert: 25.06.2010, 15:09

Beitrag von Daihedz »

Hallo in die Runde der HW-Pufferregler

In (m)einem Setup mit einer RME HDSP-Soundkarte, 8 Kanäle Out @ 96kHz kann ich dank der Buschel-Optimierung von Playhr sämtliche HW-Puffergrössen fahren, welche der ALSA-Treiber erlaubt. D.h. es laufen in meinem Fall HW-Puffergrösse von anorektischen 256Bytes (!) bis hin zu opulenten 16384Bytes genau gleich gut und für Stunden stabil. Das ist erst mal eine Premiere mit/bei plahrt und hoch erfreulich - lasst die Korken knallen und deshalb auch an dieser Stelle ein anerkennendes Bravo Bravissimo für Andree und ein herzliches Dankeschön dazu!!! Dein Aufwand an Zeit, Hirnschmalz und an empirischer Forschung war doch erheblich genug, um zu diesem Resultat zu gelangen.

Aber die nun funkelnagelneue, super-funktionale Playhrt-BE (BE wie Buschel-Edition) schafft ein neues, bislang unbekanntes Problem: Die Qual der Wahl. Welche HW-Puffergrösse ist nun die günstigste - Früher mit dem Original-playhrt war es relativ einfach zu beantworten, sinngemäss à la "probier doch mal und nimm dann diejenige, bei welcher es möglichst lange funktioniert". Und jetzt - was nun - nun funktionieren neuerdings alle HW-Puffergrösse und dies bis in alle Ewigkeit ...

Meine, nun doch ernst gemeinte Frage deshalb: Gibt es, in Abhängigkeit von technischen Kriterien, "günstigste" HW-Puffergrössen? Und falls ja, gibt es Verfahren, diese günstigsten Werte herauszufinden? Auf meinem System entsagten mir bereits diverse Tests diese Antwort: Cyclictest z.B. ergibt keine schlüssigen Unterschiede, indem sämtliche Latenzen in etwa in demselben Bereich bleiben (max. 57mms für die CPU, auf welcher einzig playhrt läuft). Auch top zeigt mit allen mögichen HW-Puffergrössen immer dieselben 6.5%-7% CPU-Auslastung.

Bestpuffersuchende Grüsse
Simon
Bild
frankl
Aktiver Hörer
Beiträge: 421
Registriert: 20.01.2013, 01:43
Wohnort: Aachen

Beitrag von frankl »

Hallo Andree, Simon und andere Interessierte,

ich finde es sehr schön, dass hier auch mal andere sich den Code genauer ansehen und den eigenen Wünschen anpassen!

Im Moment finde ich keine Zeit dazu, aber beim nächsten Update meiner Programme werde ich mir die Version von Andree nochmal genauer anschauen und eventuell auch eine Version zum ständigen Nachführen in 'playhrt' einbauen.

Eine solche Regelung widerspricht allerdings meiner ursprünglichen Grundidee für dieses Programm, die gerade auf der Beobachtung beruhte, dass das Programm stundenlang ohne Nachführung laufen kann, nachdem man zu Anfang einmal brauchbare Parameter gefunden hat.

In meinem Setup wäre Andrees Variante nicht so interessant, weil ich das Upsamplen und Falten mittels 'bufhrt' und 2 Rechnern vom eigentlichen Abspielen entkopple. Wenn da eine Regelung in 'playhrt' nötig würde, dann müsste ich das über mehrere Programme ausdehnen, damit zwischendrin keine Betriebssytembuffer voll- oder leerlaufen, was ich ja gerade vermeiden will. Bei mir kann ich stundenlang 192k/32 Daten abspielen und der Füllstand des Hardwarepuffers ändert sich nur um wenige Kilobytes.

Eine Alternative beim Nachführen wäre es noch, die 'extra-bytes-per-second' abzuschalten und statt dessen dynamisch die Anzahl der zu schreibenden Bytes nach dem aktuellen Pufferstand zu bestimmen. So machen es vermutlich die meisten Abspielprogramme. Ich weiß nicht, was besser ist. Die Hauptfrage ist, ob man bei einem Hörvergleich Unterschiede wahrnehmen kann.

Zum Jitter des Timings in 'playhrt': Ich habe eine Version von 'highrestest' mit so einer ähnlichen Schleife, wie sie Andree oben vorgestellt hat.
Damit kann man sehr schön sehen, was eine isolierte CPU ausmacht, auf der man nur explizit mittels 'taskset' Prozesse starten kann. Ich messe 10 Sekunden lang mit je 1000 sleeps die Genauigkeit der Zeit nach dem Aufwachen:

Code: Alles auswählen

Measuring actual precision of monotonic clock for 10 seconds ...
    Min diff: -5298 ns, max diff: 5665 ns,
    avg. diff: 0 ns
   diff in ns      count
 < -10*500        10
    -9*500        98
    -8*500        642
    -7*500        156
    -6*500        0
    -5*500        3
    -4*500        6
    -3*500        47
    -2*500        43
    -1*500        70
    0*500        7986
    1*500        30
    2*500        42
    3*500        47
    4*500        4
    5*500        5
    6*500        4
    7*500        88
    8*500        650
    9*500        154
 >  10*500        14
Die maximale Abweichung ist etwa 5 Mikrosekunden, meistens ist man im Bereich unter 250 Nanosekunden, und es gibt noch Peaks bei ca +/-4000 Nanosekunden, das ist wohl eine Überlagerung mit der Hauptschleife des Schedulers für diesen Core. Auf meinem Notebook sind die Abweichungen um einen Faktor 10 größer. Es wäre sehr interessant zu wissen, wie man das noch besser hinbekommen kann.

Zur Frage nach den 'refreshmem' Aufrufen in meinen Programmen: Wenn es nur um die Bits geht, die hinten rauskommen, dann kann man die einfach weglassen. Das Thema wurde früher in den Threads Rewrite Data und Bit-identische Musik-Dateien auf derselben Festplatte
diskutiert. Wenn man wissen will, ob man das im eigenen Setup/ einer eigenen Version der Programme nutzen will, muss man kritische Hörtests machen.

Und zu Simons Frage, was die "richtigen" oder "besten" Puffergrößen sind: das lässt sich so vermutlich nicht beantworten. Ich versuche die Puffer immer möglichst klein zu machen unter der Nebenbedingung, dass alles stabil läuft. In Anwendungen, wo es auf geringe Latenz ankommt, sind natürlich auch kleine Puffergrößen erstrebenswert. Wenn es im Prinzip mit mehren Puffergrößen stabil läuft, bleibt nur der Hörtest für die ultimative Entscheidung. Wenn es Dir auf das Sparen von CPU-Zeit ankommt, könnte das Verhältnis von Puffergrößen und gewissen Cachegrößen eine Rolle spielen; bei 'playhrt' spart aber vor allem eine kleinere '--loops-per-second' CPU Zeit.

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

Beitrag von Buschel »

Hallo zusammen,

bzgl. der Idealen Puffergröße sehe ich das so wie Frank: die kleinste stabil laufende. Damit rückt auch der letzte Datenrefresh näher an den Zeitpunkt der Nutzung dieser Daten.

@Frank: Meine Frage zum Datenrefresh bezog sich nicht auf den Refresh an sich, sondern auf dessen Implementierung. playhrt liest jeden einzelnen Wert aus, invertiert die Bits hin und zurück und schreibt sie an dieselbe Stelle zurück. Das ist recht aufwändig. Ich frage mich, warum du diese Form gewählt hast. Ein reines neuschreiben der Daten über einen Kopieren in einen Puffer, gefolgt von Rückkopieren auf die ursprüngliche Stelle über memcpy() wäre vermutlich um einiges schneller und weniger Last für die CPU.

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

Beitrag von frankl »

Hallo Andree,

ich habe die C-Version der Routine auf x86 kaum getestet. Ich habe irgendetwas gemacht, das alle Bits mal umdreht. Gut möglich, dass Dein Vorschlag auch ok und sogar besser ist. Da kann man nur rumprobieren und hören, denn die Theorie dahinter ist sehr vage.

Mein Haupteinsatz ist ja auf ARM-CPUs und für den Fall enthalten meine Programme Assembler Code für das Refresh. Da hatte ich damals mit verschiedenen Registern und Bitoperationen rumgespielt und längere Hörtests gemacht.

Beim Kompilieren muss man übrigens mit den Optimierungen des Compilers aufpassen, damit dieser den Code nicht als unnötig erkennt und komplett rausschmeißt.

Viele Grüße,
Frank
Bild
Buschel
Aktiver Hörer
Beiträge: 838
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
Antworten