Record PCM data to memory stream or byte array

Started by kafffee,

kafffee

#50
QuoteIs your SyncTrackEnd function is getting called?

No, oddly not...

QuoteIf not, are you calling BASS_ChannelStop on the stream or BASS_Stop?

No. I start and stop recoprding, then I hit "play", which will execute PlayPause_Execute. It should actually be called PlayStopExecute, I did not rename that yet. For pausing playback, I have another button.

In my opinion we should take a closer look at these lines of code (you can find this in my StartPlayBack() procedure):


 Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()       'get remainder of division PlayBackPositionInBytes / BytesPerSample; BytesPerSample = number of channels * bit resolution / 8
PlayBackArray = New Byte(CInt(BytesWritten - PlayBackPositionInBytes - Rest) - 1) {}  'initializing PlayBackArray to the right required size; only whole samples because of Rest; BytesWritten: total amount of bytes recorded in RecordedData

 Array.Copy(RecordedData, PlayBackPositionInBytes, PlayBackArray, 0, PlayBackArray.Length)  'copy data from the recording to PlayBackArray; RecordedData= complete recording; PlayBackPositionInBytes = starting position of playback in RecordedData

 Bass.BASS_ChannelSetPosition(PlayBackChannel, 0)
 Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length Or CInt(BASSStreamProc.BASS_STREAMPROC_END))  'as you are not a .NET user, are you sure this is right? CInt will convert value to integer, Or is true when one of the two or both values are true;
'also: sometimes I still get BASS_ERROR_ILLPARAM. even though this should not happen anymore, because I cut off the remainder of not complete samples with Rest in line 2
 Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)

 Bass.BASS_ChannelPlay(PlayBackChannel, False)

Does this code seem correct to you?

Ian @ un4seen

Quote from: kafffee
QuoteIs your SyncTrackEnd function is getting called?

No, oddly not...

Try also setting a BASS_SYNC_FREE sync on the stream and put a breakpoint in the callback function to see if/when that gets called when running in the debugger. If you then check the callstack (make sure you have mixed-mode debugging enabled), perhaps it'll lead back to an unintentionally early BASS_StreamFree call to explain why playback never reaches the end.

Quote from: kafffeeIn my opinion we should take a closer look at these lines of code (you can find this in my StartPlayBack() procedure):


 Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()       'get remainder of division PlayBackPositionInBytes / BytesPerSample; BytesPerSample = number of channels * bit resolution / 8
PlayBackArray = New Byte(CInt(BytesWritten - PlayBackPositionInBytes - Rest) - 1) {}  'initializing PlayBackArray to the right required size; only whole samples because of Rest; BytesWritten: total amount of bytes recorded in RecordedData

 Array.Copy(RecordedData, PlayBackPositionInBytes, PlayBackArray, 0, PlayBackArray.Length)  'copy data from the recording to PlayBackArray; RecordedData= complete recording; PlayBackPositionInBytes = starting position of playback in RecordedData

 Bass.BASS_ChannelSetPosition(PlayBackChannel, 0)
 Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length Or CInt(BASSStreamProc.BASS_STREAMPROC_END))  'as you are not a .NET user, are you sure this is right? CInt will convert value to integer, Or is true when one of the two or both values are true;
'also: sometimes I still get BASS_ERROR_ILLPARAM. even though this should not happen anymore, because I cut off the remainder of not complete samples with Rest in line 2
 Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)

 Bass.BASS_ChannelPlay(PlayBackChannel, False)

Does this code seem correct to you?

That seems OK, but note the playback stream would need to be recreated (not reused) whenever the sample format changes. Not doing so could explain the BASS_ERROR_ILLPARAM error because your "BytesPerSample" value doesn't match the stream's.

kafffee

#52
Okay, did that and I found out about it, but there is something else not right.

After I removed the BASS_STREAM_AUTOFREE flag, it seemed to work three or four times (my SyncTrackEnd function was called and my waveform was okay, too), and then there I was at it again.

I tried a thousand times now, but so far I never got to get it working again.

I still get that BASS_ERROR_ILLPARAM randomly, as it seems... Even though there is no change in BytesPerSample.

Edit:
I put PlayBackPositionInBytes = 0, so there cant't be no ILL_PARAM and that worked.

After playback has stopped (automatically), I checked again as you said in the previous reply:

ChannelIsActive: BASS_ACTIVE_STOPPED
ChannelGetPosition (BASS_POS_BYTE): 0
PlayBackArray Length: 643860
RecordedData Length: 10584000
BytesWritten: 643860
StartPosition in Bytes (sample-locked): 0



But still it stops before all audio data has been played, that is audible and my SyncTrackEnd function is not being called either...

Ian @ un4seen

Quote from: kafffeeI still get that BASS_ERROR_ILLPARAM randomly, as it seems... Even though there is no change in BytesPerSample.

Ah. Looking at the code again, you need to change it like this:

Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()       'get remainder of division PlayBackPositionInBytes / BytesPerSample; BytesPerSample = number of channels * bit resolution / 8
PlayBackPositionInBytes = PlayBackPositionInBytes - Rest
PlayBackArray = New Byte(CInt(BytesWritten - PlayBackPositionInBytes) - 1) {}  'initializing PlayBackArray to the right required size; only whole samples because of Rest; BytesWritten: total amount of bytes recorded in RecordedData

Quote from: kafffeeAfter playback has stopped (automatically), I checked again as you said in the previous reply:

ChannelIsActive: BASS_ACTIVE_STOPPED
ChannelGetPosition (BASS_POS_BYTE): 0


BASS_ChannelGetPosition will return the position where the stream stopped at, so that 0 looks like the stream has been rewound with BASS_ChannelSetPosition afterwards or it was recreated?

kafffee

Quoteyou need to change it like this

Okay that works, thanks :-)

QuoteBASS_ChannelGetPosition will return the position where the stream stopped at, so that 0 looks like the stream has been rewound with BASS_ChannelSetPosition afterwards or it was recreated?

Okay, done some changes, now I get this:

ChannelIsActive: BASS_ACTIVE_STOPPED
ChannelGetPosition (BASS_POS_BYTE): 589176
PlayBackArray Length: 589176
RecordedData Length: 10584000
BytesWritten: 589176
StartPosition in Bytes (sample-locked): 0


There is still something wrong: I get these values when I set PlayBackPositionInBytes = 0.
So PlayBackArray Length should be the same as RecordedData Length, right?
The playback position marker moves too fast and stops before it reaches the end.

My SyncTrackEnd is being called the right way now...

What's wrong with that now?

Ian @ un4seen

Shouldn't "BytesWritten" and "RecordedData Length" be the same? Note that you're calculating "PlayBackArray Length" from "BytesWritten", so the latter's value seems to be where the problem is.

kafffee

Ah sure I got it working :-)

Now there's another problem I am desperate about:

My SyncTrackEnd callback function does not get called with this:

Public Sub StartPlayBack(argStartPosInBytes As Long, argEndPosInBytes As Long)

    Dim PlayBackPositionInBytes As Long = argStartPosInBytes
    Dim PlayBackPositionEndInBytes = argEndPosInBytes

    Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()  'remove bytes that are not whole samples
    PlayBackPositionInBytes = PlayBackPositionInBytes - Rest

    Rest = PlayBackPositionEndInBytes Mod BytesPerSample()    'remove bytes that are not whole samples
    PlayBackPositionEndInBytes = PlayBackPositionEndInBytes - Rest

 
    PlayBackArray = New Byte(CInt(PlayBackPositionEndInBytes - PlayBackPositionInBytes) - 1) {}  'inititiallize array to the right length
    Array.Copy(RecordedData, PlayBackPositionInBytes, PlayBackArray, 0, PlayBackArray.Length)      'copy data from recording data to playback array


    Bass.BASS_ChannelSetPosition(PlayBackChannel, 0)          'clear buffer
    Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length Or CInt(BASSStreamProc.BASS_STREAMPROC_END))
    Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)

    Bass.BASS_ChannelSetDevice(PlayBackChannel, MainModule.MeineEinstellungen.LautsprecherIndex)
    Bass.BASS_ChannelPlay(PlayBackChannel, False)
    Debug.WriteLine("play: " & Bass.BASS_ErrorGetCode.ToString)

    SyncTrackEnd = New SYNCPROC(AddressOf PlayBackStopped)
    Bass.BASS_ChannelSetSync(PlayBackChannel, BASSSync.BASS_SYNC_END, 0, SyncTrackEnd, IntPtr.Zero)
    Dim error As String = Bass.BASS_ErrorGetCode.ToString         'always returns BASS_OK

    SyncTrackFreed = New SYNCPROC(AddressOf PlayBackFreed)
    Bass.BASS_ChannelSetSync(PlayBackChannel, BASSSync.BASS_SYNC_FREE, 0, SyncTrackFreed, IntPtr.Zero)
    Debug.WriteLine(Bass.BASS_ErrorGetCode.ToString)

    PlayPauseIcon = StoppedIcon    'do UI stuff
    PeakChannel = PlayBackChannel
    If Not PeakTimer.Enabled Then PeakTimer.Start()
    PlayBackStartPosInBytes = PlayBackPositionInBytes
    If Not PlayBackProgressTimer.Enabled Then PlayBackProgressTimer.Start()
    IsPlaying = True
End Sub

It gets called like this:

StartPlayBack(PlayBackPositionInBytes, RecordedData.Length)  'RecordedData is a byte array with the complete recording data, PlayBackPositionInBytes is the starting point
when I want to playback from PlayBackPositionInBytes to the end.

Whereas when I only play a part of RecordedData, the SnycTrackEnd callback is being correctly called:

StartPlayBack(Objekt.Start, Objekt.End)
Waht could be the reason for this. In both cases, I get BASS_OK when I call  Bass.BASS_ChannelSetSync(PlayBackChannel, BASSSync.BASS_SYNC_END, 0, SyncTrackEnd, IntPtr.Zero)

Ian @ un4seen

What does BASS_ChannelIsActive(PlayBackChannel) report when it's at the end, after the SyncTrackEnd should have been called?

From the BASS_ChannelSetPosition call to "clear buffer", it looks like you're reusing the stream? If so, you don't need to set the syncs again each time, as the previously set syncs will still be there. Also, I would recommend setting the syncs before calling BASS_ChannelPlay, so the stream can't end before you've set the syncs when it's very short.

kafffee

#58
QuoteFrom the BASS_ChannelSetPosition call to "clear buffer", it looks like you're reusing the stream? If so, you don't need to set the syncs again each time, as the previously set syncs will still be there. Also, I would recommend setting the syncs before calling BASS_ChannelPlay, so the stream can't end before you've set the syncs when it's very short.

Sounds good. I will do that.

QuoteWhat does BASS_ChannelIsActive(PlayBackChannel) report when it's at the end, after the SyncTrackEnd should have been called?

Its still on BASS_ACTIVE_PLAYING

_______________________________________________________________

Edit:

I found the mistake. Instead of this...:

StartPlayBack(PlayBackPositionInBytes, RecordedData.Length)
I had to do this...:

StartPlayBack(PlayBackPositionInBytes, BytesWritten)
..otherwise the channel would be playing back empty data, as RecordedData was initialized with the maximum length of the track, not the actual audio data...

Thanks for the hint, it made me find the mistake :-)

I wish to you and all the readers a wonderful Christmas and a good start into 2025...