Author Topic: [SOLVED] BASS_ChannelGetData: only seg. faults (source code included)  (Read 548 times)

GUMeyer

  • Posts: 16
Hallo,

as i described in https://www.un4seen.com/forum/?topic=19858.0, i want to access the sample data via BASS_ChannelGetData. I now have written a first, "quick and dirty" program that should just get me the data. But all i get are segmentation faults and i don't understand why.
Prerequisites: I use the VLC-Media player to play the attached WAV-File. The used device is (at the moment) hard-coded (Device #5) so it should be changed. As i have no microphone at my notebook i use a program called "Line1 (Virtual Audio Cable") to send the audio from the VLC-Player to my program.
Maybe someone can have a look at the following sourcecode (please ignore small blemishes - i know there is much to improve, but remember "Quick and dirty  ;)) and give me hints where i have to change the code to get my wished sample data?

-------------------------------------------------------------snip----------------------------------------------------------------------

program sync004;

uses
  LCLIntf, LCLType, LMessages, windows, Classes, SysUtils, crt, BASS;
type
  PSampleBuffer = ^TSampleBuffer;
  TSampleBuffer = array of SmallInt;

var
   win: HWND = 0;
   WaveStream: TMemoryStream ;
   rchan: HRecord = 0;
   Chan: HStream = 0;
   AktDeviceName : string;
   SampleBuffer : TSampleBuffer;
   PSBuffer     : PSampleBuffer;
   devinf       : BASS_DEVICEINFO;
   isDeviceCorrectJN : string;
   SampleIndex : integer;
   SampleValue : integer;

function RecordingCallback(Handle: HRecord; buffer: Pointer; length: DWord; user: Pointer): LongBool; stdcall;
begin
  Result := True;
  PSBuffer:=nil;
    BASS_ChannelGetData(rchan, PSBuffer, BASS_DATA_FIXED);
    for SampleIndex:= 0 to 127 do
      begin
        SampleValue:=PSBuffer^[SampleIndex];
        Writeln(inttostr(SampleValue));
      end;
end;  // function RecordingCallback

begin
  if not BASS_Init(-1, 44100, 0, win, nil) then
     Halt(0);
  BASS_RecordGetDeviceInfo(5, devinf);
  WriteLn(devinf.name);
  WriteLn('Ist das das richtige Device? (J/N)');    // Diese Prüfung sollte
  ReadLn(isDeviceCorrectJN);                        // später entfallen können
  if isDeviceCorrectJN[1] = 'n' then                // oder sicher gemacht
  begin                                             // werden (Fehlerprüfungen
    BASS_Free;                                      // bei der Eingabe oder
    Halt(0);                                        // geeignet abgesicherte
  end;                                              // Eingabe)
  if not BASS_RecordInit(5) then    // Entspr. Stand 20220923 ist das "LINE 1 (Virtual Audio Cable)"
  begin
    BASS_Free;
    Halt(0);
  end;  // if not BASS_RecordInit(5)
  AktDeviceName:=BASS_RecordGetInputName(5);
  BASS_RecordSetInput(5, BASS_INPUT_ON, -1);
  rchan:=BASS_RecordStart(44100, 1, 0, @RecordingCallback, nil);
  BASS_Free;
  Readln;
end.

-----------------------------------------------------------snip--------------------------------------------------------------------

many thanks in advance

Greetings
GUMeyer
« Last Edit: 1 Oct '22 - 06:25 by GUMeyer »

David_AVD

  • Posts: 80
My understanding is that you would only use BASS_ChannelGetLevel outside of the record callback.

I have done something like this to monitor an input for audio activity:

Code: [Select]
procedure TAudio.OnTimer(Sender: TObject);
var
  Levels: DWORD;
  L, R: Word;
begin
  if fStream > 0 then
  begin
    Levels := BASS_ChannelGetLevel(fStream);
    L := LoWord(Levels);
    R := HiWord(Levels);
    ....
    ....
  end;
end;

If you're using the record callback, just access the buffer it gives you. In my example I didn't use the callback.

Code: [Select]
function RecordingCallback(Handle: HRecord; buffer: Pointer; length: DWord; user: Pointer): boolean; stdcall;
begin
  Result := True;
end;

GUMeyer

  • Posts: 16
Hallo,

My understanding is that you would only use BASS_ChannelGetLevel outside of the record callback.

I have done something like this to monitor an input for audio activity:

Code: [Select]
procedure TAudio.OnTimer(Sender: TObject);
var
  Levels: DWORD;
  L, R: Word;
begin
  if fStream > 0 then
  begin
    Levels := BASS_ChannelGetLevel(fStream);
    L := LoWord(Levels);
    R := HiWord(Levels);
    ....
    ....
  end;
end;
That looks interesting. And after a look in the help it seems that this function could work for me. I will give it a try. Thank you!

If you're using the record callback, just access the buffer it gives you. In my example I didn't use the callback.

Code: [Select]
function RecordingCallback(Handle: HRecord; buffer: Pointer; length: DWord; user: Pointer): boolean; stdcall;
begin
  Result := True;
end;
[/quote]
Well, you use a timer. As i want it as simple as possible, i started my "Explorations" with a simple command-line application and there i do not have a timer. But as you say i will use the recording callback - there i also have the buffer to access.

I will try your described solution. And i will give feedback - positive or negative.
Have a nice week. Thank you.

Greetings
GUMeyer

Ian @ un4seen

  • Administrator
  • Posts: 25446
function RecordingCallback(Handle: HRecord; buffer: Pointer; length: DWord; user: Pointer): LongBool; stdcall;
begin
  Result := True;
  PSBuffer:=nil;
    BASS_ChannelGetData(rchan, PSBuffer, BASS_DATA_FIXED);
    for SampleIndex:= 0 to 127 do
      begin
        SampleValue:=PSBuffer^[SampleIndex];
        Writeln(inttostr(SampleValue));
      end;
end;  // function RecordingCallback

From the "just access the buffer it gives you" comment in your last post, it looks like you've already fixed the problem, but just in case... The captured data is indeed in the "buffer" parameter received by the callback, so you don't need to call BASS_ChannelGetData to get it. The crash you had with the code above will be because "PSBuffer" is nil, so it ends up trying to access address 0.

GUMeyer

  • Posts: 16
function RecordingCallback(Handle: HRecord; buffer: Pointer; length: DWord; user: Pointer): LongBool; stdcall;
begin
  Result := True;
  PSBuffer:=nil;
    BASS_ChannelGetData(rchan, PSBuffer, BASS_DATA_FIXED);
    for SampleIndex:= 0 to 127 do
      begin
        SampleValue:=PSBuffer^[SampleIndex];
        Writeln(inttostr(SampleValue));
      end;
end;  // function RecordingCallback

From the "just access the buffer it gives you" comment in your last post, it looks like you've already fixed the problem, but just in case... The captured data is indeed in the "buffer" parameter received by the callback, so you don't need to call BASS_ChannelGetData to get it.
Hmm...first results lead me to a general question: What is the difference between "BASS_ChannelGetData" and "Bass_ChannelGetLevel"? I just changed my code from the the first to the second version and stored the read data to a file. But it seems that "BASS_ChannelGetLevel" is not what i need. I do not need the levels.

The crash you had with the code above will be because "PSBuffer" is nil, so it ends up trying to access address 0.
Ouch...sorry.
Ok, next try...
Thank you

Ian @ un4seen

  • Administrator
  • Posts: 25446
Hmm...first results lead me to a general question: What is the difference between "BASS_ChannelGetData" and "Bass_ChannelGetLevel"? I just changed my code from the the first to the second version and stored the read data to a file. But it seems that "BASS_ChannelGetLevel" is not what i need. I do not need the levels.

BASS_ChannelGetData gives you the latest sample data, while BASS_ChannelGetLevel(Ex) gives you the peak level of the sample data. You would use the latter if you just want the level, and the former if you want to do something else with the data. As mentioned, a recording's callback function (RECORDPROC) will receive the data too, and that would usually be the best place to process it.

What do you want to do with the data?

GUMeyer

  • Posts: 16
Hmm...first results lead me to a general question: What is the difference between "BASS_ChannelGetData" and "Bass_ChannelGetLevel"? I just changed my code from the the first to the second version and stored the read data to a file. But it seems that "BASS_ChannelGetLevel" is not what i need. I do not need the levels.

BASS_ChannelGetData gives you the latest sample data, while BASS_ChannelGetLevel(Ex) gives you the peak level of the sample data. You would use the latter if you just want the level, and the former if you want to do something else with the data. As mentioned, a recording's callback function (RECORDPROC) will receive the data too, and that would usually be the best place to process it.
Does this means that:
What do you want to do with the data?
i do not have to use a function like BASS_ChannelGetData to process the data, provided by the Buffer? So i would use a RECORDPROC like this (unnecessary parameters and variable declarations are here ommited):
Code: [Select]
function RECORDPROC(Handle : HRECORD; Buffer : Pointer; ...) : LongBool; stdcall;
type PSampleBuffer : ^TSampleBuffer;
       TSampleBuffer : array[0..127] of integer;
var PSB : PSampleBuffer;
     i : integer;
begin
  PSB:=Buffer;
  for i:=0 to 127 do
    writeln(PSB^[i];  // later there will be some processing for the integer values in the array
  Result:=True;   // to continue recording
end;
Or do i still misunderstand this all?

What i want to do: I want to write a program that reads an LTC (https://en.wikipedia.org/wiki/Linear_timecode and especially https://en.wikipedia.org/wiki/File:Timecode-audacity.png), decodes it to get the timecode information and to convert this then into MTC (https://en.wikipedia.org/wiki/MIDI_timecode). The final target is to have a software called Synchronizer and that can synchronize a MIDI-Device (in my case a midi sequencer) to an LTC-Source (in my case a 4-track-tape-recorder with a timecode-track on track 4). For the decoding of the LTC-Samplevalues according to the description in https://en.wikipedia.org/wiki/Differential_Manchester_encoding  i already have a piece of software-code. And the generation of the MTC i have also already started. So my first task is to get the sample values of the LTC to decode them

Many thanks for the patience

Greetings
GUMeyer

Ian @ un4seen

  • Administrator
  • Posts: 25446
i do not have to use a function like BASS_ChannelGetData to process the data, provided by the Buffer? So i would use a RECORDPROC like this (unnecessary parameters and variable declarations are here ommited):
Code: [Select]
function RECORDPROC(Handle : HRECORD; Buffer : Pointer; ...) : LongBool; stdcall;
type PSampleBuffer : ^TSampleBuffer;
       TSampleBuffer : array[0..127] of integer;
var PSB : PSampleBuffer;
     i : integer;
begin
  PSB:=Buffer;
  for i:=0 to 127 do
    writeln(PSB^[i];  // later there will be some processing for the integer values in the array
  Result:=True;   // to continue recording
end;

Yes. I'm not a Delphi user myself, so I'm not sure about the validity of that code, but the general idea to process the provided buffer instead of using BASS_ChannelGetData is correct. Note that the RECORDPROC "length" parameter tells you how much data (bytes) there is in the buffer, so that's the amount that you should process, or at least no more than that.

David_AVD

  • Posts: 80
From the latest code, it looks like the OP is wanting to store a fixed amount of data (128 samples) each time?

Not sure how that will work if the buffer length can change each call.

Do you need all of the data without missing any?

GUMeyer

  • Posts: 16

Yes. I'm not a Delphi user myself, so I'm not sure about the validity of that code, but the general idea to process the provided buffer instead of using BASS_ChannelGetData is correct. Note that the RECORDPROC "length" parameter tells you how much data (bytes) there is in the buffer, so that's the amount that you should process, or at least no more than that.
Oh, thank you. That helps a lot.

Now i have to find out why my
Code: [Select]
for i:=0 to 127 do writeln(PSB^[i]; causes the lazarus debugger to fail on the start of my program. Without a WriteLn (output to console) there are no errors (visible). But i wanted to see the content of the array.

David_AVD

  • Posts: 80
You probably shouldn't be doing anything that takes too long inside the recording callback.

Are you checking that the buffer even has enough data in it? Otherwise you'll be accessing random memory.

GUMeyer

  • Posts: 16
From the latest code, it looks like the OP is wanting to store a fixed amount of data (128 samples) each time?
the 128 is only a first try. The (python-)code i use as template uses even 2048. But for the "exploration" i am satisfied with a small array size.

Not sure how that will work if the buffer length can change each call.
That is why i will use the Length-Parameter. I have already understood that i need a dynamic array with the problem that
Do you need all of the data without missing any?
i always need at least enough bytes to recognize a LTC-Frame. And if i understand the biphase-mark-coding correctly i need at least two bytes to gain the "LTC-Bytes" (1 LTC-Frame = 80 Bytes).

GUMeyer

  • Posts: 16
You probably shouldn't be doing anything that takes too long inside the recording callback.
I read about this in the help-file. That is the reason while i wanted to check the content of the Buffer.

Are you checking that the buffer even has enough data in it? Otherwise you'll be accessing random memory.
I already learned that i should use the Length-Parameter for this. Thank you though.

jpf

  • Posts: 182
I'm not a Lazarus user myself, but I have a similar problem with doing GUI suff inside callbacks. The problem seems to be, as Ian explained more than once, that thr Bass channel processing code is running in a different thread than your GUI is. So the chances that you acces the same memory (Buffer) at the same time is high. That would cause the segmentation fault.

In my case I was able to overcome the problem by first copying the buffer to an appwise scope buffer, and then accessing it outside the callback (using a timer, for instance). Before accesing this buffer from the timer I lock the bass channel to the main thread of my app like
BASS_ChannelLock(Handle, True)
This stops the processing of the channel for a while.
When you're done with your accessing the buffer you must unlock the channel like
BASS_ChannelLock(Handle, False)
So the processing can go on.

It seem to me that it would be much easier to you to just request data using BASS_ChannelGetData, that is thread safe. You can do this inside a timer. This would also allow you to request exactly the amount of data that you need to recognize one LTC-Frame, and at the same time keep the latency to a minimum. You may need to ask for more than one LTC-Frame of data from time to time to keep in sync in case some data is lost (that will certainly happen when you pause, FF or REW the tape recorder because the pulses will be distorted then).

Hope this helps.

Edit:
Maybe in the Lazarus environment the GUI stuff is thread safe, so I'm not sure that your printing the buffer contents inside the callback is causing the segmentation fault. It is in the VB6 IDE, but not in the compiled exe.
« Last Edit: 28 Sep '22 - 17:41 by jpf »

GUMeyer

  • Posts: 16
I'm not a Lazarus user myself, but I have a similar problem with doing GUI suff inside callbacks. The problem seems to be, as Ian explained more than once, that thr Bass channel processing code is running in a different thread than your GUI is. So the chances that you acces the same memory (Buffer) at the same time is high. That would cause the segmentation fault.

Ah, thank you. That is the explanation i needed. So i really have to
In my case I was able to overcome the problem by first copying the buffer to an appwise scope buffer, and then accessing it outside the callback (using a timer, for instance). Before accesing this buffer from the timer I lock the bass channel to the main thread of my app like
BASS_ChannelLock(Handle, True)
This stops the processing of the channel for a while.
When you're done with your accessing the buffer you must unlock the channel like
BASS_ChannelLock(Handle, False)
So the processing can go on.
- ok, i understand.

It seem to me that it would be much easier to you to just request data using BASS_ChannelGetData, that is thread safe. You can do this inside a timer.

These two things i will use because the "clean console version" is a thing i cannot get to run properly. So today i decided to use (after learning how) a timer in the console version.

This would also allow you to request exactly the amount of data that you need to recognize one LTC-Frame, and at the same time keep the latency to a minimum.
This is then my next problem. And you present me the solution. Many thanks!

You may need to ask for more than one LTC-Frame of data from time to time to keep in sync in case some data is lost (that will certainly happen when you pause, FF or REW the tape recorder because the pulses will be distorted then).
Well Pause, FF or REW are not the problem on the MIDI-side because after the start i send a full MIDI-Timecode-Frame and then only so call "quarter-frames" that only serve as a kind of clock. And at pause a full frame is sent again to point the MIDI-Sequencer to the correct time position. The cases of FF or REW can be covered by also sending the full frame and then to wait a short time (i will have to look up this length) until the transmission of the quarter-frames starts again.

Hope this helps.
It helps a lot. I now have to think about all and then to implement the necessary code.

Edit:
Maybe in the Lazarus environment the GUI stuff is thread safe, so I'm not sure that your printing the buffer contents inside the callback is causing the segmentation fault. It is in the VB6 IDE, but not in the compiled exe.
I don't know. A strong indicator for the GUI stuff not being thread safe is the point that i can do everything non gui-related in the callback but as soon as i access the gui, by writing something for example i get the segmentation fault.

GUMeyer

  • Posts: 16
In my case I was able to overcome the problem by first copying the buffer to an appwise scope buffer, and then accessing it outside the callback (using a timer, for instance). Before accesing this buffer from the timer I lock the bass channel to the main thread of my app like
BASS_ChannelLock(Handle, True)
This stops the processing of the channel for a while.
When you're done with your accessing the buffer you must unlock the channel like
BASS_ChannelLock(Handle, False)
So the processing can go on.
YES! That did it. At least at first glance. I have to verify but my logfile looks promising  :)

It seem to me that it would be much easier to you to just request data using BASS_ChannelGetData, that is thread safe. You can do this inside a timer.
This brings me also closer to the solution. I feel like being "on the right path" ;D

You may need to ask for more than one LTC-Frame of data from time to time to keep in sync in case some data is lost (that will certainly happen when you pause, FF or REW the tape recorder because the pulses will be distorted then).
In the current version i use an array for 2048 integer-values - this should be enough values ;)


Hope this helps.
It helps a lot. I now have to think about all and then to implement the necessary code.

Many thanks. I will not set this thread to "solved" because i want still want to do some software-tests. But as soon as the question is sure answered i will.

jpf

  • Posts: 182
Great! I'm glad that at least once I was able to help.

GUMeyer

  • Posts: 16
I'm glad that you could help me! See it as a motivation for my next questions ;D ...and those will come, i'm sure ;)
At the moment i'm fhinking about the different data formats, i can retrieve and how i can use them. But the questions to this "complex" will be discussed, if necessary, in a new thread.

Thanks to all for reading my problem and trying to help!

GUMeyer

  • Posts: 16
The reason for the question doesn't exists anymore! Now it's time to plan and implement the next steps of my project, but the then maybe coming questions need a new thread.
As a conclusion (and also as support for others that may have a similar problem to mine) i will post the relevant code-fragments here (if it's allowed).
Hint: I changed from a command-line-version to a "normal" GUI-Version because there are some things (as an easy integration of a timer) easier possible:
Code: [Select]
[Code is for Lazarus, especially the callback needs some changes for a delphi-version]
type
  PSampleBuffer = ^TSampleBuffer;
  TSampleBuffer = array[0..2047] of Real; //integer;

var
  win: THandle;
  devinf       : BASS_DEVICEINFO;
  isDeviceCorrectJN : boolean;
  AktDeviceName : string;
  rchan: HRecord = 0;
  Chan: HStream = 0;
  SampleBuffer : TSampleBuffer;
  PSBuffer : PSampleBuffer;
  DebugFile : text;
  LineInDebugFile : string;   

function RecordingCallback(Handle: HRecord; Buffer : Pointer; length: DWord; user: Pointer): LongBool; stdcall;
begin
  Result := True;
end;  // function RecordingCallback

procedure TForm1.FormCreate(Sender: TObject);
begin
  if not BASS_Init(-1, 44100, 0, Form1.Handle, nil) then
  begin
    Edit1.Text:='Problem bei BASS: Init';
    Halt(0);
  end;  // if not BASS_Init(-1, 44100, 0, Form1.Handle, nil)
  BASS_RecordGetDeviceInfo(5, devinf);
  Edit1.Text:=devinf.name;
  isDeviceCorrectJN:=MessageDlg('Ist dieses Device richtig? (' + devinf.name+')' , mtConfirmation, [mbYes,mbNo], 0) = mrYes;
  if not isDeviceCorrectJN then
  begin
    BASS_Free;
    Halt(0);
  end;  // if not isDeviceCorrectJN
  if not BASS_RecordInit(5) then
  begin
    Edit1.Text:='Problem beim Initialisieren von Device';
    BASS_Free;
    Halt(0);
  end;  // if not BASS_RecordInit(5)
  AktDeviceName:=BASS_RecordGetInputName(5);
  BASS_RecordSetInput(5, BASS_INPUT_ON, 0.5);  // nur Werte zwischen 0 und 1 sind sinnvoll (-1 = aktuelle Lautstärke)
  rchan:=BASS_RecordStart(44100, 1,0, @RecordingCallback, nil);
end;  // procedure TForm1.FormCreate

procedure TForm1.Timer1Timer(Sender: TObject);
var samplecount : longint;
  i : integer;
begin
  samplecount:= BASS_ChannelGetData(rchan, PSBuffer, BASS_DATA_FLOAT);;
  if samplecount = -1 then
  FehlerAusgabe(BASS_ErrorGetCode)
  else
  begin
    BASS_ChannelLock(Chan, True);
    for i:=0 to 2047 do
    begin
      LineInDebugFile:=FloatToStr(PSBuffer^[i]) + #13#10;
      Write(DebugFile, LineInDebugFile);
    end;
    BASS_ChannelLock(Chan, False);
  end;
end;  // procedure TForm1.Timer1Timer
The naming of the components (form, timer, buttons, etc.) will be changed, but for demonstation i leave it here. I also know there are many parts that could be better coded.

Thank you again - now how can i mark the topic/question as "Solved"?

jpf

  • Posts: 182
There may be other ways to do it, but this is how I did it once:

If you're the original poster of the thread, you can edit/modify the "Subject / Started by" (title) of the thread.

In the upper right corner of each of your posts or replies there's a "Modify" icon (paper + pencil) / link that you can hit. It gets you to a "Modify message" web page. There you can edit the "Subject" / title of the post. The "Subject" of the first post in the thread is also the "Subject / Started by" title shown in the "Un4seen Developments Forum » Developments » BASS" web page. Since you started the thread, you can modify the "Subject / Started by" title of the thread by editing the "Subject" of the first post in the thread.

Just write in your own words "SOLVED:" or "[SOLVED]" or whatever you'd like before your original "BASS_ChannelGetData: only seg. faults (source code included)" title, like
"[SOLVED] BASS_ChannelGetData: only seg. faults (source code included)".

GUMeyer

  • Posts: 16
Thank you..  :)