Author Topic: [SOLVED] 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?  (Read 5034 times)

0 Members and 1 Guest are viewing this topic.

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
How to pipe 24bits samples from the sound card, as float 32 into another program?
No resampling please, only data format conversion.

There are 3 different formats to deal with:
- the source (ALSA driver of an audio card) produces 24bits at each sample
- the 24 bits are packed as S32_LE by the recording program, arecord, and can pipe to stdout
- the playing program, baudline (an audio spectrum analyzer), doesn't know s32le, but can read float 32 from stdin

The goal is to get all the 24bits into 'baudline', without lowering the resolution at 16bits and without resampling.

Tried so far:
1. - piping through sox, very cumbersome syntax and slows down the frame rate in 'baudline'
2. - convert the format with python:
        - read S32_LE from stdin
        - convert S32_LE to f32le
        - write f32le to stdout

The Python program is an example from stackoverflow, which works, except 'baudline' displays rubbish, something is not OK with the format conversion (inside the process_data(buffer) there are a few failed attempts to convert, all commented out now):

Code: [Select]
#!/usr/bin/env python3

########
# g.py #
########

# run python3 with stin stdout stderr unbuffered (-u)
# arecord -D hw:1,4 -B 10000 -r 96000 -f S32_LE -c 2 | python3 -u ./le32_to_le32f.py | aplay
#
# arecord -D hw:1,2 -B 10000 -r 96000 -f S32_LE -c 2 | \
#   python3 -u ./le32s_to_le32f.py | \
#   ./baudline -stdin -record -channels 2 -format le32f

import signal
import sys

from struct import pack, unpack

def process_data(buffer):
    # convert s32le into f32le
    ## buffer = pack('<f', float((int.from_bytes(buffer, "little")/256)))
    ##pack('<f', float((int.from_bytes(buffer, "little")/256)))
    ##    pack('<f', float(int.from_bytes(buffer, "little", signed=True))/float(2**23) )
    ##    print(buffer)
    ##    print(int.from_bytes(buffer, "little", signed=True))
    ##    print(int.from_bytes(buffer, "little", signed=True)/256)
    ##    print(float(int.from_bytes(buffer, "little", signed=True)))
    ##    print()

    sys.stdout.buffer.write(buffer)
    sys.stdout.buffer.flush()


# def read_stdin_stream(handler, chunk_size=1024):
def read_stdin_stream(handler, chunk_size=8192):
    with sys.stdin as f:
        while True:
            buffer = f.buffer.read(chunk_size)
            if buffer == b'':
                break
            handler(buffer)


def signal_handler(sig, frame):
    sys.stdout.buffer.flush()
    sys.exit(0)


def main():
    signal.signal(signal.SIGINT, signal_handler)

    # notice the `chunk_size` of 1 for this particular example
    # read_stdin_stream(process_data, chunk_size=1)
    read_stdin_stream(process_data, chunk_size=4)


if __name__ == "__main__":
    main()



Tried to read the docs from https://docs.python.org/3.8/library/struct.html#struct-examples and the first example there doesn't work:  :-//
Code: [Select]
$ python3
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
struct.error: unpack requires a buffer of 16 bytes
>>> calcsize('hhl')
16
>>>



Since not even the example from the manual works, thought I might ask instead of keep poking blindly at it.
To show the alignement of the 24bits inside the S32_LE, this is what a hex dump for noise only looks like (the small +/- noise signal around zero is useful here to see the layout of the 24bits into 32 and the endianness):

Code: [Select]
$ arecord -v -D hw:1,2 -B 10000 -r 128000 -f S32_LE -c 2 | hd
...
0019b0f0  00 60 00 00 00 a0 ff ff  00 c0 01 00 00 e0 fd ff  |.`..............|
0019b100  00 a0 fd ff 00 90 05 00  00 10 03 00 00 60 fa ff  |.............`..|
0019b110  00 40 ff ff 00 30 03 00  00 e0 fe ff 00 e0 00 00  |.@...0..........|
0019b120  00 90 04 00 00 40 ff ff  00 d0 fd ff 00 60 02 00  |.....@.......`..|
0019b130  00 a0 fe ff 00 d0 fb ff  00 70 04 00 00 10 02 00  |.........p......|
0019b140  00 10 f9 ff 00 a0 02 00  00 20 06 00 00 a0 fb ff  |......... ......|
0019b150  00 90 fe ff 00 30 04 00  00 70 fd ff 00 c0 ff ff  |.....0...p......|
0019b160  00 10 03 00 00 f0 fa ff  00 60 fe ff 00 d0 04 00  |.........`......|

If there is a 4 bytes variable called "buffer", how to convert "buffer" from S32_LE into f32le, preferably using struct from Python?
« Last Edit: August 08, 2022, 05:29:25 pm by RoGeorge »
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1182
  • Country: pl
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #1 on: August 07, 2022, 11:39:47 am »
Note: float32 has a 23-bit mantissa, so you will inevitably lose 1 bit at the maximum level.

What about using ffmpeg?
Code: [Select]
ffmpeg -f s32le -i pipe: -c:a pcm_f32le -f f32le pipe:
The first -f sets the input format, -i pipe: instructs ffmpeg to read from stdin, -c:a sets the output codec to PCM (float32 LE) and the second -f the output file format to raw float32 LE.
People imagine AI as T1000. What we got so far is glorified T9.
 
The following users thanked this post: RoGeorge

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3793
  • Country: gb
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #2 on: August 07, 2022, 11:43:29 am »
so s32le means  "PCM signed 32-bit little-endian"
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 
The following users thanked this post: RoGeorge

Offline magic

  • Super Contributor
  • ***
  • Posts: 6733
  • Country: pl
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #3 on: August 07, 2022, 12:37:01 pm »
Good enough for x86/ARM Linux.

Code: [Select]
#include <stdio.h>

int main() {
int i;
while (fread(&i, 4, 1, stdin)) {
float f = i;
fwrite(&f, 4, 1, stdout);
}
}
 
The following users thanked this post: RoGeorge

Offline adeuring

  • Contributor
  • Posts: 15
  • Country: de
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #4 on: August 07, 2022, 03:24:44 pm »
Tried to read the docs from https://docs.python.org/3.8/library/struct.html#struct-examples and the first example there doesn't work:  :-//
Code: [Select]
$ python3
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
struct.error: unpack requires a buffer of 16 bytes
>>> calcsize('hhl')
16
>>>

Welcome to the messy world of C types ;)! I guess that the documentation was written on a 32 bit platform: h represents a C short integer, which has quite reliably 16 bits; l represents a C long integer, which has 32 bits in the example from the documentation but 64 bits on your machine...

If you run the example with a slight modification, it works:

Code: [Select]
>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>> unpack('hhl', b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00')
(1, 2, 3)
>>> unpack('hhl', pack('hhl', 1, 2, 3))
(1, 2, 3)
>>>

(Just use your mouse to copy the result of the pack() call as an argument into the unpack() call.)

The table under "Format Characters" in the docs (https://docs.python.org/3.8/library/struct.html#format-characters) mentions i as another 32bit integer type which seems to indeed have 32 bits on today's 64bit platforms:
Code: [Select]
>>> pack('i', 2)
b'\x02\x00\x00\x00'

So, try pack('hhi', 1, 2, 3) instead.

Quote
If there is a 4 bytes variable called "buffer", how to convert "buffer" from S32_LE into f32le, preferably using struct from Python?

I am not absolutely sure about the meaning of "S32_LE" and "f32le". Assuming that "S32_LE" means a 32 bit integer in "little endian" order and "f32le" means a 32 bit float number in "little endian" order, this might work
(See https://docs.python.org/3.8/library/struct.html#byte-order-size-and-alignment):

Code: [Select]
>>> # Create 32 bit "example input".
>>> pack('<i', 123123)
b'\xf3\xe0\x01\x00'
>>> # unpack the 4 byte string, convert to "Python internal float" and pack as 32bit float
>>> pack('<f', float(unpack('<i', b'\xf3\xe0\x01\x00')[0]))
b'\x80y\xf0G'

Final remarks:
1. @magic showed a nice example of a much more efficient C solution for your problem. Using Python introduces a huge amount of overhead. (Numpy has, IIRC, also some mechanisms to load arrays from/into byte sequences or buffers - that might be another option. Not as fast as a pure C implementation but for sure faster than my example if used for buffers with, let's say, 512 4 byte numbers or more.
2. There is a reason why building a C program from source starts quite often with running a script called "configure" or "autoconf". One important purpose is to figure out which size a given C type has for the current platform and compiler. The source code of these propgrams does not use the standard C types like int, short or long, but int8, int16, int32, which are defined in special C header files that were generated by configure or autoconf.
 
The following users thanked this post: RoGeorge

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3793
  • Country: gb
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #5 on: August 07, 2022, 03:46:37 pm »
I am not absolutely sure about the meaning of "S32_LE" and "f32le". Assuming that "S32_LE" means a 32 bit integer in "little endian" order and "f32le" means a 32 bit float number in "little endian" order


I haven't checked the doc, but if S32_LE means signed 32 bit LE, well .. I am not sure about the range of the final float32 le, may be (if it goes between -1..+1) it requires normalization or adjustment  :-//

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 
The following users thanked this post: RoGeorge

Offline adeuring

  • Contributor
  • Posts: 15
  • Country: de
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #6 on: August 07, 2022, 03:50:35 pm »
I am not absolutely sure about the meaning of "S32_LE" and "f32le". Assuming that "S32_LE" means a 32 bit integer in "little endian" order and "f32le" means a 32 bit float number in "little endian" order


I haven't checked the doc, but if S32_LE means signed 32 bit LE, well .. I am not sure about the range of the final float32 le, may be (if it goes between -1..+1) it requires normalization or adjustment  :-//

Oops... You are of course right – some kind of "scaling" is most likely necessary.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6171
  • Country: fi
    • My home page and email address
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #7 on: August 07, 2022, 04:23:13 pm »
For conversion of native-endian int32_t to float in a pipe, I'd use something like
Code: [Select]
// SPDX-License-Identifier: CC0-1.0
//
// Converts native byte order signed 32-bit integers to native byte order floats.
//
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

#ifndef  BLOCK_SIZE
#define  BLOCK_SIZE  8192
#endif

int main(void)
{
    int32_t     in_buf[BLOCK_SIZE];
    float       out_buf[BLOCK_SIZE];

    while (1) {
        size_t  have = 0;

        /* Read some (complete) int32_t's. */
        do {
            ssize_t  n = read(STDIN_FILENO, (char *)(in_buf) + have, (sizeof in_buf) - have);
            if (n > 0)
                have += n;
            else
            if (n == 0)
                return EXIT_SUCCESS;
            else
                return EXIT_FAILURE;
        } while (!have || (have % sizeof in_buf[0]));

        /* Count in number of samples in input. */
        have /= sizeof in_buf[0];

        /* Convert to float. */
        {
            float *const end = out_buf + have;
            int32_t     *src = in_buf;
            float       *dst = out_buf;

            while (dst < end)
                *(dst++) = *(src++);
        }

        /* Write floats. */
        {
            const char *const end = (const char *)(out_buf + have);
            const char       *ptr = (const char *)(out_buf);

            while (ptr < end) {
                ssize_t  n = write(STDOUT_FILENO, ptr, (size_t)(end - ptr));
                if (n > 0)
                    ptr += n;
                else
                    return EXIT_FAILURE;
            }
        }
    }

    /* Never reached. */
    return EXIT_FAILURE;
}
Note that it uses low-level <unistd.h> I/O, to avoid any sort of buffering.

Interestingly, this only uses three syscalls: read, write, and return/exit_group, and in POSIXy systems including Linux, STDIN_FILENO==0 and STDOUT_FILENO == 1, so one could write this in freestanding C in Linux, using e.g. GCC extended asm for the syscall wrappers on each target architecture.
 
The following users thanked this post: RoGeorge

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6171
  • Country: fi
    • My home page and email address
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #8 on: August 07, 2022, 04:56:18 pm »
Here's the freestanding version for Linux on x86 or x86-64:
Code: [Select]
// SPDX-License-Identifier: CC0-1.0
//
// Converts native byte order signed 32-bit integers to native byte order floats.
//
// To compile, use
//  gcc -Wall -O2 -static -s -ffreestanding -nostdlib  this.c  -o  executable
//
#include <stdint.h>

#if defined(__x86_64__)
/*
 * Linux on x86-64
*/
#define  SYS_exit     60
#define  SYS_read      0
#define  SYS_write     1

static inline long syscall3(long num, long arg1, long arg2, long arg3)
{
    long ret;
    asm volatile ( "syscall"
                 : "=a" (ret)
                 : "a" (num), "D" (arg1), "S" (arg2), "d" (arg3)
                 : "rcx", "r11", "memory");
    return ret;
}

static inline long syscall1(long num, long arg1)
{
    long ret;
    asm volatile ( "syscall"
                 : "=a" (ret)
                 : "a" (num), "D" (arg1)
                 : "rcx", "r11", "memory");
    return ret;
}

#elif defined(__i386__)
/*
 * Linux on x86
*/
#define  SYS_exit      1
#define  SYS_read      3
#define  SYS_write     4

static inline long syscall3(long num, long arg1, long arg2, long arg3)
{
    long ret;
    asm volatile ( "int $0x80"
                 : "=a" (ret)
                 : "a" (num), "b" (arg1), "c" (arg2), "d" (arg3)
                 : "cc", "memory");
    return ret;
}

static inline long syscall1(long num, long arg1)
{
    long ret;
    asm volatile ( "int $0x80"
                 : "=a" (ret)
                 : "a" (num), "D" (arg1)
                 : "ecx", "edx", "cc", "memory");
    return ret;
}

#else
#error  Unsupported architecture.
#endif

/*
 * Linux kernel interface (same across architectures)
*/

static inline long  read_stdin(void *buffer, long length)
{
    return syscall3(SYS_read, 0, (long)buffer, length);
}

static inline long  write_stdout(const void *buffer, long length)
{
    return syscall3(SYS_write, 1, (long)buffer, length);
}

static inline void exit_success(void)
{
    syscall1(SYS_exit, 0);
    while (1) /* nothing */;
}

static inline void exit_failure(void)
{
    syscall1(SYS_exit, 1);
    while (1) /* nothing */;
}

/*
 * Application itself
*/

#ifndef  BLOCK_SIZE
#define  BLOCK_SIZE  8192
#endif

void _start(void)
{
    int32_t     in_buf[BLOCK_SIZE];
    float       out_buf[BLOCK_SIZE];

    while (1) {
        long  have = 0;

        /* Read some (complete) int32_t's. */
        do {
            long  n = read_stdin((char *)(in_buf) + have, (sizeof in_buf) - have);
            if (n > 0)
                have += n;
            else
            if (n == 0)
                exit_success();
            else
                exit_failure();
        } while (!have || (have % sizeof in_buf[0]));

        /* Count in number of samples in input. */
        have /= sizeof in_buf[0];

        /* Convert to float. */
        {
            float *const end = out_buf + have;
            int32_t     *src = in_buf;
            float       *dst = out_buf;

            while (dst < end)
                *(dst++) = *(src++);
        }

        /* Write floats. */
        {
            const char *const end = (const char *)(out_buf + have);
            const char       *ptr = (const char *)(out_buf);

            while (ptr < end) {
                long  n = write_stdout(ptr, (long)(intptr_t)(end - ptr));
                if (n > 0)
                    ptr += n;
                else
                    exit_failure();
            }
        }
    }

    /* Never reached. */
    exit_success();
}
Compiles to a static binary less than 9k in size.  There's a lot of bloat even in standard C nowadays...
 
The following users thanked this post: Ed.Kloonk, RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #9 on: August 08, 2022, 06:47:15 am »
So far I've accidentally found that 'arecord' first puts a header before streaming, and now suspect 'baudline' might read it and take hints about the data format from that header, so maybe I should prepare a proper header for float instead of turning the header itself into float.  ;D

Also 'baudline' might expect the float to be scaled down to +/-1.0 range.

Posting an 'arecord' header grab and RIFF WAVfmt header format just for the docs:
Code: [Select]
// arecord RIFF WAVfmt header
    // format copied from [url]http://www.topherlee.com/software/pcm-tut-wavformat.html[/url]
    // Positions Sample Value Description
    // 1 - 4 "RIFF" Marks the file as a riff file. Characters are each 1 byte long.
    // 5 - 8 File size (integer) Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you'd fill this in after creation.
    // 9 -12 "WAVE" File Type Header. For our purposes, it always equals "WAVE".
    // 13-16 "fmt " Format chunk marker. Includes trailing null
    // 17-20 16 Length of format data as listed above
    // 21-22 1 Type of format (1 is PCM) - 2 byte integer
    // 23-24 2 Number of Channels - 2 byte integer
    // 25-28 44100 Sample Rate - 32 byte integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.
    // 29-32 176400 (Sample Rate * BitsPerSample * Channels) / 8.
    // 33-34 4 (BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo
    // 35-36 16 Bits per sample
    // 37-40 "data" "data" chunk header. Marks the beginning of the data section.
    // 41-44 File size (data) Size of the data section.
    // Sample values are given above for a 16-bit stereo source.

// random data to observe indianness sample of the 32 bits stream incoming from arecord with -f S32_LE
// audio signal was a cable with both L/R channels grounded, so small +/- noise values around zero
    // $ arecord -D hw:1,2 -B 10000 -r 48000 -f S32_LE -c 2 -s 128 -i | hd
    // Recording WAVE 'stdin' : Signed 32 bit Little Endian, Rate 48000 Hz, Stereo
    // 00000000  52 49 46 46 24 04 00 00  57 41 56 45 66 6d 74 20  |RIFF$...WAVEfmt |
    // 00000010  10 00 00 00 01 00 02 00  80 bb 00 00 00 dc 05 00  |................|
    // 00000020  08 00 20 00 64 61 74 61  00 04 00 00 00 00 00 00  |.. .data........|
    // 00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    // *
    // 00000210  00 00 00 00 00 00 00 00  00 00 00 00 f0 ff ff ff  |................|
    // 00000220  f0 ff ff ff e0 ff ff ff  e0 ff ff ff 20 ff ff ff  |............ ...|
    // 00000230  a0 fd ff ff 60 df ff ff  c0 b4 ff ff f0 81 fe ff  |....`...........|
    // 00000240  30 83 fd ff e0 19 fa ff  d0 c9 f8 ff b0 79 f7 ff  |0............y..|
    // 00000250  40 16 f8 ff c0 89 fa ff  60 6e fa ff 80 71 f9 ff  |@.......`n...q..|
    // 00000260  a0 9a f8 ff 10 02 f9 ff  20 5e f9 ff c0 3e fa ff  |........ ^...>..|
    // 00000270  f0 20 f9 ff 10 71 f9 ff  b0 d3 f9 ff d0 e1 fc ff  |. ...q..........|
    // 00000280  10 f1 fb ff 10 84 fc ff  d0 8d fb ff 50 b2 f8 ff  |............P...|
    // 00000290  50 a9 f7 ff 10 3b f8 ff  f0 67 f9 ff 70 32 fb ff  |P....;...g..p2..|
    // 000002a0  f0 3e fb ff 50 70 f7 ff  a0 31 f7 ff 70 85 fa ff  |.>..Pp...1..p...|
    // 000002b0  c0 e6 fa ff 00 f2 f7 ff  80 6a f8 ff 00 85 fa ff  |.........j......|
    // 000002c0  e0 42 fa ff 40 b7 fc ff  20 65 fc ff 60 7c 08 00  |.B..@... e..`|..|
    // 000002d0  b0 8e 08 00 c0 d5 0f 00  d0 ea 0f 00 20 0e 0b 00  |............ ...|
    // 000002e0  70 f8 0a 00 40 54 0c 00  a0 1f 0c 00 b0 a1 0c 00  |p...@T..........|
    // 000002f0  b0 f8 0c 00 40 39 0b 00  60 0f 0b 00 10 7c 0d 00  |....@9..`....|..|
    // 00000300  60 55 0d 00 20 55 0b 00  a0 f9 0a 00 f0 e5 0b 00  |`U.. U..........|
    // 00000310  f0 4e 0c 00 30 da 0a 00  70 0f 0b 00 a0 45 0b 00  |.N..0...p....E..|
    // 00000320  20 38 0b 00 70 ce 0a 00  c0 a7 0a 00 40 f7 0b 00  | 8..p.......@...|
    // 00000330  40 ad 0b 00 c0 66 0b 00  10 ef 0a 00 30 c4 0b 00  |@....f......0...|
    // 00000340  d0 27 0c 00 f0 38 0c 00  b0 b0 0b 00 20 fe 0b 00  |.'...8...... ...|
    // 00000350  80 c4 0b 00 d0 c3 0b 00  80 a7 0b 00 50 c1 0c 00  |............P...|
    // 00000360  90 7a 0c 00 e0 1b 0c 00  80 4f 0c 00 40 56 0c 00  |.z.......O..@V..|
    // 00000370  a0 9d 0c 00 80 8b 0c 00  60 3f 0c 00 a0 ac 0c 00  |........`?......|
    // 00000380  b0 48 0c 00 a0 df 0b 00  50 34 0c 00 60 74 0c 00  |.H......P4..`t..|
    // 00000390  c0 5e 0c 00 70 c0 0b 00  40 54 0c 00 c0 c7 0b 00  |.^..p...@T......|
    // 000003a0  80 02 0c 00 d0 a2 0b 00  60 18 0c 00 80 bc 0b 00  |........`.......|
    // 000003b0  10 ce 0b 00 80 a2 0b 00  20 ff 0b 00 a0 ea 0b 00  |........ .......|
    // 000003c0  00 f5 0b 00 50 ae 0b 00  70 41 0b 00 f0 5c 0b 00  |....P...pA...\..|
    // 000003d0  80 60 0b 00 40 29 0c 00  80 7c 0c 00 10 29 0b 00  |.`..@)...|...)..|
    // 000003e0  40 68 0b 00 d0 16 0b 00  90 33 0b 00 30 8b 0b 00  |@h.......3..0...|
    // 000003f0  40 a9 0b 00 30 e7 0a 00  70 4c 0b 00 e0 f4 0a 00  |@...0...pL......|
    // 00000400  80 5b 0a 00 60 1f 07 00  a0 ff 06 00 60 0c fb ff  |.[..`.......`...|
    // 00000410  30 68 fb ff e0 13 f4 ff  80 31 f4 ff 50 c2 fa ff  |0h.......1..P...|
    // 00000420  90 2d fa ff 40 e8 f8 ff  50 21 f9 ff              |.-..@...P!..|
    // 0000042c

// indianness at runtime from [url]https://sourceforge.net/p/predef/wiki/Endianness/[/url]
    // #include <stdint.h>
    //
    // enum {
    //   ENDIAN_UNKNOWN,
    //   ENDIAN_BIG,
    //   ENDIAN_LITTLE,
    //   ENDIAN_BIG_WORD,   /* Middle-endian, Honeywell 316 style */
    //   ENDIAN_LITTLE_WORD /* Middle-endian, PDP-11 style */
    // };
    //
    // int endianness(void)
    // {
    //   union
    //   {
    //     uint32_t value;
    //     uint8_t data[sizeof(uint32_t)];
    //   } number;
    //
    //   number.data[0] = 0x00;
    //   number.data[1] = 0x01;
    //   number.data[2] = 0x02;
    //   number.data[3] = 0x03;
    //
    //   switch (number.value)
    //   {
    //   case UINT32_C(0x00010203): return ENDIAN_BIG;
    //   case UINT32_C(0x03020100): return ENDIAN_LITTLE;
    //   case UINT32_C(0x02030001): return ENDIAN_BIG_WORD;
    //   case UINT32_C(0x01000302): return ENDIAN_LITTLE_WORD;
    //   default:                   return ENDIAN_UNKNOWN;
    //   }
    // }


Side note, noticed this sound card is not 24/96, must be some marketing crafted lie.  From the data output by 'arecord' the stream seems to be 20bits/96kHz, 24 bits is only when sampling at 48kHz.  I remember Creative lied like this before, when they advertised a sound card as being 24bits, when in fact only the CODEC was 24 bits but all the rest was working in 16 bits.

Thank you all for the help, will try each method.

Offline magic

  • Super Contributor
  • ***
  • Posts: 6733
  • Country: pl
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #10 on: August 08, 2022, 06:56:14 am »
Output format of arecord can be changed, but I don't know what baudline expects.
I'm pretty sure that sox and mplayer could accept either WAV or RAW on in and out.

If you are having problems with those programs creating too much latency or something, perhaps there are options to reduce buffer size.
I can't see myself sitting and reinventing this particular wheel ;D
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #11 on: August 08, 2022, 07:25:31 am »
At first I thought it would be just pipe-ing, hoping for a common float format between 'arecord' and 'baudline'.  Then I thought some standard audio tool like 'sox' would do it, to convert the format between the two, maybe 10 minutes of man readings to figure out the syntax, and that's it!

One day later, counting bits and bytes, and reverse engineer headers from a hex dump.  ::)

From the man pages, 'arecord' knows these formats:
Code: [Select]
       -f --format=FORMAT
              Sample format
              Recognized sample formats are: S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE
              S24_BE  U24_LE  U24_BE  S32_LE  S32_BE  U32_LE U32_BE FLOAT_LE FLOAT_BE
              FLOAT64_LE  FLOAT64_BE  IEC958_SUBFRAME_LE  IEC958_SUBFRAME_BE   MU_LAW
              A_LAW  IMA_ADPCM  MPEG  GSM  SPECIAL  S24_3LE  S24_3BE  U24_3LE U24_3BE
              S20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE
              Some of these may not be available on selected hardware
              The available format shortcuts are:
              -f cd (16 bit little endian, 44100, stereo) [-f S16_LE -c2 -r44100]
              -f cdr (16 bit big endian, 44100, stereo) [-f S16_BE -c2 -f44100]
              -f dat (16 bit little endian, 48000, stereo) [-f S16_LE -c2 -r48000]
              If no format is given U8 is used.

However, when I try other formats arecord tells this:
Code: [Select]
$ arecord -D hw:1,2 -B 10000 -r 48000 -f FLOAT_LE -c 2 -s 128 | hd
Recording WAVE 'stdin' : Float 32 bit Little Endian, Rate 48000 Hz, Stereo
arecord: set_params:1368: Sample format non available
Available formats:
- U8
- S16_LE
- S32_LE
« Last Edit: August 08, 2022, 10:39:40 am by RoGeorge »
 

Online Ed.Kloonk

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #12 on: August 08, 2022, 07:25:45 am »
Regarding 20-bit sound cards.

In the late 90's, I had a Echo Darla 1st gen which had the 20-bit ADC. A great piece of hardware. Fantasic noise floor for a pci card. But the software to access it always sucked.

I've got recordings taken from rare vinyl all stored in 24-bit or 32-bit in various integer or float combinations because there was something wrong with the least significant nibble being randomly flipped. Later on, when needed, I would use the MAD mp3 encoder, sometimes just sent 16-bits undithered. Yuk.

I don't blame the devs. At the time, I understand they developed the card first on a Mac. Those cards had firmware uploaded by the driver on init so somewhere in the conversion to winintel (hence the byte order), firstly with a driver for libmm and later for AISO or was it directx? It became a bit of a dog's breakfast. Worked alright on Alsa briefly until you know what came along.

iratus parum formica
 

Online Ed.Kloonk

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #13 on: August 08, 2022, 07:30:34 am »
So far I've accidentally found that 'arecord' first puts a header before streaming, and now suspect 'baudline' might read it and take hints about the data format from that header, so maybe I should prepare a proper header for float instead of turning the header itself into float.  ;D



My memory is foggy here but with .wav RIFF headers, I vaguely remember that when the data segment was bigger than 32 bits another header was injected at that point. Thanks MS.

iratus parum formica
 

Online Ed.Kloonk

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #14 on: August 08, 2022, 07:36:16 am »
So far I've accidentally found that 'arecord' first puts a header before streaming, and now suspect 'baudline' might read it and take hints about the data format from that header, so maybe I should prepare a proper header for float instead of turning the header itself into float.  ;D



My memory is foggy here but with .wav RIFF headers, I vaguely remember that when the data segment was bigger than 32 bits another header was injected at that point. Thanks MS.

I'm starting to rememer now. Depending on the application, we had to fool the wavfile reader into thinking the bitspace was something else. I can remember going into a binary editor and tweaking the sample vals so early versions of cool edit would play nice with +16 bits.
iratus parum formica
 
The following users thanked this post: RoGeorge

Offline magic

  • Super Contributor
  • ***
  • Posts: 6733
  • Country: pl
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #15 on: August 08, 2022, 08:10:50 am »
Regarding file formats, run man arecord and type /wav[ENTER] ;)
As for sample formats, it surely supports many, but you are constrained by what the hardware can provide.

And now I realized that this can be dealt with easily :palm:
Replace hw:N with plughw:N and you will use an ALSA plugin which can convert stuff for you. Beware that it will also downmix to mono or interpolate the sample rate if you select output parameters not supported by hardware.
 
The following users thanked this post: RoGeorge

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #16 on: August 08, 2022, 08:51:14 am »
Regarding file formats, run man arecord and type /wav[ENTER] ;)
As for sample formats, it surely supports many, but you are constrained by what the hardware can provide.

And now I realized that this can be dealt with easily :palm:
Replace hw:N with plughw:N and you will use an ALSA plugin which can convert stuff for you. Beware that it will also downmix to mono or interpolate the sample rate if you select output parameters not supported by hardware.

You are in the proper direction

An ALSA plugin should do this

If i find time i will try to drop scratch example
.  I have a pile of such plugins

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #17 on: August 08, 2022, 10:06:57 am »
Regarding file formats, run man arecord and type /wav[ENTER] ;)
As for sample formats, it surely supports many, but you are constrained by what the hardware can provide.

And now I realized that this can be dealt with easily :palm:
Replace hw:N with plughw:N and you will use an ALSA plugin which can convert stuff for you. Beware that it will also downmix to mono or interpolate the sample rate if you select output parameters not supported by hardware.

Exactly this, it's part of alsa already. `plughw` will do whatever is needed to match your needed format, if you match everything except for the sample format it will only convert the format.

As for the arecord header, `arecord -t raw` will output just the plain samples, no header.

Example, record from device 0, format little-endian float, at 96kHz, two channels and output to stdout without a header.
Code: [Select]
arecord -D plughw:0 -t raw -f FLOAT_LE -r 96000 -c 2

If the hardware doesn't support FLOAT_LE, `plughw` will convert for you.
« Last Edit: August 08, 2022, 10:12:04 am by gnif »
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #18 on: August 08, 2022, 10:36:58 am »
Tried to read the docs from https://docs.python.org/3.8/library/struct.html#struct-examples and the first example there doesn't work:  :-//
Code: [Select]
$ python3
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
struct.error: unpack requires a buffer of 16 bytes
>>> calcsize('hhl')
16
>>>

Welcome to the messy world of C types ;)! I guess that the documentation was written on a 32 bit platform: h represents a C short integer, which has quite reliably 16 bits; l represents a C long integer, which has 32 bits in the example from the documentation but 64 bits on your machine...

Now it all makes sense, thank you.  Switched to C anyways, because of more control in handling data formats.



However, in all the C tests from yesterday I was using '2^n' for scaling numbers, thinking '^' means "to the power of"  :palm: , while the '^' operator in C is bitwise xor.  No wonder why all the scaling attempts from yesterday were behaving randomly.  ;D

Now that I've noticed that, first C example from magic works just fine, though the float range expected by 'baudline seems to be +/-32768.0 just like it would be for a 16 bits ADC, but float, not +/-1.0, not +/-231, not +/-223.

Code: [Select]
#include <stdio.h>

int main() {
int i;
while (fread(&i, 4, 1, stdin)) {
float f = i;
        f = f/65536;  //??? should be 8388608, to scale the 24bits of the ADC to +/-1.0 float
        //apparently 'baudline' expects the +/-32768.0 range for incoming le32f data ???
fwrite(&f, 4, 1, stdout);
}
}

// gcc -Wall -Wfatal-errors int_to_float_from_magic_EEVblog.c && arecord -D hw:1,2 -B 10000 -r 48000 -f S32_LE -c 2 -i | ./a.out | bl -format le32f

Though the 'baudline' looks just the same for 24bits as if it were all S16_LE when eyeballing at it, the SNR is 2dB better, from 48dB relative to the fundamental for 16bits sampling, to 50dB for 24bits sampling.

I should file a bug report, for why the C '^' operator doesn't behave like the spreadsheet '^' operator!  >:D
Will try the other solutions, too, to learn more, thank you all, solved!  :D

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #19 on: August 08, 2022, 11:14:32 am »
Code: [Select]
arecord -D plughw:1,2 -r 96000 -c 2 -f FLOAT_LE -t raw | bl -format le32f
Nice, this works, too, thank you, but the frame rate in baudline becomes very low, just like it was with sox or ffmpeg.  However, the produced float range is +/-1.0, while baudline expects +/-32768.0, so the signal is barely visible.  Couldn't find any scaling or volume in the arecord's man.

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #20 on: August 08, 2022, 11:45:52 am »
Code: [Select]
arecord -D plughw:1,2 -r 96000 -c 2 -f FLOAT_LE -t raw | bl -format le32f
Nice, this works, too, thank you, but the frame rate in baudline becomes very low, just like it was with sox or ffmpeg.  However, the produced float range is +/-1.0, while baudline expects +/-32768.0, so the signal is barely visible.  Couldn't find any scaling or volume in the arecord's man.

Standard range for float audio is +/-1.0, where S16 is -32767  to 32768. I have not used baudline but a quick look at it's documentation shows it supports this... why not just use S16 and allow plughw to convert to it?

Edit: Actually it seems that baudline also has a `-scaleby` parameter, can't you use this?
Code: [Select]
baudline -stdin -format le32f -scaleby 32768

Edit2:

Got this working in realtime with the following command:

arecord -D plughw:0 -t raw -f FLOAT_LE -r 44100 -c 1 | ./baudline -stdin -format le32f -scaleby 32768 -record

Also with PipeWire if you are fortunate enough to be on a distro that has moved to it:

pw-record --format f32 --channels 1 --rate 44100 - | ./baudline -stdin -format le32f -scaleby 32768 -record
« Last Edit: August 08, 2022, 12:07:43 pm by gnif »
 
The following users thanked this post: RoGeorge

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #21 on: August 08, 2022, 12:36:46 pm »
To my ubuntu 20.04, arecord with plughw: (or sox, or ffmpeg) gives a much slower refresh in baudline making it all jumpy, than using hw: and converting to float with C.  Found the -scaleby parameter in baudline, thought that would be a strange number to remember 1/65536=0.0000152587890625, so conversion with C is more convenient and doesn't slow down the framerate.

Don't have PipeWire, Ubuntu switched to it starting this year, but my install is still using PulseAudio.  However, I was trying to avoid any higher audio layers because of possible (and unwanted) resampling or conversions these layers might add.

Trying to use 24 instead of the normal 16 bits because the soundcard can sample in 24 bits, and I am using it as a measuring instrument.  For a measuring instrument, the extra bits are valuable.

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #22 on: August 08, 2022, 12:42:53 pm »
To my ubuntu 20.04, arecord with plughw: (or sox, or ffmpeg) gives a much slower refresh in baudline making it all jumpy, than using hw: and converting to float with C.

It sounds like you have a larger issue at hand, I am getting real-time, smooth output even at 96kHz no matter how I feed baudline.

As a test, do you get smooth framerates if you feed it /dev/urandom instead? If so you have an issue with your sound device, perhaps your period size/count is not dealing well and you need to tune this some.

Further to this, what is your sound device?
« Last Edit: August 08, 2022, 01:05:06 pm by gnif »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #23 on: August 08, 2022, 01:19:25 pm »
Standard DMIX plugin 

Code: [Select]
pcm.FLOATCNVT {
type dmix
ipc_key 2048
ipc_key_add_uid 0
ipc_perm 0660
slave {
pcm { type hw card 0 }

format "FLOAT_LE"
rate 96000
channels 2
}
bindings {
0 0
1 1
}
}

ctl.FLOATCNVT {
type hw
card 0
}

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #24 on: August 08, 2022, 01:23:23 pm »
Standard DMIX plugin 

Code: [Select]
pcm.FLOATCNVT {
type dmix
ipc_key 2048
ipc_key_add_uid 0
ipc_perm 0660
slave {
pcm { type hw card 0 }

format "FLOAT_LE"
rate 96000
channels 2
}
bindings {
0 0
1 1
}
}

ctl.FLOATCNVT {
type hw
card 0
}

Paul

This is exactly what plughw is already doing
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #25 on: August 08, 2022, 01:25:15 pm »
Standard DMIX plugin 

Code: [Select]
pcm.FLOATCNVT {
type dmix
ipc_key 2048
ipc_key_add_uid 0
ipc_perm 0660
slave {
pcm { type hw card 0 }

format "FLOAT_LE"
rate 96000
channels 2
}
bindings {
0 0
1 1
}
}

ctl.FLOATCNVT {
type hw
card 0
}

Paul

This is exactly what plughw is already doing

ALSALIB will parse the defined configures at any call.

Unless you have them defined - it will not magically fall from the sky...

It must be defined somewhere. Or... written in the command line

Paul
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #26 on: August 08, 2022, 01:29:02 pm »
To my ubuntu 20.04, arecord with plughw: (or sox, or ffmpeg) gives a much slower refresh in baudline making it all jumpy, than using hw: and converting to float with C.

It sounds like you have a larger issue at hand, I am getting real-time, smooth output even at 96kHz no matter how I feed baudline.

As a test, do you get smooth framerates if you feed it /dev/urandom instead? If so you have an issue with your sound device, perhaps your period size/count is not dealing well and you need to tune this some.

Further to this, what is your sound device?

Not sure how to feed data, do you have an example of the command, please?

Couldn't make arecord to read from /dev/urandom or from stdin, and if you mean to test by feeding /dev/ramdom to baudline, directly, if I do this:
Code: [Select]
cat /dev/urandom | bl -format le32f -samplerate 96000
# bl is an alias for "baudline_1.08_linux_x86_64/baudline -stdin -record -channels 2 "
then cat will flood too much data into baudline, making baudline unresponsive.

I guess some larger buffers come into play inside arecord (or sox or ffmpeg) when using plughw: instead of hw:

Otherwise it all seems to be working fine, baudline shows about 60fps+ and fluid move (the monitor is 60Hz and the driver syncs to that to avoid video tearing).

The soundcard is an onboard Sound Core3D Recon 3D internal (ca0132 chipset from Creative, the followup of emu10k1/2, Audigy, etc.), MB ASRock Fatal1ty Z97 Professional, nVidia GTX760/2GB, i7-4790K, 32GB RAM, SSD.
« Last Edit: August 08, 2022, 01:35:40 pm by RoGeorge »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #27 on: August 08, 2022, 01:32:07 pm »
Not sure how to feed data, do you have an example of the command, please?

Couldn't make arecord to read from /dev/urandom or from stdin, and if you mean to test by feeding /dev/ramdom to baudline, directly, if I do this:
Code: [Select]
cat /dev/urandom | bl -format le32f -samplerate 96000
# bl is an alias for "baudline_1.08_linux_x86_64/baudline -stdin -record -channels 2 "
then cat will flood too much data into baudline, making baudline unresponsive.

I guess some larger buffers come into play inside arecord (or sox or ffmpeg) when using plughw: instead of hw:

Otherwise it all seems to be working fine, baudline shows about 60fps+ and fluid move (the monitor is 60Hz and the driver syncs to that to avoid video tearing).


There seems to be a necessary climbing of some steps....

the use of  ALSA LIB  plugins is straight  like  -D plug:[ANY_DEFINED_PLUG_THING]

But...   your system REQUIRE  the proper  CARD DEFINITIONs  and  NODES (which mostly are messed and subverted by systemd and other problems...)

Basically you just invoke  aplay or arecord with the required config plugin

I have some dozens plugins defined to handle al sort of complex things...
including AUDIO ROUTING..  when not using JACK


Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #28 on: August 08, 2022, 01:34:58 pm »
Not sure how to feed data, do you have an example of the command, please?

Couldn't make arecord to read from /dev/urandom or from stdin, and if you mean to test by feeding /dev/ramdom to baudline, directly, if I do this:
Code: [Select]
cat /dev/urandom | bl -format le32f -samplerate 96000
# bl is an alias for "baudline_1.08_linux_x86_64/baudline -stdin -record -channels 2 "
then cat will flood too much data into baudline, making baudline unresponsive.

I guess some larger buffers come into play inside arecord (or sox or ffmpeg) when using plughw: instead of hw:

Otherwise it all seems to be working fine, baudline shows about 60fps+ and fluid move (the monitor is 60Hz and the driver syncs to that to avoid video tearing).

Ah yes, sorry about that, I forgot that the audio device will be rate limiting the input. In any case, I still suspect an issue with your capture method/configuration. What is your sound device?
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #29 on: August 08, 2022, 01:38:42 pm »
Not sure how to feed data, do you have an example of the command, please?

Couldn't make arecord to read from /dev/urandom or from stdin, and if you mean to test by feeding /dev/ramdom to baudline, directly, if I do this:
Code: [Select]
cat /dev/urandom | bl -format le32f -samplerate 96000
# bl is an alias for "baudline_1.08_linux_x86_64/baudline -stdin -record -channels 2 "
then cat will flood too much data into baudline, making baudline unresponsive.

I guess some larger buffers come into play inside arecord (or sox or ffmpeg) when using plughw: instead of hw:

Otherwise it all seems to be working fine, baudline shows about 60fps+ and fluid move (the monitor is 60Hz and the driver syncs to that to avoid video tearing).


There seems to be a necessary climbing of some steps....

the use of  ALSA LIB  plugins is straight  like  -D plug:[ANY_DEFINED_PLUG_THING]

But...   your system REQUIRE  the proper  CARD DEFINITIONs  and  NODES (which mostly are messed and subverted by systemd and other problems...)

Basically you just invoke  aplay or arecord with the required config plugin

I have some dozens plugins defined to handle al sort of complex things...
including AUDIO ROUTING..  when not using JACK


Paul

Please just stop, as mentioned in another thread, I am not just some general Linux user, I am both a kernel developer and one of my specialities is the Linux audio subsystems. I have written both audio drivers, user space audio applications, DSP chains and full audio pipelines/engines for applications. I am a contributor to some of the largest and most widely used FOSS projects that make use of audio under Linux and the author of the audio engine used by Kodi (formally XBMC) where we ripped out all the ALSA asoundrc junk in favour of directly using alsalib correctly. I am currently working on a DSP processing tool for general desktop users that does real time processing of audio without JACK, etc... your claim is nothing special.
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #30 on: August 08, 2022, 01:45:17 pm »
The soundcard is an onboard Sound Core3D (Recon3D internal ?) (ca0132 chipset from Creative, the followup of emu10k1/2, Audigy, etc.), MB ASRock Fatal1ty Z97 Professional, nVidia GTX760/2GB, i7-4790K, 32GB RAM, SSD.

It is known as buggy, long cumbersome history, with drivers windows only then Creative open sourced later the drivers from some other descendent chip (ca0100) and somebody took the time to sniff the messages exchange between the Windows driver and the Windows OS, then he crafted the ca0132 Linux driver.

Only a week ago I was trying to modify the drivers and recompile, but if nobody bothered fixing all that in the last 10 years, then maybe it's not worth doing it, or maybe is not even possible.

Here's a list of bugs I'm seeing:
https://www.eevblog.com/forum/programming/audio-in-linux/msg4308922/#msg4308922

And this is the github repo (inside another topic that might add details about this particular driver, but please keep in mind I was having no idea what I was doing/writing/testing there):
https://www.eevblog.com/forum/programming/compile-a-linux-kernel-module/msg4327933/#msg4327933

Connor McAdams is the one who reversed engineer all then wrote the ca0132 patch for Linux.
This is his hda patch for ca0132 chip:  https://github.com/torvalds/linux/blob/master/sound/pci/hda/patch_ca0132.c
« Last Edit: August 08, 2022, 01:59:23 pm by RoGeorge »
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #31 on: August 08, 2022, 01:55:53 pm »
The soundcard is an onboard...

This is all I needed to see :). Onboard sound devices are notorious for issues like that which you are seeing here, they commonly suffer from interrupt jitter which causes latency issues. If your device is getting into an under/overrun state it can cause stream stalls while it get's reinitialised by alsalib (DMA xfers stop and have to be re-started). Unfortunately when using `arecord` it's impossible to determine when/if these stalls are occurring.

The reason why the C application in the pipeline is avoiding this issue somewhat is due to buffering. I suggest you have a play with different period sizes and buffer sizes with arecord. I see you were specifying the`-B` switch which sets the buffer time, but this doesn't control the number of buffers. The flags to play with are `--period-size` and `--buffer-size`.

The buffer-size is a multiple of the period-size, so to have two periods you would want buffer-size to be 2x the period-size. The minimum periods you can run with is usually 2, but some cards (like onboard) need 3 or more. If this doesn't help also increase your buffer-size. Keep it to powers of 2, for example:

period-size=512
buffer-size=1024

Play with these values and see if it helps your use case. Obviously the larger the buffers, the more latency that will be introduced, however for your use case I don't think that this matters much.
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #32 on: August 08, 2022, 02:47:11 pm »
I ve place to important  tips for properly setting device nodes...

They have been erased....  reason being unknown...

Anyway the tip still holds:  The OP has issues with the system nodes
unless he can properly fix them.. it will be hard to achieve proper ALSA goals.

Posted how nice a CRAPPY ON BOARD MOBO can perform at DAW level this days.. (including low latency MIDI resonse with real time scoring )

also erased...  so ... I rather mention for those who can see how the tips are relevant
to contact me off topic..

anyone welcome . as usual

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #33 on: August 08, 2022, 02:51:24 pm »
I ve place to important  tips for properly setting device nodes...

They have been erased....  reason being unknown...

Off topic and outside the scope of the problem, this is not a full DAW and does not have the issues you are describing. Audio nodes as you understand them are not part of this issue when talking to the hardware directly via `hw` or `plughw`. The issue is the application `baudline` is having a framerate issue when the audio source is coming from arecord, but for some reason the issue is somewhat resolved when coming via another process, this indicates that additional buffering (via stdio) is resolving this problem to a minor extent and points at period counts and buffer sizes being the issue along with the low quality integrated audio solution of the system.

Posted how nice a CRAPPY ON BOARD MOBO can perform at DAW level this days.. (including low latency MIDI resonse with real time scoring )

also erased...  so ... I rather mention for those who can see how the tips are relevant
to contact me off topic..

MIDI has no relationship at all with PCM, completely off topic.
Last warning, stop posting off topic things and over-complicating an issue that is already being diagnosed.
« Last Edit: August 08, 2022, 02:54:09 pm by gnif »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #34 on: August 08, 2022, 02:53:14 pm »
I ve place to important  tips for properly setting device nodes...

They have been erased....  reason being unknown...

Off topic and outside the scope of the problem, this is not a full DAW and does not have the issues you are describing. Audio nodes do not play a part when talking directly to the hardware via `hw` or `plughw`.

Posted how nice a CRAPPY ON BOARD MOBO can perform at DAW level this days.. (including low latency MIDI resonse with real time scoring )

also erased...  so ... I rather mention for those who can see how the tips are relevant
to contact me off topic..

MIDI has no relationship at all with PCM, completely off topic.
Last warning, stop posting off topic things and over-complicating an issue that is already being diagnosed.

They just do.

Unless properly set the applications will not find them...

and  i am guessing that in the OP case... he has a void node being read from the PIPE.

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #35 on: August 08, 2022, 02:55:16 pm »
They just do.

The MIDI device will not even be open! far out, learn how ALSA works, the PCM device is what is in use here, stop polluting this thread.

https://github.com/alsa-project/alsa-utils/blob/master/aplay/aplay.c#L829
Quote
err = snd_pcm_open(&handle, pcm_name, stream, open_mode);

Is not `snd_rawmidi_open`
« Last Edit: August 08, 2022, 03:00:50 pm by gnif »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #36 on: August 08, 2022, 02:58:50 pm »
They just do.

The MIDI device will not even be open! far out, learn how ALSA works, the PCM device is what is in use here, stop polluting this thread.

My dear... of course  MIDI has not been the OP issue.

But it is a very important issue TO SETUP THE WHOLE SYTEM NODES.

I ve posted how a cheap crap on board mobo CAN PERFORM REAL TIME..

AUDIO AND MIDI AND SCORING in real time..

YOU probable erased..  and threats to myself will not  change the issue.

The system must be  set... the nodes must be valid..

AND ... real time performance will be fine..

If you feel fine to insult me and ban me go ahead...

It seems to be your goal..

Paul
« Last Edit: August 08, 2022, 03:05:24 pm by PKTKS »
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 6733
  • Country: pl
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #37 on: August 08, 2022, 03:00:23 pm »
To my ubuntu 20.04, arecord with plughw: (or sox, or ffmpeg) gives a much slower refresh in baudline making it all jumpy, than using hw: and converting to float with C.  Found the -scaleby parameter in baudline, thought that would be a strange number to remember 1/65536=0.0000152587890625, so conversion with C is more convenient and doesn't slow down the framerate.
Don't think so.
Quote
If the floating point samples are in the domain {-1., +1.} then the proper scaling command would be "-scaleby 32767."

I don't know what "slow down the framerate" means; it's probably some buffering problem - not enough or too much?.
I would start with recording something to file and tweaking those buffer/period options until the recording comes out right.

If WAV files record right but piping the same data into baudrate doesn't, then maybe you need more buffering. Perhaps insert cat between them? :o
 
The following users thanked this post: gnif

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #38 on: August 08, 2022, 03:06:19 pm »
But it is a very important issue TO SETUP THE WHOLE SYTEM NODES.

 :palm: By using `hw` or `plughw` you bypass the entire asound system and take direct control of the hardware device.... it BYPASSES the system you're talking about. Again learn how the ALSA subsystem works,

https://github.com/torvalds/linux/blob/20cf903a0c407cef19300e5c85a03c82593bde36/sound/core/pcm_native.c#L2820

`snd_pcm_open` calls directly into the kernel and when we use `hw` there is a direct line to the hardware, NOTHING IN THE MIDDLE.
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #39 on: August 08, 2022, 03:10:36 pm »
ok my dear..  i take none of your insults...

Facts still remain...    you remind me my 7y old bonehead nephew...

Impossible just so..

GO ahead delete my stuff and  ban me ....    nothing prevents you..

erasing my posts will not change facts.

My stuff is working flawless based on what you think wrong

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #40 on: August 08, 2022, 03:15:34 pm »
If you feel fine to insult me and ban me go ahead...

It seems to be your goal..

If it were my goal you would already be banned mate. I have stated several times now to stop posting your suggestions here because they are WRONG. I have 15 years of experience with ALSA and how the internals of it work, including the emu10k core that the OPs sound card is loosely based on, along with the issues that it had when creative stopped support of it.

ok my dear..  i take none of your insults...

I have not insulted you once.

Facts still remain...    you remind me my 7y old bonehead nephew...

Yet here you are insulting me

Impossible just so..

GO ahead delete my stuff and  ban me ....    nothing prevents you..

Except the fact that I have been trying to give you a fair chance...

erasing my posts will not change facts.

They will remove useless information that has no bearing on this issue.

My stuff is working flawless based on what you think wrong

EXACTLY, YOURS
YOUR SETUP
YOUR HARDWARE
YOUR CUSTOM KERNEL,
YOUR CUSTOM REQUIREMENTS
YOUR DAW SETUP.

This is NOT what the OP has, nor what the OP is dealing with, nor does the OP need real time processing as is needed in a DAW setup.

Final warning, I have been more than fair here.
« Last Edit: August 08, 2022, 03:20:01 pm by gnif »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #41 on: August 08, 2022, 03:19:26 pm »
You already  deleted so much relevant stuff...

to twist support your point and make me look like a rant idiot...
Included some hard to find clues...

Anyone reading this crippled (by you) posts will have a hard time to understand.
Nevertheless.  Anyone interested is very welcome to contact me with such issues.

I have not much to add...as half you deleted...

It  adds up even being worst than ban.. being crippled by twisted deformed opinions

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #42 on: August 08, 2022, 03:23:25 pm »
You already  deleted so much relevant stuff...

to twist support your point and make me look like a rant idiot...
Included some hard to find clues...

Anyone reading this crippled (by you) posts will have a hard time to understand.
Nevertheless.  Anyone interested is very welcome to contact me with such issues.

I have not much to add...as half you deleted...

It  adds up even being worst than ban.. being crippled by twisted deformed opinions

Paul

Keep stating this crap, we do not delete posts but move them to a deleted topic where all moderators and server admins can see the full history.  I have posted exactly why you are wrong and provided proof positive of your misinformation in both the ALSA sources and the Linux kernel itself and removed your posts you posted AFTER I told you to stop because it's miss-information and is not helpful here.

As for your deleted posts, you offered a few screenshots and a script... none of the information provided was useful in any meaningful sense. The issue here is a buffering/interrupt issue, it may even be an issue with the baudline application itself, we have not yet isolated the exact cause, but your continual insistence that its the configuration of a subsystem we have bypassed entirely is just idiotic.
« Last Edit: August 08, 2022, 03:27:24 pm by gnif »
 

Offline PKTKS

  • Super Contributor
  • ***
  • Posts: 1766
  • Country: br
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #43 on: August 08, 2022, 03:27:55 pm »
You stated very well:

- i HAVE MY STUFF (  kernel, DAW etc_etall ) working fine.

I just have no other way to put examples to the OP or anyone else...

Can you image a better example than a FULLL WORKING SYSTEM..

You deleted them and still threat me to your biased already conceived point.

My examples hold just true.. working fine.. and *ANYONE* can duplicate them..

point is .. you deleted them...

What is it??  it bothers you?   you feel envy?  threats make you feel good?

Paul
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #44 on: August 08, 2022, 03:31:19 pm »
- i HAVE MY STUFF (  kernel, DAW etc_etall ) working fine.

So you think the OP has the same sound device as you with the same buffering configuration, the same IRQ patterns, the same codecs, and all your configurations apply to his device also?

Can you image a better example than a FULLL WORKING SYSTEM..

Yes, one that matches the OPs hardware and replicates his issue so it can be properly diagnosed.

I have had enough, you have had enough warnings.
Edit: PKTKS temp banned for 3 days
« Last Edit: August 08, 2022, 03:37:29 pm by gnif »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6171
  • Country: fi
    • My home page and email address
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #45 on: August 08, 2022, 03:37:30 pm »
The reason why the C application in the pipeline is avoiding this issue somewhat is due to buffering. I suggest you have a play with different period sizes and buffer sizes with arecord. I see you were specifying the`-B` switch which sets the buffer time, but this doesn't control the number of buffers. The flags to play with are `--period-size` and `--buffer-size`.
That's exactly why I wrote my suggested converter like I did: it does the minimum number of read syscalls to obtain the data already in the pipe that consists of an integer number of convertable samples, up to a compile-time maximum number of samples, and immediately forwards the converted data.  Should yield minimum latency, but will be affected by those arecord options.

If one uses <stdio.h> fread()/fgetc()/getc()/getchar()/fwrite()/fputc()/putchar(), then the standard C library will add its own software buffers in between.

While I do approve of the Unix philosophy, and chaining tools to get complex stuff done, when they start interfering with the task at hand (like dropping data), it is time to write a better tool, methinks.

I already have written a clunky VU meter program for Ed Kloonk on top of ALSA in a different thread around here somewhere, that could be easily modified to do e.g. DFT on the raw data using just ALSA and FFTW3 libraries (and Gtk+3 for the UI, if desired).  Then, the optimum buffer/period size would be either one or one half DFT/FFT window (depending on the desired overlap and windowing function).  This stuff isn't difficult, except when you are also fighting against hardware or its drivers... (or are a burned out husk of a man like I am, I guess; which is why I never finished that program for Ed.  Sorry, Ed.)
 
The following users thanked this post: Ed.Kloonk, gnif

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #46 on: August 08, 2022, 03:45:12 pm »
The reason why the C application in the pipeline is avoiding this issue somewhat is due to buffering. I suggest you have a play with different period sizes and buffer sizes with arecord. I see you were specifying the`-B` switch which sets the buffer time, but this doesn't control the number of buffers. The flags to play with are `--period-size` and `--buffer-size`.
That's exactly why I wrote my suggested converter like I did: it does the minimum number of read syscalls to obtain the data already in the pipe that consists of an integer number of convertable samples, up to a compile-time maximum number of samples, and immediately forwards the converted data.  Should yield minimum latency, but will be affected by those arecord options.

If one uses <stdio.h> fread()/fgetc()/getc()/getchar()/fwrite()/fputc()/putchar(), then the standard C library will add its own software buffers in between.

While I do approve of the Unix philosophy, and chaining tools to get complex stuff done, when they start interfering with the task at hand (like dropping data), it is time to write a better tool, methinks.

I already have written a clunky VU meter program for Ed Kloonk on top of ALSA in a different thread around here somewhere, that could be easily modified to do e.g. DFT on the raw data using just ALSA and FFTW3 libraries (and Gtk+3 for the UI, if desired).  Then, the optimum buffer/period size would be either one or one half DFT/FFT window (depending on the desired overlap and windowing function).  This stuff isn't difficult, except when you are also fighting against hardware or its drivers... (or are a burned out husk of a man like I am, I guess; which is why I never finished that program for Ed.  Sorry, Ed.)

Indeed, which is why I believe arecord piped to the tool should also work fine if the buffering parameters are adjusted, but in reality we are at the mercy of the drivers and whatever the closed source `baudline` application is doing with the data as it takes it... I mean, `baudline` itself may be stalling due to some other factor for just long enough to cause a problem due to other workloads on the system, etc.

While I can make it work fine for me, on my "unconfigured nodes" system ( :-DD), my hardware is very very different from the OPs hardware, and as such all factors must still be considered.
« Last Edit: August 08, 2022, 03:55:56 pm by gnif »
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #47 on: August 08, 2022, 04:01:08 pm »
Playing more with baudline, seems there are some stats you can obtain that might be useful (attached).
Right click and select `stats`, see if the video FPS and transforms/sec remains stable, it might help indicate if you have a general system performance issue with this workload.
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #48 on: August 08, 2022, 05:04:26 pm »
Yes, noticed it, the 60+ fps I was writing to have when pipe-ing with C is from the baudline's stats 'menu'.

Just to be sure I'm not misleading, the whole sound works OK in audio/video play, and record, and all the daily use of the desktop.

The choppy thing of 5fps-like only happens while measuring the spectrum with baudline, and it does not affect anything else except the spectrum image rolling in baudline.  Movies or audio plays fine at the same time with a choppy spectrogram in baudline.
 
I guess the same, some buffering problem specific to baudline only, except it doesn't worth investigating since I can just avoid it by pipe-ing through the C converter.

This is all hobby level, curious to see baudline plots with 24bits and how they differ from the ones in 16 bits, which thanks to everybody's help here, now I've learn how to do that.  I'm sorry to see bans because of my topic.  :-[
« Last Edit: August 08, 2022, 05:08:09 pm by RoGeorge »
 

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #49 on: August 08, 2022, 05:08:42 pm »
I'm sorry to see bans because of my topic.  :-[

Not your fault mate, don't feel bad. Unfortunately Linux audio is one of those topics that has a million "experts" but very few actual engineers. Thankfully you have had help from those that understand the issue here and have found you a workable solution.

If you feel this is solved I will lock this thread to prevent it being necro'd when PKTKS is able to participate again.

Btw, I have spent a bit of time trying to replicate your fault and I have found no combination of configurations that trigger it.
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #50 on: August 08, 2022, 05:28:49 pm »
TBH, I don't mind if incorrect info will be posted, no matter by whom, though maybe do not lock the topic, please.

I was planning to test the buferless C convertor from Nominal Animal, and post how it went.  Just that I don't have the time to study his code write now (at a brief look seems something that might be interesting to look closer), but will do it later or tomorrow, and post how it goes.  If you know it will be further rants, then close it and I'll PM to Nominal Animal, or open another one about that code only.  Your call.

From my side, it was solved when I've posted the successfull printscreens and the change in noise floor with 24/16 bits:
thank you all, solved!  :D
« Last Edit: August 08, 2022, 05:32:54 pm by RoGeorge »
 
The following users thanked this post: Ed.Kloonk, gnif

Offline gnif

  • Administrator
  • *****
  • Posts: 1672
  • Country: au
No worries, I will keep this topic on my watch list :)
 
The following users thanked this post: RoGeorge

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6171
  • Country: fi
    • My home page and email address
Re: 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe?
« Reply #52 on: August 08, 2022, 05:58:50 pm »
The choppy thing of 5fps-like only happens while measuring the spectrum with baudline, and it does not affect anything else except the spectrum image rolling in baudline.
Well, 5 fps corresponds to a DFT with a 0.2 second non-overlapping windows, doesn't it?  Does the minimum frequency happen to be 5 Hz?

I do not use baudline, so I don't know how tunable its spectrum (discrete Fourier transform) options are.

When using FFTW3 for DFTs, it is important to gather "wisdom" first for the selected window size.  This "wisdom" is, simply put, testing the different methods of performing the same DFT (window size); and once found, will yield an extremely fast transform on that machine on that window size.

The window size affects the spectrum, because the windowing function itself acts like a filter.  (When the window is advanced by half, and the spectra summed, you often get a better time-domain representation of the actual signal spectrum.  You could, in theory, advance the window sample by sample, but that just interpolates between the different spectrum representations, and does not really provide any new information.)

A windowed sinc has a very steep (but not vertical) stop band, and very flat low pass response, so it works well for this kind of application.  It would be more efficient if it was incorporated into the DFT (it would save one multiplication per sample), but any desktop machine should be able to do this without noticeable slowdown at any window size one wants.  (Just remember that with a minimum frequency f, the maximum update rate is 2/f when advancing each DFT by half a window, and 1/f when advancing each DFT by a full window.  Having audio buffers of exactly 2/f or 4/f in duration will yield minimum latency and best overall throughput.  4/f buffer size can help on multicore machines.)

In practice, the program would reads those fractional buckets of samples, then apply the windowed sinc to 2 or 4 consecutive buckets, and DFT to the windowed samples.  (The older half of the buckets can then be recycled for new data.)  The DFT consists of complex numbers, with the absolute magnitude (square root of the sum of squared real and imaginary parts) the interesting part, and the phase (atan2(imaginary part, real part)) often not visualized at all.  This is then updated to the display, usually as a graph, but sometimes also as an intensity diagram in a continuously scrolling display.  Each of these sub-tasks are well handled by a separate thread, utilizing multiple cores simultaneously.  Whenever the DFT-to-absolute-magnitudes thread has constructed a new spectrogram, it just pushes it to a queue and uses a thread-safe notification to tell the UI toolkit that a new one is available for drawing.  The total latency does depend on how much "extra" CPU power you have available, but should not be more than one or two full windows in duration, which should be acceptable for realtime spectrum analysis.

I was planning to test the buferless C convertor from Nominal Animal, and post how it went.
That would be nice.  Note that if you want to add scaling, say divide each sample by 32768, you can just change the conversion loop to
Code: [Select]
        /* Convert to float. */
        {
            float *const end = out_buf + have;
            int32_t     *src = in_buf;
            float       *dst = out_buf;

            while (dst < end)
                *(dst++) = (float)(*(src++)) / 32768.0f;
        }
or, if you prefer to do something more complicated, then maybe
Code: [Select]
        /* Convert to float. */
        {
            float *const end = out_buf + have;
            int32_t     *src = in_buf;
            float       *dst = out_buf;

            while (dst < end) {
                const double  sample = *(src++);
                *(dst++) = sample / 32768.0;  /* Or some other operations */
            }
        }
It is not technically bufferless either; it does read up to BLOCK_SIZE samples, whatever is immediately available in the pipe, and converts and outputs them as soon as possible.  It never waits for more data to arrive, unless it has already processed all previous data (or the data it has does not end at a sample boundary).  You can increase BLOCK_SIZE if you want (by using gcc -Wall -O2 -DBLOCK_SIZE=65536 ... when compiling); anything above 262144 is probably useless (because of the default pipe size limitation in Linux).  A more proper term would be that it tries to use the same buffer size as its input, but not more than BLOCK_SIZE samples at a time.
 

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6146
  • Country: ro
Wow, 125+ fps, thank you!  :-+

So far that's the fastest one.  BLOCK_SIZE almost doesn't matter, but I had to specify 10ms of buffer in arecord (-B 10000).  Though, the code is way over my head, with all the low access and the inline.  :)

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6171
  • Country: fi
    • My home page and email address
Well, in case it is useful, let me describe the code!

If not, no worries; I'm perfectly happy to just leave this here in case someone else may stumble on this later on, and find this useful.



The core idea is that we use a single read() call, to obtain whatever is already available in the pipe.  See man 7 pipe, especially the "I/O on pipes" and "Pipe capacity" sections.
To avoid dealing with partial samples, we do additional reads until we have a multiple of 4 characters (or a full BLOCK_SIZE) buffer.

If read() returns 0, it indicates end-of-input, and the program can exit.  For simplicity, if this occurs after a supplementary read because the previous ones did not return an integral number of samples, we just throw the entire tail buffer away.  (This really is intended to be used as a real-time continuous data filter.  To fix this, we could just set a flag ending the outer loop iterations, instead of immediately returning.)

Read should return -1 on error, but because of certain old bugs related to 32-vs-64, I like to consider all other negative values as EIO errors as well. 
(Technically, read will return -1 with errno==EINTR when a signal is delivered to a handler installed without SA_RESTART flag; and if nonblocking, errno==EAGAIN or errno==EWOULDBLOCK if no data is available, but since this program does not have userspace handlers and we can assume standard input and output are blocking (uh, not nonblocking), we can safely consider all of them as actual errors the program cannot deal with.)

On the write side, we assume that most reads we do, are of the original buffer size, and therefore will fit in the output buffer in a single write() call.
However, the code does not assume that indeed will happen every single time.  Instead, it uses a loop to write the entire converted output buffer.
See the man 2 write man page for description and rules and further information.

Since there is no cleanup or such to do when complete, and we exit the program directly from within the loop, the outermost loop is an forever one.  I prefer while (1) { ... }, some others prefer for (;;) { ... }; either one would work fine here.

The logic thus described, let's open up the important parts.  For clarity, I'll separate each chunk with a horizontal line.


Code: [Select]
        /* Read some (complete) int32_t's. */
        do {
            ssize_t  n = read(STDIN_FILENO, (char *)(in_buf) + have, (sizeof in_buf) - have);
            if (n > 0)
                have += n;
            else
            if (n == 0)
                return EXIT_SUCCESS;
            else
                return EXIT_FAILURE;
        } while (!have || (have % sizeof in_buf[0]));
Since read() returns the number of chars, we need to consider our input buffer as a buffer of chars.  Within this loop, have is the number of chars we already have in the in_buf.  Thus, (char *)(in_buf) + have is a pointer to the first unused char in in_buf, and (sizeof in_have) - have is the number of unused chars in it.  We do a read(), trying to fill the rest of the buffer –– and since have starts at 0, we actually try to fill in the entire buffer.  Of course, read() will block until there is some data in the pipe, and then return that data (up to the limit we specified).  n is the actual number of chars we read.

The if clauses check if we did get any data; and if not, exit the program.  (Again, while we may throw some already read data away, we only do so if that already read data did not end at a valid sample boundary.  To me, this is sufficient to indicate the tail part of the data is suspect anyway, and not worth processing.)

The loop condition can be read in English as "as long as have is zero, or have is not a multiple of the size of in_buf array elements".



After the above loop is done, we divide have by the size of the in_buf array elements, so that it becomes the number of samples we have in in_buf.  Because our reads started at the beginning of the buffer, we know the data is properly aligned.  Essentially, it is at this point that we change from treating the input as a stream of chars, and interpret it as the representation of the in_buf array elements.
(I like having such clear logical transition points.)



The next part, the conversion from in_buf to [/tt]out_buf[/tt],
Code: [Select]
        /* Convert to float. */
        {
            float *const end = out_buf + have;
            int32_t     *src = in_buf;
            float       *dst = out_buf;

            while (dst < end)
                *(dst++) = *(src++);
        }
is just the pointer version of the simple loop
Code: [Select]
        for (size_t i = 0; i < have; i++) {
            out_buf[i] = in_buf[i];
        }
Why did I write it in the pointer form, when the simple loop is so much more readable?

I'm a creature of habit, and GCC used to generate better code when using pointers, compared to array indexing, on some architectures.  x86 and x86-64 has powerful indexing built in to its instruction set, so the simple loop tended to be preferable on x86 and x86-64 even using old versions of GCC.
Or, you could equally say that since I was in the pointer-logic-thought-mode when writing the code, I just didn't stop and think before I wrote the loop, and just let my fingers type the solution when I was already thinking something else.

Even examining the possible code generated for the two loops at Compiler Explorer shows I really should have written the array indexing form instead.  What can I say in my defense? I never claimed the code was the best I could think of, I only indicated this would be something I would write in a couple of minutes to perform the task I needed it to perform, with the logic I described above.
There is always room to learn and improve.



The final part, writing out the (full) converted out_buf, uses the same logic as the read loop, except that this time we loop until the buffer is empty.
end points to just past the final char to be written.  Note how (char *)(out_buf + have) is the char pointer to the have'th element; it is deliberately NOT (char *)(out_buf) + have, which would be a char pointer to the have'th char.

Both ptr (pointing to the first char that needs to be written) and end (pointing to the char following the last char to be written, or first char after the data to be written) are pointers to char, because that is the units in which write() operates with.  The expression (size_t)(end - ptr) is the number of chars between the two pointers, if and only if ptr <= end (or equivalently end >= ptr).

The loop condition can be read in English as "while we have chars between ptr and end to be written":
Code: [Select]
            while (ptr < end) {
                ssize_t  n = write(STDOUT_FILENO, ptr, (size_t)(end - ptr));
                if (n > 0)
                    ptr += n;
                else
                    return EXIT_FAILURE;
            }
All error cases are aggregated into the else clause.  In theory, write() should return a positive value, or -1 with errno set (see man 2 write for a full description).  I personally consider both other negative values and 0 as equivalent to EIO error.  (For descriptions of errno codes, see man 3 errno.)

Again, the key thing is that the low-level write() call does not necessarily write the entire buffer: it can return a short count, for example because the output is to a pipe and the pipe is nearly full (because the reader is too slow in reading).  It will block until at least one char is written, or an error occurs.
(If the descriptor was nonblocking, it would return -1 with errno==EINTR if interrupted by a signal delivery to an userspace handler installed without SA_RESTART flag, errno==EAGAIN or errno==EWOULDBLOCK if nothing can be written immediately, and so on.)

If standard output is a pipe, and the read end closes its end, this process will be killed by the SIGPIPE signal the first time we attempt to write to the pipe after the read end is closed.  We can catch or ignore that signal, in which case the write would fail with errno==EINTR or errno==EPIPE.  However, since being killed by SIGPIPE is fine with us (humans using this tool for its stated purpose), we don't need to worry about this either.



Even if you personally –– whoever might be reading this post –– do not find this useful, I think there is a chance it might be useful to someone finding this thread later using a web search due to the keywords (stdin stdout pipe conversion).

In particular, those comparing the freestanding implementation to the standard library implementation might find it useful, because it illustrates how the logic stays the same even when the standard library is not used, and how GCC/clang/ICC extended assembly can be used to interface to something completely different (in this case, to a Linux kernel providing us with read, write, and exit/exit_group syscalls).

In a very real way, the standard C library is an abstraction we can replace if we wanted to, as long as we can devise a sane/effective/acceptable function interfaces for the things we need.  Because the three syscalls this program needs are so simple, I just used the Linux equivalents for the API.
In particular, in all Linux architectures, sizeof (long) == sizeof (void *), so that long has the same properties as intptr_t, and unsigned long the same properties as uintptr_t.  This is why the function wrappers around the syscalls use long and not some other type.  Other, non-Linux (compatible) systems, use other conventions, so this is the kind of thing one has to think about when creating interfaces and APIs in freestanding C.
 
The following users thanked this post: Ed.Kloonk, gnif, phs, newbrain, RoGeorge


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf