Search code examples
c#.netms-media-foundationsharpdx

ByteStream source in SharpDX.MediaFoundation hanging


I have been trying to get a custom audio stream to work with SharpDX.MediaFoundation.

To this end I have wrapped my audio object in a class that implements System.IO.Stream as follows:

public class AudioReaderWaveStream : System.IO.Stream
{
    byte[] waveHeader = new byte[44];
    AudioCore.IAudioReader reader = null;
    ulong readHandle = 0xffffffff;

    long readPosition = 0;

    public AudioReaderWaveStream(AudioCore.CEditedAudio content)
    {
        reader = content as AudioCore.IAudioReader;
        readHandle = reader.OpenDevice();

        int sampleRate = 0;
        short channels = 0;
        content.GetFormat(out sampleRate, out channels);

        System.IO.MemoryStream memStream = new System.IO.MemoryStream(waveHeader);
        using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(memStream))
        {
            bw.Write("RIFF".ToCharArray());
            bw.Write((Int32)Length - 8);
            bw.Write("WAVE".ToCharArray());
            bw.Write("fmt ".ToCharArray());
            bw.Write((Int32)16);
            bw.Write((Int16)3);
            bw.Write((Int16)1);
            bw.Write((Int32)sampleRate);
            bw.Write((Int32)sampleRate * 4);
            bw.Write((Int16)4);
            bw.Write((Int16)32);
            bw.Write("data".ToCharArray());
            bw.Write((Int32)reader.GetSampleCount() * 4);
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (readHandle != 0xffffffff)
        {
            reader.CloseDevice(readHandle);
            readHandle = 0xfffffffff;
        }
        base.Dispose(disposing);
    }

    ~AudioReaderWaveStream()
    {
        Dispose();
    }

    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanSeek
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }


    public override long Length
    {
        get
        {
            // Number of float samples + header of 44 bytes.
            return (reader.GetSampleCount() * 4) + 44;
        }
    }

    public override long Position
    {
        get
        {
            return readPosition;
        }
        set
        {
            readPosition = value;
        }
    }

    public override void Flush()
    {
        //throw new NotImplementedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (count <= 0)
            return 0;

        int retCount = count;
        if (Position < 44)
        {
            int headerCount = count;
            if ( Position + count >= 44 )
            {
                headerCount = 44 - (int)Position;
            }
            Array.Copy(waveHeader, Position, buffer, offset, headerCount);
            offset += headerCount;
            Position += headerCount;
            count -= headerCount;
        }
        if (count > 0)
        {
            float[] readBuffer = new float[count/4];
            reader.Seek(readHandle, Position - 44);
            reader.ReadAudio(readHandle, readBuffer);

            Array.Copy(readBuffer, 0, buffer, offset, count);
        }
        return retCount;
    }

    public override long Seek(long offset, System.IO.SeekOrigin origin)
    {
        if (origin == System.IO.SeekOrigin.Begin)
        {
            readPosition = offset;
        }
        else if (origin == System.IO.SeekOrigin.Current)
        {
            readPosition += offset;
        }
        else
        {
            readPosition = Length - offset;
        }
        return readPosition;
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }
}

I then take this object and create a source resolver using it as follows:

// Create a source resolver.
SharpDX.MediaFoundation.ByteStream      sdxByteStream   = new ByteStream( ARWS );
SharpDX.MediaFoundation.SourceResolver  resolver        = new SharpDX.MediaFoundation.SourceResolver();
ComObject                               source          = (ComObject)resolver.CreateObjectFromStream( sdxByteStream, "File.wav",  SourceResolverFlags.MediaSource );

However every time I'm doing this it hangs on the CreateObjectFromStream call. I've had a look inside SharpDX to see whats going on and it seems the actual hang occurs when it makes the call to the underlying interface through CreateObjectFromByteStream. I've also looked to see what data is actually read from the byte stream. It reads the first 16 bytes which includes the 'RIFF', the RIFF size, the 'WAVE' and the 'fmt '. Then nothing else.

Has anyone got any ideas what I could be doing wrong. I've tried all sorts of combinations of the SourceResolverFlags but nothing seems to make any difference. It just hangs.

It does remind me somewhat of interthread marshalling but all the media foundation calls are made from the same thread so I don't think its that. I'm also fairly sure that MediaFoundation uses free threading so this shouldn't be a problem anyway.

Has anyone any idea what I could possibly be doing wrong?

Thanks!


Solution

  • Ok I have come up with a solution to this. It looks like I may be having a COM threading issue. The read happens in a thread and that thread was calling back to the main thread which the function was called from.

    So I used the async version of the call and perform an Application.DoEvents() to hand across control where necessary.

    Callback cb     = new Callback( resolver );
    IUnknown cancel = null;
    resolver.BeginCreateObjectFromByteStream( sdxByteStream, "File.wav", (int)(SourceResolverFlags.MediaSource | SourceResolverFlags.ByteStream), null, out cancel, cb, null );
     if ( cancel != null )
     {
         cancel.Dispose();
     }
    
     while( cb.MediaSource == null )
     {
         System.Windows.Forms.Application.DoEvents();
     }
    
     SharpDX.MediaFoundation.MediaSource     mediaSource     = cb.MediaSource;
    

    I really hate COM's threading model ...