|
std66
Posts: 20
|
 |
« on: 10 Apr '12 - 22:13 » |
Quote
|
Hi!
I'm developing my radio player software in C# and I'm using Bass.NET to play internet radios. I want to make my software able to rewind radio streams. I can store the received data in MemoryStream using the DOWNLOADPROC callback, but how can I get Bass to play from this memory stream? If I rewind the position, will Bass continue receiving data, or is it necessary to write my own data receiver method? How can I do this?
Thank you in advance.
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15270
|
 |
« Reply #1 on: 11 Apr '12 - 16:22 » |
Quote
|
If you have the downloaded data in memory, you can pass that to BASS_StreamCreateFile (with mem=TRUE) to create a stream in order to play it. That will be separate from the source internet stream, which you can continue to play (and download data from) if you wish.
Note that the memory block that you pass to BASS_StreamCreateFile needs to persist for the lifetime of that stream, ie. until you call BASS_StreamFree. I'm not a .Net user myself, so I'm not familiar with how things work there, but in general it means that you shouldn't reallocate/enlarge the buffer (eg. to add more data), as that could result in the data being moved to another memory location.
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #2 on: 11 Apr '12 - 23:27 » |
Quote
|
Thanks. Well, I created two streams. The first one is created by Bass.BASS_StreamCreateURL (downloadstream), and the other one is by Bass.BASS_StreamCreateFile (playstream). In the DownloadProc callback I call the BASS_StreamPutData method to pass the byte array to playstream, but it receives only the first four seconds (and I can replay/rewind it any time) and downloadstream stops downloading. I don't need to use any memory streams in this way, but why does the download stops?
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15270
|
 |
« Reply #3 on: 12 Apr '12 - 14:23 » |
Quote
|
BASS_StreamPutData only applies to streams that have been created by BASS_StreamCreate (with STREAMPROC_PUSH), not BASS_StreamCreateFile, so that call will be failing (BASS_ERROR_NOTAVAIL). Note that system is also only for playing PCM sample data, not encoded data (as received in a DOWNLOADPROC). A similar system is available for encoded data though, via BASS_StreamCreateFileUser and BASS_StreamPutFileData. Please see the documentation for details on that. One thing to note is that the stream will have a fixed length buffer for the data (determined by the BASS_CONFIG_NET_BUFFER setting), so there will be a limit to the amount of data that you can have queued up via BASS_StreamPutFileData. If that is a problem, you could use an intermediate buffer (eg. your MemoryStream), and have your DOWNLOADPROC add data to it and have the stream's FILEREADPROC read data from it (instead of using BASS_StreamPutFileData). It could basically look something like this... void CALLBACK DownloadProc(void *buffer, DWORD length, void *user) { // write "length" bytes of data from "buffer" to your intermediate buffer }
void CALLBACK FileCloseProc(void *user) { // close the file }
QWORD CALLBACK FileLenProc(void *user) { return 0; // indeterminate length }
DWORD CALLBACK FileReadProc(void *buffer, DWORD length, void *user) { // read up to "length" bytes of data from your intermediate buffer into "buffer" }
...
BASS_FILEPROCS fileprocs={FileCloseProc, FileLenProc, FileReadProc, NULL}; stream=BASS_StreamCreateFileUser(STREAMFILE_BUFFER, 0, &fileprocs, NULL); // create the stream
Even if you do want to eventually use BASS_StreamPutFileData (and STREAMFILE_BUFFERPUSH), I would suggest starting with the above, as you will still need a FILEREADPROC then to provide the initial data to BASS_StreamCreateFileUser (so that it can create the stream). Regarding the stopped download, did you stop playback? Data will only be downloaded as space becomes available for it in the stream's download buffer, ie. as data is taken from the buffer for playback. So you will need to keep playing the stream in order to receive more data. If you don't want to hear it, you can mute it by setting its BASS_ATTRIB_VOL to 0 via BASS_ChannelSetAttribute.
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #4 on: 12 Apr '12 - 23:24 » |
Quote
|
I think it will work, but I have a problem with it. I uploaded the source code here: http://users.atw.hu/std66/stream.txt"this.Storage" is a MemoryStream. As I open a URL using the Open method, the FileProc_Close method (FILECLOSEPROC) closes the MemoryStream, so the Download method can't write data to it (but the first four seconds are played, I think it plays from MemoryStream). If I don't call the "this.Storage.Close()" method in FileProc_Close, the Bass doesn't play the radio stream (but Download method writes data to the MemoryStream) and the stream's total length is -1. But why does the FileProc_Close method close the stream?
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15270
|
 |
« Reply #5 on: 13 Apr '12 - 15:47 » |
Quote
|
I'm not very familiar with MemoryStream, but it looks to me like the problem in your code is that the Read and Write calls are using the same position, ie. the Read will be trying to read from the end of the data (where the Write left off). You need to have separate read and write positions. Perhaps there is a way to create 2 MemoryStreams that share the same buffer, or you can use Begin/EndRead and Begin/EndWrite instead?
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #6 on: 15 Apr '12 - 13:04 » |
Quote
|
I tried an other way. Instead of creating the playback channel with BASS_StreamCreateFileUser, I created it with BASS_StreamCreatePush. In the DownloadProc method I add the received data to the playback channel using BASS_StreamPutFileData, but the BASS_ChannelGetLength function always returns -1 (even if the amount of downloaded data is more than 2MB), so the playback doesn't start. private void Download(IntPtr Buffer, int Length, IntPtr User) { if (this.Storage != null) { if (Buffer == IntPtr.Zero) { this.Storage.Close(); } else { byte[] Buf = new byte[Length]; Marshal.Copy(Buffer, Buf, 0, Length); this.Storage.Write(Buf, 0, Length); Bass.BASS_StreamPutFileData(this.lStream, Buffer, Length); } } else { Bass.BASS_StreamPutFileData(this.lStream, Buffer, Length); } }
public override bool Open(dynamic Source) { this.FreeStream(); //Free this.lStream
this.Source = Source;
this.Downloader = new DOWNLOADPROC(Download); this.Storage = new MemoryStream(); //(Re)creating MemoryStream, but in this case I don't need MemoryStream
this.DownloadStream = Bass.BASS_StreamCreateURL(Source, 0, BASSFlag.BASS_STREAM_STATUS | BASSFlag.BASS_STREAM_BLOCK, this.Downloader, (IntPtr) 0);
//Creating the playback stream. Channel frequency and channel count are received from DownloadStream BASS_CHANNELINFO CHInfo = Bass.BASS_ChannelGetInfo(this.DownloadStream); this.lStream = Bass.BASS_StreamCreatePush(CHInfo.freq, CHInfo.chans, BASSFlag.BASS_DEFAULT, IntPtr.Zero);
if (this.DownloadStream == 0 && this.lStream == 0) { this.WriteErrorCode(); return false; }
//Waiting the buffer to be full bool Ready = false; while (!Ready) { int CurrentPuffer = (int)Bass.BASS_StreamGetFilePosition(this.lStream, BASSStreamFilePosition.BASS_FILEPOS_BUFFER); int TotalPuffer = (int)Bass.BASS_StreamGetFilePosition(this.lStream, BASSStreamFilePosition.BASS_FILEPOS_END); int PufferLoadInPercent = CurrentPuffer * 100 / TotalPuffer;
if (PufferLoadInPercent == 100) { this.GetTags(); Ready = true; } }
//Start downloading Bass.BASS_ChannelPlay(this.DownloadStream, false); Bass.BASS_ChannelSetAttribute(this.DownloadStream, BASSAttribute.BASS_ATTRIB_VOL, 0);
return true; }
I have no idea what to do.
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15270
|
 |
« Reply #7 on: 16 Apr '12 - 17:02 » |
Quote
|
BASS_StreamCreatePush is for playing PCM sample data, so that won't work for the encoded data recieved in your DOWNLOADPROC function. If you would like to use BASS_StreamCreatePush, you could do that by replacing the DOWNLOADPROC function with a DSPPROC function (via BASS_ChannelSetDSP), which will receive decoded sample data. The DSPPROC function could look something like this... void CALLBACK DspProc(HDSP handle, DWORD channel, void *buffer, DWORD length, void *user) { BASS_StreamPutData(pushstream, buffer, length); // feed the sample data to the push stream }
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #8 on: 16 Apr '12 - 19:02 » |
Quote
|
Thanks. The playback is working. Now I can make it rewindable I think.
|
|
|
|
|
Logged
|
|
|
|
|
DesperateUser
Guest
|
 |
« Reply #9 on: 17 Apr '12 - 12:39 » |
Quote
|
Glad to hear that you got it working. Can you perhaps put the final code together again as in the stream.txt you uploaded above? I'm working on almost the same problems here: http://www.un4seen.com/forum/?topic=13663.0That would help me a lot. Thanks 
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #10 on: 17 Apr '12 - 13:42 » |
Quote
|
I'll upload my code when I get home from school. I haven't implemented the rewinding yet, because I'm having problems with it, so the playback works only now.
There's another problem too. This way uses lots of system memory (downloader stream, playback stream and the MemoryStream [this is required for saving the downloaded data to file] too).
|
|
|
|
« Last Edit: 17 Apr '12 - 13:49 by std66 »
|
Logged
|
|
|
|
|
DesperateUser
Guest
|
 |
« Reply #11 on: 17 Apr '12 - 13:46 » |
Quote
|
Ok thanks, I want to be able to seek in the already downloaded stream too. So maybe we can finally get that working together.
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #12 on: 17 Apr '12 - 18:30 » |
Quote
|
|
|
|
|
|
Logged
|
|
|
|
|
DesperateUser
Guest
|
 |
« Reply #13 on: 18 Apr '12 - 12:06 » |
Quote
|
Thanks for the code.
I think there is a Bass.BASS_StreamFree(this.DownloadStream); at the beginning of the open method. Otherwise it will give some errors if you open a new stream too fast.
But I'm currently stuck on the streams length. Im not sure how to get the available stream length, so I can seek through the stream, because the Storage length is compressed or vice versa?
|
|
|
|
|
Logged
|
|
|
|
|
DesperateUser
Posts: 3
|
 |
« Reply #14 on: 18 Apr '12 - 12:08 » |
Quote
|
... finally created an account.  Thanks for the code. I think there is a Bass.BASS_StreamFree(this.DownloadStream); missing at the beginning of the open method. Otherwise it will give some errors if you open a new stream too fast. But I'm currently stuck on the streams length. Im not sure how to get the available stream length, so I can seek through the stream, because the Storage length is compressed or vice versa?
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #15 on: 18 Apr '12 - 13:33 » |
Quote
|
The method URLPlayback.GetStreamLength is used for displaying the available encoded data, not the playback stream's length. I think you can use the Bass.BASS_ChannelGetLength method to get the playback stream's length.
Thanks for noticing the StreamFree. I have overridden the PlaybackAbstract.FreeStream method in URLPlayback to close DownloadStream too.
|
|
|
|
« Last Edit: 18 Apr '12 - 13:49 by std66 »
|
Logged
|
|
|
|
|
DesperateUser
Posts: 3
|
 |
« Reply #16 on: 18 Apr '12 - 14:03 » |
Quote
|
I've tried this code for the stream length: public virtual double getLength(bool InSeconds) { int LengthInBytes = (int)Bass.BASS_ChannelGetLength(this.Stream, BASSMode.BASS_POS_BYTES); return (InSeconds) ? Bass.BASS_ChannelBytes2Seconds(this.Stream, LengthInBytes) : LengthInBytes; }
But LengthInBytes is always -1. Most probably because its no file but a webstream? And how would you seek then? I think we would have to read the bytes from Storage then to put them to the DSP function? But I'm not sure how to achieve that.
|
|
|
|
|
Logged
|
|
|
|
|
Ian @ un4seen
Administrator
Posts: 15270
|
 |
« Reply #17 on: 18 Apr '12 - 15:19 » |
Quote
|
I haven't implemented the rewinding yet, because I'm having problems with it, so the playback works only now.
To be able to play from various positions within the data, you would be better off using your own buffer to collect the sample data in the DSPPROC function and then feed that into a STREAMPROC function for playback, instead of using BASS_StreamPutData. That gives you control over where in the buffer the data comes from, but I guess you will have the same issue that you had with the MemoryStream before, ie. the buffer needs to have separate read and write positions. If you can solve that problem, I think the rest should quickly fall into place 
|
|
|
|
|
Logged
|
|
|
|
|
std66
Posts: 20
|
 |
« Reply #18 on: 20 Apr '12 - 23:15 » |
Quote
|
Ok, so I've created a STREAMPROC function to read MemoryStream, but BASS doesn't call this function. DSPPROC writes the data to the MemoryStream, but BASS doesn't play from it. Here's the code: private MemoryStream RawStorage; private DSPPROC DSPHandler; private STREAMPROC MemStreamReader;
//DSPPROC - Works correctly and writes data to MemoryStream private void DSPCallback(int Handle, int Channel, IntPtr Buffer, int Length, IntPtr User) { byte[] Buf = new byte[Length]; Marshal.Copy(Buffer, Buf, 0, Length);
this.RawStorage.Write(Buf, 0, Length); }
//STREAMPROC - BASS doesn't call it private int StreamRead(int Handle, IntPtr Buffer, int Length, IntPtr User) { if (this.RawStorage != null) { byte[] Data = new byte[Length]; int BytesRead = this.RawStorage.Read(Data, 0, Length);
Marshal.Copy(Data, 0, Buffer, Length);
if (BytesRead < Length) { BytesRead |= (int)BASSStreamProc.BASS_STREAMPROC_END; }
return BytesRead; } else { return 0; } }
public override bool Open(dynamic Source) { //Some not so important calls //...
this.RawStorage = new MemoryStream(); this.DSPHandler = new DSPPROC(this.DSPCallback); this.MemStreamReader = new STREAMPROC(this.StreamRead);
//Create download stream and pass the DSPPROC to it. It's OK //...
//Create playback stream BASS_CHANNELINFO CHInfo = Bass.BASS_ChannelGetInfo(this.DownloadStream); this.lStream = Bass.BASS_StreamCreate(CHInfo.freq, CHInfo.chans, BASSFlag.BASS_DEFAULT, this.MemStreamReader, IntPtr.Zero);
//Wait for download stream's buffer to be full and start playing download stream to receive data //... }
What am I doing wrong?
|
|
|
|
|
Logged
|
|
|
|
|
DesperateUser
Posts: 3
|
 |
« Reply #19 on: 21 Apr '12 - 09:18 » |
Quote
|
In your Streamread and the dspcallback method you need to set the position of the memorystream. long readpos = 0; long writepos = 0;
private void DSPCallback(int Handle, int Channel, IntPtr Buffer, int Length, IntPtr User) { byte[] Buf = new byte[Length]; Marshal.Copy(Buffer, Buf, 0, Length);
this.RawStorage.Position = writepos; this.RawStorage.Write(Buf , 0, length); writepos = this.RawStorage.Position; }
private int StreamRead(int Handle, IntPtr Buffer, int Length, IntPtr User) { if (this.RawStorage != null) { byte[] Data = new byte[Length]; this.RawStorage.Position = readpos; int BytesRead = this.RawStorage.Read(Data, 0, Length); readpos += BytesRead;
Marshal.Copy(Data, 0, Buffer, Length);
if (BytesRead < Length) { BytesRead |= (int)BASSStreamProc.BASS_STREAMPROC_END; }
return BytesRead; } else { return 0; } }
This should let you play the sound.
|
|
|
|
|
Logged
|
|
|
|
|