I wrote a program that displays live images from a sensor. The problem is that it keeps eating up the memory.
void tmr_Tick(object sender, EventArgs e)
{
object bpic = cam.GrabFrame();
uint[] pic = cam.ColorPipe(bpic, out width, out height, out bitdepth);
byte[] header = GetHeader.BuildHeader(width, height, bitdepth);
byte[] img = GetHeader.UintImageToByte(pic, width, height, bitdepth);
MemoryStream ms = new MemoryStream();
GC.Collect();
ms.Write(header, 0, header.Length);
ms.Write(img, 0, img.Length);
pictureBox1.Image.Dispose();
pictureBox1.Image = Image.FromStream(ms);
ms.Flush();
ms.Dispose();
}
}
class GetHeader
{
#region Image
public static byte[] UintImageToByte(uint[] img, uint nWidth, uint nHeight, uint nBitsPerPixel)
{
int nBytesPerPixel = (int)nBitsPerPixel / 8;
byte[] AoB = new byte[nWidth * nHeight * nBytesPerPixel];
int j = 0;
byte[] pixel;
// Get each uint value from image and convert it to bytes
for (int i = 0; i < img.Length; i++)
{
pixel = BitConverter.GetBytes(img[i]);
// Add each pixel bytes to the image array
for (int k = nBytesPerPixel - 1; k >= 0; k--)
{
AoB[j++] = pixel[k];
}
}
byte temp;
// Swap the elements to create a true color image
for (int i = 0; i < AoB.Length / 2; i++)
{
temp = AoB[i];
AoB[i] = AoB[AoB.Length - 1 - i];
AoB[AoB.Length - 1 - i] = temp;
}
return AoB;
}
#endregion
#region Header
/* IMPORTANT!!!!!
* All numerical values are little endian!
* 4660 in decimal will be 3421 in hex not 1234!
* IMPORTANT!!!!! */
private const uint HEADER_SIZE = 54;
private const string FOUR_BYTE_ZERO = "00000000";
private const string TWO_BYTE_ZERO = "0000";
private const string BM = "424D";
public static byte[] BuildHeader(uint nWidth, uint nHeight, uint nBitsPerPixel)
{
// Substring is to get the amount of bytes needed.
// BitConverter currently returns 4 bytes and in case it will change
// the substring will take the amount of bytes it needs.
string sWidth = BitConverter.ToString(BitConverter.GetBytes(nWidth)).Replace("-", "").Substring(0, 8);
string sHeight = BitConverter.ToString(BitConverter.GetBytes(nHeight)).Replace("-", "").Substring(0, 8);
string sBitsPerPixel = BitConverter.ToString(BitConverter.GetBytes(nBitsPerPixel)).Replace("-", "").Substring(0, 4);
uint nImageSize = ((nWidth * nHeight) * (nBitsPerPixel / 8));
string sFileSize = BitConverter.ToString(BitConverter.GetBytes(nImageSize + HEADER_SIZE)).Replace("-", "").Substring(0, 8);
string sImageSize = BitConverter.ToString(BitConverter.GetBytes(nImageSize)).Replace("-", "").Substring(0, 8);
string sHeader = String.Empty;
sHeader += BM; // 0h | File tpye | 2 bytes
sHeader += sFileSize; // 2h | File size | 4 bytes
sHeader += TWO_BYTE_ZERO; // 6h | N/A | 2 bytes
sHeader += TWO_BYTE_ZERO; // 8h | N/A | 2 bytes
sHeader += "36000000"; // Ah | Data offset | 4 bytes
sHeader += "28000000"; // Eh | DIB Size | 4 bytes
sHeader += sWidth; // 12h | Width | 4 bytes
sHeader += sHeight; // 16h | Height | 4 bytes
sHeader += "0100"; // 1Ah | Color planes | 2 bytes
sHeader += sBitsPerPixel; // 1Ch | Bits per pixel | 2 bytes
sHeader += FOUR_BYTE_ZERO; // 1Eh | Compression | 4 bytes
sHeader += sImageSize; // 22h | Image size | 4 bytes
sHeader += FOUR_BYTE_ZERO; // 26h | Horizontal res | 4 bytes
sHeader += FOUR_BYTE_ZERO; // 2Ah | Vertical res | 4 bytes
sHeader += FOUR_BYTE_ZERO; // 2Eh | Color pallete | 4 bytes
sHeader += FOUR_BYTE_ZERO; // 32h | Important colors | 4 bytes
byte[] Header = StringToAoB(sHeader);
return Header;
}
private static byte[] StringToAoB(string str)
{
byte[] AoB = new byte[str.Length / 2];
for (int i = 0, j = 0; i < AoB.Length; i++, j += 2)
{
AoB[i] = byte.Parse(HexToInt("0x" + str[j] + str[j + 1]).ToString());
}
return AoB;
}
private static int HexToInt(string Hex)
{
Hex = Hex.Replace("0x", "");
return Int32.Parse(Hex, System.Globalization.NumberStyles.AllowHexSpecifier);
}
#endregion
The critical functions are tmr_Tick and UintImageToByte.
cam.GrabFrame() returns a single dimension array containing raw image data.
cam.ColorPipe() arranges it to contain uint representation of each pixel in a single dimension array.
I have no idea on where the problem seems to be, if its memory is not getting cleaned off the memory stream or maybe the interval is too fast (200 ms) for the gc to tag unused memory and clean it up.
I would greatly appreciate if anyone would take some time to look at the code and give some pointers on how it may be fixed.
I've tried using (MemoryStream ms....) block, to no avail.
The ms.Flush(), ms.Dispose() and GC.Collect() are attempts to try and get rid of the used memory, still no go.
Related
I am trying to write user configuration data (Data Page 55, 0x37) to a smart fitness device (bicycle trainer) via FE-C over Bluetooth. Although using different values or using the max values I do not notice any change in resistance. Is there a mistake processing the data? The second method (private static byte[] CreateFECUserConfiguration()) returns a byte array which will be written to the device.
private async Task WriteUserConfiguration(GattCharacteristic characteristic)
{
DataWriter writer = new DataWriter();
byte[] bytes = CreateFECUserConfiguration();
writer.WriteBytes(bytes);
var valResult = await characteristic.WriteValueAsync(writer.DetachBuffer());
if (valResult == GattCommunicationStatus.Success)
{
Debug.WriteLine("Write UserConfiguration Successful");
}
}
// create values for testing, will be provided by the user later on
private static byte[] CreateFECUserConfiguration()
{
byte[] bytes = new byte[13]; // size of message
UInt16 userWeight = (ushort)(655.34 / 0.01); // 0-655.34
byte[] userWeightBytes = BitConverter.GetBytes(userWeight);
byte bicycleWheelDiameterOffset = 10; // 0-10, 0.5 byte
UInt16 bicycleWeight = 50 * 20; // 0 – 50, * 20, 1.5 byte
// start merging bicycle wheel diameter offset and bicycle weight + putting them in the right order
byte[] tempWheelDiameterOffset = new byte[1] { bicycleWheelDiameterOffset };
BitArray bicycleWheelDiameterOffsetBits = new BitArray(tempWheelDiameterOffset);
byte[] testbicycleWeightBytes = BitConverter.GetBytes(bicycleWeight);
BitArray testBicycleWeight = new BitArray(testbicycleWeightBytes);
bool[] tempBicycleWeightPartTwo = new bool[8] { testBicycleWeight[4], testBicycleWeight[5], testBicycleWeight[6], testBicycleWeight[7], testBicycleWeight[8], testBicycleWeight[9], testBicycleWeight[10], testBicycleWeight[11] };
BitArray bicycleWeightBitsTwo = new BitArray(tempBicycleWeightPartTwo);
bool[] mergeBitsAsBools = new bool[8] { testBicycleWeight[0], testBicycleWeight[1], testBicycleWeight[2], testBicycleWeight[3], bicycleWheelDiameterOffsetBits[0], bicycleWheelDiameterOffsetBits[1], bicycleWheelDiameterOffsetBits[2], bicycleWheelDiameterOffsetBits[3] };
BitArray tempMergeWheelDiameterOffsetPlusBicycleWeight = new BitArray(mergeBitsAsBools);
byte[] wheelDiameterOffsetPlusBicycleWeight = new byte[1];
byte[] bicycleWeightByteTwo = new byte[1];
tempMergeWheelDiameterOffsetPlusBicycleWeight.CopyTo(wheelDiameterOffsetPlusBicycleWeight, 0);
bicycleWeightBitsTwo.CopyTo(bicycleWeightByteTwo, 0);
//end merging
byte bicycleWheelDiameter = (byte)(0.5 / 0.01); // 0 – 2.54m
byte gearRatio = (byte)(1 / 0.03); // 0.03 – 7.65
bytes[0] = 0xA4;
bytes[1] = 0x09; // lenght
bytes[2] = 0x4F; // message type
bytes[3] = 0x05; // channel
bytes[4] = 0x37; // Page Number 55
bytes[5] = userWeightBytes[0]; // User Weight LSB
bytes[6] = userWeightBytes[1]; // User Weight MSB
bytes[7] = 0xFF; // Reserved for future use
bytes[8] = wheelDiameterOffsetPlusBicycleWeight[0]; // Bicycle Wheel Diameter Offset 0,5 byte + Bicycle Weight LSN (probably typo in documentation -> LSB?) 0,5 byte
bytes[9] = bicycleWeightByteTwo[0]; // BicycleWeight MSB
bytes[10] = bicycleWheelDiameter; // Bicycle Wheel Diameter
bytes[11] = gearRatio; // Gear Ration
bytes[12] = ComputeChecksum(bytes); // Method to calcute checksum
return bytes;
}
Ant+ FE-C Data Page 55 (0x3)
Ant+ message format structure
In C I simply do a memcpy from my data buffer array to the address of my struct.
I am not sure how to do this in C# for the desktop side of things. This is my struct in C#
struct frame_type
{
public UInt32 start_of_frame;
public UInt32 frame_id;
public UInt16 frame_len;
public UInt32 crc;
public UInt32 end_of_frame;
}
And I have a Datareceived serial port callback, the dataIn variable below is a string but obviously I can change it something else to make it easier to grab all those bytes and assemble the frame.
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
dataIN = port.ReadExisting();
//increment variable and when i have 34 bytes assemble a frame
//and checkl if it is an ack frame.
bytes_received_count++;
if(bytes_received_count == 34)
{
//assemble a frame_type frame
}
this.Invoke(new EventHandler(sendFirmware));
}
So any suggestions are welcome.
UPDATE:
I ended up with this code after some research:
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
for (int i = 0; i < 34; i++)
{
bytes_received[i] = (byte)port.ReadByte();
}
assemble_frame_from_port_bytes();
// this.Invoke(new EventHandler(sendFirmware));
}
public void assemble_frame_from_port_bytes()
{
frame.start_of_frame = (UInt32)(bytes_received[3] << 24 | bytes_received[2] << 16 | bytes_received[1] << 8 | bytes_received[0] << 0);
frame.frame_id = (UInt32)(bytes_received[7] << 24 | bytes_received[6] << 16 | bytes_received[5] << 8 | bytes_received[4] << 0);
frame.frame_len = (UInt16)(bytes_received[9] << 8 | bytes_received[8] << 0);
int idx = 10;
for (int i = 0; i < 16; i++)
{
payload[i] = bytes_received[idx++];
}
frame.crc = (UInt32)(bytes_received[29] << 24 | bytes_received[28] << 16 | bytes_received[27] << 8 | bytes_received[26] << 0);
frame.end_of_frame = (UInt32)(bytes_received[33] << 24 | bytes_received[32] << 16 | bytes_received[31] << 8 | bytes_received[30] << 0);
}
It gets the job done, is it great? I dont know but it serves its purpose at this stage. C# surely is a great powerful tool but in the hands of a fool it is crippled haha.
In C I simply do a memcpy from my data buffer array to the address of my struct.
I suppose the following is something similar, but I'm not in a position to test it:
[StructLayout(LayoutKind.Sequential)]
struct frame_type
{
public UInt32 start_of_frame;
public UInt32 frame_id;
public UInt16 frame_len;
public UInt32 crc;
public UInt32 end_of_frame;
public static implicit operator frame_type(byte[] data)
{
unsafe
{
fixed (byte* b = &data[0])
{
return *(frame_type*)b;
}
}
}
}
It should allow you to just straight assign a byte array of length 18 to the struct
frame_type f = aByteArray18Long;
Of course, it is the root of all evil and a safe approach would perhaps look like:
static public implicit operator frame_type(byte[] b)
{
var f = new frame_type();
f.start_of_frame = (uint)(b[0]<<24 | b[1]<<16 | b[2]<<8 | b[3]);
f.frame_id = (uint)(b[4]<<24 | b[5]<<16 | b[6]<<8 | b[7]);
f.frame_len = (ushort)(b[8]<<8 | b[9]);
f.crc = (uint)(b[10]<<24 | b[11]<<16 | b[12]<<8 | b[13]);
f.end_of_frame = (uint)(b[14]<<24 | b[15]<<16 | b[16]<<8 | b[17]);
return f;
}
ps; it's perhaps easiest to get your bytes by something like:
var buf = new byte[18];
for(int x = 0; x<18; x++)
buf[x] = port.Read();
ok big caveat, I have no serial port so cannot test
MemoryStream buffer = new MemoryStream();
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) {
for (int i = 0; i < port.BytesToRead; i++) {
if (buffer.Length == 34)
break;
// write needs an array even though its only 1
byte[] bytes = new bytes[1];
bytes[0] = port.ReadByte();
buffer.Write(bytes, 0, 1);
}
if (buffer.Length == 34){
// ok we now have the next 34 bytes in buffer
var frame = new frame_type();
// rewind to the beginning
buffer.Seek(0, SeekOrigin.Begin);
using (var br = new BinaryReader(buffer)) {
frame.start_of_frame = br.ReadUInt32();
frame.frame_id = br.ReadUint32();
.......
}
// rewind to be ready for next block
buffer.Seek(0, SeekOrigin.Begin);
}
}
the core is using BinaryReader this reads serialized elements from a byte stream, ReadUInt32 pulls the next 4 bytes and marshalls to a UIn32 etc.
The fiddly bit is getting the bytes from thr port into a stream since we cant connect a stream directly to the port (I coulsnt see how to do it) so I use a MemoryStream which, as the name suggests , is a byte stream in memory
I'm using Digital Persona SDK to scan fingerprints in wsq format, for requeriment I need 512 x 512 image, the SDK only export 357 x 392 image.
The sdk provide a method to compress captured image from device in wsq format and return a byte array that I can write to disk.
-I've tried to allocate a buffer of 262144 for 512 x 512 image.
-Fill the new buffer with white pixel data each byte to value 255.
-Copy the original image buffer into the new image buffer. The original image doesn’t need to be centered but it's important to make sure to copy without corrupting the image data.
To summarize I've tried to copy the old image into the upper right corner of the new image.
DPUruNet.Compression.Start();
DPUruNet.Compression.SetWsqBitrate(95, 0);
Fid capturedImage = captureResult.Data;
//Fill the new buffer with white pixel data each byte to value 255.
byte[] bytesWSQ512 = new byte[262144];
for (int i = 0; i < bytesWSQ512.Length; i++)
{
bytesWSQ512[i] = 255;
}
//Compress capturedImage and get bytes (357 x 392)
byte[] bytesWSQ = DPUruNet.Compression.CompressRaw(capturedImage.Views[0].Width, capturedImage.Views[0].Height, 500, 8, capturedImage.Views[0].RawImage, CompressionAlgorithm.COMPRESSION_WSQ_NIST);
//Copy the original image buffer into the new image buffer
for (int i = 0; i < capturedImage.Views[0].Height; i++)
{
for (int j = 0; j < capturedImage.Views[0].Width; j++)
{
bytesWSQ512[i * bytesWSQ512.Length + j ] = bytesWSQ[i * capturedImage.Views[0].Width + j];
}
}
//Write bytes to disk
File.WriteAllBytes(#"C:\Users\Admin\Desktop\bytesWSQ512.wsq", bytesWSQ512);
DPUruNet.Compression.Finish();
When running that snippet I get IndexOutOfRangeException, I don't know if the loop or the calculation of indexes for new array are right.
Here is a representation of what I'm trying to do.
If someone is trying to achieve something like this or padding a raw image, I hope this will help.
DPUruNet.Compression.
DPUruNet.Compression.SetWsqBitrate(75, 0);
Fid ISOFid = captureResult.Data;
byte[] paddedImage = PadImage8BPP(captureResult.Data.Views[0].RawImage, captureResult.Data.Views[0].Width, captureResult.Data.Views[0].Height, 512, 512, 255);
byte[] bytesWSQ512 = Compression.CompressRaw(512, 512, 500, 8, paddedImage, CompressionAlgorithm.COMPRESSION_WSQ_NIST);
And the method to resize (pad) the image is:
public byte[] PadImage8BPP(byte[] original, int original_width, int original_height, int desired_width, int desired_height, byte pad_color)
{
byte[] canvas_8bpp = new byte[desired_width * desired_height];
for (int i = 0; i < canvas_8bpp.Length; i++)
canvas_8bpp[i] = pad_color; //Fill background. Note this type of fill will fail histogram checks.
int clamp_y_begin = 0;
int clamp_y_end = original_height;
int clamp_x_begin = 0;
int clamp_x_end = original_width;
int pad_y = 0;
int pad_x = 0;
if (original_height > desired_height)
{
int crop_distance = (int)Math.Ceiling((original_height - desired_height) / 2.0);
clamp_y_begin = crop_distance;
clamp_y_end = original_height - crop_distance;
}
else
{
pad_y = (desired_height - original_height) / 2;
}
if (original_width > desired_width)
{
int crop_distance = (int)Math.Ceiling((original_width - desired_width) / 2.0);
clamp_x_begin = crop_distance;
clamp_x_end = original_width - crop_distance;
}
else
{
pad_x = (desired_width - original_width) / 2;
}
//We traverse the captured image (either whole image or subset)
for (int y = clamp_y_begin; y < clamp_y_end; y++)
{
for (int x = clamp_x_begin; x < clamp_x_end; x++)
{
byte image_pixel = original[y * original_width + x];
canvas_8bpp[(pad_y + y - clamp_y_begin) * desired_width + pad_x + x - clamp_x_begin] = image_pixel;
}
}
return canvas_8bpp;
}
I'm trying to convert a RenderTargetBitmap to a byte array that will then get sent off to an external monochrome OLED screen. I know that for the bitmap to display correctly the bit/byte alignment should be LSB to MSB & Top to Bottom:
But I can't figure out how to get the RenderTargetBitmap's pixeldata in that format.
For the moment I've got:
RenderTargetBitmap renderTargetBitmap; //This is already set higher up
DataReader reader = DataReader.FromBuffer(await renderTargetBitmap.GetPixelsAsync());
// Placeholder for reading pixels
byte[] pixel = new byte[4]; // RGBA8
// Write out pixels
int index = 0;
byte[] array = new byte[renderTargetBitmap.PixelWidth*renderTargetBitmap.PixelHeight];
using (reader)
{
//THIS IS WHERE I THINK I'M SCREWING UP
for (int x = 0; x < rHeight; x++)
{
for (int y = 0; x < rWidth; y++)
{
reader.ReadBytes(pixel);
if (pixel[2] == 255)
array[index] = 0xff;
else
array[index] = 0x00;
index++;
}
}
}
sh1106.ShowBitmap(buffer); //Send off the byte array
I faced the same issue, this is what I did to get it working (It will convert the BGRA8 output to a 1BPP output that can then be used on a monochrome display, SSD1306 in my case)
At 1BPP output means that 8 pixels are stored in 1 byte. So you need to convert every 4 bytes into 1 bit.
public async Task Draw()
{
ActiveCanvas.UpdateLayout();
ActiveCanvas.Measure(ActiveCanvas.DesiredSize);
ActiveCanvas.Arrange(new Rect(new Point(0, 0), ActiveCanvas.DesiredSize));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap = new RenderTargetBitmap();
await renderBitmap.RenderAsync(ActiveCanvas, (int)ActiveCanvas.DesiredSize.Width, (int)ActiveCanvas.DesiredSize.Height);
DataReader bitmapStream = DataReader.FromBuffer(await renderBitmap.GetPixelsAsync());
if (_device != null)
{
byte[] pixelBuffer_1BPP = new byte[(int)(renderBitmap.PixelWidth * renderBitmap.PixelHeight) / 32];
pixelBuffer_1BPP.Initialize();
using (bitmapStream)
{
while (bitmapStream.UnconsumedBufferLength > 0)
{
uint index = (uint)(renderBitmap.PixelWidth * renderBitmap.PixelHeight * 4) - bitmapStream.UnconsumedBufferLength;
for (int bit = 0; bit < 8; bit++)
{
bitmapStream.ReadBytes(BGRA8);
byte value = (byte)(((BGRA8[0] & 0x80) | (BGRA8[1] & 0x80) | (BGRA8[2] & 0x80)) == 0x80 ? 1 : 0);
pixelBuffer_1BPP[index] |= (byte)(value << (7 - bit));
}
}
_device.DrawBitmap(0, 0, pixelBuffer_1BPP, (short)renderBitmap.PixelWidth, (short)renderBitmap.PixelHeight, Colors.White);
}
}
}
I'm currently trying to do pitch shifting of a wave file using this algorithm
https://sites.google.com/site/mikescoderama/pitch-shifting
Here my code which use the above implementation, but with no luck. The outputted wave file seems to be corrupted or not valid.
The code is quite simple, except for the pitch shift algorithm :)
It load a wave file, it reads the wave file data and put it in a
byte[] array.
Then it "normalize" bytes data into -1.0f to 1.0f format (as
requested by the creator of the pitch shift algorithm).
It applies the pitch shift algorithm and then convert back the
normalized data into a bytes[] array.
Finally saves a wave file with the same header of the original wave
file and the pitch shifted data.
Am I missing something?
static void Main(string[] args)
{
// Read the wave file data bytes
byte[] waveheader = null;
byte[] wavedata = null;
using (BinaryReader reader = new BinaryReader(File.OpenRead("sound.wav")))
{
// Read first 44 bytes (header);
waveheader= reader.ReadBytes(44);
// Read data
wavedata = reader.ReadBytes((int)reader.BaseStream.Length - 44);
}
short nChannels = BitConverter.ToInt16(waveheader, 22);
int sampleRate = BitConverter.ToInt32(waveheader, 24);
short bitRate = BitConverter.ToInt16(waveheader, 34);
// Normalized data store. Store values in the format -1.0 to 1.0
float[] in_data = new float[wavedata.Length / 2];
// Normalize wave data into -1.0 to 1.0 values
using(BinaryReader reader = new BinaryReader(new MemoryStream(wavedata)))
{
for (int i = 0; i < in_data.Length; i++)
{
if(bitRate == 16)
in_data[i] = reader.ReadInt16() / 32768f;
if (bitRate == 8)
in_data[i] = (reader.ReadByte() - 128) / 128f;
}
}
//PitchShifter.PitchShift(1f, in_data.Length, (long)1024, (long)32, sampleRate, in_data);
// Backup wave data
byte[] copydata = new byte[wavedata.Length];
Array.Copy(wavedata, copydata, wavedata.Length);
// Revert data to byte format
Array.Clear(wavedata, 0, wavedata.Length);
using (BinaryWriter writer = new BinaryWriter(new MemoryStream(wavedata)))
{
for (int i = 0; i < in_data.Length; i++)
{
if(bitRate == 16)
writer.Write((short)(in_data[i] * 32768f));
if (bitRate == 8)
writer.Write((byte)((in_data[i] * 128f) + 128));
}
}
// Compare new wavedata with copydata
if (wavedata.SequenceEqual(copydata))
{
Console.WriteLine("Data has no changes");
}
else
{
Console.WriteLine("Data has changed!");
}
// Save modified wavedata
string targetFilePath = "sound_low.wav";
if (File.Exists(targetFilePath))
File.Delete(targetFilePath);
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath)))
{
writer.Write(waveheader);
writer.Write(wavedata);
}
Console.ReadLine();
}
The algorithm here works fine
https://sites.google.com/site/mikescoderama/pitch-shifting
My mistake was on how i was reading the wave header and wave data. I post here the fully working code
WARNING: this code works only for PCM 16 bit (stereo/mono) waves. Can be easily adapted to works with PCM 8 bit.
static void Main(string[] args)
{
// Read header, data and channels as separated data
// Normalized data stores. Store values in the format -1.0 to 1.0
byte[] waveheader = null;
byte[] wavedata = null;
int sampleRate = 0;
float[] in_data_l = null;
float[] in_data_r = null;
GetWaveData("sound.wav", out waveheader, out wavedata, out sampleRate, out in_data_l, out in_data_r);
//
// Apply Pitch Shifting
//
if(in_data_l != null)
PitchShifter.PitchShift(2f, in_data_l.Length, (long)1024, (long)10, sampleRate, in_data_l);
if(in_data_r != null)
PitchShifter.PitchShift(2f, in_data_r.Length, (long)1024, (long)10, sampleRate, in_data_r);
//
// Time to save the processed data
//
// Backup wave data
byte[] copydata = new byte[wavedata.Length];
Array.Copy(wavedata, copydata, wavedata.Length);
GetWaveData(in_data_l, in_data_r, ref wavedata);
//
// Check if data actually changed
//
bool noChanges = true;
for (int i = 0; i < wavedata.Length; i++)
{
if (wavedata[i] != copydata[i])
{
noChanges = false;
Console.WriteLine("Data has changed!");
break;
}
}
if(noChanges)
Console.WriteLine("Data has no changes");
// Save modified wavedata
string targetFilePath = "sound_low.wav";
if (File.Exists(targetFilePath))
File.Delete(targetFilePath);
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath)))
{
writer.Write(waveheader);
writer.Write(wavedata);
}
Console.ReadLine();
}
// Returns left and right float arrays. 'right' will be null if sound is mono.
public static void GetWaveData(string filename, out byte[] header, out byte[] data, out int sampleRate, out float[] left, out float[] right)
{
byte[] wav = File.ReadAllBytes(filename);
// Determine if mono or stereo
int channels = wav[22]; // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels
// Get sample rate
sampleRate = BitConverter.ToInt32(wav, 24);
int pos = 12;
// Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
while(!(wav[pos]==100 && wav[pos+1]==97 && wav[pos+2]==116 && wav[pos+3]==97)) {
pos += 4;
int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
pos += 4 + chunkSize;
}
pos += 4;
int subchunk2Size = BitConverter.ToInt32(wav, pos);
pos += 4;
// Pos is now positioned to start of actual sound data.
int samples = subchunk2Size / 2; // 2 bytes per sample (16 bit sound mono)
if (channels == 2)
samples /= 2; // 4 bytes per sample (16 bit stereo)
// Allocate memory (right will be null if only mono sound)
left = new float[samples];
if (channels == 2)
right = new float[samples];
else
right = null;
header = new byte[pos];
Array.Copy(wav, header, pos);
data = new byte[subchunk2Size];
Array.Copy(wav, pos, data, 0, subchunk2Size);
// Write to float array/s:
int i=0;
while (pos < subchunk2Size)
{
left[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]);
pos += 2;
if (channels == 2)
{
right[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]);
pos += 2;
}
i++;
}
}
// Return byte data from left and right float data. Ignore right when sound is mono
public static void GetWaveData(float[] left, float[] right, ref byte[] data)
{
// Calculate k
// This value will be used to convert float to Int16
// We are not using Int16.Max to avoid peaks due to overflow conversions
float k = (float)Int16.MaxValue / left.Select(x => Math.Abs(x)).Max();
// Revert data to byte format
Array.Clear(data, 0, data.Length);
int dataLenght = left.Length;
int byteId = -1;
using (BinaryWriter writer = new BinaryWriter(new MemoryStream(data)))
{
for (int i = 0; i < dataLenght; i++)
{
byte byte1 = 0;
byte byte2 = 0;
byteId++;
NormalizedToBytes_16(left[i], k, out byte1, out byte2);
writer.Write(byte1);
writer.Write(byte2);
if (right != null)
{
byteId++;
NormalizedToBytes_16(right[i], k, out byte1, out byte2);
writer.Write(byte1);
writer.Write(byte2);
}
}
}
}
// Convert two bytes to one double in the range -1 to 1
static float BytesToNormalized_16(byte firstByte, byte secondByte)
{
// convert two bytes to one short (little endian)
short s = (short)((secondByte << 8) | firstByte);
// convert to range from -1 to (just below) 1
return s / 32678f;
}
// Convert a float value into two bytes (use k as conversion value and not Int16.MaxValue to avoid peaks)
static void NormalizedToBytes_16(float value, float k, out byte firstByte, out byte secondByte)
{
short s = (short)(value * k);
firstByte = (byte)(s & 0x00FF);
secondByte = (byte)(s >> 8);
}
sorry to revive this but I tried that pitchshifter class and, while it works, I get crackles in the audio while pitching down(0.5f). You work out a way around that?