Search code examples
c#marshallingunsafe

How to copy float* to IntPtr?


Short description of my task:

As parameter of my function I got some buffer (IntPtr).

I need to extract some information from this buffer and copy information to audioFrame.AudioBuffer buffer (IntPtr).

Problem:

Needed information placed in channelData[c] (float*), I need to copy this information to destStart (IntPtr).

Code:

private void SomeFunc(IntPtr buffer)
{

...
AudioFrame audioFrame; // audioFrame.AudioBuffer is IntPtr
...

unsafe
{
    float** channelData = (float**)buffer.ToPointer();
    for (int c = 0; c < 2; c++)
    {
        IntPtr destStart = new IntPtr(audioFrame.AudioBuffer.ToInt64() + (c * audioFrame.ChannelStride));
        Marshal.Copy(channelData[c], 0, destStart, audioFrame.NumSamples); ///< problem in this line, channelData[c] is float*
    }
}

...

}

Edit Little more context: I got this buffer from CEF (https://github.com/cefsharp/CefSharp). In fact this function work as callback. When I got new audio data I need to send this data throught NDI (https://www.ndi.tv/)

AudioFrame is wrapper over the NDI structure

public struct audio_frame_v2_t
    {
    // The sample-rate of this buffer
    public int  sample_rate;

    // The number of audio channels
    public int  no_channels;

    // The number of audio samples per channel
    public int  no_samples;

    // The timecode of this frame in 100ns intervals
    public Int64    timecode;

    // The audio data
    public IntPtr   p_data;

    // The inter channel stride of the audio channels, in bytes
    public int  channel_stride_in_bytes;

    // Per frame metadata for this frame. This is a NULL terminated UTF8 string that should be
    // in XML format. If you do not want any metadata then you may specify NULL here.
    public IntPtr   p_metadata;

    // This is only valid when receiving a frame and is specified as a 100ns time that was the exact
    // moment that the frame was submitted by the sending side and is generated by the SDK. If this
    // value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined.
    public Int64    timestamp;
}

Solution

  • Perhaps consider spans here:

    var fromSpan = new Span<float>(channelData[c], audioFrame.NumSamples);
    var toSpan = new Span<float>(destStart.ToPointer(), audioFrame.NumSamples);
    fromSpan.CopyTo(toSpan);
    

    Or Buffer.MemoryCopy:

    var size = sizeof(float) * audioFrame.NumSamples;
    Buffer.MemoryCopy(channelData[c], destStart.ToPointer(), size, size);
    

    (note that in both cases it would be better to include knowledge of the actual buffer sizes if you have it, to avoid buffer overflow scenarios; I've just assumed the sizes are valid, for simplicity; there's also Unsafe.CopyBlock which works a lot like Buffer.MemoryCopy)