Just a quickie..
I wrote the original version of this in VB6 about 4 years ago, and published it as freeware (might even be floating around the wild somewhere still..), and decided it was a good starter app for learning C#, when I began migrating my skills almost two years ago. So, the uncompleted version of this has been sitting in a folder on my dev box for some time, just waiting for a few tweaks - which I got around to the other day. I thought this would be a good target freeware, because unfortunately, many of us have had the misfortune of being robbed at some point in our lives, and students, young working people, or someone in an apartment may not be able to afford or install a proper alarm system.
I use a Creative 5.1 speaker rig, and with the amp cranked, the siren this sounds is at least as loud as a conventional alarm system, and would probably cause an intruder to bolt rather than attempt to find the source and disarm.
The original used the avicap32.dll library, which seemed to work fine on my older Logitech camera, but will not work on newer devices (this was tested with both an Acer notebook camera, and an MS Lifecam). At this point, I realized I had to go to the DirectShow library to capture video from a wider range of input devices. A search on CodeProject turned up the Motion Detection project by Andrew Kirillov. I removed dependencies on the AForge library, and reworked the AVI reader/writer classes, but the DirectShow wrappers and detection algorithms are his work (thanks Andrew).
The highlights
When the application loads, it tests for the WMV AVI recording codec by enumerating the installed codecs; if it isn't found, a dialog is launched, asking the user to install it. The installation executable is an embedded resource, which is pulled from the resource assembly and recreated as a file in the temp directory, then launched with the ShellExecute
API.
private void codecTest()
{
FilterCollection filters = new FilterCollection(FilterCategory.VideoCompressorCategory);
bool found = false;
foreach (Filter filter in filters)
{
found = filter.Name.Contains("Media Video 9");
if (found)
break;
}
if (!found)
{
if (MessageBox.Show("The video capture feature requires the Microsoft " +
"WMV9 codec. Do you want to install this now?",
"Codec Installation Required",
MessageBoxButtons.OKCancel,
MessageBoxIcon.Question) == DialogResult.OK)
{
executeCodec();
}
else
{
_bCanRecord = false;
}
}
}
private bool executeCodec()
{
try
{
Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(
"CamAlarm.Resources.wmv9VCMsetup.exe"))
{
if (stream != null)
{
BinaryReader br = new BinaryReader(stream);
using (FileStream fs = new FileStream(
Environment.GetEnvironmentVariable("TEMP") +
"\\wmv9VCMsetup.exe", FileMode.Create))
{
BinaryWriter bw = new BinaryWriter(fs);
byte[] bt = new byte[stream.Length];
stream.Read(bt, 0, bt.Length);
bw.Write(bt);
br.Close();
bw.Close();
fs.Close();
}
executeProgram(Environment.GetEnvironmentVariable("TEMP") +
"\\wmv9VCMsetup.exe");
}
}
return true;
}
catch { return false; }
}
A timer is run that waits for the camera to focus in, then the control panels are activated. Settings like the keycode and option control states are stored in the application's default properties. When first run, or if the keycode property is cleared, the Arm/Disarm button acts as the keycode set button. The sounds used in the application are instances of the System.Media.SoundPlayer
class, instanced and loaded with a sound file when the app initializes:
private void loadWarning()
{
if (_cWarningSound != null)
_cWarningSound.Dispose();
_cWarningSound = new SoundPlayer(CamAlarm.Resource1.beep);
_cWarningSound.Load();
}
The camera's motion trigger-state is polled periodically using a custom timer class; if motion has occurred, an alarm timer is fired, which will loop until the alarm is deactivated, or the max alarm timeout occurs:
private void _cAlarmTimer_Tick(object sender)
{
if (_eAlarmState == AlarmState.Arming)
{
_iTimerTick++;
if (_iTimerTick > stringToInt(txtArmingDelay.Text))
armAlarm();
else
updateStatus();
}
else if (_eAlarmState == AlarmState.Armed)
{
if (_iActiveTick > 5)
triggerAlarm();
}
else if (_eAlarmState == AlarmState.Sounding)
{
_iTimerTick++;
if (_iTimerTick > stringToInt(txtDuration.Text))
{
if (_iActiveTick < 5)
disarmAlarm();
else
soundAlarm();
}
else
{
soundAlarm();
}
}
else if (_eAlarmState == AlarmState.Standby)
{
stopAlarm();
}
else if (_eAlarmState == AlarmState.Triggered)
{
_iTimerTick++;
if (_iTimerTick > stringToInt(txtAlarmDelay.Text))
{
soundAlarm();
}
else
{
if (!chkSilent.Checked)
playWarning();
}
}
}
When the alarm is in Armed
mode, all but the keypad and disarm switch are disabled. This is done by disabling the GroupBox
that houses the controls. The main form close button also needs to be deactivated. This is done both by cancelling the form's exit in the FormClosing
event, and by disabling the Close button using the GetSystemMenu
/EnableMenuItem
API:
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (_bCloseDisabled)
{
e.Cancel = true;
}
else
{
closeCamera();
closeFile();
saveSettings();
if (_cAlarmTimer != null)
_cAlarmTimer.Dispose();
}
}
private void enableClose(bool enable)
{
if (enable)
EnableMenuItem(GetSystemMenu(this.Handle, false), SC_CLOSE, MF_ENABLED);
else
EnableMenuItem(GetSystemMenu(this.Handle, false), SC_CLOSE, MF_GRAYED);
_bCloseDisabled = !enable;
}
All in all, fairly simple approach; though I wasn't all that generous with comments in code, it is fairly straightforward..
Using the application..
Keep the camera view up where pets won't trigger it, and away from windows. Crank the volume on your speakers, and adjust application volumes through the mixer; CamAlarm has an option to push to the maximum volume when the alarm is activated, but this won't mean much if the master volume is down low.
Well.. enjoy! If you find any bugs, be sure to leave a message.
Change log
May 17, 2010
- First time loading of help page creates page in user app folder.
- Alarm volume raised after triggering warning beeps.
- Fixed lock when capturing video, changed from
Monitor.Enter
to TryEnter
in Camera.cs. - Added tooltip to Clear and Video buttons.
- Added menu buttons to disabled items when alarm is activated.
June 16, 2010
- Fixed codec URL in
executeCodec()
. - Fixed disabled OK button in device selection form.