Author Topic: Class BASS_RecordStart  (Read 277 times)

Ed1966

  • Posts: 179
Class BASS_RecordStart
« on: 19 Apr '21 - 08:19 »
Hi There  :)

I want to try BASS_RecordStart and wrote a little Class wrapper.
But it's not working. no sound and sometimes AV.
What is wrong or missing?
Thank you in advanced.

Code: [Select]
unit DefaultRecordInitStart;

interface

uses
  Winapi.Windows,
  Dynamic_Bass {Bass},
  Dynamic_Bassmix {Bassmix},
  Dynamic_WASAPI {Basswasapi};

type
  { Microphone }
  RECORD_DefaultClass = class
    class function Init: Boolean;
    class function Start: Boolean;
    class function Free: Boolean;
    class function GetStream: HSTREAM;
  end;

implementation

var
  DefaultInputDevice: Integer = 0;
  DefaultInputStream: HSTREAM = 0;

function MyRecordProc(channel: HSTREAM; chan: DWORD; buffer: Pointer; len: DWORD; user: Pointer): Boolean; stdcall;
begin
  Result := True;
end;

class function RECORD_DefaultClass.Init: Boolean;
begin
  // Use 'Default' Input
  BASS_SetConfig(BASS_CONFIG_REC_WASAPI, 1);
  Result := BASS_RecordInit(-1);
end;

class function RECORD_DefaultClass.Start: Boolean;
var
  RECORDINFO: BASS_RECORDINFO;
begin
  BASS_RecordGetInfo(RECORDINFO);
  DefaultInputDevice := BASS_RecordGetDevice;
  DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2 {RECORDINFO.inputs}, 0, @MyRecordProc, nil);
  Result := BASS_ChannelSetAttribute(DefaultInputStream, BASS_ATTRIB_VOL, 1);
end;

class function RECORD_DefaultClass.Free: Boolean;
begin
  Result := BASS_RecordSetDevice(DefaultInputDevice);
  if (Result) then
    Result := BASS_RecordFree;
end;

class function RECORD_DefaultClass.GetStream: HSTREAM;
begin
  Result := DefaultInputStream;
end;

end.

Call this with little TEST source.
Now i use SilenceMixer18 with my original WASAPI and that's working.
note: Silence is not silence volume. More for broadcast so you can turn off volume.

Code: [Select]
  if RECORD_DefaultClass.Init then
  begin
    if RECORD_DefaultClass.Start then
    begin
      SilenceMixer18.AddChannelExt(RECORD_DefaultClass.GetStream, 0); // BASSMixerInit
      SilenceMixer18.ChannelPlay(RECORD_DefaultClass.GetStream);
    end;

Kind regards,
Eduard.

Ian @ un4seen

  • Administrator
  • Posts: 23545
Re: Class BASS_RecordStart
« Reply #1 on: 19 Apr '21 - 15:35 »
Code: [Select]
function MyRecordProc(channel: HSTREAM; chan: DWORD; buffer: Pointer; len: DWORD; user: Pointer): Boolean; stdcall;

That line has an extra parameter, so it's probably what's causing the problem. It should look like this:

Code: [Select]
function MyRecordProc(handle: HRECORD; buffer: Pointer; len: DWORD; user: Pointer): Boolean; stdcall;

Does the problem still happen after changing to that?

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #2 on: 19 Apr '21 - 18:42 »
Code: [Select]
DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2 {RECORDINFO.inputs}, 0, @MyRecordProc, nil);Your RecordProc are only passing the Result so in this case  its not needed.
that should be enough
Code: [Select]
DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2 {RECORDINFO.inputs}, 0, nil, nil);

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #3 on: 20 Apr '21 - 11:07 »
@Ian: function MyRecordProc changed but no sound.
@Chis: Yes now record is working.  :)

This is my call. Is BASS_SAMPLE_FLOAT a right choice for line-in but also for microphone?
Code: [Select]
  // Record with 5ms period
  DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2, MAKELONG(BASS_SAMPLE_FLOAT, 5), nil, nil);

Final question:
How must I play directly to default output soundcard?
(Maybe i think to difficult)

Regards,
Eduard.


Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #4 on: 20 Apr '21 - 14:01 »
Quote
The HIWORD - use MAKELONG(flags,period) - can be used to set the period (in milliseconds) between calls to the callback function
so in your example  you are not Using a Recordproc
so the call should be
 
Code: [Select]
DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, nil, nil);
Quote
Final question:
How must I play directly to default output sound card?
Do you will just play the Record Stream or do want to play a other AudioFile?
A Audiofile can you play with Bass_StreamCreateFile...
« Last Edit: 20 Apr '21 - 14:10 by Chris »

Ian @ un4seen

  • Administrator
  • Posts: 23545
Re: Class BASS_RecordStart
« Reply #5 on: 20 Apr '21 - 16:42 »
Final question:
How must I play directly to default output soundcard?

Do you want to forward the recorded data to the output device? If so, there are 2 ways you could do that. You could use a push stream (BASS_StreamCreate with STREAMPROC_PUSH) and have a RECORDPROC function call BASS_StreamPutData to feed the captured data to it. Or you could create a normal custom stream (BASS_StreamCreate without STREAMPROC_PUSH) and have that call BASS_ChannelGetData on the recording (with no RECORDPROC). In the latter case, you will probably want to disable playback buffering on the stream to minimize latency by setting BASS_ATTRIB_BUFFER to 0 (via BASS_ChannelSetAttribute). In both cases, the stream should have the same sample format as the recording. Please see the documentation for details on the mentioned functions/options.

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #6 on: 20 Apr '21 - 19:55 »
Maybe you have an example because I don't quite get it.
My preference is for the first solution so that I can leave my class as it is. And then work with RECORD_DefaultClass.GetStream 

So far I have come to:
AStream := BASS_StreamCreate(RECORD_DefaultClass.GetFrequency, 2, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT, STREAMPROC_PUSH, nil);

« Last Edit: 21 Apr '21 - 02:52 by Ed1966 »

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #7 on: 21 Apr '21 - 12:04 »
Code: [Select]
function RecordingCallback(FHandle: HRECORD; buffer: Pointer; length: DWord;
  user: Pointer): boolean; stdcall;
begin
  BASS_StreamPutData(AStream, buffer, length);
  Result := True;
end;

Code: [Select]
DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, @RecordingCallback, nil);
Code: [Select]
AStream := BASS_StreamCreate(RECORD_DefaultClass.GetFrequency, 2, BASS_SAMPLE_FLOAT, STREAMPROC_PUSH, nil);

Bass_ChannelPlay(ASstream,false);
« Last Edit: 21 Apr '21 - 15:02 by Chris »

Ian @ un4seen

  • Administrator
  • Posts: 23545
Re: Class BASS_RecordStart
« Reply #8 on: 21 Apr '21 - 13:50 »
Yep, something like that except it should be the push stream's handle ("AStream" instead of "FHandle") in the BASS_StreamPutData call. You may also want to request a shorter period in the BASS_RecordStart call to reduce latency (the default is 100ms).

In case you want to try the other "normal custom stream" method that I mentioned, it would look something like this (in C):

Code: [Select]
stream = BASS_StreamCreate(freq, chans, BASS_SAMPLE_FLOAT, StreamProc, 0); // create output stream
BASS_ChannelSetAttribute(stream, BASS_ATTRIB_BUFFER, 0); // disable playback buffering
record = BASS_RecordStart(freq, chans, BASS_SAMPLE_FLOAT, 0, 0); // start recording (without RECORDPROC)
BASS_ChannelPlay(stream, 0); // start output

...

DWORD CALLBACK StreamProc(HSTREAM handle, void *buffer, DWORD length, void *user)
{
return BASS_ChannelGetData(record, buffer, length); // get data from the recording
}

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #9 on: 21 Apr '21 - 15:35 »
There is no sound when I test both functions?
ErrorLevel 0 Ian, ErrorLevel 5 Chris.

Code: [Select]
{------------------------------------------------------------------------------}

class function RECORD_DefaultClass.StartIan: Boolean;
var
  ARecord: HRECORD;
  AStream: HSTREAM;

  function StreamProc(handle: HSTREAM; buffer: pointer; length: DWORD; user: Pointer): DWORD; stdcall;
  begin
     Result := BASS_ChannelGetData(ARecord, buffer, length); // get data from the recording
  end;

begin
  BASS_SetConfig(BASS_CONFIG_REC_WASAPI, 1); // 0 = DirectSound
  Result := BASS_RecordInit(-1);
  if (Result) then
  begin
    BASS_RecordGetInfo(RECORDINFO);
    AStream := BASS_StreamCreate(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, @StreamProc, nil); // create output stream
    BASS_ChannelSetAttribute(AStream, BASS_ATTRIB_BUFFER, 0); // disable playback buffering
    ARecord := BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, nil, nil); // start recording (without RECORDPROC)
    BASS_ChannelPlay(AStream, False); // start output
  end;
  ShowMessage(IntToStr(BASS_ErrorGetCode)); // 0
end;

class function RECORD_DefaultClass.StartChris: Boolean;
var
  AStream: HSTREAM;

  function RecordingCallback(FHandle: HRECORD; buffer: Pointer; length: DWord; user: Pointer): boolean; stdcall;
  begin
    BASS_StreamPutData(FHandle, buffer, length);
    Result := True;
  end;

begin
  BASS_SetConfig(BASS_CONFIG_REC_WASAPI, 1); // 0 = DirectSound
  Result := BASS_RecordInit(-1);
  if (Result) then
  begin
    DefaultInputStream:= BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, @RecordingCallback, nil);
    AStream := BASS_StreamCreate(RECORD_DefaultClass.GetFrequency, 2, BASS_SAMPLE_FLOAT, STREAMPROC_PUSH, nil);
    Bass_ChannelPlay(AStream, False);
  end;
  ShowMessage(IntToStr(BASS_ErrorGetCode)); // 5
end;

{------------------------------------------------------------------------------}

My first sample with Mixer is correct.
Code: [Select]
if RECORD_DefaultClass.Init(-1) then
  begin
    if RECORD_DefaultClass.Start then
    begin
      SilenceMixer18.AddChannelExt(RECORD_DefaultClass.GetStream, 0); // BASSMixerInit
      SilenceMixer18.ChannelPlay(RECORD_DefaultClass.GetStream);

Ian @ un4seen

  • Administrator
  • Posts: 23545
Re: Class BASS_RecordStart
« Reply #10 on: 21 Apr '21 - 16:13 »
Please try logging the return value from the BASS_ChannelGetData call to see what that is.

In the "StartChris" case, error code 5 = BASS_ERROR_HANDLE, so it looks like the BASS_StreamCreate call failed. Call BASS_ErrorGetCode straight after BASS_StreamCreate to find out the error code from that.

Also confirm what device you're using in your BASS_Init call.

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #11 on: 21 Apr '21 - 16:34 »
@ Ed1966 du you have Init Bass ?
Because I didN't see a Bass_Init in your class.

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #12 on: 21 Apr '21 - 17:02 »
Little working Test of Ians suggestion:

Code: [Select]
var
  FRecord: HRecord;
  FStream: HStream;

procedure Error(Value: integer);
begin
  if (BASS_ErrorGetCode <> 0) then
    ShowMessage('Pos ' + Value.ToString + #13#10 + (BASS_ErrorGetCode)
      .ToString);
end;

function StreamProc(FHandle: HStream; buffer: Pointer; length: DWORD;
  user: Pointer): DWORD stdcall;
begin
  Result := BASS_ChannelGetData(FRecord, buffer, length);

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Bass_Init(-1, 48000, 0, handle, nil);
  Bass_RecordInit(-1);
  FStream := BASS_StreamCreate(48000, 2, BASS_SAMPLE_FLOAT, @StreamProc, nil);
  // create output stream
  BASS_ChannelSetAttribute(FStream, BASS_ATTRIB_BUFFER, 0);
  // disable playback buffering

  // Start the stuff
  FRecord := Bass_RecordStart(48000, 2, BASS_SAMPLE_FLOAT, nil, nil);

  BASS_ChannelPlay(FStream, false); // start output

end;

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #13 on: 21 Apr '21 - 17:55 »
BASS_Init was previously called. I have now put everything in a separate project and that is going well. Both version (Ian, Chris) and Mixer (Me) are now working.

I'm going to check everything and rewrite my RecordClass for these three variants. Maybe see you next week for some additional questions.

Thank you very much for your time.  :)

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #14 on: 26 Apr '21 - 14:49 »
Hello again,

I have now finished the overall picture and attached a splitter to it for broadcast. But now I got two solutions from you and my question is which one is the best, if there is one?
I also applied MAKELONG again to BASS_RecordStart.

Should I be using RecordAndSplitA or RecordAndSplitB?
Here are the two sources. I think they both look good. If not let me know.

Code: [Select]
  procedure RecordAndSplitA;
  begin
    AStreamIan := BASS_StreamCreate(RECORDINFO.freq, 2, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT, @StreamProc, nil);
    ARecordIan := BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, nil, nil);

    AMixerSplitIan := BASS_Split_StreamCreate(AStreamIan, BASS_STREAM_DECODE, nil);
    APlaySplitIan := BASS_Split_StreamCreate(AStreamIan, 0, nil);
  end;

Code: [Select]
  procedure RecordAndSplitB;
  begin
    ARecordChris := BASS_RecordStart(RECORDINFO.freq, 2, MAKELONG(BASS_SAMPLE_FLOAT, 5), @RecordingCallback, nil);
    AStreamChris := BASS_StreamCreate(RECORDINFO.freq, 2, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT, STREAMPROC_PUSH, nil);

    AMixerSplitChris := BASS_Split_StreamCreate(AStreamChris, BASS_STREAM_DECODE, nil);
    APlaySplitChris := BASS_Split_StreamCreate(AStreamChris, 0, nil);
  end;

Kind regards,
Eduard.

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #15 on: 26 Apr '21 - 15:24 »
Hello, for what specially is the Split Stuff ?
If I right understand you want
Stream1 for encoding (sending to a Shoutcast/Icecast Server,
Stream2 für hearing what you speak in your Microphone?

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #16 on: 26 Apr '21 - 17:56 »
That's correct. Or a line in mixer panel broadcast and listen without delay via direct output.

Then the question is actually which of the two is the best because they both work.
(RecordAndSplitA or RecordAndSplitB)

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #17 on: 27 Apr '21 - 11:35 »
Maybe I don't explain it good  :D

Here my WORKING CODE:

Question is
// Choose between RecordAndSplitA or RecordAndSplitB ???
They both work but witch one must i choose for final code?
Next I want put VST on AGlobalStream 
I know how to do that.
So with one A or B?

Code: [Select]
var
  // Must be Global!
  AGlobalRecord: HRECORD;
  AGlobalStream: HSTREAM;
  AMixerSplitter,
  AOutputSplitter: HSTREAM;
  AGlobalMixer: HSTREAM;

procedure TfrmBassTestForm.btn_IAN_RecordTestClick(Sender: TObject);
var
  RECORDINFO: BASS_RECORDINFO;
  DEVICEINFO: BASS_DEVICEINFO;

  procedure PlayStream(AStream: HSTREAM);
  begin
    BASS_ChannelSetAttribute(AStream, BASS_ATTRIB_BUFFER, 0); // disable playback buffering
    BASS_ChannelSetAttribute(AStream, BASS_ATTRIB_VOL, 2);
    BASS_ChannelPlay(AStream, False); // start output
  end;

  procedure PlayMixer(AStream: HSTREAM);
  begin
    AGlobalMixer := BASS_Mixer_StreamCreate(RECORDINFO.freq, 2, BASS_UNICODE or BASS_SAMPLE_FLOAT or BASS_STREAM_AUTOFREE);
    BASS_Mixer_StreamAddChannel(AGlobalMixer, AStream, 0);
    BASS_ChannelSetAttribute(AGlobalMixer, BASS_ATTRIB_VOL, 2);
    BASS_ChannelPlay(AGlobalMixer, False);
  end;

  // Choose between RecordAndSplitA or RecordAndSplitB ???
  procedure RecordAndSplitA;

    function StreamProc(FHandle: HSTREAM; buffer: Pointer; length: DWORD; user: Pointer): DWORD stdcall;
    begin
      Result := BASS_ChannelGetData(AGlobalRecord, buffer, length);
    end;

  begin
    AGlobalStream := BASS_StreamCreate(RECORDINFO.freq, 2, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT, @StreamProc, nil);
    AGlobalRecord := BASS_RecordStart(RECORDINFO.freq, 2, BASS_SAMPLE_FLOAT, nil, nil);

    AMixerSplitter := BASS_Split_StreamCreate(AGlobalStream, BASS_STREAM_DECODE, nil);
    AOutputSplitter := BASS_Split_StreamCreate(AGlobalStream, 0, nil);
  end;

  // Choose between RecordAndSplitA or RecordAndSplitB ???
  procedure RecordAndSplitB;

    function RecordingCallback(FHandle: HRECORD; buffer: Pointer; length: DWORD; user: Pointer): Boolean; stdcall;
    begin
      BASS_StreamPutData(AGlobalStream, buffer, length);
      Result := True;
    end;

  begin
    AGlobalRecord := BASS_RecordStart(RECORDINFO.freq, 2, MAKELONG(BASS_SAMPLE_FLOAT, 5), @RecordingCallback, nil);
    AGlobalStream := BASS_StreamCreate(RECORDINFO.freq, 2, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT, STREAMPROC_PUSH, nil);

    AMixerSplitter := BASS_Split_StreamCreate(AGlobalStream, BASS_STREAM_DECODE, nil);
    AOutputSplitter := BASS_Split_StreamCreate(AGlobalStream, 0, nil);
  end;

begin
  BASS_SetConfig(BASS_CONFIG_REC_WASAPI, 1); // 0 = DirectSound

  BASS_StreamFree(AGlobalMixer); // Free
  BASS_StreamFree(AGlobalStream); // Free

  BASS_RecordFree;
  Bass_RecordInit(-1); // Default
  BASS_RecordGetInfo(RECORDINFO);

  BASS_SetDevice(GetDefaultOutput); // Default (let op kan ook -1 zijn, geen Default)
  BASS_GetDeviceInfo(BASS_GetDevice, DEVICEINFO);

  RecordAndSplitB; // OR RecordAndSplitA

  // PlayStream(AOutputSplitter); OR MIXER OR BOTH

  PlayMixer(AMixerSplitter);
end;

Regards,
Eduard.

Chris

  • Posts: 1933
Re: Class BASS_RecordStart
« Reply #18 on: 27 Apr '21 - 14:25 »
You want a VST Effekt on the Stream what is sending to the ICE/Shout Server or that what you want to hear ?

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #19 on: 28 Apr '21 - 06:25 »
Both: AGlobalStream
So you hear on broadcast and output the same sound

Ian @ un4seen

  • Administrator
  • Posts: 23545
Re: Class BASS_RecordStart
« Reply #20 on: 29 Apr '21 - 17:11 »
I would lean towards using the STREAMPROC method (not STREAMPROC_PUSH) but there isn't really a massive difference between them. One issue with that method is that any BASS_ChannelGetData/Level(Ex) calls on the recording would take data away from the STREAMPROC but there is actually a new "NOREMOVE" option coming in the next BASS release to avoid that. If you would like to use that option now, it's in the latest build:

   www.un4seen.com/stuff/bass.zip

See the BASS_DATA_NOREMOVE and BASS_LEVEL_NOREMOVE flags in the updated BASS.H file.

Ed1966

  • Posts: 179
Re: Class BASS_RecordStart
« Reply #21 on: 30 Apr '21 - 07:27 »
Thank you. I can go further for testing.  ;)