Last active
January 2, 2016 13:39
-
-
Save rkaiser0324/8311273 to your computer and use it in GitHub Desktop.
Switched to using STREAMFILE_BUFFER as per Ian's response at http://www.un4seen.com/forum/?topic=15463.msg107562#msg107562. Works even better on Windows (playback is smoother) but on iOS MyFileProcUserRead() is only called at the beginning. See thread for more details.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using Un4seen.Bass; | |
| using System.Runtime.InteropServices; | |
| using System.Diagnostics; | |
| using System.IO; | |
| using System.Net; | |
| using Un4seen.Bass.AddOn.Aac; | |
| namespace BassConsole | |
| { | |
| class ADTSTestClass | |
| { | |
| private static BASS_FILEPROCS _myStreamCreateUser; | |
| // As per http://www.un4seen.com/forum/?topic=9818.msg68454#msg68454 | |
| private static SYNCPROC _mySync; | |
| private static BinaryReader _br; | |
| private static int _bytesReadByReader = 0; | |
| public static void playRawAacStream(string strUrl, bool useBuffer) | |
| { | |
| //You might disable the splash screen by calling the following method prior to any other BASS method: | |
| BassNet.Registration("[email protected]", "xxx"); | |
| #if __IOS__ || ANDROID | |
| // no op | |
| #else | |
| int s = Bass.BASS_PluginLoad("bass_aac.dll"); | |
| if (s == 0) | |
| throw new Exception("cannot load aac dll"); | |
| #endif | |
| var ut = Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_UPDATETHREADS); | |
| Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_PREBUF, 0); | |
| Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_BUFFER, 10000); | |
| // init BASS using the default output device | |
| if (Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero)) | |
| { | |
| // create a stream channel from a file | |
| //int stream = Bass.BASS_StreamCreateFile("Mind.m4a", 0, 0, BASSFlag.BASS_DEFAULT); | |
| if (useBuffer) | |
| { | |
| // This doesn't work | |
| playfrombuffer(strUrl); | |
| } | |
| else | |
| { | |
| // This works fine | |
| int stream = Bass.BASS_StreamCreateURL(strUrl, 0, 0, null, IntPtr.Zero); | |
| if (stream != 0) | |
| { | |
| // play the stream channel | |
| Bass.BASS_ChannelPlay(stream, false); | |
| } | |
| else | |
| { | |
| // error creating the stream | |
| Console.WriteLine("Stream error: {0}", Bass.BASS_ErrorGetCode()); | |
| } | |
| string[] tags = Bass.BASS_ChannelGetTagsMETA(stream); | |
| if (tags != null) | |
| { | |
| foreach (string tag in tags) | |
| Console.WriteLine(tag); | |
| } | |
| #if __IOS__ | |
| // No-op | |
| #else | |
| // wait for a key | |
| Console.WriteLine("Press any key to exit"); | |
| Console.ReadLine(); | |
| #endif | |
| // free the stream | |
| Bass.BASS_StreamFree(stream); | |
| // free BASS | |
| Bass.BASS_Free(); | |
| } | |
| } | |
| } | |
| private static void playfrombuffer(string strUrl) | |
| { | |
| // creating the user file callback delegates | |
| _myStreamCreateUser = new BASS_FILEPROCS( | |
| new FILECLOSEPROC(MyFileProcUserClose), | |
| new FILELENPROC(MyFileProcUserLength), | |
| new FILEREADPROC(MyFileProcUserRead), | |
| new FILESEEKPROC(MyFileProcUserSeek)); | |
| _mySync = new SYNCPROC(EndSync); | |
| // open the file... | |
| HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("http://3023.live.streamtheworld.com/ALTROCK_S01A_AAC_SC"); | |
| req.Timeout = 10 * 1000; // 10 s | |
| // req.UserAgent = "WinampMPEG/5.09"; | |
| // req.Headers.Add("Icy-MetaData", "1"); | |
| //req. | |
| int stream = 0; | |
| bool playing = false; | |
| using (Stream networkStream = req.GetResponse().GetResponseStream()) | |
| { | |
| Debug.WriteLine(String.Format("Content Length = {0}, content type = {1} ", req.GetResponse().ContentLength, req.GetResponse().ContentType)); | |
| Debug.WriteLine("Headers: " + req.GetResponse().Headers.ToString()); | |
| _br = new BinaryReader(networkStream); | |
| while (true) | |
| { | |
| //arr = _br.ReadBytes(10000); | |
| //_ms.Write(arr, 0, arr.Length); | |
| if (!playing) | |
| { | |
| #if __IOS__ | |
| stream = Bass.BASS_StreamCreateFileUser(BASSStreamSystem.STREAMFILE_BUFFER, BASSFlag.BASS_STREAM_AUTOFREE | BASSFlag.BASS_STREAM_RESTRATE, _myStreamCreateUser, IntPtr.Zero); | |
| #else | |
| stream = BassAac.BASS_AAC_StreamCreateFileUser(BASSStreamSystem.STREAMFILE_BUFFER, BASSFlag.BASS_STREAM_AUTOFREE, _myStreamCreateUser, IntPtr.Zero); | |
| #endif | |
| if (stream == 0) | |
| { | |
| Debug.WriteLine("could not create stream" + Bass.BASS_ErrorGetCode()); | |
| } | |
| // play the channel | |
| Bass.BASS_ChannelPlay(stream, false); | |
| Bass.BASS_ChannelSetSync(stream, BASSSync.BASS_SYNC_END, | |
| 0, _mySync, IntPtr.Zero); | |
| playing = true; | |
| /* | |
| // Add remaining bytes | |
| var remainingchunk = new List<byte>(); | |
| while (_ms.Position < _ms.Length - 1) | |
| { | |
| remainingchunk.Add((byte)_ms.ReadByte()); | |
| } | |
| if (remainingchunk.Count > 0) | |
| { | |
| int addedDataInitial = Bass.BASS_StreamPutFileData(stream, remainingchunk.ToArray(), remainingchunk.ToArray().Length); | |
| if (addedDataInitial == -1) | |
| { | |
| Debug.WriteLine(String.Format("Error initial in BASS_StreamPutFileData {0}, trying to add bytes {1}", Bass.BASS_ErrorGetCode(), remainingchunk.ToArray().Length)); | |
| } | |
| else | |
| { | |
| Debug.WriteLine("initial BASS_StreamPutFileData: added bytes " + remainingchunk.ToArray().Length); | |
| } | |
| } | |
| */ | |
| } | |
| else | |
| { | |
| /* | |
| int addedData = Bass.BASS_StreamPutFileData(stream, arr, arr.Length); | |
| if (addedData == -1) | |
| { | |
| Debug.WriteLine(String.Format("Error in BASS_StreamPutFileData {0}, trying to add bytes {1}", Bass.BASS_ErrorGetCode(), arr.Length)); | |
| } | |
| * */ | |
| } | |
| Debug.WriteLine(String.Format("{0} buffer={1} posdownload={2} poscurrent={3} connected={4} start={5} end={6} avail={7} bytesread={8}", | |
| Bass.BASS_ChannelIsActive(stream), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_BUFFER), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_DOWNLOAD), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_CURRENT), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_CONNECTED), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_START), | |
| Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_END), | |
| Bass.BASS_ChannelGetData(stream, new byte[1], (int)BASSData.BASS_DATA_AVAILABLE), | |
| _bytesReadByReader | |
| )); | |
| // if (Bass.BASS_ChannelIsActive(stream) == BASSActive.BASS_ACTIVE_STOPPED) | |
| // Bass.BASS_ChannelPlay(stream, false); | |
| System.Threading.Thread.Sleep(300); | |
| } | |
| } | |
| #if __IOS__ | |
| // This shouldn't ever happen | |
| Debug.WriteLine("Stream Ended"); | |
| #else | |
| // wait for a key | |
| Console.WriteLine("Press any key to exit"); | |
| Console.ReadLine(); | |
| #endif | |
| // free the stream | |
| Bass.BASS_StreamFree(stream); | |
| // free BASS | |
| Bass.BASS_Free(); | |
| } | |
| #if __IOS__ | |
| delegate void MyFileProcUserCloseCallback(IntPtr user); | |
| [MonoTouch.MonoPInvokeCallback(typeof(MyFileProcUserCloseCallback))] | |
| #endif | |
| private static void MyFileProcUserClose(IntPtr user) | |
| { | |
| if (_br == null) | |
| return; | |
| // I don't think we ever want to close this? | |
| //_ms.Close(); | |
| Debug.WriteLine("File Closed"); | |
| } | |
| #if __IOS__ | |
| delegate long MyFileProcUserLengthCallback(IntPtr user); | |
| [MonoTouch.MonoPInvokeCallback(typeof(MyFileProcUserLengthCallback))] | |
| #endif | |
| private static long MyFileProcUserLength(IntPtr user) | |
| { | |
| Debug.WriteLine("MyFileProcUserLength"); | |
| // Return 0 always, as this is a live stream; I read this somewhere in the forums | |
| return 1000 * 1000; | |
| //if (_fs == null) | |
| // return 0L; | |
| //return _fs.Length; | |
| } | |
| private static byte[] _data; | |
| #if __IOS__ | |
| delegate int MyFileProcUserReadCallback(IntPtr buffer, int length, IntPtr user); | |
| [MonoTouch.MonoPInvokeCallback(typeof(MyFileProcUserReadCallback))] | |
| #endif | |
| private static int MyFileProcUserRead(IntPtr buffer, int length, IntPtr user) | |
| { | |
| if (_br == null) | |
| return 0; | |
| try | |
| { | |
| // at first we need to create a byte[] with the size of the requested length | |
| _data = new byte[length]; | |
| // read the file into data | |
| int bytesread = _br.Read(_data, 0, length); | |
| // and now we need to copy the data to the buffer | |
| // we write as many bytes as we read via the file operation | |
| Marshal.Copy(_data, 0, buffer, bytesread); | |
| Debug.WriteLine(String.Format("MyFileProcUserRead: requested {0}, read {1} ", length, bytesread)); | |
| _bytesReadByReader += bytesread; | |
| return bytesread; | |
| } | |
| catch { | |
| Debug.WriteLine("MyFileProcUserRead: exception"); | |
| return 0; | |
| } | |
| } | |
| #if __IOS__ | |
| delegate bool MyFileProcUserSeekCallback(long offset, IntPtr user); | |
| [MonoTouch.MonoPInvokeCallback(typeof(MyFileProcUserSeekCallback))] | |
| #endif | |
| private static bool MyFileProcUserSeek(long offset, IntPtr user) | |
| { | |
| Debug.WriteLine("MyFileProcUserSeek: seeking " + offset); | |
| return false; | |
| //if (_br == null) | |
| // return false; | |
| //try | |
| //{ | |
| // long pos = _ms.Seek(offset, SeekOrigin.Begin); | |
| // return true; | |
| //} | |
| //catch | |
| //{ | |
| // return false; | |
| //} | |
| } | |
| #if __IOS__ | |
| delegate void EndSyncCallback(int handle, int channel, int data, IntPtr user); | |
| [MonoTouch.MonoPInvokeCallback(typeof(EndSyncCallback))] | |
| #endif | |
| private static void EndSync(int handle, int channel, int data, IntPtr user) | |
| { | |
| //do stuff | |
| Debug.WriteLine("EndSync: stream ended - should not ever happen, bytesread=" + _bytesReadByReader); | |
| } | |
| } | |
| } |
Author
Author
This version finally does the trick. The key (identified by Ian) was that we cannot set the Icy-MetaData header. Shoutcast inserts metadata if that is set, and apparently this metadata makes the CoreAudio AAC decoder on iOS think the stream has ended. Other decoders (like the BASS AAC one) tolerate this, but CoreAudio does not.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added the EndSync callback, and found that on iOS this gets raised after a few seconds. It never gets raised on Windows.