Visual Studio 2019
C#
SDR# plug-in (for non NET 5 versions)
.NET Framework 4.6
RDS data from SDR# to RdsSpy with TCP without virtual audio cable
Asynchronous TCP socket

Source code and dll (updated 2021-03-02 - many fixes for more stable operation): https://github.com/OH1GIU-P/sdrsharp-rds2rdsspy

Copy SDRSharp.RdsiPlugin.dll from ...SDRSharp.RdsiPlugin\bin\Release to SDR# folder and add magic line to Plugins.xml
<add key="Rdsi" value="SDRSharp.RdsiPlugin.RdsiPlugin,SDRSharp.RdsiPlugin"/>
Connect from RdsSpy to SDR# seems to require to do 2 times: file -> play stream operation


Needs some tuning, buffering in plug-in needed because it seems that RdsSpy cannot receive/process data from TCP stream as fast as SDR# provides RDS group data.

scrrds.jpg
https://i.ibb.co/sH7Qfjw/scrrds.jpg

using System.Windows.Forms;
using SDRSharp.Common;
using SDRSharp.Radio;

namespace SDRSharp.RdsiPlugin
{
    public class RdsiPlugin : ISharpPlugin
    {
        private const string _displayName = "SDR#2RdsSpy";
        private ISharpControl _control;
        private RdsiPluginPanel _guiControl;

        public UserControl Gui
        {
            get { return _guiControl; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public void Close()
        {
            _guiControl.Close();
        }

        public void Initialize(ISharpControl control)
        {
            _control = control;
            _guiControl = new RdsiPluginPanel(_control);
            control.RegisterStreamHook((object)new RdsHwnd(_guiControl), ProcessorType.RDSBitStream);
        }
    }
}

 

using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using SDRSharp.Common;

namespace SDRSharp.RdsiPlugin
{
    public partial class RdsiPluginPanel : UserControl
    {
        private bool _socketSend = false;
        private int _clientConnect = 0;
        private readonly ISharpControl _control;
        private int _cnt = 0;
        private IPHostEntry _host = null;
        private IPAddress _ipAddress = null;
        private IPEndPoint _localEndPoint = null;
        private Socket _listener = null;
        private Socket _handler = null;

        public RdsiPluginPanel(ISharpControl control)
        {
            InitializeComponent();
            _control = control;
            labelCount.Text = "0";
            labelConn.Text = "Not connected";
            numPort.Value = 3122;
            IPHostEntry _host = Dns.GetHostEntry("localhost");
            int i;
            for (i = 0; i < _host.AddressList.Length; i++)
            {
                if (_host.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
                {
                    break;
                }
            }
            _ipAddress = _host.AddressList[i];
            _localEndPoint = new IPEndPoint(_ipAddress, Convert.ToInt32(numPort.Value));
        }

        public void socketSend(String groups)
        {
            if (_socketSend)
            {
                byte[] msg = Encoding.ASCII.GetBytes(groups);
                try
                {
                    _handler.Send(msg);
                    _cnt++;
                }
                catch (SocketException ex)
                {
                    _socketSend = false;
                    labelConn.Text = "Connection forcibly closed";
                }
                labelCount.Text = _cnt.ToString();
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            _cnt = 0;
            _socketSend = false;
            _clientConnect = 0;
            labelCount.Text = "0";
            labelConn.Text = "Not connected";
            _localEndPoint.Port = Convert.ToInt32(numPort.Value);
            _localEndPoint.Address = _ipAddress;
            try
            {
                _listener = new Socket(_ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _listener.Bind(_localEndPoint);
                _listener.Listen(1);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Listen: " + ex.Message);
                closeListener();
                return;
            }

            labelConn.Text = "Connecting ...";
            buttonStart.Enabled = false;
            buttonStop.Enabled = true;
            try
            {
                _listener.BeginAccept(ClientConnected, null);
            }
            catch (Exception ex)
            {
                MessageBox.Show("BeginAccept: " + ex.Message);
                buttonStart.Enabled = true;
                buttonStop.Enabled = false;
                labelConn.Text = "Not connected";
                closeListener();
                return;
            }
            while (true)
            {
                if (_clientConnect != 0)
                {
                    break;
                }
                Application.DoEvents();
            }
            if (_clientConnect == 2)
            {
                return;
            }
            labelConn.Text = "Connected";
            buttonStart.Enabled = false;
            buttonStop.Enabled = true;
            numPort.Enabled = false;
            _socketSend = true;
        }

        void ClientConnected(IAsyncResult ar)
        {
            if (_clientConnect == 2)
            {
                return;
            }
            _handler = _listener.Accept();
            _clientConnect = 1;
        }
        private void buttonStop_Click(object sender, EventArgs e)
        {
            _socketSend = false;
            _clientConnect = 2;
            closeListener();
            closeHandler();
            labelConn.Text = "Not connected";
            labelCount.Text = "0";
            numPort.Enabled = true;
            buttonStart.Enabled = true;
            buttonStop.Enabled = false;
        }

        public void closeListener()
        {
            if (_listener != null)
            {
                _listener.Close();
                _listener.Dispose();
                _listener = null;
            }
        }

        public void closeHandler()
        {
            if (_handler != null)
            {
                _handler.Shutdown(SocketShutdown.Both);
                _handler.Close();
                _handler = null;
            }
        }
        public void Close()
        {
            _socketSend = false;
            _clientConnect = 2;
            closeHandler();
            closeListener();
        }
    }
}

 

 

 

using System.Text;
using SDRSharp.Radio;

namespace SDRSharp.RdsiPlugin
{
    public unsafe class RdsHwnd : IRdsBitStreamProcessor, IBaseProcessor
    {
        private bool _enabled = true;
        private readonly RdsiPluginPanel _rdsiPluginPanel;

        public RdsHwnd(RdsiPluginPanel _rdsPanelRef)
        {
            _rdsiPluginPanel = _rdsPanelRef;
        }

        public bool Enabled
        {
            get
            {
                return _enabled;
            }
            set
            {
                _enabled = value;
            }
        }
        public void Process(ref RdsFrame _rdsFrame)
        {
            _rdsFrame.Filter = false;
            string pic = string.Format("{0:X}", _rdsFrame.GroupA).Trim();
            if (pic.Length != 4)
                return;
            StringBuilder _sb = new StringBuilder();
            _sb.Append(pic);
            _sb.Append(string.Format("{0:X}", _rdsFrame.GroupB));
            _sb.Append(string.Format("{0:X}", _rdsFrame.GroupC));
            _sb.AppendLine(string.Format("{0:X}", _rdsFrame.GroupD));
            _rdsiPluginPanel.socketSend(_sb.ToString());
        }
    }

}