Get waveform data from audio file using FFMPEG - c#

I am writing an application that needs to get the raw waveform data of an audio file so I can render it in an application (C#/.NET). I am using ffmpeg to offload this task but it looks like ffmpeg can only output the waveform data as a png or as a stream to gnuplot.
I have looked at other libraries to do this (NAudio/CSCore) however they require windows/microsoft media foundation and since this app is going to be deployed to azure as a web app I can not use them.
My strategy was to just read the waveform data from the png itself but this seems hacky and over the top. The ideal output would be a fix sampled series of peaks in an array where each value in the array is the peak value (ranging from 1-100 or something, like this for example).

Sabona budi,
Wrote about the manual way to get waveform but then to show you an example, I found this code which does what you want (or at the least, you can learn something from it).
1) Use FFmpeg to get array of samples
Try the example code shown here : http://blog.wudilabs.org/entry/c3d357ed/?lang=en-US
Experiment with it, try tweaking with suggestions from manual etc... In that shown code just change string path to point to your own file-path. Edit the proc.StartInfo.Arguments section to replace the last section to look like:
proc.StartInfo.Arguments = "-i \"" + path + "\" -vn -ac 1 -filter:a aresample=myNum -map 0:a -c:a pcm_s16le -f data -";
That myNum from the part aresample=myNum is calculated by :
44100 * total Seconds = X.
myNum = X / WaveForm Width.
Finally use the ProcessBuffer function with this logic :
static void ProcessBuffer(byte[] buffer, int length)
{
float val; //amplitude value of a sample
int index = 0; //position within sample bytes
int slicePos = 0; //horizontal (X-axis) position for pixels of next slice
while (index < length)
{
val = BitConverter.ToInt16(buffer, index);
index += sizeof(short);
// use number in va to do something...
// eg: Draw a line on canvas for part of waveform's pixels
// eg: myBitmap.SetPixel(slicePos, val, Color.Green);
slicePos++;
}
}
If you want to do it manually without FFmpeg. You could try...
2) Decode audio to PCM
You could just load the audio file (mp3) into your app and first decode that to PCM (ie: raw digital audio). Then read just the PCM numbers to make the waveform. Don't read numbers directly from bytes of compression math like MP3.
These PCM data values (about audio amplitudes) go into a byte array. If your sound is 16-bit then you extract the PCM value by reading each sample as a short (ie: getting value of two consecutive bytes at once since 16 bits == 2 bytes length).
Basically when you have 16-bit audio PCM inside a byte array, every two bytes represents an audio sample's amplitude value. This value becomes your height (loudness) at each slice. A slice is a 1-pixel vertical line from a time in the waveform.
Now sample rate means how many samples per-second. Usually 44100 samples (44.1 khz). You can see that using 44 thousand pixels to represent one second of sound is not feasible, so divide total seconds by required waveform width. Take the result & multiply by 2 (to cover two bytes) and that is how you much you jump-&-sample the amplitudes as you form the waveform. Do this in a while loop.

You can use the function described in this tutorial to get the raw data decoded from an audio file as an array of double values.
Summarizing from the link:
The function decode_audio_file takes 4 parameters:
path: the path of the file to decode
sample_rate: the desired sample rate for the output data
data: a pointer to a pointer to double precision values, where the extracted data will be stored
size: a pointer to the length of the final extracted values array (number of samples)
It returns 0 upon success, and -1 in case of failure, assorted with error message written to the stderr stream.
The function code is below:
#include <stdio.h>
#include <stdlib.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
int decode_audio_file(const char* path, const int sample_rate, double** data, int* size) {
// initialize all muxers, demuxers and protocols for libavformat
// (does nothing if called twice during the course of one program execution)
av_register_all();
// get format from audio file
AVFormatContext* format = avformat_alloc_context();
if (avformat_open_input(&format, path, NULL, NULL) != 0) {
fprintf(stderr, "Could not open file '%s'\n", path);
return -1;
}
if (avformat_find_stream_info(format, NULL) < 0) {
fprintf(stderr, "Could not retrieve stream info from file '%s'\n", path);
return -1;
}
// Find the index of the first audio stream
int stream_index =- 1;
for (int i=0; i<format->nb_streams; i++) {
if (format->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
stream_index = i;
break;
}
}
if (stream_index == -1) {
fprintf(stderr, "Could not retrieve audio stream from file '%s'\n", path);
return -1;
}
AVStream* stream = format->streams[stream_index];
// find & open codec
AVCodecContext* codec = stream->codec;
if (avcodec_open2(codec, avcodec_find_decoder(codec->codec_id), NULL) < 0) {
fprintf(stderr, "Failed to open decoder for stream #%u in file '%s'\n", stream_index, path);
return -1;
}
// prepare resampler
struct SwrContext* swr = swr_alloc();
av_opt_set_int(swr, "in_channel_count", codec->channels, 0);
av_opt_set_int(swr, "out_channel_count", 1, 0);
av_opt_set_int(swr, "in_channel_layout", codec->channel_layout, 0);
av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_MONO, 0);
av_opt_set_int(swr, "in_sample_rate", codec->sample_rate, 0);
av_opt_set_int(swr, "out_sample_rate", sample_rate, 0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", codec->sample_fmt, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0);
swr_init(swr);
if (!swr_is_initialized(swr)) {
fprintf(stderr, "Resampler has not been properly initialized\n");
return -1;
}
// prepare to read data
AVPacket packet;
av_init_packet(&packet);
AVFrame* frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Error allocating the frame\n");
return -1;
}
// iterate through frames
*data = NULL;
*size = 0;
while (av_read_frame(format, &packet) >= 0) {
// decode one frame
int gotFrame;
if (avcodec_decode_audio4(codec, frame, &gotFrame, &packet) < 0) {
break;
}
if (!gotFrame) {
continue;
}
// resample frames
double* buffer;
av_samples_alloc((uint8_t**) &buffer, NULL, 1, frame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
int frame_count = swr_convert(swr, (uint8_t**) &buffer, frame->nb_samples, (const uint8_t**) frame->data, frame->nb_samples);
// append resampled frames to data
*data = (double*) realloc(*data, (*size + frame->nb_samples) * sizeof(double));
memcpy(*data + *size, buffer, frame_count * sizeof(double));
*size += frame_count;
}
// clean up
av_frame_free(&frame);
swr_free(&swr);
avcodec_close(codec);
avformat_free_context(format);
// success
return 0;
}
You will need the following flags to compile a program that uses : -lavcodec-ffmpeg -lavformat-ffmpeg -lavutil -lswresample
Depending on your system and installation, it could also be: -lavcodec -lavformat -lavutil -lswresample
and its usage is below:
int main(int argc, char const *argv[]) {
// check parameters
if (argc < 2) {
fprintf(stderr, "Please provide the path to an audio file as first command-line argument.\n");
return -1;
}
// decode data
int sample_rate = 44100;
double* data;
int size;
if (decode_audio_file(argv[1], sample_rate, &data, &size) != 0) {
return -1;
}
// sum data
double sum = 0.0;
for (int i=0; i<size; ++i) {
sum += data[i];
}
// display result and exit cleanly
printf("sum is %f", sum);
free(data);
return 0;
}

Related

FFmpeg.AutoGen example of how to split audio file

I want to use the FFmpeg.AutoGen project from here: https://github.com/Ruslan-B/FFmpeg.AutoGen
I'm not familiar with the ffmpeg API, so I wanted to get an example of how to split an audio file into little files, for example the audio file is about 2 hours long (can be mp3,ogg,wav,etc.) and I want to split it into several little files of x minutes. The splitting should be done on the main audio file with timestamps from and to, for example from = 00:25:00 (meaning 25 minutes), to = 00:31:12 (meaning 31 minutes, 12 seconds) and the output should be the section from the main audio file, the result is 00:06:12 (meaning 6 minutes, 12 seconds) long.
How can this task be achieved with the project? Also a C example can help me, I would try to convert it into the framework.
Thanks for your responses.
FFmpeg.AutoGen
What I think you need to do:
Decode audio file
Extract audio samples from the desired start timestamp until desired end timestamp
Encode the extracted samples
Write new audio file
Here are some sources in C which might help you to put it all together:
Read any audio file type: Libavcodec tutorial: decode virtually any audio file in C/C++. Note: avcodec_decode_audio4 is already deprecated (see New AVCodec API) You should use avcodec_send_packet / avcodec_receive_frame.
Encode / decode audio: FFmpeg examples - especially decode_audio.c / encode_audio.c
I think that the effort to use FFmpeg.AutoGen is too high for your use-case and therefore I propose 2 alternatives: Use NAudio or FFmpeg via command line
NAudio
This code reads an MP3, extracts a defined segment and writes it to a WAV file. It is based on the blog post Concatenating Segments of an Audio File with NAudio and can be tweaked quite easily.
using NAudio.Wave;
using System;
namespace NAudioSegments
{
class SegmentProvider : IWaveProvider
{
private readonly WaveStream sourceStream;
private int segmentStart, segmentDuration;
public SegmentProvider(WaveStream sourceStream)
{
this.sourceStream = sourceStream;
}
public WaveFormat WaveFormat => sourceStream.WaveFormat;
public void DefineSegment(TimeSpan start, TimeSpan duration)
{
if (start + duration > sourceStream.TotalTime)
throw new ArgumentOutOfRangeException("Segment goes beyond end of input");
segmentStart = TimeSpanToOffset(start);
segmentDuration = TimeSpanToOffset(duration);
sourceStream.Position = segmentStart;
}
public int TimeSpanToOffset(TimeSpan ts)
{
var bytes = (int)(WaveFormat.AverageBytesPerSecond * ts.TotalSeconds);
bytes -= (bytes % WaveFormat.BlockAlign);
return bytes;
}
public int Read(byte[] buffer, int offset, int count)
{
int totalBytesRead = 0;
int bytesRead = 0;
do
{
bytesRead = ReadFromSegment(buffer, offset + totalBytesRead, count - totalBytesRead);
totalBytesRead += bytesRead;
} while (totalBytesRead < count && bytesRead != 0);
return totalBytesRead;
}
private int ReadFromSegment(byte[] buffer, int offset, int count)
{
var bytesAvailable = (int)(segmentStart + segmentDuration - sourceStream.Position);
var bytesRequired = Math.Min(bytesAvailable, count);
return sourceStream.Read(buffer, offset, bytesRequired);
}
}
class Program
{
static void Main(string[] args)
{
using (var source = new Mp3FileReader(#"<input-path>"))
{
var segmentProvider = new SegmentProvider(source);
// Add desired splitting e.g. start at 2 seconds, duration 1 second
segmentProvider.DefineSegment(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1));
WaveFileWriter.CreateWaveFile(#"<output-path>", segmentProvider);
}
}
}
}
FFmpeg via command line
You could invoke ffmpeg directly from your C# code via the System.Diagnostics.Process class (see e.g. this SO question) instead of using FFmpeg.AutoGen.
You could then use the following command line for automatically splitting the audio file in segemnts of the same length beginning at 00:00:00
ffmpeg -i in.m4a -f segment -segment_time 300 -c copy out%03d.m4a
or you can change the start time with the parameter -ss (replace <start-time> with the number of seconds). You would need to repeat this for every segment.
ffmpeg -ss <start-time> -i in.m4a -c copy -t 300 out.m4a
Source on superuser

How to decompress just one frame using NAudio?

I am simply trying to take any or first frame from an mp3 file and then decompress it.
internal void Read3(string location) //take in file location
{
Mp3FileReader mp3 = new Mp3FileReader(location); //make a Mp3FileReader
SECTIONA: //to jump back here when needed.
byte[] _passedBuffer = Decompress(mp3.ReadNextFrame()); //passed the decompressed byte array here.
int jump_size = mp3.WaveFormat.Channels *2; //just to get how many bytes to skip
for (int i = 0; i < _passedBuffer.Length; i += jump_size)
{
short FinalSample = BitConverter.ToInt16(_passedBuffer, i);
if (jump_size == 4) //converting the bytes to Int16,nothing special here.
{
FinalSample = (short)(((BitConverter.ToInt16(_passedBuffer, i + 2)) + FinalSample) / 2);
}
Console.Write(FinalSample+"|"); //and writing it down to Console.
}
Console.WriteLine("Frames are Written,continue to next frame?");
if (Convert.ToChar(Console.Read()) == 'y') //asking to go on or not.
{ goto SECTIONA; }
}
private byte[] Decompress(Mp3Frame fm)
{
var buffer = new byte[16384 * 4]; //big enough buffer size
WaveFormat wf = new Mp3WaveFormat(fm.SampleRate, fm.ChannelMode == ChannelMode.Mono ? 1 : 2, fm.FrameLength, fm.BitRate); //creating a new WaveFormat
IMp3FrameDecompressor decompressor = new AcmMp3FrameDecompressor(wf); //passing in to AcmMp3FrameDecompressor.
decompressor.DecompressFrame(fm, buffer, 0); //running the DecompressFrame method and then passing back the buffer.
return buffer;
}
Now the Mp3FileReader is reading the Frame correctly as I checked the Frame's RawData. Now I am trying to decompress that Frame and then convert its PCM data into Int16 in that only For Loop but every Int16 FinalSample value is returning 0.
I know that just using Mp3FileReader.Read(Buffer,Offset,Length) will get the job done but for all the frames so:
how do I do it for just one frame?
what is wrong with my code because of which I am getting all zeros?
I know that RawData is ok, so something must be wrong with Decompress method, How do I setup a decompressor for mp3 file?
You can use the AcmMp3FrameDecompressor or DmoMp3FrameDecompressor to decompress individual MP3 frames.
You need to check the return value of Decompress to see how much data was returned. It's possible for the first frame not to return anything.
You should also create a single frame decompressor and use it for all frames in the MP3 file, as it retains state between calls.

Decoding H.264 NALU Stream C#

1- Live Bosch IP camera Stream
2- Stream format is H.264 AVC
3- The NALUs are packed in the stream item as described in "ISO/IEC 14496-15 Advanced Video Coding (AVC) file format", i.e. single NALUs are separated by a 4 byte length field in network byte order.
private unsafe byte[] PostProcessPayload(Guid codingType, byte[] dataBuffer)
{
if ((codingType == VsdkGuids.CodingType_H264) && m_convertH264StreamItems)
{
//
// "This stream item consists of one or multiple H.264 Network Abstraction Layer Units
// (NALUs) ITU-T Rec. H.264 / ISO/IEC 14496-10 Advanced Video Coding belonging to the same timestamp.
// It is guaranteed that each item including an IDR picture will also include the corresponding picture
// parameter set and sequence parameter set NALUs.
//The NALUs are packed in the stream item as described in
// "ISO/IEC 14496-15 Advanced Video Coding (AVC) file format", i.e. single NALUs are separated
// by a 4 byte length field in network byte order.
// Since the frame boundary is implicitly communicated (each stream item represents a frame),
// frames usually do not carry an explicit access unit delimiter at the end.
// In case interlaced video is received, two fields are packet into a single stream item."
//
// The following code section replaces the 4 byte NALU lengths with NALU separators and appends an access delimiter at frame end.
// The data buffer that contains the H.264 frame must be allocated with enough headroom (6 bytes after the last NALU)
// for the access delimiter. When such a processed payload data is written into a file of type *.h264, the VLC player for example is able
// to play that file (in this Complete C# Sample, connect a device proxy, set the property ConvertH264StreamItems of
// a video datastream to "true", add the device proxy to the end nodes, start this video stream, and stop it after a while. Then simply
// rename the written DataStream_{unique identifier}_Video.dat file to *.h264 and play this file with VLC).
int remainingLength = dataBuffer.Length - H264_ACCESS_DELIMITER.Length;
fixed (byte* pDataBuffer = &dataBuffer[0], pDelimiterBuffer = &H264_ACCESS_DELIMITER[0], pSeparatorBuffer = &H264_NALU_SEPARATOR[0])
{
byte* pData = pDataBuffer;
while (remainingLength > H264_NALU_SEPARATOR.Length)
{
int naluLength = System.Net.IPAddress.NetworkToHostOrder(*(Int32*)pData);
// replace NALU length with NALU separator
for (int i = 0; i < H264_NALU_SEPARATOR.Length; i++)
pData[i] = pSeparatorBuffer[i];
// goto next NALU
int offsetToNextNalu = H264_NALU_SEPARATOR.Length + naluLength;
pData += offsetToNextNalu;
remainingLength -= offsetToNextNalu;
}
if (remainingLength != 0)
Common.Log("Warning: Inconsistency in postprocessing of H.264 stream item, remaining length {0} is not zero", remainingLength);
// append access delimiter after last NALU, there will be no access violation here because the buffer had been allocated with enough headroom
for (int i = 0; i < H264_ACCESS_DELIMITER.Length; i++)
pData[i] = pDelimiterBuffer[i];
}
}
return dataBuffer;
}
How to get frame image from the dataBuffer in C#?

How to save Stream on server without dialog box in silverligh5?

i created one app, which is recording user voice from system Mic, and save file on system Harddisk using dialog box, which is working fine !
but my requirement is, i want to save Stream on server without dialog box
this is what i tried so far:-
private SaveFileDialog saveFileDialog = new SaveFileDialog()
{
Filter = "Audio files(*.wav)|*.wav"
};
protected void SaveFile()
{
if (saveFileDialog.ShowDialog() == false)
{
return;
}
StatusText = "Saving...";
Stream stream = saveFileDialog.OpenFile();
WavManager.SavePcmToWav(_sink.BackingStream, stream, _sink.CurrentFormat);
stream.Close();
MessageBox.Show("Your record is saved.");
GoToStartState();
}
public class WavManager
{
public static void SavePcmToWav(Stream rawData, Stream output, AudioFormat audioFormat)
{
if (audioFormat.WaveFormat != WaveFormatType.Pcm)
throw new ArgumentException("Only PCM coding is supported.");
BinaryWriter bwOutput = new BinaryWriter(output);
// Write down the WAV header.
// Refer to http://technology.niagarac.on.ca/courses/ctec1631/WavFileFormat.html
// for details on the format.
// Note that we use ToCharArray() when writing fixed strings
// to force using the char[] overload because
// Write(string) writes the string prefixed by its length.
// -- RIFF chunk
bwOutput.Write("RIFF".ToCharArray());
// Total Length Of Package To Follow
// Computed as data length plus the header length without the data
// we have written so far and this data (44 - 4 ("RIFF") - 4 (this data))
bwOutput.Write((uint)(rawData.Length + 36));
bwOutput.Write("WAVE".ToCharArray());
// -- FORMAT chunk
bwOutput.Write("fmt ".ToCharArray());
// Length Of FORMAT Chunk (Binary, always 0x10)
bwOutput.Write((uint)0x10);
// Always 0x01
bwOutput.Write((ushort)0x01);
// Channel Numbers (Always 0x01=Mono, 0x02=Stereo)
bwOutput.Write((ushort)audioFormat.Channels);
// Sample Rate (Binary, in Hz)
bwOutput.Write((uint)audioFormat.SamplesPerSecond);
// Bytes Per Second
bwOutput.Write((uint)(audioFormat.BitsPerSample * audioFormat.SamplesPerSecond * audioFormat.Channels / 8));
// Bytes Per Sample: 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo
bwOutput.Write((ushort)(audioFormat.BitsPerSample * audioFormat.Channels / 8));
// Bits Per Sample
bwOutput.Write((ushort)audioFormat.BitsPerSample);
// -- DATA chunk
bwOutput.Write("data".ToCharArray());
// Length Of Data To Follow
bwOutput.Write((uint)rawData.Length);
// Raw PCM data follows...
// Reset position in rawData and remember its origin position
// to restore at the end.
long originalRawDataStreamPosition = rawData.Position;
rawData.Seek(0, SeekOrigin.Begin);
// Append all data from rawData stream into output stream.
byte[] buffer = new byte[4096];
int read; // number of bytes read in one iteration
while ((read = rawData.Read(buffer, 0, 4096)) > 0)
{
bwOutput.Write(buffer, 0, read);
}
rawData.Seek(originalRawDataStreamPosition, SeekOrigin.Begin);
}
}

Play dynamically-created simple sounds in C# without external libraries

I need to be able to generate dynamically a waveform and play it, using C#, without any external libraries, and without having to store sound files on the hard disk. Latency isn't an issue; the sounds would be generated well before they are needed by the application.
Actually the Console.Beep() method might meet my needs if it weren't for the fact that Microsoft says it isn't supported in 64-bit versions of Windows.
If I generate my own sound dynamically I can get a more fancy than a simple beep. For example, I could make a waveform from a triangle wave that increases in frequency from 2 KHz to 4 KHz while decaying in volume. I don't need fancy 16-bit stereo, just 8-bit mono is fine. I don't need dynamic control over volume and pitch, just basically generate a soundfile in memory and play it without storing it.
Last time I needed to generate sounds was many years ago, on Apple II, on HP workstations, and on my old Amiga computer. Haven't needed to do it since then, and it seems that something simple that I describe has gotten a lot more complicated. I am having trouble believing that something so simple seems so hard. Most of the answers I see refer to NAudio or similar libraries, and that isn't an option for this project (aside from the fact that pulling in an entire library just to play a tone seems like a waste).
Based on one of the links in the answers I received, and some other pages I found about .wav header formats, here is my working code for a little class that generates an 8-bit "ding!" sound with a user-specified frequency and duration. It's basically a beep that decays linearly to zero in amplitude during the specified duration.
public class AlertDing {
private SoundPlayer player = null;
private BinaryWriter writer = null;
/// <summary>
/// Dynamically generate a "ding" sound and save it to a memory stream
/// </summary>
/// <param name="freq">Frequency in Hertz, e.g. 880</param>
/// <param name="tenthseconds">Duration in multiple of 1/10 second</param>
public AlertDing(double freq, uint tenthseconds) {
string header_GroupID = "RIFF"; // RIFF
uint header_FileLength = 0; // total file length minus 8, which is taken up by RIFF
string header_RiffType = "WAVE"; // always WAVE
string fmt_ChunkID = "fmt "; // Four bytes: "fmt "
uint fmt_ChunkSize = 16; // Length of header in bytes
ushort fmt_FormatTag = 1; // 1 for PCM
ushort fmt_Channels = 1; // Number of channels, 2=stereo
uint fmt_SamplesPerSec = 14000; // sample rate, e.g. CD=44100
ushort fmt_BitsPerSample = 8; // bits per sample
ushort fmt_BlockAlign =
(ushort)(fmt_Channels * (fmt_BitsPerSample / 8)); // sample frame size, in bytes
uint fmt_AvgBytesPerSec =
fmt_SamplesPerSec * fmt_BlockAlign; // for estimating RAM allocation
string data_ChunkID = "data"; // "data"
uint data_ChunkSize; // Length of header in bytes
byte [] data_ByteArray;
// Fill the data array with sample data
// Number of samples = sample rate * channels * bytes per sample * duration in seconds
uint numSamples = fmt_SamplesPerSec * fmt_Channels * tenthseconds / 10;
data_ByteArray = new byte[numSamples];
//int amplitude = 32760, offset=0; // for 16-bit audio
int amplitude = 127, offset = 128; // for 8-audio
double period = (2.0*Math.PI*freq) / (fmt_SamplesPerSec * fmt_Channels);
double amp;
for (uint i = 0; i < numSamples - 1; i += fmt_Channels) {
amp = amplitude * (double)(numSamples - i) / numSamples; // amplitude decay
// Fill with a waveform on each channel with amplitude decay
for (int channel = 0; channel < fmt_Channels; channel++) {
data_ByteArray[i+channel] = Convert.ToByte(amp * Math.Sin(i*period) + offset);
}
}
// Calculate file and data chunk size in bytes
data_ChunkSize = (uint)(data_ByteArray.Length * (fmt_BitsPerSample / 8));
header_FileLength = 4 + (8 + fmt_ChunkSize) + (8 + data_ChunkSize);
// write data to a MemoryStream with BinaryWriter
MemoryStream audioStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(audioStream);
// Write the header
writer.Write(header_GroupID.ToCharArray());
writer.Write(header_FileLength);
writer.Write(header_RiffType.ToCharArray());
// Write the format chunk
writer.Write(fmt_ChunkID.ToCharArray());
writer.Write(fmt_ChunkSize);
writer.Write(fmt_FormatTag);
writer.Write(fmt_Channels);
writer.Write(fmt_SamplesPerSec);
writer.Write(fmt_AvgBytesPerSec);
writer.Write(fmt_BlockAlign);
writer.Write(fmt_BitsPerSample);
// Write the data chunk
writer.Write(data_ChunkID.ToCharArray());
writer.Write(data_ChunkSize);
foreach (byte dataPoint in data_ByteArray) {
writer.Write(dataPoint);
}
player = new SoundPlayer(audioStream);
}
/// <summary>
/// Call this to clean up when program is done using this sound
/// </summary>
public void Dispose() {
if (writer != null) writer.Close();
if (player != null) player.Dispose();
writer = null;
player = null;
}
/// <summary>
/// Play "ding" sound
/// </summary>
public void Play() {
if (player != null) {
player.Stream.Seek(0, SeekOrigin.Begin); // rewind stream
player.Play();
}
}
}
Hopefully this should help others who are trying to produce a simple alert sound dynamically without needing a sound file.
The following article explains how *.wav file can be generated and played using SoundPlayer. Be aware that SoundPlayer can take a stream as an argument, so you can generate wav-file contents in a MemoryStream and avoid saving to a file.
http://blogs.msdn.com/b/dawate/archive/2009/06/24/intro-to-audio-programming-part-3-synthesizing-simple-wave-audio-using-c.aspx
I tried out the code-snipped from Anachronist (2012-10) - and it is working for me.
biggest hurdle for me:
get rid of the systematic "clicking-noise" at the end of "AlertDing" wav.
This is caused by a "soft-bug" in the code snipped:
for (uint i = 0; i < numSamples - 1; i += fmt_Channels)
needs to change to
for (uint i = 0; i < numSamples; i += fmt_Channels)
if not changed, a systematic "zero" will be generated at the end of each "play", causing a sharp clicking noise. (= amplitude jumps 0->min->0)
the original question implies "without clicking noise" of course :)

Categories