Skip to content

Instantly share code, notes, and snippets.

@rkaiser0324
Last active January 2, 2016 13:39
Show Gist options
  • Select an option

  • Save rkaiser0324/8311273 to your computer and use it in GitHub Desktop.

Select an option

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.
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);
}
}
}
@rkaiser0324
Copy link
Author

Added the EndSync callback, and found that on iOS this gets raised after a few seconds. It never gets raised on Windows.

@rkaiser0324
Copy link
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