Play an audio stream in client browse

donovan 21 Reputation points
2024-07-20T11:24:10.01+00:00

I'm trying to send my soundcard audio to a HTTP stream with NAaudio so any browser on the LAN can open this one. Like an LAN Radiostation or something and use the default audio player from the browser. My sound is recorded with bytes, send, but the browser don't let me play the stream. It opens the page with player in chrome. But I'm not able to play the bytes from my soundcard .

       public bool live;
        public MMDeviceEnumerator MDE = new();
        public MMDevice device_out;
        public MMDevice device_in;
        public WaveIn WaveIn = new();
        public WaveOut WaveOut = new();
        public BufferedWaveProvider BWP;
        public string Port = "8572";
        public HttpListener server;
        public string ipAddress = "";

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        MMDeviceEnumerator Audio_out = new();
        MMDevice Devices_out = Audio_out.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
        device_out = Devices_out;

        BWP = new(new WaveFormat(44100, 16, 2));
        VolumeWaveProvider16 volumeProvider = new(BWP);
        WaveOut.Init(volumeProvider);
        WaveOut.Play();

        WaveIn = new WaveIn
        {
            WaveFormat = new WaveFormat(44100, 1)
        };

        MMDeviceEnumerator Audio_in = new();
        MMDevice devices_in = MDE.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Multimedia);
        device_in = devices_in;
        WaveIn.StartRecording();
        WaveIn.DataAvailable += AudioInput_DataAvailable;
    }

    private void AudioInput_DataAvailable(object? sender, WaveInEventArgs e)
    {
        float max = 0;
        for (int index = 0; index < e.BytesRecorded; index += 2)
        {
            short sample = (short)((e.Buffer[index + 1] << 8) | e.Buffer[index + 0]);
            var sample32 = sample / 32768f;
            if (sample32 < 0) sample32 = -sample32;
            if (sample32 > max) max = sample32;
        }

        BWP.AddSamples(e.Buffer, 0, e.BytesRecorded);
    }

    public void StartServer()
    {
        var host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (var ip in host.AddressList)
        {
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {
                ipAddress = ip.ToString();
            }
        }

        server = new();
        server.Prefixes.Add("http://*:8572/");
        server.Start();
		live = true;
        clientTasks = new ConcurrentDictionary<string, Task>();

        Task.Run(() => HandleRequests());
        T_IP.Text = "http://" + ipAddress.ToString() + ":8572/stream";
    }

    private async Task HandleRequests()
    {
        while (server.IsListening)
        {
            HttpListenerContext context = await server.GetContextAsync();
            var task = Task.Run(() => ProcessRequest(context));
            clientTasks.TryAdd(context.Request.RemoteEndPoint.ToString(), task);
        }
    }

    private async Task ProcessRequest(HttpListenerContext context)
    {
        HttpListenerResponse response = context.Response;
        response.ContentType = "audio/wav";
        response.StatusCode = 206;
        response.StatusDescription = "OK";
        response.ProtocolVersion = new Version("1.0");

        byte[] buffer = new byte[BWP.BufferLength];

        while (context.Response.OutputStream.CanWrite)
        {
            int bytesRead = BWP.Read(buffer, 0, buffer.Length);
            await response.OutputStream.WriteAsync(buffer, 0, bytesRead);
            context.Response.Headers.Add("Content-Range", $"bytes 0-{buffer.Length - 1}/{buffer.Length}");
            await response.OutputStream.FlushAsync();
        }

        response.OutputStream.Close();
        clientTasks.TryRemove(context.Request.RemoteEndPoint.ToString(), out _);
    }

     
    public void StopServer()
    {
        live = false;
        server.Stop();
    }


C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,011 questions
{count} votes

3 answers

Sort by: Most helpful
  1. Carl Lockett 0 Reputation points
    2024-07-20T18:53:42.3166667+00:00

    I did notice two things in your code;

    1. There are two methods for "public void StartServer()".
    2. You declare the Boolean value "live" to be false and I don't see where you have changed it to true. This would mean it is always false and never actually allow the stream to open.

  2. Bruce (SqlWork.com) 66,706 Reputation points
    2024-07-20T19:50:45.08+00:00

    Your code is buggy. You are adding the range header after the content, and possibly more than once in a loop. Also you are not reading the range request header to determine what range to return, nor even if it’s a range request.


  3. Jiale Xue - MSFT 46,466 Reputation points Microsoft Vendor
    2024-07-23T14:43:52.6333333+00:00

    Hi @donovan , Welcome to Microsoft Q&A,

    The browser needs to know the audio format, so you need to add the WAV file header information at the beginning of the audio stream.

    Make sure your audio data is sent to the browser continuously.

    1. Add WAV header information: In the ProcessRequest method, create and send the WAV header information. This header information tells the browser the format of the audio stream.
    private async Task ProcessRequest(HttpListenerContext context)
    {
    
    HttpListenerResponse response = context.Response;
    response.ContentType = "audio/wav";
    response.StatusCode = 200;
    response.StatusDescription = "OK";
    
    // Create WAV header information
    byte[] wavHeader = CreateWavHeader(BWP.WaveFormat, 0);
    await response.OutputStream.WriteAsync(wavHeader, 0, wavHeader.Length);
    await response.OutputStream.FlushAsync();
    
    byte[] buffer = new byte[BWP.BufferLength];
    
    // Continuously send audio data
    while (context.Response.OutputStream.CanWrite)
    {
        int bytesRead = BWP.Read(buffer, 0, buffer.Length);
        await response.OutputStream.WriteAsync(buffer, 0, bytesRead);
        await response.OutputStream.FlushAsync();
    }
    
    response.OutputStream.Close();
    clientTasks.TryRemove(context.Request.RemoteEndPoint.ToString(), out _);
    }
    
    1. Method to create WAV header information: This method creates a WAV header containing audio format information.
    private byte[] CreateWavHeader(WaveFormat format, int dataLength)
    {
        using (MemoryStream memoryStream = new MemoryStream(44))
        using (BinaryWriter writer = new BinaryWriter(memoryStream))
        {
            int bitsPerSample = format.BitsPerSample;
            int bytesPerSecond = format.SampleRate * format.Channels * (bitsPerSample / 8);
            short blockAlign = (short)(format.Channels * (bitsPerSample / 8));
    
            writer.Write(Encoding.UTF8.GetBytes("RIFF"));
            writer.Write(dataLength + 36);
            writer.Write(Encoding.UTF8.GetBytes("WAVE"));
            writer.Write(Encoding.UTF8.GetBytes("fmt "));
            writer.Write(16);
            writer.Write((short)1);
            writer.Write((short)format.Channels);
            writer.Write(format.SampleRate);
            writer.Write(bytesPerSecond);
            writer.Write(blockAlign);
            writer.Write((short)bitsPerSample);
            writer.Write(Encoding.UTF8.GetBytes("data"));
            writer.Write(dataLength);
    
            return memoryStream.ToArray();
        }
    }
    

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.