Lazarus

Programming => General => Topic started by: AL on July 28, 2020, 08:07:43 pm

Title: Help with Alsa C conversion.
Post by: AL on July 28, 2020, 08:07:43 pm
I am trying to convert a C or C++ code to Lazarus.  I am using fpAlsa.
So far I have a working code to list the PCM devices except that at the end of the list I do not detect the end of the array and this generate a SIGSEV error.

Here is the original C code:
Code: C  [Select][+][-]
  1. char **hints;
  2. /* Enumerate sound devices */
  3. int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
  4. if (err != 0)
  5.    return;//Error! Just return
  6.  
  7. char** n = hints;
  8. while (*n != NULL) {
  9.  
  10.     char *name = snd_device_name_get_hint(*n, "NAME");
  11.  
  12.     if (name != NULL && 0 != strcmp("null", name)) {
  13.         //Copy name to another buffer and then free it
  14.         free(name);
  15.     }
  16.     n++;
  17. }//End of while
  18.  
  19. //Free hint buffer too
  20. snd_device_name_free_hint((void**)hints);
  21.  
  22.  

My translation is this:

Code: Pascal  [Select][+][-]
  1. Procedure ListDevices ;
  2. Var Hints : PPPointer ;
  3.     err,i   : integer;
  4.     n     : PPointer ;
  5.     Name,Desc,IOID :Pchar ;
  6.  
  7. begin
  8.  
  9.   new(Hints);
  10.   err := snd_device_name_hint(-1,'pcm', Hints) ;
  11.   if err <> 0 then begin
  12.     ShowMessage('Error accessing devices, Error:'+ inttoStr(err));
  13.     exit ;
  14.   end;
  15.   showMessage('No Error in hint');
  16.  
  17.   n := Hints^  ;
  18.  
  19.    try
  20.     try
  21.      while (n <> nil)    do begin
  22.         name := snd_device_name_get_hint(n^,Pchar('NAME')) ;
  23.         desc := snd_device_name_get_hint(n^,'DESC') ;
  24.         IOID := snd_device_name_get_hint(n^,'IOID') ;
  25.        If IOID <> '' then
  26.           if IOID = 'Input' then
  27.                Showmessage(string(Name) + ' | ' + String(desc) + ' | ' + string(IOID)) ;
  28.  
  29.       Inc(N) ;
  30.    //If snd_device_name_get_hint(n^^,Pchar('NAME')) ='' then break;
  31.       end;
  32.   except
  33.     on E: Exception do
  34.         ShowMessage(E.Message);
  35.    end;
  36.  
  37.   finally
  38.   showmessage('end of List') ;
  39.   end;
  40.  
  41.   err := snd_device_name_free_hint (hints^) ;
  42.  
  43.   if err <> 0 then begin
  44.     ShowMessage('Error freeing hints, Error:'+ inttoStr(err));
  45.     exit ;
  46.   end;
  47.     showMessage('No Error in free');
  48. end;                  
  49.  

The types from fpAlsa are :
PPointer            = ^Pointer;
  PPPointer           = ^PPointer;

My problem is that while (n <> nil)  is never detected.
Is there a mean to detect the end of this "C" array of pointer?

Thanks.

     
Title: Re: Help with Alsa C conversion.
Post by: Warfley on July 28, 2020, 08:19:29 pm
Code: C  [Select][+][-]
  1. while (*n != NULL) {
dereferences n, so the pascal code must be:
Code: Pascal  [Select][+][-]
  1.      while (n^ <> nil)

PS: your code produces a bunch of memory leaks, try sticking to the c code, because it doesn't
Title: Re: Help with Alsa C conversion.
Post by: AL on July 28, 2020, 10:56:54 pm
Code: C  [Select][+][-]
  1. while (*n != NULL) {
dereferences n, so the pascal code must be:
Code: Pascal  [Select][+][-]
  1.      while (n^ <> nil)
I was sure I tried that, but evidently not.  It does work now.  Thank you

Quote
PS: your code produces a bunch of memory leaks, try sticking to the c code, because it doesn't
Sure will do!  :-)
Title: Re: Help with Alsa C conversion.
Post by: marcov on July 28, 2020, 11:14:19 pm
Code: C  [Select][+][-]
  1. char **hints;
  2.  
  3. int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
  4.  

Note that a double pointer is declared, but the address is typecast (to triple void pointer) and passed to the function. Effectively this is like passing a double pointer as VAR.

If the header is the same as the C (triple pointer), then your code is wrong. If your code has an attempt to ease with a VAR hints :ppchar or var hints:ppointer  declaration, then its is ok.

[/code]
Title: Re: Help with Alsa C conversion.
Post by: jamie on July 29, 2020, 01:45:18 am
If I understand the C code correctly, the first function is returning an allocated pointer from the C function so this means you do not use NEW to create it because at the end it is calling another C function to free the resources.

 So all that is needed is a double pointer..

 PPounter should be ok and the first initial call defines it using the @Hint;

 It will store a pointer value to be used indirectly from which the compiler should be able to translate it.

Title: Re: Help with Alsa C conversion.
Post by: AL on July 29, 2020, 02:09:53 am
The translated procedure in fpAlsa use a PPPointer as parameter for hints.
So I have no choice but to use that.
Tried without a New and got a sigsev.

As I said, it is working fine now.
Thanks

Title: Re: Help with Alsa C conversion.
Post by: Warfley on July 29, 2020, 02:57:37 am
During my last post I didn't have had much time at hand, but now I have, here is my translation:
Code: Pascal  [Select][+][-]
  1. var
  2.   hints, n: PPChar; // the same as char ** in C
  3.   err: Integer;
  4.   rawName, rawDesc, rawIOId: PChar;
  5.   name, desc, IOId: String;
  6. begin
  7.   err := snd_device_name_hint(-1, "pcm", PPPointer(@hints)); // note that a pointer to a PPChar is a PPPChar, which can be casted to PPPointer
  8.   if err <> 0 then
  9.   begin
  10.     Exit; // you probably want to raise an exception here
  11.   end;
  12.   try
  13.     n := hints;
  14.     while Assigned(n^) do // same as n^ <> nil, I just like the Assigned syntax more
  15.     begin
  16.       // recieve PChar data
  17.       rawName = snd_device_name_get_hint(n^, 'NAME');
  18.       rawDesc = snd_device_name_get_hint(n^, 'DESC');
  19.       rawIOID = snd_device_name_get_hint(n^, 'IOID');
  20.  
  21.       // copy to pascal string to make handling easier
  22.       name := rawName;
  23.       desc := rawDesc;
  24.       ioid := rawIOID;
  25.  
  26.       // After we ported these strings to pascal we can free the memory allocated as PChar
  27.       // Probably this requires the use of the C memory manager (unit cmem) or the use of a specialized function provided by the lib
  28.       // simply by the names I would think that this is probably snd_device_name_free_hint(PPointer(@rawName)); but thats just a guess
  29.       // but I'm pretty sure the following is pretty broken without a c memory manager
  30.       // you should probably also check if the pointer is already nil so you don't try to free nil
  31.       FreeMem(rawName);
  32.       FreeMem(rawDesc);
  33.       FreeMem(rawIOID);
  34.  
  35.       if (Name.Length > 0) and (Name <> 'null')
  36.       and (Desc.Length > 0) and (Desc <> 'null')
  37.       and (IOID.Length > 0) and (IOID <> 'null') then
  38.       begin
  39.           // do whatever you want here
  40.       end;
  41.       Inc(n);
  42.     end; // While
  43.   finally
  44.     //Free hint buffer too
  45.     snd_device_name_free_hint(PPointer(hints));
  46.   end;
  47. end;

Note, rather than
Code: Pascal  [Select][+][-]
  1. n := hints;
  2. while Assigned(n^) do
  3. begin
  4.  ...
  5.  Inc(n);
  6. end
you could use an integer variable i like this:
Code: Pascal  [Select][+][-]
  1. i := 0;
  2. while Assigned(hints[i]) do
  3. begin
  4.  ...
  5.  Inc(i);
  6. end
Unless you are in mode Delphi, there you can't access Pointers like arrays (well you can but only primitive types like PByte)
Title: Re: Help with Alsa C conversion.
Post by: TRon on July 29, 2020, 04:24:21 am
After we ported these strings to pascal we can free the memory allocated as PChar
cmem.free
Quote
Probably this requires the use of the C memory manager (unit cmem) or the use of a specialized function provided by the lib simply by the names I would think that this is probably snd_device_name_free_hint(PPointer(@rawName));
afaik snd_device_name_free_hint() should only be used for the hints list. I've never seen it used for anything else.

Quote
you should probably also check if the pointer is already nil so you don't try to free nil
Definitely.
Title: Re: Help with Alsa C conversion.
Post by: AL on July 29, 2020, 05:06:00 am
Thanks, will try that tomorrow when I get a minute.
Title: Re: Help with Alsa C conversion.
Post by: Warfley on July 29, 2020, 01:49:33 pm
cmem.free

It should be noted that for using cmem, it must be the very first unit in the uses clauses auf your lpr file, because it registers a new memory manager and other units might have initialization blocks so the cmem unit  needs it's initialization to be executed before any other
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on July 29, 2020, 02:20:21 pm
cmem.free

It should be noted that for using cmem, it must be the very first unit in the uses clauses auf your lpr file, because it registers a new memory manager and other units might have initialization blocks so the cmem unit  needs it's initialization to be executed before any other

Followed immediately by HeapTrc if necessary. This is the sort of thing that's worked for me in the past:

Code: [Select]
(* Note manual addition of cmem below, this is required to allow strings and    *)
(* objects to be shared/transferred between the main program and a shared       *)
(* library. Note also relative position of HeapTrc, if cmem is used it is not   *)
(* possible to specify this at the project level (i.e. use FPC's -gh option).   *)

uses
{$ifdef USE_CMEM }
  cmem, {$ifdef USE_HEAPTRC } HeapTrc, {$endif }
{$endif }
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Interfaces, // this includes the LCL widgetset
  Forms, UnyokedFrontendCode, AlsaMidi, DeviceSelectCode, EventCountersCode
{$ifdef USE_STATIC } , Backend {$else } , DynaMod, BackendDynamic,
Screensaver {$endif USE_STATIC } ;

That was a rather convoluted project which explicitly used cmem so that menus etc. could be copied from a backend shared object plugin to the frontend program. I'm not saying it's perfect, just that it worked for me with ALSA etc.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: marcov on July 29, 2020, 03:11:02 pm
Note that CMEM is only necessary, if the libraries try to free your allocations, or you must free the libraries allocations.

In both cases, the library is defective. In normal cases this shouldn't be needed. I can't even the remember the last case that I needed it.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on July 29, 2020, 03:29:50 pm
Note that CMEM is only necessary, if the libraries try to free your allocations, or you must free the libraries allocations.

In both cases, the library is defective. In normal cases this shouldn't be needed. I can't even the remember the last case that I needed it.

Are you saying that an executable and explicit dynamically-loaded library now share the same FPC memory manager? That certainly didn't used to be the case (although I last looked at in in some depth in about 2013).

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: marcov on July 29, 2020, 03:35:43 pm
Note that CMEM is only necessary, if the libraries try to free your allocations, or you must free the libraries allocations.

In both cases, the library is defective. In normal cases this shouldn't be needed. I can't even the remember the last case that I needed it.

Are you saying that an executable and explicit dynamically-loaded library now share the same FPC memory manager?

No. Should it ?  Both do their own allocation using (ultimately) mmap ? As long as the twain don't meet...

I could be wrong, I don't *nix that much anymore. 
Title: Re: Help with Alsa C conversion.
Post by: PascalDragon on July 29, 2020, 03:52:22 pm
Note that CMEM is only necessary, if the libraries try to free your allocations, or you must free the libraries allocations.

In both cases, the library is defective. In normal cases this shouldn't be needed. I can't even the remember the last case that I needed it.

Are you saying that an executable and explicit dynamically-loaded library now share the same FPC memory manager? That certainly didn't used to be the case (although I last looked at in in some depth in about 2013).

marcov is saying that as long as the used library does not require you to free memory passed back from the library or to free memory passed into the library then you do not need cmem. A shared memory manager is only required if memory allocated in module A is freed in module B. Using memory allocated in module A in module B has never been a problem.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on July 29, 2020, 03:57:40 pm
marcov is saying that as long as the used library does not require you to free memory passed back from the library or to free memory passed into the library then you do not need cmem. A shared memory manager is only required if memory allocated in module A is freed in module B. Using memory allocated in module A in module B has never been a problem.

That seems reasonable, but my recollection is that- certainly at one time- if you wanted to do things like merge a menu defined (in a form) in a plugin into the main form, then you had to use cmem. I might have reason to play with this again at some point, in which case I'll report back.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: PascalDragon on July 29, 2020, 04:20:31 pm
Because here you're mixing memory allocations of the different involved modules. It's already enough if module A tries to deallocate a string that was allocated in module B (and with reference counting that can happen rather quickly)
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on July 29, 2020, 04:33:42 pm
Right. That was the additional case that I tried to highlight several messages ago.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: AL on July 29, 2020, 05:36:40 pm
@warfley : Your code produce a sigsev at the freemem even using Cmem.
I did put Cmem as the first uses in lpr and in the unit.

I am working on the capture part now, I will surely need some guidance on this also.
Will post my work later.
Title: Re: Help with Alsa C conversion.
Post by: TRon on July 29, 2020, 06:09:17 pm
@warfley : Your code produce a sigsev at the freemem even using Cmem.
I did put Cmem as the first uses in lpr and in the unit.
Show us some code, or it didn't happen  :D

Code: Pascal  [Select][+][-]
  1. program hinting;
  2.  
  3. // https://forum.lazarus.freepascal.org/index.php/topic,50782.15.html
  4.  
  5. {$MODE OBJFPC}{$H+}
  6.  
  7. uses
  8.   cmem, baseunix, sysutils, asoundlib;
  9.  
  10. procedure dohop;
  11. var
  12.   hints : ppchar;
  13.   err   : cint;
  14.   n     : ppchar;
  15.   name  : pchar;
  16.   desc  : pchar;
  17.   ioid  : pchar;
  18. begin
  19.   err := snd_device_name_hint(-1, 'pcm', @hints);
  20.   if (err = 0) then
  21.   begin
  22.     n := hints;
  23.     while n^ <> nil do
  24.     begin
  25.       name := snd_device_name_get_hint(n^, 'NAME');
  26.       desc := snd_device_name_get_hint(n^, 'DESC');
  27.       ioid := snd_device_name_get_hint(n^, 'IOID');
  28.  
  29.       if Assigned(name) and (name <> 'null') then
  30.       begin
  31.         WriteLn('       Name of device : ', name);
  32.         WriteLn('Description of device : ', desc);
  33.         WriteLn('   I/O type of device : ', ioid);
  34.         WriteLn;
  35.       end;
  36.  
  37.       if Assigned(name) then CMem.Free(name);
  38.       if Assigned(desc) then CMem.Free(desc);
  39.       if Assigned(ioid) then CMem.Free(ioid);
  40.  
  41.       inc(n);
  42.     end;
  43.  
  44.     // Free hints buffer
  45.     snd_device_name_free_hint(PPointer(hints));
  46.   end
  47.   else WriteLn('Unable to obtain device name hints');
  48. end;
  49.  
  50. begin
  51.   dohop;
  52. end.
  53.  

I've ran that on one of my pi/s:
Code: [Select]
./hinting
       Name of device : default:CARD=ALSA
Description of device : bcm2835 ALSA, bcm2835 ALSA
Default Audio Device
   I/O type of device : Output

       Name of device : sysdefault:CARD=ALSA
Description of device : bcm2835 ALSA, bcm2835 ALSA
Default Audio Device
   I/O type of device : Output

       Name of device : dmix:CARD=ALSA,DEV=0
Description of device : bcm2835 ALSA, bcm2835 ALSA
Direct sample mixing device
   I/O type of device : Output

       Name of device : dmix:CARD=ALSA,DEV=1
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI
Direct sample mixing device
   I/O type of device : Output

       Name of device : dmix:CARD=ALSA,DEV=2
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI1
Direct sample mixing device
   I/O type of device : Output

       Name of device : dsnoop:CARD=ALSA,DEV=0
Description of device : bcm2835 ALSA, bcm2835 ALSA
Direct sample snooping device
   I/O type of device : Output

       Name of device : dsnoop:CARD=ALSA,DEV=1
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI
Direct sample snooping device
   I/O type of device : Output

       Name of device : dsnoop:CARD=ALSA,DEV=2
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI1
Direct sample snooping device
   I/O type of device : Output

       Name of device : hw:CARD=ALSA,DEV=0
Description of device : bcm2835 ALSA, bcm2835 ALSA
Direct hardware device without any conversions
   I/O type of device : Output

       Name of device : hw:CARD=ALSA,DEV=1
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI
Direct hardware device without any conversions
   I/O type of device : Output

       Name of device : hw:CARD=ALSA,DEV=2
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI1
Direct hardware device without any conversions
   I/O type of device : Output

       Name of device : plughw:CARD=ALSA,DEV=0
Description of device : bcm2835 ALSA, bcm2835 ALSA
Hardware device with all software conversions
   I/O type of device : Output

       Name of device : plughw:CARD=ALSA,DEV=1
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI
Hardware device with all software conversions
   I/O type of device : Output

       Name of device : plughw:CARD=ALSA,DEV=2
Description of device : bcm2835 ALSA, bcm2835 IEC958/HDMI1
Hardware device with all software conversions
   I/O type of device : Output

       Name of device : default:CARD=vc4hdmi
Description of device : vc4-hdmi,
Default Audio Device
   I/O type of device : Output

       Name of device : sysdefault:CARD=vc4hdmi
Description of device : vc4-hdmi,
Default Audio Device
   I/O type of device : Output

       Name of device : front:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
Front speakers
   I/O type of device : Output

       Name of device : iec958:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
IEC958 (S/PDIF) Digital Audio Output
   I/O type of device : Output

       Name of device : dmix:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
Direct sample mixing device
   I/O type of device : Output

       Name of device : dsnoop:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
Direct sample snooping device
   I/O type of device : Output

       Name of device : hw:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
Direct hardware device without any conversions
   I/O type of device : Output

       Name of device : plughw:CARD=vc4hdmi,DEV=0
Description of device : vc4-hdmi,
Hardware device with all software conversions
   I/O type of device : Output
Title: Re: Help with Alsa C conversion.
Post by: AL on July 30, 2020, 02:00:50 am
Is there a way to increase microphone gain in ALSA?
I am using a Logitec USB on Ubuntu 20.04 and I need to be very close to the mic to record properly.
Thanks
Title: Re: Help with Alsa C conversion.
Post by: AL on July 30, 2020, 03:21:33 am
Another question,  I would like to use the no Block version which uses threads.
Any hints on using this command:
Code: C  [Select][+][-]
  1.     if ((err = snd_pcm_open(&chandle, cdevice, SND_PCM_STREAM_CAPTURE, block ? 0 : SND_PCM_NONBLOCK)) < 0) {
  2.         printf("Record open error: %s\n", snd_strerror(err));
  3.         return 0;

So far my code for capture is this:

P
Code: Pascal  [Select][+][-]
  1. Procedure StartRecording ;
  2. Const Buffer_Frames = NBPoints;  // in Main   =16384
  3. LABEL DoItAgain ;
  4.  
  5. Var i,err    : Integer;
  6.     //Samplerate     : Int32 = 8192 ; defined in Main
  7.     buffer           : Pchar ;
  8.  // Capture_handle   : Psnd_pcm_t ;  // declared as global to unit
  9.     hw_params        : Psnd_pcm_hw_params_t ;
  10.     format           : snd_pcm_format_t = SND_PCM_FORMAT_S16_LE ;  // 16 bits signed little endian
  11.     direction        : integer = 0 ;
  12.     //Channels       : int32 = 1 ; //mono    In Main
  13. begin
  14.   new(Capture_handle) ;
  15.   new(hw_params) ;
  16.  // volume adjust
  17.  //snd_mixer_selem_id_set_index(sid, 100);
  18. //snd_mixer_selem_id_set_name(sid, Pchar(deviceTable[MainForm.CBListDevices.itemIndex].iname));
  19.  
  20.                                                                                                                         //originally 0
  21.   err := snd_pcm_open(@capture_handle,Pchar(deviceTable[MainForm.CBListDevices.itemIndex].iname),SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK)  ;
  22.   If err < 0 then begin PostError('Error opening PCM') ; Exit ;end;
  23.  
  24.   err := snd_pcm_hw_params_malloc (@hw_params)  ;
  25.  
  26.   If err < 0 then begin PostError('Error hwparams'); Exit ;end;
  27.  
  28.   err := snd_pcm_hw_params_any (capture_handle, hw_params) ;
  29.   if err< 0 then begin PostError('Cannot initialize hardware parameter structure') ; Exit ; end ;
  30.                                                                   // SND_PCM_ACCESS_MMAP_NONINTERLEAVED
  31.   err := snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
  32.   if err < 0 then begin PostError('Cannot access PCM type');Exit;end;
  33.  
  34.   err := snd_pcm_hw_params_set_format (capture_handle, hw_params, format) ;
  35.   if err < 0 then begin Posterror('Cannot set format') ; Exit; end;
  36.  
  37.   err := snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, @samplerate, @direction);
  38.   if err < 0 then begin Posterror('Cannot set rate') ; Exit ; end;
  39.  
  40.   err := snd_pcm_hw_params_set_channels (capture_handle, hw_params, channels) ;   // 1 channel = mono
  41.   if err < 0 then begin PostError('Cannot set channel count') ; Exit ; end;
  42.  
  43.   err := snd_pcm_hw_params (capture_handle, hw_params) ;
  44.   if err < 0 then Begin PostError('Cannot set parameters') ; Exit ; end ;
  45.  
  46.   snd_pcm_hw_params_free (hw_params);
  47.  
  48.   err := snd_pcm_prepare (capture_handle) ;
  49.  
  50.   if err < 0 then begin PostError('Cannot prepare audio interface for use'); Exit ; end;
  51.  
  52.   buffer := malloc (Buffer_Frames * snd_pcm_format_width(format) DIV 8 * Channels);
  53.  // showmessage('Buffer allocated');
  54.  
  55. DoItAgain:
  56.   snd_pcm_wait(capture_handle, 2000);
  57.  
  58.   err := snd_pcm_readi (capture_handle, buffer, buffer_frames);
  59.  
  60.   if err <> buffer_frames then
  61.   begin
  62.         PostError ('Read from audio interface failed. Error = '+inttostr(err));
  63.  
  64. //*****************error -11 is reported here ************
  65.         exit ;
  66.   end
  67.   else
  68.   begin     //we have a full buffer
  69.    //    ShowMessage('Read successfull');
  70.    // here I need to transfer the buffer in the process buffer
  71.  
  72.    FillWord(SavedFrameData,length(SavedFrameData),0);  // remove garbage
  73.    move(buffer^,savedFrameData,buffer_frames*2) ;// CopyMemory(@savedFrameData, @buffer, buffer_frames*2 );     // 2 bytes per frame
  74.   Showmessage('Buffer copied') ;
  75.       // Find the max value
  76.        gMax := 0 ;
  77.        gMaxIndex := 0 ;
  78.        For i := 0 to  length(SavedFrameData)-1 do
  79.         begin
  80.          if abs(SavedFrameData[i])> gmax then       // use only positives values
  81.          begin
  82.           gMax := Abs( SavedFrameData[i]);
  83.           gMaxIndex := i ;
  84.          End;
  85.         End;
  86.         PostError('Gmax ' + inttostr(gmax));
  87.        showmessage('gMAx ' + inttostr(gmax)) ;
  88.        If gMax > gSensitivity then        // Check if level is higher than sensitivity set level
  89.         begin
  90.              MainForm.Timer1.Enabled := False ;      // stop visual buffer
  91.              MainForm.ProgressBar1.Position := 0;
  92.              MainForm.ProgressBar1.Update ;
  93.              MainForm.RBdataReady.checked := true ;     // set an event to draw graphs
  94.  
  95.         end   // Max > sensivity
  96.         else
  97.         begin
  98.          MainForm.ProgressBar1.Position := 0;
  99.          MainForm.ProgressBar1.Update ;
  100.          goto DoItAgain ;
  101.         end;
  102.       End ;    // end buffer complet
  103.       begin
  104.        MainForm.ProgressBar1.Position := 0;
  105.        MainForm.ProgressBar1.Update ;
  106.        goto DoItAgain ;
  107.       end;
  108.  
  109.     Processing := False   ;
  110.     free(Buffer) ;     // should it be free or freemem?
  111.    //showmessage('Buffer freed') ;
  112.    //snd_pcm_close (Capture_handle);
  113.   // ShowMessage( 'audio interface closed');
  114. // if assigned(capture_handle) then free(capture_handle) ;
  115. //  if assigned(hw_params) then free(hw_params) ;
  116. end;      
     

Still very preliminary, adapted from a C code,
It does work when I do not use the NONBLOCK parameter, but it freeze the GUI until finished.
I have added the Uses Cthreads but the 'magic' did not happened.
I guess there is a little more to it.
Title: Re: Help with Alsa C conversion.
Post by: Cyrax on July 30, 2020, 03:37:30 am
You need to create a subclass of TThread class and do your magick in your subclass Execute method.
Title: Re: Help with Alsa C conversion.
Post by: TRon on July 30, 2020, 06:15:51 am
Another question,  I would like to use the no Block version which uses threads.
Which isn't any of your concern as the ALSA library does that. Of course it does have some implications such as some calls will return immediately without waiting.

Code: Pascal  [Select][+][-]
  1.   new(Capture_handle) ;
Why ?

Code: Pascal  [Select][+][-]
  1.   new(hw_params) ;
Why ?

Especially since you are using snd_pcm_hw_params_malloc, as it should.

Later on your code reads:
Code: Pascal  [Select][+][-]
  1. // if assigned(hw_params) then free(hw_params) ;
And that will throw you an error.

Use snd_pcm_hw_params_free() instead.

Code: Pascal  [Select][+][-]
  1. buffer := malloc (Buffer_Frames * snd_pcm_format_width(format) DIV 8 * Channels);
Although it can't hurt, but why not using FreePascal's memory functions here instead of cmem ?

cmem should only be used in case you need to free memory that was allocated by the c-library or in case the c-library free's your memory.

Quote
//*****************error -11 is reported here ************
Actually that is EAGAIN, see https://www.freepascal.org/docs-html/rtl/baseunix/esyseagain.html

Meaning that your buffer wasn't fully saturated and you need to make another call to read.


Code: Pascal  [Select][+][-]
  1. Showmessage('Buffer copied') ;
Better write something to a memo or some other method of outputting.

Code: Pascal  [Select][+][-]
  1.              MainForm.Timer1.Enabled := False ;      // stop visual buffer
using a timer ? To do what exactly ? Note that using a timer could screw things up for you considerably when used in combination with ALSA.

Code: Pascal  [Select][+][-]
  1.  goto DoItAgain ;
... well... i shouldn't have to go into that discussion as some members seem to think it is a perfectly valid method of using goto as long as the code is readable... in my book it is unnecessary evil which has no place at all in Pascal code.

I believe that in the particular case of your code a simple while true do begin (code) end, using continue and break should be able to do the trick as well. But I also believe your loop is wrong (see EAGAIN).

Code: Pascal  [Select][+][-]
  1. free(Buffer) ;     // should it be free or freemem?
  2.  
cmem.free since you allocated it with cmem.malloc.

Quote
It does work when I do not use the NONBLOCK parameter, but it freeze the GUI until finished.
Yeah, no wonder. You need to update the gui (application.processmessages)

Quote
I have added the Uses Cthreads but the 'magic' did not happened.
Threads need to be programmed. Your code doesn't magically becomes threaded by adding the threads unit to your uses clause.

Not that recording requires any threads. Because that is what the non-blocking is meant for, to give you time to update other stuff.

Always, always always use snd_pcm_close after a successful call to snd_pcm_open. ALSA is pretty peculiar when it comes to not releasing memory and/or handles and is able to drive you insane when you run such code multiple times, even after you have corrected your mistakes. Make sure to reset often in case you are starting to doubt yourself  :D

edit: added remarks on hw_params (de)allocaction as I initially missed that.
Title: Re: Help with Alsa C conversion.
Post by: julkas on July 30, 2020, 09:21:40 am
If you don't want cmem , you can call free().
Example - https://ideone.com/xN0Sg5

EDIT - IIRC Alsa is *NIX, so don't use cmem  only for one function, use cmem , if you really need it.
Title: Re: Help with Alsa C conversion.
Post by: AL on August 04, 2020, 03:33:57 am
Thank you all for your comments.
Sorry for the reply delay,  I was out for a few days.
I will study that in the next days and possibly come back with more questions.
Title: Re: Help with Alsa C conversion.
Post by: AL on August 05, 2020, 03:48:12 am
Here is my code for my 2 procedures.  Much better now.
Thanks to all especially TRon who put me on the right track.
I had to use Cmem only to free the name Hint.

There might be still unnecessary or missing code. Please let me know .  This is good education.



Code: Pascal  [Select][+][-]
  1. Procedure ListDevices ;     // this will list only input devices
  2. Var Hints   : PPointer ;
  3.     err,i   : integer;
  4.     n       : PPointer ;
  5.     Name,Desc,IOID :Pchar ;
  6.  
  7. begin
  8.   i := 0;
  9.   err := snd_device_name_hint(-1,'pcm', @Hints) ;
  10.   if err <> 0 then begin
  11.     ShowMessage('Error accessing devices, Error:'+ inttoStr(err));
  12.     exit ;
  13.   end;
  14.  // First count devices
  15.   n := Hints  ;
  16.    try
  17.     try
  18.      while (n^ <> nil) do
  19.      begin
  20.         name := snd_device_name_get_hint(n^,Pchar('NAME')) ;
  21.         desc := snd_device_name_get_hint(n^,'DESC') ;
  22.         IOID := snd_device_name_get_hint(n^,'IOID') ;
  23.        If IOID <> '' then
  24.           if IOID = 'Input' then  Inc (i) ;
  25.       Inc(N) ;
  26.       If assigned(name) then cmem.Free(Name);
  27.       If assigned(desc) then cmem.Free(Desc);
  28.       If assigned(IOID) then cmem.Free(IOID);
  29.       end;
  30.   except
  31.     on E: Exception do
  32.         ShowMessage(E.Message);
  33.    end;
  34.  
  35.   finally
  36.  
  37.   end;
  38.  
  39.   // second fill the array
  40.   setlength(DeviceTable,i )  ;  // last item is flag -1
  41.   n := Hints  ;
  42.   i := 0 ;
  43.    try
  44.     try
  45.      while (n^ <> nil)    do begin
  46.         name := snd_device_name_get_hint(n^,Pchar('NAME')) ;
  47.         desc := snd_device_name_get_hint(n^,'DESC') ;
  48.         IOID := snd_device_name_get_hint(n^,'IOID') ;
  49.        If IOID <> '' then
  50.           if IOID = 'Input' then
  51.                begin
  52.                //Showmessage(string(Name) + ' | ' + String(desc) + ' | ' + string(IOID)) ;
  53.                DeviceTable[i].iIndex := i ;
  54.                DeviceTable[i].iName := String(name) ;
  55.                DeviceTable[i].iDesc := String(desc) ;
  56.                Inc (i) ;
  57.                end;
  58.       Inc(N) ;
  59.       If assigned(name) then cmem.Free(Name);
  60.       If assigned(desc) then cmem.Free(Desc);
  61.       If assigned(IOID) then cmem.Free(IOID);
  62.       end;
  63.   except
  64.     on E: Exception do
  65.         ShowMessage(E.Message);
  66.    end;
  67.  
  68.   finally
  69.    DeviceTable[i].iIndex := -1 ;     // end of array is marked as -1
  70.   end;
  71.  
  72.   err := snd_device_name_free_hint (hints) ;
  73.   if err <> 0 then begin
  74.     ShowMessage('Error freeing hints, Error:'+ inttoStr(err));
  75.     exit ;
  76.   end;
  77. end;            
     

The next procedure will record 2 seconds of PCM to a buffer.   
Code: Pascal  [Select][+][-]
  1. Procedure StartRecording ;
  2. Const Buffer_Frames = NBPoints;  // in Main   = 16384
  3.  
  4. Var
  5.   // Samplerate       : Int32 = 8192 ; defined in Main
  6.   // Capture_handle   : Psnd_pcm_t ;  // declared as global to unit
  7.   // Channels         : int32 = 1 ; //mono    defined In Main
  8.      buffer           : Pchar ;
  9.      hw_params        : Psnd_pcm_hw_params_t ;
  10.      format           : snd_pcm_format_t = SND_PCM_FORMAT_S16_LE ;  // 16 bits signed little endian
  11.      direction        : integer = 0 ;
  12.      i,err            : Integer;
  13. begin
  14.                                                                                                                              //originally 0
  15.   err := snd_pcm_open(@capture_handle,Pchar(deviceTable[MainForm.CBListDevices.itemIndex].iname),SND_PCM_STREAM_CAPTURE,0 (*SND_PCM_NONBLOCK*))  ;
  16.   If err < 0 then begin PostError('Error opening PCM') ; Exit ;end;
  17.  
  18.   err := snd_pcm_hw_params_malloc (@hw_params)  ;
  19.   If err < 0 then begin PostError('Error hwparams'); Exit ;end;
  20.  
  21.   err := snd_pcm_hw_params_any (capture_handle, hw_params) ;
  22.   if err< 0 then begin PostError('Cannot initialize hardware parameter structure') ; Exit ; end ;
  23.                                                                   // SND_PCM_ACCESS_MMAP_NONINTERLEAVED
  24.   err := snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
  25.   if err < 0 then begin PostError('Cannot access PCM type');Exit;end;
  26.  
  27.   err := snd_pcm_hw_params_set_format (capture_handle, hw_params, format) ;
  28.   if err < 0 then begin Posterror('Cannot set format') ; Exit; end;
  29.  
  30.   err := snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, @samplerate, @direction);
  31.   if err < 0 then begin Posterror('Cannot set rate') ; Exit ; end;
  32.   if Direction  <> 0 then begin Posterror('Cannot set exact rate') ; Exit; end;
  33.  
  34.   err := snd_pcm_hw_params_set_channels (capture_handle, hw_params, channels) ;   // 1 channel = mono
  35.   if err < 0 then begin PostError('Cannot set channel count') ; Exit ; end;
  36.  
  37.   err := snd_pcm_hw_params (capture_handle, hw_params) ;
  38.   if err < 0 then Begin PostError('Cannot set parameters') ; Exit ; end ;
  39.  
  40.   snd_pcm_hw_params_free (hw_params);
  41.  
  42.   err := snd_pcm_prepare (capture_handle) ;
  43.   if err < 0 then begin PostError('Cannot prepare audio interface for use'); Exit ; end;
  44.  
  45.   buffer := GetMem (Buffer_Frames * snd_pcm_format_width(format) DIV 8 * Channels);
  46.  
  47. Repeat
  48.  
  49.   Application.processmessages ;
  50. //  snd_pcm_wait(capture_handle, 2000);
  51.   err := snd_pcm_readi (capture_handle, buffer, buffer_frames);
  52.  
  53.   if err <> buffer_frames then
  54.   begin
  55.         PostError ('Read from audio interface failed. Error = '+inttostr(err));
  56.         exit ;
  57.   end
  58.   else
  59.   begin     //we have a full buffer
  60.  
  61.    // here I need to transfer the buffer in the process buffer
  62.  
  63.    FillWord(SavedFrameData,length(SavedFrameData),0);  // remove garbage
  64.    Move(buffer^,SavedFrameData,buffer_frames*2) ;
  65.  
  66.    // Find the max value
  67.    gMax := 0 ;
  68.    gMaxIndex := 0 ;
  69.    For i := 0 to  length(SavedFrameData)-1 do
  70.       begin
  71.          if abs(SavedFrameData[i])> gmax then       // use only positives values
  72.          begin
  73.           gMax := Abs( SavedFrameData[i]);
  74.           gMaxIndex := i ;
  75.          End;
  76.       End;
  77.      If gMax > gSensitivity then        // Check if level is higher than sensitivity set level
  78.        begin
  79.           StopVisualBuffer ;
  80.           MainForm.RBdataReady.checked := true ;     // set an event to draw graphs
  81.        end   // gMax > sensivity
  82.        else
  83.        begin   // gMax is lower then sensitivity
  84.          ResetVisualBuffer ;
  85.        end;
  86.       End  ;   // end full buffer
  87.    until gMax > gSensitivity ;
  88.  
  89.    Processing := False   ;
  90.    FreeMem(Buffer) ;
  91.    //snd_pcm_close (Capture_handle)  is called from main by Stop Recording ;
  92.  
  93. end;
  94.  
  95. Procedure StopRecording ;
  96. begin
  97.     snd_pcm_Close (Capture_Handle) ;
  98. end;                                            
  99.  
The last problem I have is that I use a progress bar driven by a timer to show/imitate the buffer filling.  In windows and Mac the GUI does update.  In Linux I have not found a way yet.
During the read, the interface is not updated, so the progress bar does not grow!

Possibly the non Blocking might do the trick, but so far I have not found a way to use it.
Ideas welcome!

Thank you.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 05, 2020, 09:03:26 am
The last problem I have is that I use a progress bar driven by a timer to show/imitate the buffer filling.  In windows and Mac the GUI does update.  In Linux I have not found a way yet.
During the read, the interface is not updated, so the progress bar does not grow!

Presumably you're referring to your snd_pcm_readi() call. I've used ALSA for MIDI where since it's in a background thread the GUI remains live, but otherwise you might have to transfer smaller chunks with an APM in the loop... which will obviously introduce the risk of latency problems.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: AL on August 06, 2020, 02:17:30 am
Possibly I need to go interrupt-driven.
Not totally sure how to do it.  I need to do more reading  unless someone has some code to share.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 06, 2020, 09:57:50 am
Magic word is probably "event", not "interrupt". I've not done it but there's a couple of hints at the end of https://www.linuxjournal.com/article/6735

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: TRon on August 06, 2020, 12:31:39 pm
Here is my code for my 2 procedures.  Much better now.
Nice. See, how that cleans up your code ?  :)

Quote
Thanks to all especially TRon who put me on the right track.
Thank you, but it seems you are still having issues so perhaps I forgot something ?

Quote
I had to use Cmem only to free the name Hint.
afaik the hints are the only returns values that require this call. On the other hand I only have used the most common ALSA operations.

Quote
The last problem I have is that I use a progress bar driven by a timer to show/imitate the buffer filling.  In windows and Mac the GUI does update.  In Linux I have not found a way yet.
During the read, the interface is not updated, so the progress bar does not grow!
Strange.

Have you tried MarkMLI's suggestion of using a smaller capture buffer ? The only thing different would be that the buffer is smaller so you would have to read multiple buffers in case you wish to record/copy a larger amount of samples.

Because you update your progressbar using a timer, what happens if you place the processmessages in your timer event, after you've updated the progressbar (instead of doing so inside your read loop) ?

Quote
Possibly the non Blocking might do the trick, but so far I have not found a way to use it.
We're talking ALSA here, but also in general: that is a bit too broad statement  :)

What have you tried and what were the result when your tried them ?

Does your code still locks-up if you set it to non-block ? or is it for example that your read loop is problematic ?


What you could also try is doing a latency test. ALSA comes accompanied with an example, see here https://www.alsa-project.org/wiki/Test_latency.c (actual code is linked on that page).

If the latency test works for your hardware, then the issue is probably related to the code that you've tried. At least you would perhaps be able to get some inspiration from that code  ;)

To make sure, I repeat the warning about ALSA sometimes being finicky. If your code ever failed for whatever reason and handles/memory was not freed correctly then ALSA is able to throw in very strange error's (on successive runs) that make no sense (at least not to me) and can lead to confusion when you try to debug things. Reset often in such cases.
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 06, 2020, 03:21:16 pm
Quote
I repeat the warning about ALSA sometimes being finicky.

Huh, even more, ALSA is obsolete now.
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

If you use Portaudio library, it can use or ALSA or PulseAudio (if installed on your system).
You will directly see the difference of latency and quality if you use PulseAudio drivers vs ALSA drivers.

Fre;D 
Title: Re: Help with Alsa C conversion.
Post by: TRon on August 06, 2020, 03:38:04 pm
Huh, even more, ALSA is obsolete now.
So true indeed Fred vS

Even ALSA developers/documentation advise to use for example jack.

I did take my queue from Topic Starter though. I have only tried to use it with FPC because I noticed there was a unit for using it with FPC and wanted see how that would work.

My conclusion was that it was decent enough. However, you do need to have a fair bit of understanding of ALSA before you can actually make it do the things you want to.

Pulseaudio, as you suggested, is indeed also a far better option imho.
Title: Re: Help with Alsa C conversion.
Post by: AL on August 06, 2020, 03:46:16 pm
Quote
Quote
What have you tried and what were the result when your tried them ?

Does your code still locks-up if you set it to non-block ? or is it for example that your read loop is problematic
In nonblocking mode the readi returns with EAGAIN  and I do not know how to handle that properly.  Tried a while loop checking err value to match the buffer_frames value with no success.

Quote
Quote
Because you update your progressbar using a timer, what happens if you place the processmessages in your timer event, after you've updated the progressbar (instead of doing so inside your read loop) ?
Good idea tis may do it.  Will try it.


Do we have a FPC unit that bridge to the PulseAudio API?
Title: Re: Help with Alsa C conversion.
Post by: PascalDragon on August 06, 2020, 03:58:21 pm
Quote
I repeat the warning about ALSA sometimes being finicky.

Huh, even more, ALSA is obsolete now.
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please. From what I know PulseAudio still relies on the kernel's ALSA infrastructure (which does not mean however that it uses libalsa).
Title: Re: Help with Alsa C conversion.
Post by: TRon on August 06, 2020, 04:02:34 pm
In nonblocking mode the readi returns with EAGAIN  and I do not know how to handle that properly.  Tried a while loop checking err value to match the buffer_frames value with no success.
Ah, that is a step further when you reported issues with using it the first time.

Have you already been able to look at the latency example ? If you did, where you also able to locate and understand how EAGAIN is handled with the loop ? (just ignore the fact that the c-example also play/time the data in order to be able to calculate the latency)

Quote
Do we have a FPC unit that bridge to the PulseAudio API?
Not that I have used this myself but here it seems there is a version: https://github.com/andrewd207/fpc-pulseaudio

Perhaps Fred vS (author of UOS sound library) or someone else knows about another version somewhere ?
Title: Re: Help with Alsa C conversion.
Post by: TRon on August 06, 2020, 04:10:42 pm
Quote
I repeat the warning about ALSA sometimes being finicky.

Huh, even more, ALSA is obsolete now.
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please. From what I know PulseAudio still relies on the kernel's ALSA infrastructure (which does not mean however that it uses libalsa).
I can not speak for Fred vS but, I interpreted his words as not using ALSA directly (anymore).

afaik pulseaudio is userland, which is more user-friendly, while ALSA is responsible for the direct communication with the underlying device hardware.

But correct me if wrong, i'm no expert.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 06, 2020, 04:20:11 pm
afaik pulseaudio is userland, which is more user-friendly, while ALSA is responsible for the direct communication with the underlying device hardware.

But correct me if wrong, i'm no expert.

Neither am I, but I've had to look at this a little bit in an (unsuccessful) attempt to sort out some startup dependencies.

I agree that ALSA is part of the kernel, but it appears to have a mutual dependency on PulseAudio, at least for certain media types (my interest is MIDI, which I use for annunciators).

But correct me if wrong, I'm no expert :-)

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 06, 2020, 05:53:26 pm
Quote
Perhaps Fred vS (author of UOS sound library) or someone else knows about another version somewhere ?
I use the one from Andrew :
https://github.com/andrewd207/fpc-pulseaudio

And there is also the source from Martin:
https://github.com/mse-org/mseide-msegui/blob/master/lib/common/audio

uos uses PortAudio library that can deal with ALSA, PulseAudio, Jack and many others.
http://www.portaudio.com/

Quote
I can not speak for Fred vS but, I interpreted his words as not using ALSA directly (anymore).
Yes, they have their own source now and dont rely to ALSA anymore.

Fre;D
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 06, 2020, 06:07:27 pm
Quote
 
Quote
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please

From https://en.wikipedia.org/wiki/PulseAudio

In a typical installation scenario under Linux, the user configures ALSA to use a virtual device provided by PulseAudio. Thus, applications using ALSA will output sound to PulseAudio, which then uses ALSA itself to access the real sound card. PulseAudio also provides its own native interface to applications that want to support PulseAudio directly, as well as a legacy interface for ESD applications, making it suitable as a drop-in replacement for ESD.

Fre;D
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 06, 2020, 06:10:41 pm
Citation required, from ALSA or Linux projects rather than hearsay.

I'm sorry, but this is something where something authoritative is really needed.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 06, 2020, 07:10:04 pm
Citation required, from ALSA or Linux projects rather than hearsay.

I'm sorry, but this is something where something authoritative is really needed.

MarkMLl

Ok, ok, indeed Wikipedia is the biggest hearsay, sorry for that ridiculous link.

[EDIT] Anyway you may check that long topic in mailing list, even I did not agree with Martin and PulseAudio working without ALSA:

http://mseide-msegui-talk.13964.n8.nabble.com/MSEide-MSEgui-talk-MSE-pcaudio-td610i20.html

The discussion begin from  Mar 09, 2018; 10:57pm Re: [MSEide-MSEgui-talk] MSE pcaudio.

But the argument of Martin are too strong, he win.

Fre;D
Title: Re: Help with Alsa C conversion.
Post by: dliw on August 06, 2020, 08:40:17 pm
Quote
 
Quote
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please

From https://en.wikipedia.org/wiki/PulseAudio

In a typical installation scenario under Linux, the user configures ALSA to use a virtual device provided by PulseAudio. Thus, applications using ALSA will output sound to PulseAudio, which then uses ALSA itself to access the real sound card. PulseAudio also provides its own native interface to applications that want to support PulseAudio directly, as well as a legacy interface for ESD applications, making it suitable as a drop-in replacement for ESD.

Fre;D

Please read this carefully. It only talks about the user facing API: If PulseAudio is active and an (old) application uses the ALSA API directly, it gets redirected to PulseAudio: Application -> Wrapper -> PulseAudio. But (newer) applications can also use the PulseAudio interface directly. However, In both cases PulseAudio uses ALSA to stream the data to the audio hardware.

From https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture:
Advanced Linux Sound Architecture (ALSA) is a software framework and part of the Linux kernel that provides an application programming interface (API) for sound card device drivers.
The sound servers PulseAudio and JACK (low-latency professional-grade audio editing and mixing), the higher-level abstraction APIs OpenAL, SDL audio, etc. work on top of ALSA and implemented sound card device drivers. On Linux systems, ALSA succeeded the older Open Sound System (OSS).

Edit: In case you don't believe Wikipedia:
From the official page: https://www.freedesktop.org/wiki/Software/PulseAudio/Backends/ALSA/
The primary backend used on Linux is ALSA.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 06, 2020, 08:42:16 pm
Thanks for the additional info Fred.

Whatever its strengths, Wp is at best a secondary source... or even worse if we take its "Article relies excessively on primary sources" strictures to heart. As an example, I'd point to its claim that FPC supports SPARC64 on Linux and SPARC on SunOS/Solaris... the code might be in there but historically I've been the one testing SPARC on actual hardware and I've been able to put minimal time into it since the 2.7.1 era.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: AL on August 07, 2020, 01:42:41 am
Tried several things.

Adding application.processmessages to the timer event does not work.
Even adding the following code right after the read does not update the progressbar.
I believe there is a problem with the progressbar itself.
 
Code: Pascal  [Select][+][-]
  1. Application.processmessages ;
  2.  
  3.   err := snd_pcm_readi (capture_handle, buffer, buffer_frames);
  4.   mainform.ProgressBar1.Position:= 1000;
  5.   Application.ProcessMessages ;
This should put the progressbar at mid position, but nothing happen.

This is my first experience with Linux (Ubuntu 20.04) .
The widget is GTK2 is that OK, could / should I use something else?
Title: Re: Help with Alsa C conversion.
Post by: AL on August 07, 2020, 02:50:36 am
Made a test project with a progressbar and it work OK.
Made a project with a timer and it work OK.

If I make the procedure stop with a show message, then the progressbar does work.
Like:
Code: Pascal  [Select][+][-]
  1. Repeat
  2.    ShowMessage (' PorgressBar');
  3.  
  4.   err := snd_pcm_readi (capture_handle, buffer, buffer_frames);
  5.  
  6.   if err <> buffer_frames then
  7.   begin
  8.         PostError ('Read from audio interface failed. Error = '+inttostr(err));
  9.         exit ;
  10.   end
  11.   else
  12.   begin     //we have a full buffer      

So it is really the read that prevent it from working.
Title: Re: Help with Alsa C conversion.
Post by: AL on August 07, 2020, 03:56:52 am
I finally resorted to split my read in small chunks and call application.processmessages at each read.
The following is now working and the progressbar updating properly.
Code: Pascal  [Select][+][-]
  1. Procedure StartRecording ;
  2. Const Buffer_Frames = NBPoints;  // in Main   = 16384    int 16 bits
  3.  
  4. Var
  5.   // Samplerate       : Int32 = 8192 ; defined in Main
  6.   // Capture_handle   : Psnd_pcm_t ;  // declared as global to unit
  7.   // Channels         : int32 = 1 ; //mono    defined In Main
  8.      buffer           : Pchar ;
  9.      hw_params        : Psnd_pcm_hw_params_t ;
  10.      format           : snd_pcm_format_t = SND_PCM_FORMAT_S16_LE ;  // 16 bits signed little endian
  11.      direction        : integer = 0 ;
  12.      i,err ,readsize  : Integer;
  13. begin
  14.   ReadSize := Buffer_Frames  div 16 ; // 2048 bytes 16 times     1024 read * 2 bytes
  15.   err := snd_pcm_open(@capture_handle,Pchar(deviceTable[MainForm.CBListDevices.itemIndex].iname),SND_PCM_STREAM_CAPTURE,0(*SND_PCM_NONBLOCK*))  ;
  16.   If err < 0 then begin PostError('Error opening PCM') ; Exit ;end;
  17.  
  18.   err := snd_pcm_hw_params_malloc (@hw_params)  ;
  19.   If err < 0 then begin PostError('Error hwparams'); Exit ;end;
  20.  
  21.   err := snd_pcm_hw_params_any (capture_handle, hw_params) ;
  22.   if err< 0 then begin PostError('Cannot initialize hardware parameter structure') ; Exit ; end ;
  23.                                                                   // SND_PCM_ACCESS_MMAP_NONINTERLEAVED
  24.   err := snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
  25.   if err < 0 then begin PostError('Cannot access PCM type');Exit;end;
  26.  
  27.   err := snd_pcm_hw_params_set_format (capture_handle, hw_params, format) ;
  28.   if err < 0 then begin Posterror('Cannot set format') ; Exit; end;
  29.  
  30.   err := snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, @samplerate, @direction);
  31.   if err < 0 then begin Posterror('Cannot set rate') ; Exit ; end;
  32.   if Direction  <> 0 then begin Posterror('Cannot set exact rate') ; Exit; end;
  33.  
  34.   err := snd_pcm_hw_params_set_channels (capture_handle, hw_params, channels) ;   // 1 channel = mono
  35.   if err < 0 then begin PostError('Cannot set channel count') ; Exit ; end;
  36.  
  37.   err := snd_pcm_hw_params (capture_handle, hw_params) ;
  38.   if err < 0 then Begin PostError('Cannot set parameters') ; Exit ; end ;
  39.  
  40.   snd_pcm_hw_params_free (hw_params);
  41.  
  42.   err := snd_pcm_prepare (capture_handle) ;
  43.   if err < 0 then begin PostError('Cannot prepare audio interface for use'); Exit ; end;
  44.  
  45.   buffer := GetMem ((Buffer_Frames * snd_pcm_format_width(format) DIV 8 * Channels) div 16);
  46.  
  47.  
  48.   FillWord(SavedFrameData,length(SavedFrameData),0);  // remove garbage
  49.  
  50. Repeat
  51.   for i := 0 to 15 do begin
  52.     err := snd_pcm_readi (capture_handle, buffer, ReadSize);
  53.     Move(buffer^,SavedFrameData[i*ReadSize],ReadSize *2)  ;
  54.     // SavedFrameData is int16 array no need to multiply ReadSize in index
  55.     Application.ProcessMessages ;
  56.    end;
  57.  
  58. // we assume we have a full buffer
  59.  
  60.    // Find the max value
  61.    gMax := 0 ;
  62.    gMaxIndex := 0 ;
  63.    For i := 0 to  length(SavedFrameData)-1 do
  64.       begin
  65.          if abs(SavedFrameData[i])> gmax then       // use only positives values
  66.          begin
  67.           gMax := Abs( SavedFrameData[i]);
  68.           gMaxIndex := i ;
  69.          End;
  70.       End;
  71.      If gMax > gSensitivity then        // Check if level is higher than sensitivity set level
  72.        begin
  73.         //  StopVisualBuffer ;
  74.           MainForm.RBdataReady.checked := true ;     // set an event to draw graphs
  75.        end   // gMax > sensivity
  76.        else
  77.        begin   // gMax is lower then sensitivity
  78.          ResetVisualBuffer ;
  79.        end;
  80.  
  81.    until gMax > gSensitivity ;
  82.  
  83.    Processing := False   ;
  84.    FreeMem(Buffer) ;
  85.    //snd_pcm_close (Capture_handle)  is called from main by Stop Recording ;
  86.  
  87. end;
  88.  
  89. Procedure StopRecording ;
  90. begin
  91.     snd_pcm_Close (Capture_Handle) ;
  92. end;                                    

Thank you to all for the great help.
Title: Re: Help with Alsa C conversion.
Post by: PascalDragon on August 07, 2020, 09:28:50 am
Quote
 
Quote
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please

From https://en.wikipedia.org/wiki/PulseAudio

In a typical installation scenario under Linux, the user configures ALSA to use a virtual device provided by PulseAudio. Thus, applications using ALSA will output sound to PulseAudio, which then uses ALSA itself to access the real sound card. PulseAudio also provides its own native interface to applications that want to support PulseAudio directly, as well as a legacy interface for ESD applications, making it suitable as a drop-in replacement for ESD.

Fre;D

Please read this carefully. It only talks about the user facing API: If PulseAudio is active and an (old) application uses the ALSA API directly, it gets redirected to PulseAudio: Application -> Wrapper -> PulseAudio. But (newer) applications can also use the PulseAudio interface directly. However, In both cases PulseAudio uses ALSA to stream the data to the audio hardware.

From https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture:
Advanced Linux Sound Architecture (ALSA) is a software framework and part of the Linux kernel that provides an application programming interface (API) for sound card device drivers.
The sound servers PulseAudio and JACK (low-latency professional-grade audio editing and mixing), the higher-level abstraction APIs OpenAL, SDL audio, etc. work on top of ALSA and implemented sound card device drivers. On Linux systems, ALSA succeeded the older Open Sound System (OSS).

Edit: In case you don't believe Wikipedia:
From the official page: https://www.freedesktop.org/wiki/Software/PulseAudio/Backends/ALSA/
The primary backend used on Linux is ALSA.

Correct. One needs to differentiate between the backend drivers (where PulseAudio mainly uses ALSA on Linux) and the frontend where one can use ALSA as a wrapper towards PulseAudio or PulseAudio directly (among other possibilities).
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 07, 2020, 02:07:45 pm
Quote
 
Quote
PulseAudio that was using ALSA at the beginning has developed his own audio drivers and use this now.

Source please

From https://en.wikipedia.org/wiki/PulseAudio

In a typical installation scenario under Linux, the user configures ALSA to use a virtual device provided by PulseAudio. Thus, applications using ALSA will output sound to PulseAudio, which then uses ALSA itself to access the real sound card. PulseAudio also provides its own native interface to applications that want to support PulseAudio directly, as well as a legacy interface for ESD applications, making it suitable as a drop-in replacement for ESD.

Fre;D

Please read this carefully. It only talks about the user facing API: If PulseAudio is active and an (old) application uses the ALSA API directly, it gets redirected to PulseAudio: Application -> Wrapper -> PulseAudio. But (newer) applications can also use the PulseAudio interface directly. However, In both cases PulseAudio uses ALSA to stream the data to the audio hardware.

From https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture:
Advanced Linux Sound Architecture (ALSA) is a software framework and part of the Linux kernel that provides an application programming interface (API) for sound card device drivers.
The sound servers PulseAudio and JACK (low-latency professional-grade audio editing and mixing), the higher-level abstraction APIs OpenAL, SDL audio, etc. work on top of ALSA and implemented sound card device drivers. On Linux systems, ALSA succeeded the older Open Sound System (OSS).

Edit: In case you don't believe Wikipedia:
From the official page: https://www.freedesktop.org/wiki/Software/PulseAudio/Backends/ALSA/
The primary backend used on Linux is ALSA.

Correct. One needs to differentiate between the backend drivers (where PulseAudio mainly uses ALSA on Linux) and the frontend where one can use ALSA as a wrapper towards PulseAudio or PulseAudio directly (among other possibilities).

Hum, did you read the topics of Martin (I give it some topic before and like you I was thinking that PulseAudio still used ALSA):?

Quote
http://mseide-msegui-talk.13964.n8.nabble.com/MSEide-MSEgui-talk-MSE-pcaudio-td610i20.html
 The discussion begin from  Mar 09, 2018; 10:57pm Re: [MSEide-MSEgui-talk] MSE pcaudio

Anyway, it is maybe a problem of naming because PulseAudio has his own audio kernel now.

And yes it seems that the audio kernel used by Linux is called ALSA, and that there is also thecalled ALSA audio server (that you may compare to the PulseAudio or Jack audio server.)

Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 07, 2020, 02:16:24 pm
@  PascalDragon

All that is not clear.
And I agree, it is important to know the truth.

But does the truth still exist in 2020?

Anyway, I will open a issue in the GitLab site of PulseAudio and ask them what is their truth about PusleAudio using ALSA.

Fre;D
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 07, 2020, 02:42:21 pm
@  PascalDragon

All that is not clear.
And I agree, it is important to know the truth.

But does the truth still exist in 2020?

Anyway, I will open a issue in the GitLab site of PulseAudio and ask them what is their truth about PusleAudio using ALSA.

Fre;D

Done: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/959

I hope it will give some clarification.
(And if you see something else to ask, please say it, I will ask).
Title: Re: Help with Alsa C conversion.
Post by: PascalDragon on August 07, 2020, 02:52:31 pm
Done: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/959

I hope it will give some clarification.
(And if you see something else to ask, please say it, I will ask).

I assume this is simply a language barrier, so I will try again:

An application that wants to process audio on Linux needs to use some audio library. This can be libalsa or libpulse or whatever it is that Jack provides (I assume libjack). Now if you're using PulseAudio as audio server then libalsa will route everything to the PulseAudio server and a conversion from the ALSA API to the (internal) PulseAudio API will take place. If you use libpulse then no conversion is needed. This here is indeed where ALSA (the user space part) is no longer needed.

Now the audio server needs to handle audio devices, which can be sound cards, HDMI audio outputs or whatever. Right now there is only one infrastructure for this in the Linux kernel and that is called ALSA as well. But compared to the above this is the kernel interface. This did not change with the advent of PulseAudio, because the kernel interface is working well enough for PulseAudio to use. Thus in this context ALSA is still required by PulseAudio. This doesn't matter for an application however, because that picks its API independently of that (it can use libalsa or libpulse depending on its needs).

Thus saying ALSA as a whole is no longer required for PulseAudio is wrong. What is no longer required is the user space part.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 07, 2020, 02:58:45 pm
I think we all appreciate you doing that, Fred.

I've just been taking a bit of a look at the situation from the POV of Debian: the pulseaudio package depends on libasound2 but it's not explicitly stated that ALSA depends on Pulseaudio. However libasound2 is merely a wrapper around ALSA "glue" provided by the kernel and can be replaced... ALSA itself is a kernel thing and the low-level drivers are also part of the kernel.

The previous stuff that I've come across where ALSA appeared to depend on Pulseaudio was related to MIDI, and it's also confusing that when a MIDI-related program e.g. Rosegarden starts up it can also put things in the device list. Kill Pulseaudio and MIDI didn't work, however I don't know whether this applies to both hardware and emulated MIDI :-(

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 07, 2020, 03:01:12 pm
Now if you're using PulseAudio as audio server

What is an "audio server" in this context?

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 07, 2020, 03:05:23 pm
Done: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/959

I hope it will give some clarification.
(And if you see something else to ask, please say it, I will ask).

I assume this is simply a language barrier, so I will try again:

...

Now the audio server needs to handle audio devices, which can be sound cards, HDMI audio outputs or whatever. Right now there is only one infrastructure for this in the Linux kernel and that is called ALSA as well. But compared to the above this is the kernel interface. This did not change with the advent of PulseAudio, because the kernel interface is working well enough for PulseAudio to use. Thus in this context ALSA is still required by PulseAudio. This doesn't matter for an application however, because that picks its API independently of that (it can use libalsa or libpulse depending on its needs).

Thus saying ALSA as a whole is no longer required for PulseAudio is wrong. What is no longer required is the user space part.

Be quiet, it is not a barrier of language.

And what you explained is exactly what was in my brain before the discussion with Martin.

What I understood from him is that PulseAudio has now his own audio kernel drivers to deal with the audio cards and they are not the same as ALSA use.

Maybe I am wrong and I would like to be certified by a "official" PulseAudio man.
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 07, 2020, 03:48:55 pm
What I understood from him is that PulseAudio has now his own audio kernel drivers to deal with the audio cards and they are not the same as ALSA use.

Maybe I am wrong and I would like to be certified by a "official" PulseAudio man.

Or even better, by somebody who can speak for the kernel developers and maintainers.

I'm currently downloading the sources from kernel.org since at least part of this can be answered by reference to the build process, but my strong suspicion (which I will come back and correct if necessary) is that there are

* Kernel facilities for ALSA "glue" functionality.

* Kernel drivers for low-level devices.

* /Possibly/ kernel facilities for Pulseaudio.

However note that the low-level drivers are part of neither ALSA nor of Pulseaudio, even though they might be aware of one or the other APIs.

Slightly later: there are no files in the Linux kernel tree with a case-insensitive match of "pulseaudio" in their name. There's half a dozen content matches, entirely in documentation or comments, including in Documentation/sound/designs/jack-controls.rst "This means userspace applications like pulseaudio can switch off headphones and switch on speakers when no headphones are pluged in."

At least some of the multimedia devices (soundcards etc.) appear under Device Drivers -> Sound card support -> Advanced Linux Sound Architecture which also has settings for OSS emulation.

So Pulseaudio is NOT part of the kernel, ALSA covers various "glue" APIs, and device drivers are grouped under ALSA because they make use of some of those APIs (before ALSA, they were probably part of OSS).

There's obviously still a possibility that some of Pulseaudio is shipped as out-of-tree kernel modules, but I have no immediate reason to believe that to be the case.

Jack, OTOH, does appear to be part of the kernel to at least some extent.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: Fred vS on August 07, 2020, 04:12:40 pm
What I understood from him is that PulseAudio has now his own audio kernel drivers to deal with the audio cards and they are not the same as ALSA use.

Maybe I am wrong and I would like to be certified by a "official" PulseAudio man.

Or even better, by somebody who can speak for the kernel developers and maintainers.

I'm currently downloading the sources from kernel.org since at least part of this can be answered by reference to the build process, but my strong suspicion (which I will come back and correct if necessary) is that there are

* Kernel facilities for ALSA "glue" functionality.

* Kernel drivers for low-level devices.

* /Possibly/ kernel facilities for Pulseaudio.

However note that the low-level drivers are part of neither ALSA nor of Pulseaudio, even though they might be aware of one or the other APIs.

MarkMLl

Thanks for this Mark, it is highly appreciated and will be very useful to know the "true truth".

Fred
Title: Re: Help with Alsa C conversion.
Post by: MarkMLl on August 07, 2020, 04:13:57 pm
You're welcome Fred, and I'm sorry if we appear to be at odds over this.

MarkMLl
Title: Re: Help with Alsa C conversion.
Post by: TRon on August 07, 2020, 10:20:34 pm
I finally resorted to split my read in small chunks and call application.processmessages at each read.
Would be cheap to say told you so...  ;) (actually MarkMLl who did that if not mistaken)

How ALSA is implemented for each and every platform simply differs (and how that impacts for instance gui related updates).

Fact is however that readi is a blocking call by default (as stated in ALSA documentation), therefor it might be that other parts of your program (such as GUI) are not given enough time to be updated.

Your buffer was 2 seconds so in theory that might mean that for 2 seconds your program is completely blocked for whatever other operation during that time.

If you cut down that time, and do multiple reads you are able to 'lift' (some of) that restriction.

This can stil cause issues because updating the GUI (or doing other time-consuming operations) might take too much time so that your 'recording' buffer overruns.

That is why the non-blocking mode exist (internally by ALSA threaded if I understood the ALSA documentation correctly). That will read whatever is currently in the buffer but does not mean that the complete buffer is saturated (after a single read).

It might only be a few samples. This can be detected by the returning value from readi. So if that does not match a saturated buffer, you will receive a EAGAIN value.

Because you then know the buffer is not saturated, but the readi call did return you have time to do other stuff before you do a read again (such as updating the GUI). That usually is enough because ALSA itself is doing that call threaded as well.

If even that is not enough to do other stuff while reading the buffer then it becomes time to start thinking about putting all your ALSA related code inside a (pascal) thread.

Your code seems pretty simple in nature so should not affect your program much but, you could perhaps imagine that you are working with midi or are implementing your own sequencer. In such cases timing is particularly of the essence and requires  more attention with regards to what I wrote above.

Just meant as some additional background information in case you wish to explore other adventures using ALSA.

Quote
The following is now working and the progressbar updating properly.
Thank you for having posted your code. For sure it would be helpful to others.

Note that by using a smaller recording buffer you can also taken out some code of your read loop that consumes time (the normalising of the samples), as you did in your final code.

Using a smaller buffer also allows for toying with such things and allows to see if it does fit (time-wise) inside your read loop, if you so wish to do so.

Happy coding !
TinyPortal © 2005-2018