Author Topic: How to configure BASS_StreamCreateFileUser  (Read 544 times)

Guest

  • Guest
How to configure BASS_StreamCreateFileUser
« on: 13 Jul '23 - 12:15 »
I am using BASS_StreamCreateFileUser(STREAMFILE_BUFFERPUSH) and Bass.BASS_StreamPutFileData to playback to internet radio (Ogg-Opus format data streamed via Websocket).
However, when I replaced this method with BASS_StreamCreateFileUser(STREAMFILE_BUFFER), the sound only played for a moment.
The documentation states that this method is equivalent to BASS_StreamCreateURL, so I believe it should support continuous streams like internet radio.
Is there any configuration required?

Ian @ un4seen

  • Administrator
  • Posts: 26028
Re: How to configure BASS_StreamCreateFileUser
« Reply #1 on: 13 Jul '23 - 15:47 »
That sounds like your FILEREADPROC function probably returned 0 when no data was available, which signifies end-of-file to BASS. Whenever no data is available but more is coming, the FILEREADPROC should wait for it before returning. You should then also cancel such a wait before freeing the stream.

Guest

  • Guest
Re: How to configure BASS_StreamCreateFileUser
« Reply #2 on: 15 Jul '23 - 11:12 »
The Ogg-Opus stream from the internet radio is transmitted with the following structure:
Page0: ID Header (47 bytes)
Page1: Comment Header (89 bytes)
Page2: Audio Data Packet (approximately 2400 bytes)
...
Page(n): Audio Data Packet (approximately 2400 bytes)

In the FILEREADPROC, I simply sent Page0, Page1, Page2 in order, but it didn't work.
With STREAMFILE_BUFFERPUSH, I sent the combined data (Page0 + Page1 + Page2) to FILEREADPROC.
Do I still need to manipulate the data with STREAMFILE_BUFFER?

Code: [Select]
int MyFileProcUserRead(IntPtr buffer, int length, IntPtr user)
{
if (_br == null) //_br(BinaryReader)=Page0,Page1,Page2,,,Page(n)
return 0;

try
{
byte[] _data = new byte[length];
int bytesread = _br.Read(_data, 0, length);
Marshal.Copy(_data, 0, buffer, bytesread);
Debug.WriteLine(String.Format("MyFileProcUserRead: requested {0}, read {1} ", length, bytesread));
return bytesread;
}
catch
{
return 0;
}
}

Guest

  • Guest
Re: How to configure BASS_StreamCreateFileUser
« Reply #3 on: 16 Jul '23 - 05:01 »
The following is the behavior of FILEREADPROC.
It seems to be reading in the order of Page0, Page1, Page2...

Code: [Select]
MyFileProcUserRead: requested 512, read 47
MyFileProcUserRead: requested 512, read 0
MyFileProcUserClose
Failed to create stream: BASS_ERROR_FILEFORM

MyFileProcUserRead: requested 512, read 89
MyFileProcUserRead: requested 512, read 0
MyFileProcUserClose
Failed to create stream: BASS_ERROR_FILEFORM

MyFileProcUserRead: requested 512, read 512
MyFileProcUserRead: requested 1536, read 1536
MyFileProcUserRead: requested 1952, read 409
MyFileProcUserRead: requested 1543, read 0
MyFileProcUserClose
Failed to create stream: BASS_ERROR_FILEFORM

MyFileProcUserRead: requested 512, read 512
MyFileProcUserRead: requested 1536, read 1536
MyFileProcUserRead: requested 1952, read 409
MyFileProcUserRead: requested 1543, read 0
MyFileProcUserClose
Failed to create stream: BASS_ERROR_FILEFORM
.........

Ian @ un4seen

  • Administrator
  • Posts: 26028
Re: How to configure BASS_StreamCreateFileUser
« Reply #4 on: 17 Jul '23 - 12:31 »
Yes, the issue there is MyFileProcUserRead returning 0, which signifies end-of-file when using the STREAMFILE_BUFFER file system. Instead of returning 0, MyFileProcUserRead should wait for more data to become available and then return that.

Guest

  • Guest
Re: How to configure BASS_StreamCreateFileUser
« Reply #5 on: 10 Aug '23 - 06:44 »
After considering your feedback, I made modifications to the FILEREADPROC function, and now it works with STREAMFILE_BUFFER.
By the way, is it possible to change the length parameter of the FILEREADPROC function?
This function initially requests 512KB, and then based on the return value, it seems to request an appropriate value.
If I could customize that value, I could potentially process the data in a single call.
(The data size this time is about 2400 bytes.)
Code: [Select]
BlockingCollection<byte[]> _dataQueue = new BlockingCollection<byte[]>(); // For thread-safe queue operations
int MyFileProcUserRead(IntPtr buffer, int length, IntPtr user)
{
    int totalBytesCopied = 0;
    while (totalBytesCopied < length)
    {
        byte[] data = null;
        try
        {
            data = _dataQueue.Take(); // Wait until data is available
        }
        catch (InvalidOperationException) { break; }

        if (data != null && data.Length > 0)
        {
            int bytesToCopy = Math.Min(length - totalBytesCopied, data.Length);
            Marshal.Copy(data, 0, buffer + totalBytesCopied, bytesToCopy);
            totalBytesCopied += bytesToCopy;

            if (bytesToCopy < data.Length) // If there's remaining data, add it back to the queue
            {
                byte[] remainingData = new byte[data.Length - bytesToCopy];
                Array.Copy(data, bytesToCopy, remainingData, 0, data.Length - bytesToCopy);
                _dataQueue.Add(remainingData);
            }
        }
        else { break; }
    }

    if (totalBytesCopied > 0)
    {
        return totalBytesCopied; // Return the number of bytes read
    }
    else { return 0; }
}

void MyFileProcUserClose(IntPtr user)
{
    Console.WriteLine("StreamCreateFileUserClosed");
}

long MyFileProcUserLength(IntPtr user)
{
    return 0L; // indeterminate length
}

void OnDataReceived(byte[] data) // Receive OpusData from ClientWebSocket
{
    _dataQueue.Add(data); // Add it to the queue
}

Ian @ un4seen

  • Administrator
  • Posts: 26028
Re: How to configure BASS_StreamCreateFileUser
« Reply #6 on: 10 Aug '23 - 18:02 »
The amount of data requested in each FILEREADPROC call will vary depending on the file's format and the decoder used to handle it. But note that you can safely return less than requested (but not nothing) after the decoder has been initialized, ie. after BASS_StreamCreateFileUser returns. So you could then break out of the "while" loop in the code above as soon as any amount has been read.

Guest

  • Guest
Re: How to configure BASS_StreamCreateFileUser
« Reply #7 on: 11 Aug '23 - 08:56 »
I see, so it is understood that before initializing the decoder, processing should be based on the 'length' parameter, and after initialization, it is acceptable to process based on the received amount of data.
Thank you so much.

Code: [Select]
int stream = Bass.BASS_StreamCreateFileUser(BASSStreamSystem.STREAMFILE_BUFFER, BASSFlag.BASS_DEFAULT, _myStreamCreateUser, IntPtr.Zero);
if (stream != 0)
{
    decoderInitialized = true;
}
..............................

bool decoderInitialized = false; // Initialized flag
BlockingCollection<byte[]> _dataQueue = new BlockingCollection<byte[]>(); // For thread-safe queue operations
int MyFileProcUserRead(IntPtr buffer, int length, IntPtr user)
{
    int totalBytesCopied = 0;
    while (totalBytesCopied < length)
    {
        byte[] data = null;
        try
        {
            data = _dataQueue.Take(); // Wait until data is available
        }
        catch (InvalidOperationException) { break; }

        if (data != null && data.Length > 0)
        {
            int bytesToCopy = Math.Min(length - totalBytesCopied, data.Length);
            Marshal.Copy(data, 0, buffer + totalBytesCopied, bytesToCopy);
            totalBytesCopied += bytesToCopy;

            if (bytesToCopy < data.Length) // If there's remaining data, add it back to the queue
            {
                byte[] remainingData = new byte[data.Length - bytesToCopy];
                Array.Copy(data, bytesToCopy, remainingData, 0, data.Length - bytesToCopy);
                _dataQueue.Add(remainingData);
            }
        }
        else { break; }
       
        // Check if the decoder has been initialized and return if any amount of data has been read
        if (totalBytesCopied > 0 && decoderInitialized)
        { break; }
    }

    if (totalBytesCopied > 0)
    {
        return totalBytesCopied; // Return the number of bytes read
    }
    else { return 0; }
}

void MyFileProcUserClose(IntPtr user)
{
    Console.WriteLine("StreamCreateFileUserClosed");
}

long MyFileProcUserLength(IntPtr user)
{
    return 0L; // indeterminate length
}

void OnDataReceived(byte[] data) // Receive OpusData from ClientWebSocket()
{
    _dataQueue.Add(data); // Add it to the queue
}

Ian @ un4seen

  • Administrator
  • Posts: 26028
Re: How to configure BASS_StreamCreateFileUser
« Reply #8 on: 11 Aug '23 - 16:05 »
I see, so it is understood that before initializing the decoder, processing should be based on the 'length' parameter, and after initialization, it is acceptable to process based on the received amount of data.

That's correct, but note the returned amount should never exceed the "length" value (there might not be space for more in the file buffer).

In some cases, it may actually be OK to return less than requested even before BASS_StreamCreateFileUser returns, so long as you provide enough to initialize the decoder. In the case of Opus (with BASSOPUS), that's the header pages plus one page of audio data.