Play real-time sound buffer using C# Media.SoundPlayer - c#

I have developed a system in which a C# program receives sound buffers (byte arrays) from another subsystem. It is supposed to play the incoming buffers continuously. I searched in the web and I decided to use SoundPlayer. It works perfectly in the offline mode (play the buffers after receiving them all). However, I have a problem in the real-time mode.
In the real-time mode the program at first waits for a number of buffer arrays (for example 200) to receive and accumulate them. Then it adds a wav header and plays it. However, after that for each next 200 arrays it plays repeatedly the first buffer.
I have read following pages:
Play wav/mp3 from memory
https://social.msdn.microsoft.com/Forums/vstudio/en-US/8ac2847c-3e2f-458c-b8ff-533728e267e0/c-problems-with-mediasoundplayer?forum=netfxbcl
and according to their suggestions I implemented my code as follow:
public class MediaPlayer
{
System.Media.SoundPlayer soundPlayer;
public MediaPlayer(byte[] buffer)
{
byte[] headerPlusBuffer = AddWaveHeader(buffer, false, 1, 16, 8000, buffer.Length / 2); //add wav header to the **first** buffer
MemoryStream memoryStream = new MemoryStream(headerPlusBuffer, true);
soundPlayer = new System.Media.SoundPlayer(memoryStream);
}
public void Play()
{
soundPlayer.PlaySync();
}
public void Play(byte[] buffer)
{
soundPlayer.Stream.Seek(0, SeekOrigin.Begin);
soundPlayer.Stream.Write(buffer, 0, buffer.Length);
soundPlayer.PlaySync();
}
}
I use it like this:
MediaPlayer _mediaPlayer;
if (firstBuffer)
{
_mediaPlayer = new MediaPlayer(dataRaw);
_mediaPlayer.Play();
}
else
{
_mediaPlayer.Play(dataRaw);
}
Each time _mediaPlayer.Play(dataRaw) is called, the first buffer is played again and again; the dataRaw is updated though.
I appreciate your help.

Related

How to stop MediaPlayer from starting each time?

How would I fix my MediaPlayer code, that receives bytes, than creates a temp file, that is saving input, but for each input, the player starts from the beginning, and I want it to just play. This is my code:
Java.IO.File temp = Java.IO.File.CreateTempFile("temp", "mp3");
Java.IO.FileOutputStream fos = new Java.IO.FileOutputStream(temp);
Java.IO.FileInputStream fis = new Java.IO.FileInputStream(temp);
temp.DeleteOnExit();
MediaPlayer player = new MediaPlayer();
player.SetDataSource(fis.FD);
// If set here, there is an error
//12-09 17:29:44.472 V/MediaPlayer( 9927): setDataSource(58, 0, 576460752303423487)
//12-09 17:29:44.472 E/MediaPlayer( 9927): Unable to to create media player
while (true)
{
try
{
byte[] myReadBuffer = new byte[10000]; //Input array
mmInStream.Read(myReadBuffer, 0, myReadBuffer.Length); //Reads the incoming array into myReadBuffer
fos.Write(myReadBuffer, 0, myReadBuffer.Length); //Writes it into temp file
MediaPlayer player = new MediaPlayer(); //Creates a new object
player.SetDataSource(fis.FD); // If here, it would just start from the start each time and add more // Sets the data source to temp file
player.Prepare();
player.Start();
while (true)
{
// Checks if it can release resources
if (!player.IsPlaying)
{
player.Release();
break;
}
}
}
catch (System.IO.IOException ex)
{
System.Diagnostics.Debug.WriteLine("Input stream was disconnected", ex);
}
}
I am using Xamari Forms.
Basicaly, I get an array of bytes, that I store in a temporary file, and than try to play them. I know the MediaPlayer is recreated on every loop, since there I define the data source, but if I put it outside of the loop, it would get an error(as above).
Example:
The song starts, is played for about 2s and than it starts over but is now played for 4s, and over again and now for 6s. Each time, more of the song is revealed.
If it was a string it would be like this:
123
123456
123456789
How would I make it to play continuously, but each time it would play a only the new part?
This is a logic issue. Essentially you're writing chunks to a stream, then playing that chunk, then writing more without resetting the stream, then playing from the beginning of that stream.
What you need it to do is write a chunk to your stream, play that stream, then write a new chunk to the stream and play that stream.
Move your Java.IO.FileOutputStream fos = new Java.IO.FileOutputStream(temp); inside of your outer while() loop.
The reason this works is that you're writing to your fos then playing, then writing again but not disposing of your initial buffer data. Moving fos into your while loop forces a new object to be created which will contain the new buffer data, then it will play that. There's going to be an issue with audio skipping because of the loop and having to re-load the new data to be played.
To correct the skip you'll need to load your buffer asynchronously while it's being played. You can do this with a separate thread. You might need to adjust your buffer size or set a buffer condition. MediaPlayer contains a BufferingProgress property that might help.

High Memory Usage playing MP3's with NAudio on Key Press

I'm using C#, WPF, and NAudio.
I play an embedded resource mp3 in the application exe when a key is pressed.
If a key is pressed repeatedly, RAM usage continues to climb past 400MB and never drops.
Using Flush() and Dispose() on the objects doesn't seem to free memory even when GC is called.
This did not used to happen when I played from external resource on the hard drive using string path instead of MemoryStream. It used to stay around 50MB RAM.
public static MemoryStream ms = null;
public static WaveStream wav = null;
public static WaveOutEvent output = null;
// Embedded Resource sound1.mp3
MemoryStream sound1 = new MemoryStream(Properties.Resources.sound1);
// Key Press
//
if (e.Key == Key.Space) {
ms = new MemoryStream(StreamToBytes(sound1));
wav = new Mp3FileReader(ms);
output = new WaveOutEvent();
output.PlaybackStopped += new EventHandler<StoppedEventArgs>(Media_Ended);
output.Init(wav);
output.Play();
}
// MP3 Playback Ended
//
public static void Media_Ended(object sender, EventArgs e)
{
if (output.PlaybackState == PlaybackState.Stopped)
{
ms.Flush();
ms = null;
wav.Close();
output.Dispose();
}
}
// Convert Stream to Byte Array
//
public static byte[] StreamToBytes(MemoryStream stream)
{
...
}
Stream to Byte Array
https://stackoverflow.com/a/1080445/6806643
I convert to Byte Array back to a new Stream or the playback will not layer and will crash if 2 sounds play at once.
It's because you clicking space bar too fast :)
Each key click overwrites variables with new values. So when you click space bar 10 times in few seconds it will create 10 resources. But you keep reference to only last one created. When Media_Ended will start incoming, it will try to dispose only latest created resource.

MediaElement not playing after changing position

I have a video player which downloads a video file in chunks. After a chunk of 1MB has been downloaded, an event is called giving the MediaElement its source, and making it play. Whilst the video is being played, the rest of the 1MB chunks are downloaded until the file is complete. If only 1MB of the video is downloaded, the playback time is equal to 17 seconds(This will come in later).
When the file is completely downloaded, permission is given to the user to change the position of the video or seek it. If the user seeks to a position under or equal to 17 seconds, the MediaElement will change its position and keep on playing, however if the user seeks to a position greater than 17s, the video freezes.
This could be for the fact that the MediaElement has buffered only 1MB of the video so it'll only seek withing that timeframe, but it doesn't make sense because if I let it play without interruption, it'll play the whole video without any problem. Can someone tell me whats going on?
Code:
private void downloadchunks()
for (int i = 1; i <= 20; i++)
{
WriteStream = new System.IO.FileStream(DownloadLocation, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
//request and receive a response of 1MB of a file
rpstream = response.GetResponseStream();
byte[] buffer;
using (var SReader = new MemoryStream())
{
rpstream.CopyTo(SReader);
buffer =SReader.ToArray();
WriteStream.Seek(WritePos,SeekOrigin.Begin);
WriteStream.Write(buffer, 0, buffer.Length);
WriteStream.Close();
}
if (i==1)
{
PlayVideo();
}
}
private void PlayVideo()
{
MediaElement.Source = new uri(DownloadLocation);
MediaElement.Play();
}
I've figured it out. Just create a dummmy file before you assign it to the MediaElement, and then start the download.
File.WriteAllBytes(location, new byte[filesize]);

NAudio WasapiLoopbackCapture intermittent sound

Part 1
I have some NAudio related code
private void InitAudioOut(DateTime dtNow)
{
_pathOut = string.Format(BaseDirectory + #"\({0:HH-mm-ss dd-MM-yyyy} OUT).wav", dtNow);
_waveOut = new WasapiLoopbackCapture();
_waveOut.DataAvailable += WaveOutDataAvailable;
_waveOut.RecordingStopped += WaveOutRecordStopped;
_waveOutFileStream = new WaveFileWriter(_pathOut, _waveOut.WaveFormat);
_waveOut.StartRecording();
}
With this initialization of the sound recording process I have the followind WaveOutDataAvailable method:
private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
{
var buf= e.Buffer;
_waveOutFileStream.Write(buf, 0, buf.Length);
_waveOutFileStream.Flush();
}
The sound in the resulting file is intermittent and slow, like having "blank" sections between the sound chunks, any ideas are appreciated.
End of part 1
Part 2
There is another version of this code where i'm trying to convert the WAV stream to mp3 stream on the fly and then write it to file, it looks like this:
private void InitAudioIn(DateTime dtNow)
{
_pathIn = string.Format(BaseDirectory + #"\({0:HH-mm-ss dd-MM-yyyy} IN).mp3", dtNow);
_waveIn = new WaveInEvent();
_waveIn.WaveFormat = new WaveFormat(44100, 2);
_waveIn.DataAvailable += WaveInDataAvailable;
_waveIn.RecordingStopped += WaveInRecordStopped;
_waveInFileStream = File.Create(_pathIn);
_waveIn.StartRecording();
}
With the WaveInDataAvailable method as follows:
private void WaveInDataAvailable(object sender, WaveInEventArgs e)
{
var wavToMp3Buffer = ConvertWavToMp3(e.Buffer, _waveIn.WaveFormat);
_waveInFileStream.Write(wavToMp3Buffer, 0, wavToMp3Buffer.Length);
_waveInFileStream.Flush();
}
The ConvertWavToMp3 method:
public byte[] ConvertWavToMp3(byte[] wavContent, WaveFormat waveFormat)
{
using (var baseMemoryStream = new MemoryStream())
using (var wavToMp3Writer = new LameMP3FileWriter(baseMemoryStream, waveFormat, 64))
{
wavToMp3Writer.Write(wavContent, 0, wavContent.Length);
wavToMp3Writer.Flush();
return baseMemoryStream.ToArray();
}
}
If i don't try to convert it to MP3 and just write it as a WAV file that it's absolutely fine, but if i try the MP3 conversion through the ConvertWavToMp3 method then the sound gets slow and intermittent, what is wrong with this implementation?
First part, you are making an invalid assumption about the buffer length being the same as the number of valid bytes in the buffer. Try:
private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
{
_waveOutFileStream.Write(e.Buffer, 0, e.BytesRecorded);
}
Let the output stream handle flushing automatically. Trying to force data to disk like that will either not work or in some cases can cause unexpected results like partial block writes that can interfere with your data. Flush at the end of the recording, not during.
As to the second part...
Your code is creating a file that is the concatenation of a series of MP3 files, one for each buffer passed to your WaveInDataAvailable method, and including all the blank space at the end of those buffers. Of course it's not going to play back properly.
If you want to write an MP3 then do it directly. Make your _waveInFileStream an instance of LameMP3FileWriter and let it handle the work itself. Not only is this going to produce a much more useful output but you save yourself a lot of inefficient messing around with setting up and tearing down the encoder for every data block you receive.

Accessing MediaRecorder bytes while recording in Xamarin Android

I would like to access the audio bytes that is being recorded by MediaRecorder during the recording process so I can send them with UdpClient to a server application.
I'm able to do this with AudioRecord by doing the following (note the while(true) loop)
endRecording = false;
isRecording = true;
audioBuffer = new Byte[1024];
audioRecord = new AudioRecord (
// Hardware source of recording.
AudioSource.Mic,
// Frequency
11025,
// Mono or stereo
ChannelIn.Mono,
// Audio encoding
Android.Media.Encoding.Pcm16bit,
// Length of the audio clip.
audioBuffer.Length
);
audioRecord.StartRecording ();
while (true) {
if (endRecording) {
endRecording = false;
break;
}
try {
// Keep reading the buffer while there is audio input.
int numBytes = await audioRecord.ReadAsync (audioBuffer, 0, audioBuffer.Length);
//Send the audio data with the DataReceived event where it gets send over UdpClient in the Activity code
byte[] encoded = audioBuffer; //TODO: encode audio data, for now just stick with regular PCM audio
DataReceived(encoded);
} catch (Exception ex) {
Console.Out.WriteLine (ex.Message);
break;
}
}
audioRecord.Stop ();
audioRecord.Release ();
isRecording = false;
But I'm not sure how to get the bytes out of MediaRecorder so I can do something similar. Most of the examples I see only work with a file after the recording has been finished like the following example code from here and here.
I don't want to wait for a complete recording before it starts to send. I don't need MediaRecorder to record a file, just give me access to the bytes. But having the option to both write to a file and send the bytes would work well. Is there a way to do this, perhaps by using ParcelFileDescriptor or something else?

Categories

Resources