зеркало из https://github.com/xamarin/ios-samples.git
321 строка
7.6 KiB
C#
321 строка
7.6 KiB
C#
using System;
|
|
using AudioToolbox;
|
|
using System.Threading;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace StreamingAudio
|
|
{
|
|
/// <summary>
|
|
/// A Class to hold the AudioBuffer with all setting together
|
|
/// </summary>
|
|
internal class AudioBuffer
|
|
{
|
|
public IntPtr Buffer { get; set; }
|
|
|
|
public List<AudioStreamPacketDescription> PacketDescriptions { get; set; }
|
|
|
|
public int CurrentOffset { get; set; }
|
|
|
|
public bool IsInUse { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper around OutputQueue and AudioFileStream to allow streaming of various filetypes
|
|
/// </summary>
|
|
public class StreamingPlayback : IDisposable
|
|
{
|
|
public event EventHandler Finished;
|
|
public event Action<OutputAudioQueue> OutputReady;
|
|
|
|
// the AudioToolbox decoder
|
|
AudioFileStream fileStream;
|
|
int bufferSize = 128 * 1024;
|
|
List<AudioBuffer> outputBuffers;
|
|
AudioBuffer currentBuffer;
|
|
// Maximum buffers
|
|
int maxBufferCount = 4;
|
|
// Keep track of all queued up buffers, so that we know that the playback finished
|
|
int queuedBufferCount = 0;
|
|
// Current Filestream Position - if we don't keep track we don't know when to push the last uncompleted buffer
|
|
long currentByteCount = 0;
|
|
//Used to trigger a dump of the last buffer.
|
|
bool lastPacket;
|
|
|
|
public OutputAudioQueue OutputQueue;
|
|
|
|
public bool Started { get; private set; }
|
|
|
|
public float Volume {
|
|
get {
|
|
return OutputQueue.Volume;
|
|
}
|
|
|
|
set {
|
|
OutputQueue.Volume = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the size forearch buffer, when using a slow source use more buffers with lower buffersizes
|
|
/// </summary>
|
|
public int BufferSize {
|
|
get {
|
|
return bufferSize;
|
|
}
|
|
|
|
set {
|
|
bufferSize = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the maximum Number of Buffers to use, the count can only change after Reset is called or the
|
|
/// StreamingPlayback is freshly instantiated
|
|
/// </summary>
|
|
public int MaxBufferCount {
|
|
get {
|
|
return maxBufferCount;
|
|
}
|
|
|
|
set {
|
|
maxBufferCount = value;
|
|
}
|
|
}
|
|
|
|
public StreamingPlayback () : this (AudioFileType.MP3)
|
|
{
|
|
}
|
|
|
|
public StreamingPlayback (AudioFileType type)
|
|
{
|
|
fileStream = new AudioFileStream (type);
|
|
fileStream.PacketDecoded += AudioPacketDecoded;
|
|
fileStream.PropertyFound += AudioPropertyFound;
|
|
}
|
|
|
|
public void Reset ()
|
|
{
|
|
if (fileStream != null) {
|
|
fileStream.Close ();
|
|
fileStream = new AudioFileStream (AudioFileType.MP3);
|
|
currentByteCount = 0;
|
|
fileStream.PacketDecoded += AudioPacketDecoded;
|
|
fileStream.PropertyFound += AudioPropertyFound;
|
|
}
|
|
}
|
|
|
|
public void ResetOutputQueue ()
|
|
{
|
|
if (OutputQueue != null) {
|
|
OutputQueue.Stop (true);
|
|
OutputQueue.Reset ();
|
|
foreach (AudioBuffer buf in outputBuffers) {
|
|
buf.PacketDescriptions.Clear ();
|
|
OutputQueue.FreeBuffer (buf.Buffer);
|
|
}
|
|
outputBuffers = null;
|
|
OutputQueue.Dispose ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the OutputQueue
|
|
/// </summary>
|
|
public void Pause ()
|
|
{
|
|
OutputQueue.Pause ();
|
|
Started = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the OutputQueue
|
|
/// </summary>
|
|
public void Play ()
|
|
{
|
|
OutputQueue.Start ();
|
|
Started = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main methode to kick off the streaming, just send the bytes to this method
|
|
/// </summary>
|
|
public void ParseBytes (byte[] buffer, int count, bool discontinuity, bool lastPacket)
|
|
{
|
|
this.lastPacket = lastPacket;
|
|
fileStream.ParseBytes (buffer, 0, count, discontinuity);
|
|
}
|
|
|
|
public void Dispose ()
|
|
{
|
|
Dispose (true);
|
|
GC.SuppressFinalize (this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleaning up all the native Resource
|
|
/// </summary>
|
|
protected virtual void Dispose (bool disposing)
|
|
{
|
|
if (disposing) {
|
|
|
|
if (OutputQueue != null)
|
|
OutputQueue.Stop(true);
|
|
|
|
if (outputBuffers != null) {
|
|
foreach (var b in outputBuffers)
|
|
OutputQueue.FreeBuffer (b.Buffer);
|
|
|
|
outputBuffers.Clear ();
|
|
outputBuffers = null;
|
|
}
|
|
|
|
if (fileStream != null) {
|
|
fileStream.Close ();
|
|
fileStream = null;
|
|
}
|
|
|
|
if (OutputQueue != null) {
|
|
OutputQueue.Dispose ();
|
|
OutputQueue = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saving the decoded Packets to our active Buffer, if the Buffer is full queue it into the OutputQueue
|
|
/// and wait until another buffer gets freed up
|
|
/// </summary>
|
|
void AudioPacketDecoded (object sender, PacketReceivedEventArgs args)
|
|
{
|
|
foreach (var p in args.PacketDescriptions) {
|
|
currentByteCount += p.DataByteSize;
|
|
|
|
AudioStreamPacketDescription pd = p;
|
|
|
|
int left = bufferSize - currentBuffer.CurrentOffset;
|
|
if (left < pd.DataByteSize) {
|
|
EnqueueBuffer ();
|
|
WaitForBuffer ();
|
|
}
|
|
|
|
AudioQueue.FillAudioData (currentBuffer.Buffer, currentBuffer.CurrentOffset, args.InputData, (int)pd.StartOffset, pd.DataByteSize);
|
|
// Set new offset for this packet
|
|
pd.StartOffset = currentBuffer.CurrentOffset;
|
|
// Add the packet to our Buffer
|
|
currentBuffer.PacketDescriptions.Add (pd);
|
|
// Add the Size so that we know how much is in the buffer
|
|
currentBuffer.CurrentOffset += pd.DataByteSize;
|
|
}
|
|
|
|
if ((fileStream != null && currentByteCount == fileStream.DataByteCount) || lastPacket)
|
|
EnqueueBuffer ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flush the current buffer and close the whole thing up
|
|
/// </summary>
|
|
public void FlushAndClose ()
|
|
{
|
|
if (OutputQueue != null) {
|
|
EnqueueBuffer ();
|
|
OutputQueue.Flush ();
|
|
}
|
|
|
|
Dispose ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueue the active buffer to the OutputQueue
|
|
/// </summary>
|
|
void EnqueueBuffer ()
|
|
{
|
|
currentBuffer.IsInUse = true;
|
|
OutputQueue.EnqueueBuffer (currentBuffer.Buffer, currentBuffer.CurrentOffset, currentBuffer.PacketDescriptions.ToArray ());
|
|
queuedBufferCount++;
|
|
StartQueueIfNeeded ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wait until a buffer is freed up
|
|
/// </summary>
|
|
void WaitForBuffer ()
|
|
{
|
|
int curIndex = outputBuffers.IndexOf (currentBuffer);
|
|
currentBuffer = outputBuffers [curIndex < outputBuffers.Count - 1 ? curIndex + 1 : 0];
|
|
|
|
lock (currentBuffer) {
|
|
while (currentBuffer.IsInUse)
|
|
Monitor.Wait (currentBuffer);
|
|
}
|
|
}
|
|
|
|
void StartQueueIfNeeded ()
|
|
{
|
|
if (Started)
|
|
return;
|
|
|
|
Play ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// When a AudioProperty in the fed packets is found this callback is called
|
|
/// </summary>
|
|
void AudioPropertyFound (object sender, PropertyFoundEventArgs args)
|
|
{
|
|
if (args.Property == AudioFileStreamProperty.ReadyToProducePackets) {
|
|
Started = false;
|
|
|
|
if (OutputQueue != null)
|
|
OutputQueue.Dispose ();
|
|
|
|
OutputQueue = new OutputAudioQueue (fileStream.StreamBasicDescription);
|
|
if (OutputReady != null)
|
|
OutputReady (OutputQueue);
|
|
|
|
currentByteCount = 0;
|
|
OutputQueue.BufferCompleted += HandleBufferCompleted;
|
|
outputBuffers = new List<AudioBuffer> ();
|
|
|
|
for (int i = 0; i < MaxBufferCount; i++) {
|
|
IntPtr outBuffer;
|
|
OutputQueue.AllocateBuffer (BufferSize, out outBuffer);
|
|
outputBuffers.Add (new AudioBuffer () {
|
|
Buffer = outBuffer,
|
|
PacketDescriptions = new List<AudioStreamPacketDescription> ()
|
|
});
|
|
}
|
|
|
|
currentBuffer = outputBuffers.First ();
|
|
|
|
OutputQueue.MagicCookie = fileStream.MagicCookie;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is called when a buffer is completly read and can be freed up
|
|
/// </summary>
|
|
void HandleBufferCompleted (object sender, BufferCompletedEventArgs e)
|
|
{
|
|
queuedBufferCount--;
|
|
IntPtr buf = e.IntPtrBuffer;
|
|
|
|
foreach (var buffer in outputBuffers) {
|
|
if (buffer.Buffer != buf)
|
|
continue;
|
|
|
|
// free Buffer
|
|
buffer.PacketDescriptions.Clear ();
|
|
buffer.CurrentOffset = 0;
|
|
lock (buffer) {
|
|
buffer.IsInUse = false;
|
|
Monitor.Pulse (buffer);
|
|
}
|
|
}
|
|
|
|
if (queuedBufferCount == 0 && Finished != null)
|
|
Finished (this, new EventArgs ());
|
|
}
|
|
}
|
|
}
|