|
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows.Forms;
namespace SteganoTape {
public class WaveUtility {
/// <summary>Called by FindBeeps for every Beep detected</summary>
public event BeepFoundHandler BeepFound;
private WaveSound waveSound;
private const int beepLength = 32; // 1/32 second
public WaveSound WaveSound
{
get { return waveSound; }
}
public WaveUtility(WaveSound waveSound)
{
this.waveSound = waveSound;
}
public WaveUtility(String fileName)
{
this.waveSound = ReadFile(fileName);
}
/// <summary>Creates sine sound of a specific frequency</summary>
/// <param name="frequencyHz">Frequency in Hertz</param>
/// <param name="volumne">Amplitude</param>
/// <returns></returns>
private WaveSound CreateBeep(int frequencyHz, int volumne)
{
// samples for 1/beepLength seconds
short[] samples = new short[waveSound.Format.SamplesPerSec / beepLength];
double xValue = 0;
short yValue;
double xStep = (2 * Math.PI) / waveSound.Format.SamplesPerSec; // 1 Hz
xStep = xStep * frequencyHz;
for (int n = 0; n < samples.Length; n++) {
xValue += xStep;
yValue = (short)(Math.Sin(xValue) * volumne);
samples[n] = yValue;
}
WaveSound beep = new WaveSound(waveSound.Format, samples);
return beep;
}
/// <summary>Read a wave file</summary>
/// <param name="fileName"></param>
/// <returns></returns>
private WaveSound ReadFile(String fileName)
{
BinaryReader reader = new BinaryReader(new FileStream(fileName, FileMode.Open));
WaveFormat format = ReadHeader(reader);
int dataLength = reader.ReadInt32();
//Test
dataLength = (int)(reader.BaseStream.Length - reader.BaseStream.Position);
int maxSampleValue = 0;
int bytesPerSample = format.BitsPerSample / 8;
int countSamples = dataLength / bytesPerSample;
sbyte[] channelSamples8 = new sbyte[countSamples];
short[] channelSamples16 = new short[countSamples];
int channelSamplesIndex = 0;
int absValue;
for (int sampleIndex = 0; sampleIndex < countSamples; sampleIndex++) {
if (format.BitsPerSample == 8) {
channelSamples8[channelSamplesIndex] = reader.ReadSByte();
if (Math.Abs((int)channelSamples8[channelSamplesIndex]) > maxSampleValue) {
maxSampleValue = Math.Abs((int)channelSamples8[channelSamplesIndex]);
}
} else {
channelSamples16[channelSamplesIndex] = reader.ReadInt16();
absValue = Math.Abs((int)channelSamples16[channelSamplesIndex]);
if (absValue > maxSampleValue) {
maxSampleValue = (absValue > short.MaxValue) ? absValue - 1 : absValue;
}
}
channelSamplesIndex++;
}
reader.Close();
if (format.BitsPerSample == 8) {
for (int n = 0; n < channelSamples8.Length; n++) {
channelSamples16[n] = (short)channelSamples8[n];
}
}
return new WaveSound(format, channelSamples16);
}
/// <summary>Read a chunk of four bytes from a wave file</summary>
/// <param name="reader"></param>
/// <returns></returns>
private string ReadChunk(BinaryReader reader)
{
byte[] ch = new byte[4];
reader.Read(ch, 0, ch.Length);
return System.Text.Encoding.ASCII.GetString(ch);
}
/// <summary>
/// Read the header from a wave file, and move the
/// reader's position to the beginning of the data chunk
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private WaveFormat ReadHeader(BinaryReader reader)
{
WaveFormat format = new WaveFormat();
if (ReadChunk(reader) != "RIFF") {
throw new Exception("Invalid file format");
}
reader.BaseStream.Seek(4, SeekOrigin.Current); // .ReadInt32();
if (ReadChunk(reader) != "WAVE") {
throw new ArgumentException("Kein WAVE-Stream", "reader");
}
if (ReadChunk(reader) != "fmt ") {
throw new ArgumentException("Format-Chunk nicht gefunden", "reader");
}
int len = reader.ReadInt32();
if (len < 16) {
throw new ArgumentException("Format-Chunk hat eine ung�ltige L�nge", "reader");
}
format.FormatTag = reader.ReadInt16();
format.Channels = reader.ReadInt16();
format.SamplesPerSec = reader.ReadInt32();
format.AvgBytesPerSec = reader.ReadInt32();
format.BlockAlign = reader.ReadInt16();
format.BitsPerSample = reader.ReadInt16();
// go to beginning of wave samples
while (reader.BaseStream.Position < reader.BaseStream.Length && ReadChunk(reader) != "data")
;
return format;
}
/// <summary>Find anything but silence in the sound</summary>
/// <remarks>Raises the BeepFound event everytime a sound is detected between two blocks of silence</remarks>
/// <param name="tolerance">
/// Sample values greater than [tolerance] are sound,
/// sample values less than [tolerance] are silence
/// </param>
public void FindAnything(short tolerance) {
//size of scan window in samples
int scanWindowSize = waveSound.Format.SamplesPerSec / beepLength;
//size of scan window in seconds
float scanWindowSizeSeconds = (float)scanWindowSize / (float)waveSound.Format.SamplesPerSec;
int startIndex = -1;
int endIndex = -1;
int countSilentSamples = 0;
for (int n = 0; n < waveSound.Count; n++) {
if (Math.Abs(WaveSound[n]) > tolerance) { //found a sound
countSilentSamples = 0;
if(startIndex < 0){
startIndex = n;
}
} else if (startIndex > -1) { //searched and found silence
countSilentSamples++;
if (countSilentSamples == scanWindowSize) {
endIndex = n - scanWindowSize;
NotifyOnBeep(startIndex, endIndex, scanWindowSizeSeconds);
//scan next time window
countSilentSamples = 0;
startIndex = -1;
}
}
}
if (startIndex > -1) { //wave ends with a beep
NotifyOnBeep(startIndex, waveSound.Count-1, scanWindowSizeSeconds);
}
}
/// <summary>Raise the BeepFound event</summary>
/// <param name="startIndex">Index of the sound's first sample</param>
/// <param name="endIndex">Index of the sound's last sample</param>
/// <param name="silentSeconds"></param>
private void NotifyOnBeep(int startIndex, int endIndex, float silentSeconds) {
if (BeepFound != null) {
//get the second in the wave at which the sound stops
float second = (float)endIndex / (float)waveSound.Format.SamplesPerSec;
//notify
BeepFound(this, new BeepFoundEventArgs(
new Beep(startIndex, endIndex,
second - silentSeconds, second)));
}
}
/// <summary>Replaces a part of the sound with a beep</summary>
/// <param name="insertAtSecond">Where to put the beep</param>
/// <param name="frequencyHz">Frequency of the beep in Hertz</param>
/// <param name="volumne">Maximum sample value of the beep</param>
public void InsertBeep(float insertAtSecond, int frequencyHz, int volumne)
{
short[] beepWave = CreateBeep(frequencyHz, volumne).Samples;
int insertAtSample = (int)(waveSound.Format.SamplesPerSec * insertAtSecond);
int longWaveIndex = insertAtSample;
for (int index = 0; index < beepWave.Length; index++) {
waveSound[longWaveIndex] = beepWave[index];
longWaveIndex++;
}
}
/// <summary>Get the minimum duration a sound must have in order to hide [message]</summary>
/// <param name="message"></param>
/// <returns></returns>
public long CountRequiredSeconds(Stream message)
{
message.Position = 0;
long countSeconds = 0;
int messageByte;
byte highHalfByte;
byte lowHalfByte;
while ((messageByte = message.ReadByte()) > -1) {
highHalfByte = (byte)(messageByte >> 4);
lowHalfByte = (byte)(messageByte - (highHalfByte << 4));
//intervals of 0 seconds are not possible -> add 1 to all intervals
countSeconds += highHalfByte + 1;
countSeconds += lowHalfByte + 1;
}
return countSeconds;
}
/// <summary>Split the bytes of a message into four-bit-blocks</summary>
/// <param name="message">Stream containing the message</param>
/// <returns>Stream containing the same message with two bytes per original byte</returns>
private Stream PrepareMessage(Stream message)
{
message.Position = 0;
MemoryStream preparedMessage = new MemoryStream();
int messageByte;
int highHalfByte;
int lowHalfByte;
while ((messageByte = message.ReadByte()) > -1) {
//split into high and low part
highHalfByte = (messageByte >> 4);
lowHalfByte = (messageByte - (highHalfByte << 4));
//intervals of 0 seconds are not possible -> add 1 to all intervals
preparedMessage.WriteByte((byte)(highHalfByte + 1));
preparedMessage.WriteByte((byte)(lowHalfByte + 1));
}
preparedMessage.Position = 0;
return preparedMessage;
}
/// <summary>Hide a message in the wave</summary>
/// <param name="message">Stream containing the message</param>
/// <param name="frequencyHz">Frequency of the beeps, which will be inserted into the sound</param>
/// <param name="volumne">Maximum sample value of the beeps</param>
public void Hide(Stream message, int frequencyHz, int volumne)
{
Stream preparedMessage = PrepareMessage(message);
int messageByte;
int offset = 0;
while ((messageByte = preparedMessage.ReadByte()) > -1) {
offset += messageByte;
InsertBeep(offset, frequencyHz, volumne);
}
}
/// <summary>Read a message from a series of seconds</summary>
/// <param name="seconds">Seconds at which beeps have been found in the sound</param>
/// <returns>Stream containing the decoded message</returns>
public Stream Extract(Collection<float> seconds)
{
MemoryStream message = new MemoryStream();
byte interval;
int second;
int previousSecond = 0;
byte messageByte = 0;
bool isLowPart = false;
foreach(float floatSecond in seconds) {
second = (int)Math.Round(floatSecond);
try {
interval = (byte)(second - previousSecond);
if (interval > 16) { interval = 16; }
} catch (OverflowException ex) {
Console.WriteLine(ex);
interval = 16; //highest possible value for a half byte + 1
}
if (isLowPart) {
messageByte += (byte)(interval - 1);
message.WriteByte(messageByte);
}else{
messageByte = (byte)((interval-1) << 4);
}
isLowPart = !isLowPart;
previousSecond = second;
}
message.Position = 0;
return message;
}
/// <summary>Let "Sound Exchange" convert the sound</summary>
/// <param name="soxPath">Path and Name of sox.exe</param>
/// <param name="soxArguments">Arguments for sox.exe</param>
private void RunSox(String soxPath, String soxArguments)
{
if (Application.OpenForms.Count > 0) { //there is a visible form
Application.OpenForms[0].Cursor = Cursors.WaitCursor;
}
try {
ProcessStartInfo startInfo = new ProcessStartInfo(soxPath, soxArguments);
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
Process sox = Process.Start(startInfo);
StreamReader errorReader = sox.StandardError;
String errors = errorReader.ReadLine();
if (errors != null) {
throw new ApplicationException("sox failed: " + errors);
}
sox.WaitForExit(10000);
} finally {
if (Application.OpenForms.Count > 0) { //reset cursor for the visible form
Application.OpenForms[0].Cursor = Cursors.Default;
}
}
}
/// <summary>Convert an 8 bit sound to 16 bit.</summary>
/// <param name="tempDirectory">Path of the directory for temporary files</param>
/// <param name="soxPath">Path and Name of sox.exe</param>
public void ConvertToDefaultFormat(String tempDirectory, String soxPath)
{
String inFileName = Path.Combine(tempDirectory, "in.wav");
String outFileName = Path.Combine(tempDirectory, "out.wav");
int fileLength = this.WaveSound.SaveToFile(inFileName);
String soxArguments = String.Format(
"-t wav \"{0}\" -t .wav -c 1 -s -w \"{1}\"",
inFileName,
outFileName);
RunSox(soxPath, soxArguments);
this.waveSound = ReadFile(outFileName);
}
/// <summary>Let "Sound Exchange" perform a band pass filter on the sound</summary>
/// <param name="tempDirectory">Path of the directory for temporary files</param>
/// <param name="soxPath">Path and Name of sox.exe</param>
/// <param name="frequency">Frequency that may pass the filter</param>
/// <returns>Path of the output file</returns>
public String FindFrequency(String tempDirectory, String soxPath, int frequency)
{
String inFileName = Path.Combine(tempDirectory, "in.wav");
String outFileName = Path.Combine(tempDirectory, "out.wav");
int fileLength = this.WaveSound.SaveToFile(inFileName);
String soxArguments = String.Format(
"-t wav \"{0}\" -t .wav -c 1 -s -w \"{1}\" band {2} 10",
inFileName,
outFileName,
frequency);
RunSox(soxPath, soxArguments);
return outFileName;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.