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

0 Members and 1 Guest are viewing this topic.

Offline RoGeorgeTopic starter

  • Super Contributor
  • ***
  • Posts: 6848
  • 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 »
 

Online golden_labels

  • Super Contributor
  • ***
  • Posts: 1403
  • 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: 4267
  • 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

Online magic

  • Super Contributor
  • ***
  • Posts: 7293
  • 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: 26
  • 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: 4267
  • 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: 26
  • 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: 7072
  • 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: 7072
  • 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: 6848
  • 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.

Online magic

  • Super Contributor
  • ***
  • Posts: 7293
  • 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: 6848
  • 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

Online magic

  • Super Contributor
  • ***
  • Posts: 7293
  • 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: 1711
  • Country: au
  • Views and opinions are my own
    • AMD
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: 6848
  • 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: 6848
  • 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: 1711
  • Country: au
  • Views and opinions are my own
    • AMD
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: 6848
  • 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: 1711
  • Country: au
  • Views and opinions are my own
    • AMD
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: 1711
  • Country: au
  • Views and opinions are my own
    • AMD
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
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf