0

I followed this NAudio Demo modified to play ShoutCast.

In my full code I have to resample the incoming audio and stream it again over the network to a network player. Since I get many "clicks and pops", I came back to the demo code and I found that these artifacts are originated after the decoding block. If I save the incoming stream in mp3 format, it is pretty clear. When I save the raw decoded data (without other processing than the decoder) I get many audio artifacts.

I wonder whether I am doing some error, even if my code is almost equal to the NAudio demo.

Here the function from the example as modified by me to save the raw data. It is called as a new Thread.

 private void StreamMP3(object state)
    {

        //Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        //SettingsSection section = (SettingsSection)config.GetSection("system.net/settings");



        this.fullyDownloaded = false;
        string url = "http://icestreaming.rai.it/5.mp3";//(string)state;
        webRequest = (HttpWebRequest)WebRequest.Create(url);

        int metaInt = 0; // blocksize of mp3 data

        int framesize = 0;

        webRequest.Headers.Clear();
        webRequest.Headers.Add("GET", "/ HTTP/1.0");
        // needed to receive metadata informations
        webRequest.Headers.Add("Icy-MetaData", "1");
        webRequest.UserAgent = "WinampMPEG/5.09";

        HttpWebResponse resp = null;
        try
        {
            resp = (HttpWebResponse)webRequest.GetResponse();
        }
        catch (WebException e)
        {
            if (e.Status != WebExceptionStatus.RequestCanceled)
            {
                ShowError(e.Message);
            }
            return;
        }
        byte[] buffer = new byte[16384 * 4]; // needs to be big enough to hold a decompressed frame

        try
        {
            // read blocksize to find metadata block
            metaInt = Convert.ToInt32(resp.GetResponseHeader("icy-metaint"));

        }
        catch
        {
        }

        IMp3FrameDecompressor decompressor = null;
        byteOut = createNewFile(destPath, "salva", "raw");

        try
        {
            using (var responseStream = resp.GetResponseStream())
            {
                var readFullyStream = new ReadFullyStream(responseStream);
                readFullyStream.metaInt = metaInt;
                do
                {
                    if (mybufferedWaveProvider != null && mybufferedWaveProvider.BufferLength - mybufferedWaveProvider.BufferedBytes < mybufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4)
                    {
                        Debug.WriteLine("Buffer getting full, taking a break");
                        Thread.Sleep(500);
                    }
                    else
                    {
                        Mp3Frame frame = null;
                        try
                        {

                            frame = Mp3Frame.LoadFromStream(readFullyStream, true);

                            if (metaInt > 0)
                                UpdateSongName(readFullyStream.SongName);
                            else
                                UpdateSongName("No Song Info in Stream...");


                        }
                        catch (EndOfStreamException)
                        {
                            this.fullyDownloaded = true;
                            // reached the end of the MP3 file / stream
                            break;
                        }
                        catch (WebException)
                        {
                            // probably we have aborted download from the GUI thread
                            break;
                        }
                        if (decompressor == null)
                        {
                            // don't think these details matter too much - just help ACM select the right codec
                            // however, the buffered provider doesn't know what sample rate it is working at
                            // until we have a frame
                            WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
                            decompressor = new AcmMp3FrameDecompressor(waveFormat);
                            this.mybufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
                            this.mybufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(200); // allow us to get well ahead of ourselves

                            framesize = (decompressor.OutputFormat.Channels * decompressor.OutputFormat.SampleRate * (decompressor.OutputFormat.BitsPerSample / 8) * 20) / 1000;
                            //this.bufferedWaveProvider.BufferedDuration = 250;
                        }
                        int decompressed = decompressor.DecompressFrame(frame, buffer, 0);
                        //Debug.WriteLine(String.Format("Decompressed a frame {0}", decompressed));
                        mybufferedWaveProvider.AddSamples(buffer, 0, decompressed);

                        while (mybufferedWaveProvider.BufferedDuration.Milliseconds >= 20)
                        {
                            byte[] read = new byte[framesize];
                            mybufferedWaveProvider.Read(read, 0, framesize);
                            byteOut.Write(read, 0, framesize);
                        }                            
                    }

                } while (playbackState != StreamingPlaybackState.Stopped);
                Debug.WriteLine("Exiting");
                // was doing this in a finally block, but for some reason
                // we are hanging on response stream .Dispose so never get there
                decompressor.Dispose();
            }
        }
        finally
        {
            if (decompressor != null)
            {
                decompressor.Dispose();
            }
        }
    }
peregrinus
  • 147
  • 1
  • 12

1 Answers1

0

OK i found the problem. I included the shoutcast metadata to the MP3Frame. See the comment "HERE I COLLECT THE BYTES OF THE MP3 FRAME" to locate the correct point to get the MP3 frame with no streaming metadata.

The following code runs without audio artifacts:

private void SHOUTcastReceiverThread()
    {
        //-*- String server = "http://216.235.80.18:8285/stream"; 
        //String serverPath = "/";
        //String destPath = "C:\\temp\\";           // destination path for saved songs

        HttpWebRequest request = null; // web request
        HttpWebResponse response = null; // web response


        int metaInt = 0; // blocksize of mp3 data
        int count = 0; // byte counter
        int metadataLength = 0; // length of metadata header

        string metadataHeader = ""; // metadata header that contains the actual songtitle
        string oldMetadataHeader = null; // previous metadata header, to compare with new header and find next song

        //CircularQueueStream framestream = new CircularQueueStream(2048);
        QueueStream framestream = new QueueStream();
        framestream.Position = 0;

        bool bNewSong = false;

        byte[]  buffer = new byte[512]; // receive buffer

        byte[] dec_buffer = new byte[decSIZE];

        Mp3Frame frame;
        IMp3FrameDecompressor decompressor = null;

        Stream socketStream = null; // input stream on the web request

        // create web request
        request = (HttpWebRequest)WebRequest.Create(server);

        // clear old request header and build own header to receive ICY-metadata
        request.Headers.Clear();
        request.Headers.Add("GET", serverPath + " HTTP/1.0");
        request.Headers.Add("Icy-MetaData", "1"); // needed to receive metadata informations
        request.UserAgent = "WinampMPEG/5.09";

        // execute request
        try
        {
            response = (HttpWebResponse)request.GetResponse();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return;
        }

        // read blocksize to find metadata header
        metaInt = Convert.ToInt32(response.GetResponseHeader("icy-metaint"));

        try
        {
            // open stream on response
            socketStream = response.GetResponseStream();
            var readFullyStream = new ReadFullyStream(socketStream);
            frame = null;
            // rip stream in an endless loop
            do
            {
                if (IsBufferNearlyFull)
                {
                    Debug.WriteLine("Buffer getting full, taking a break");
                    Thread.Sleep(500);
                    frame = null;
                }
                else
                {
                    int bufLen = readFullyStream.Read(buffer, 0, buffer.Length);

                    try
                    {
                        if (framestream.CanRead && framestream.Length > 512)
                            frame = Mp3Frame.LoadFromStream(framestream);
                        else
                            frame = null;
                    }
                    catch (Exception ex)
                    {
                        frame = null;
                    }

                    if (bufLen < 0)
                    {
                        Debug.WriteLine("Buffer error 1: exit.");
                        return;
                    }

                    // processing RAW data
                    for (int i = 0; i < bufLen; i++)
                    {
                        // if there is a header, the 'headerLength' would be set to a value != 0. Then we save the header to a string
                        if (metadataLength != 0)
                        {
                            metadataHeader += Convert.ToChar(buffer[i]);
                            metadataLength--;
                            if (metadataLength == 0) // all metadata informations were written to the 'metadataHeader' string
                            {
                                string fileName = "";
                                string fileNameRaw = "";

                                // if songtitle changes, create a new file
                                if (!metadataHeader.Equals(oldMetadataHeader))
                                {
                                    // flush and close old byteOut stream
                                    if (byteOut != null)
                                    {
                                        byteOut.Flush();
                                        byteOut.Close();
                                        byteOut = null;
                                    }

                                    if (byteOutRaw != null)
                                    {
                                        byteOutRaw.Flush();
                                        byteOutRaw.Close();
                                        byteOutRaw = null;
                                    }
                                    timeStart = timeEnd;

                                    // extract songtitle from metadata header. Trim was needed, because some stations don't trim the songtitle
                                    //fileName = Regex.Match(metadataHeader, "(StreamTitle=')(.*)(';StreamUrl)").Groups[2].Value.Trim();
                                    fileName = Regex.Match(metadataHeader, "(StreamTitle=')(.*)(';)").Groups[2].Value.Trim();

                                    // write new songtitle to console for information                                        
                                    if (fileName.Length == 0)                                        
                                        fileName = "shoutcast_test";

                                    fileNameRaw = fileName + "_raw";

                                    framestream.reSetPosition();

                                    SongChanged(this, metadataHeader);
                                    bNewSong = true;

                                    // create new file with the songtitle from header and set a stream on this file
                                    timeEnd = DateTime.Now;
                                    if (bWrite_to_file)
                                    {
                                        byteOut = createNewFile(destPath, fileName, "mp3");
                                        byteOutRaw = createNewFile(destPath, fileNameRaw, "raw");
                                    }

                                    timediff = timeEnd - timeStart;
                                    // save new header to 'oldMetadataHeader' string, to compare if there's a new song starting
                                    oldMetadataHeader = metadataHeader;
                                }
                                metadataHeader = "";
                            }
                        }
                        else // write mp3 data to file or extract metadata headerlength
                        {
                            if (count++ < metaInt) // write bytes to filestream
                            {
                               //HERE I COLLECT THE BYTES OF THE MP3 FRAME
                                framestream.Write(buffer, i, 1);
                            }
                            else // get headerlength from lengthbyte and multiply by 16 to get correct headerlength
                            {
                                metadataLength = Convert.ToInt32(buffer[i]) * 16;
                                count = 0;
                            }
                        }
                    }//for   

                    if (bNewSong)
                    {
                        decompressor = createDecompressor(frame);
                        bNewSong = false;
                    }

                    if (frame != null && decompressor != null)
                    {
                        framedec(decompressor, frame);
                    }

                    // fine Processing dati RAW
                }//Buffer is not full

                SHOUTcastStatusProcess();

            } while (playbackState != StreamingPlaybackState.Stopped);

        } //try
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            if (byteOut != null)
                byteOut.Close();

            if (socketStream != null)
                socketStream.Close();

            if (decompressor != null)
            {
                decompressor.Dispose();
                decompressor = null;
            }
            if (null != request)
                request.Abort();

            if (null != framestream)
                framestream.Dispose();

            if (null != bufferedWaveProvider)
                bufferedWaveProvider.ClearBuffer();

            //if (null != bufferedWaveProviderOut)
            //    bufferedWaveProviderOut.ClearBuffer();

            if (null != mono16bitFsinStream)
            {
                mono16bitFsinStream.Close();
                mono16bitFsinStream.Dispose();
            }
            if (null != middleStream2)
            {
                middleStream2.Close();
                middleStream2.Dispose();
            }
            if (null != resampler)
                resampler.Dispose();
        }
    }


public class QueueStream : MemoryStream
{
    long ReadPosition = 0;
    long WritePosition = 0;

    public QueueStream() : base() { }

    public override int Read(byte[] buffer, int offset, int count)
    {
        Position = ReadPosition;

        var temp = base.Read(buffer, offset, count);            

        ReadPosition = Position;

        return temp;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        Position = WritePosition;

        base.Write(buffer, offset, count);

        WritePosition = Position;
    }

    public void reSetPosition()
    {
        WritePosition = 0;
        ReadPosition = 0;
        Position = 0;
    }
}


    private void framedec(IMp3FrameDecompressor decompressor, Mp3Frame frame)
    {
        int Ndecoded_samples = 0;
        byte[] dec_buffer = new byte[decSIZE];

        Ndecoded_samples = decompressor.DecompressFrame(frame, dec_buffer, 0);
        bufferedWaveProvider.AddSamples(dec_buffer, 0, Ndecoded_samples);

        NBufferedSamples += Ndecoded_samples;

        brcnt_in.incSamples(Ndecoded_samples);

        if (Ndecoded_samples > decSIZE)
        {
            Debug.WriteLine(String.Format("Too many samples {0}", Ndecoded_samples));
        }

        if (byteOut != null)
            byteOut.Write(frame.RawData, 0, frame.RawData.Length);
        if (byteOutRaw != null) // as long as we don't have a songtitle, we don't open a new file and don't write any bytes            
            byteOutRaw.Write(dec_buffer, 0, Ndecoded_samples);


        frame = null;
    }

    private IMp3FrameDecompressor createDecompressor(Mp3Frame frame)
    {
        IMp3FrameDecompressor dec = null;
        if (frame != null)
        {
            // don't think these details matter too much - just help ACM select the right codec
            // however, the buffered provider doesn't know what sample rate it is working at
            // until we have a frame

            WaveFormat srcwaveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
            dec = new AcmMp3FrameDecompressor(srcwaveFormat);

            bufferedWaveProvider = new BufferedWaveProvider(dec.OutputFormat);// decompressor.OutputFormat
            bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(400); // allow us to get well ahead of ourselves


            // ------------------------------------------------
            //Create an intermediate format with same sampling rate, 16 bit, mono  
            middlewavformat = new WaveFormat(dec.OutputFormat.SampleRate, 16, 1);
            outwavFormat = new WaveFormat(Fs_out, 16, 1);


            // wave16ToFloat = new Wave16ToFloatProvider(provider); // I have tried with and without this converter.
            wpws = new WaveProviderToWaveStream(bufferedWaveProvider);
            //Check middlewavformat.Encoding == WaveFormatEncoding.Pcm;
            mono16bitFsinStream = new WaveFormatConversionStream(middlewavformat, wpws);                        
            middleStream2 = new BlockAlignReductionStream(mono16bitFsinStream);
            resampler = new MediaFoundationResampler(middleStream2, outwavFormat);                
        }
        return dec;
    }
peregrinus
  • 147
  • 1
  • 12