Make webcam handling more stable (fix possible NPE)

This commit is contained in:
Flamefire committed 2014-04-30 22:35:31 +02:00
1 parent c31050926d
commit 63c681a057
6 files changed
+293 -175

No files matched your search

+28 -16
View File
@@ -49,58 +49,70 @@ namespace Vocaluxe.Base
return true;
}
public static void Close()
{
if (_Webcam == null)
return;
_Webcam.Close();
_Webcam = null;
}
public static bool GetFrame(ref CTexture tex)
{
return _Webcam.GetFrame(ref tex);
return _Webcam != null && _Webcam.GetFrame(ref tex);
}
public static Bitmap GetBitmap()
{
return _Webcam.GetBitmap();
}
public static void Close()
{
if (_Webcam != null)
{
_Webcam.Close();
_Webcam = null;
}
return _Webcam == null ? null : _Webcam.GetBitmap();
}
public static void Pause()
{
if (_Webcam == null)
return;
_Webcam.Pause();
}
public static void Start()
{
if (_Webcam == null)
return;
_Webcam.Start();
}
public static void Stop()
{
if (_Webcam == null)
return;
_Webcam.Stop();
}
public static void Select(SWebcamConfig c)
public static bool Select(SWebcamConfig c)
{
_Webcam.Select(c);
return _Webcam != null && _Webcam.Select(c);
}
public static void DeSelect()
{
if (_Webcam == null)
return;
_Webcam.DeSelect();
}
public static SWebcamDevice[] GetDevices()
{
return _Webcam.GetDevices();
return _Webcam == null ? null : _Webcam.GetDevices();
}
public static bool IsDeviceAvailable()
{
return _Webcam.IsDeviceAvailable();
return (_Webcam != null) && _Webcam.IsDeviceAvailable();
}
public static bool IsCapturing()
{
return _Webcam.IsCapturing();
return (_Webcam != null) && _Webcam.IsCapturing();
}
}
}
+174 -92
View File
@@ -15,6 +15,7 @@
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using AForge.Video;
using AForge.Video.DirectShow;
using System.Collections.Generic;
@@ -31,60 +32,27 @@ namespace Vocaluxe.Lib.Webcam
private readonly List<SWebcamDevice> _Devices = new List<SWebcamDevice>();
private bool _Paused;
private VideoCaptureDevice _Webcam;
private FilterInfoCollection _WebcamDevices;
private SWebcamConfig _Config;
private byte[] _Data = new byte[1];
private byte[] _Data;
private static readonly object _MutexData = new object();
private int _Width, _Height;
public void Close()
{
if (_Webcam == null)
return;
_Webcam.SignalToStop();
_Webcam.WaitForStop();
_Webcam.NewFrame -= _OnFrame;
_Data = new byte[1];
}
public bool GetFrame(ref CTexture frame)
{
lock (_MutexData)
{
if (_Webcam != null && _Width > 0 && _Height > 0 && _Data.Length == _Width * _Height * 4)
CDraw.UpdateOrAddTexture(ref frame, _Width, _Height, _Data);
}
return false;
}
public Bitmap GetBitmap()
{
if (_Webcam != null && _Width > 0 && _Height > 0 && _Data.Length == _Height * _Width * 4)
{
lock (_MutexData)
{
var bmp = new Bitmap(_Width, _Height);
BitmapData bitmapdata = bmp.LockBits(new Rectangle(0, 0, _Width, _Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_Data, 0, bitmapdata.Scan0, _Data.Length);
bmp.UnlockBits(bitmapdata);
return bmp;
}
}
return null;
}
private bool _NewFrameAvailable;
private bool _IsCapturing;
public bool Init()
{
_WebcamDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
foreach (FilterInfo info in _WebcamDevices)
FilterInfoCollection webcams = new FilterInfoCollection(FilterCategory.VideoInputDevice);
foreach (FilterInfo info in webcams)
{
var tmpdev = new VideoCaptureDevice(info.MonikerString);
if (tmpdev.VideoCapabilities.Length == 0)
continue;
var device = new SWebcamDevice
{
Name = info.Name,
MonikerString = info.MonikerString,
Capabilities = new List<SCapabilities>()
Capabilities = new List<SCapabilities>(tmpdev.VideoCapabilities.Length)
};
var tmpdev = new VideoCaptureDevice(info.MonikerString);
foreach (VideoCapabilities capabilities in tmpdev.VideoCapabilities)
{
@@ -101,44 +69,188 @@ namespace Vocaluxe.Lib.Webcam
return true;
}
private void _OnFrame(object sender, NewFrameEventArgs e)
public void Close()
{
if (_Paused)
DeSelect();
_Devices.Clear();
}
public void DeSelect()
{
if (_Webcam == null)
return;
_Webcam.Stop();
_Webcam.NewFrame -= _OnFrame;
_IsCapturing = false;
lock (_MutexData)
{
if (_Width != e.Frame.Width || _Height != e.Frame.Height || _Data.Length != e.Frame.Width * e.Frame.Height * 4)
_Data = new byte[4 * e.Frame.Width * e.Frame.Height];
_Width = e.Frame.Width;
_Height = e.Frame.Height;
BitmapData bitmapdata = e.Frame.LockBits(new Rectangle(0, 0, _Width, _Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(bitmapdata.Scan0, _Data, 0, _Data.Length);
e.Frame.UnlockBits(bitmapdata);
_Data = null;
_Webcam = null;
}
}
public void Pause()
#region ConfigSelection
private static float _GetScore(int value, int valueRequested)
{
if (_Webcam != null)
_Paused = true;
if (valueRequested <= 0)
return 1;
return 1.0f - (float)Math.Abs(value - valueRequested) / Math.Max(value, valueRequested);
}
private static VideoCapabilities _SelectWebcamConfig(VideoCapabilities[] capabilities, SWebcamConfig config)
{
int configTaken = 0;
if (config.Framerate != 0 && config.Height != 0 && config.Width != 0)
{
float bestMatchScore = 0;
for (int i = 0; i < capabilities.Length; i++)
{
float score = _GetScore(capabilities[i].AverageFrameRate, config.Framerate);
score += _GetScore(capabilities[i].FrameSize.Height, config.Height);
score += _GetScore(capabilities[i].FrameSize.Width, config.Width);
if (score >= bestMatchScore)
{
// Take the config with the best score, Higher indizes should indicate a better resolution
// so choose the last one with best score if 2 have equal scores
bestMatchScore = score;
configTaken = i;
}
}
}
return capabilities[configTaken];
}
#endregion
public bool Select(SWebcamConfig config)
{
//Close old camera connection
DeSelect();
if (_Devices.Count < 1)
return false;
string moniker = _Devices[0].MonikerString;
if (config.MonikerString != "")
{
foreach (SWebcamDevice device in _Devices)
{
if (device.MonikerString == config.MonikerString)
{
moniker = device.MonikerString;
break;
}
}
}
_Webcam = new VideoCaptureDevice(moniker);
_Webcam.VideoResolution = _SelectWebcamConfig(_Webcam.VideoCapabilities, config);
_Config.Framerate = _Webcam.VideoResolution.AverageFrameRate;
_Config.Height = _Webcam.VideoResolution.FrameSize.Height;
_Config.Width = _Webcam.VideoResolution.FrameSize.Width;
_Config.MonikerString = _Webcam.Source;
return true;
}
public void Start()
{
if (_Webcam == null)
return;
_Webcam.NewFrame -= _OnFrame;
//Subscribe to NewFrame event
_Webcam.NewFrame += _OnFrame;
_Webcam.Start();
_Paused = false;
if (_IsCapturing)
{
if (_Paused)
{
_Webcam.NewFrame += _OnFrame;
_Paused = false;
}
}
else
{
//Subscribe to NewFrame event
_Webcam.NewFrame += _OnFrame;
_Paused = false;
_Webcam.Start();
_IsCapturing = true;
}
}
public void Pause()
{
if (_Webcam != null && !_Paused)
{
_Webcam.NewFrame -= _OnFrame;
lock (_MutexData) //Make sure all events are finished or not yet startet for consistency
{
_Paused = true;
}
}
}
public void Stop()
{
if (_Webcam != null)
{
_Webcam.SignalToStop();
_Webcam.NewFrame -= _OnFrame;
lock (_MutexData) //Make sure all events are finished or not yet startet for consistency
{
_IsCapturing = false;
}
}
}
public bool GetFrame(ref CTexture frame)
{
lock (_MutexData)
{
if (_Data != null && _Data.Length == _Width * _Height * 4 && _NewFrameAvailable)
{
CDraw.UpdateOrAddTexture(ref frame, _Width, _Height, _Data);
_NewFrameAvailable = false;
return true;
}
return false;
}
}
public Bitmap GetBitmap()
{
lock (_MutexData)
{
if (_Data != null && _Data.Length == _Width * _Height * 4)
{
var bmp = new Bitmap(_Width, _Height);
BitmapData bitmapdata = bmp.LockBits(new Rectangle(0, 0, _Width, _Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_Data, 0, bitmapdata.Scan0, _Data.Length);
bmp.UnlockBits(bitmapdata);
return bmp;
}
return null;
}
}
private void _OnFrame(object sender, NewFrameEventArgs e)
{
if (_Paused)
return;
if (e.Frame.Width == 0 || e.Frame.Height == 0)
return;
lock (_MutexData)
{
if (!IsCapturing())
return;
if (_Data == null || _Data.Length != e.Frame.Width * e.Frame.Height * 4)
_Data = new byte[e.Frame.Width * e.Frame.Height * 4];
_Width = e.Frame.Width;
_Height = e.Frame.Height;
BitmapData bitmapdata = e.Frame.LockBits(new Rectangle(0, 0, _Width, _Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(bitmapdata.Scan0, _Data, 0, _Data.Length);
e.Frame.UnlockBits(bitmapdata);
_NewFrameAvailable = true;
}
}
public SWebcamDevice[] GetDevices()
@@ -148,42 +260,12 @@ namespace Vocaluxe.Lib.Webcam
public bool IsDeviceAvailable()
{
return _Devices.Count > 0;
return _Webcam != null;
}
public bool IsCapturing()
{
return _Webcam.IsRunning;
}
public bool Select(SWebcamConfig config)
{
//Close old camera connection
Close();
if (_WebcamDevices == null)
return false;
if (_WebcamDevices.Count < 1)
return false;
//No MonikerString found, try first webcam
_Webcam = config.MonikerString == "" ? new VideoCaptureDevice(_WebcamDevices[0].MonikerString) : new VideoCaptureDevice(config.MonikerString);
if (_Webcam == null)
return false;
if (config.Framerate != 0 && config.Height != 0 && config.Width != 0)
{
//_Webcam.DesiredFrameRate = config.Framerate;
//_Webcam.DesiredFrameSize = new Size(config.Width, config.Height);
}
_Config.Framerate = _Webcam.SnapshotResolution.AverageFrameRate;
_Config.Height = _Webcam.SnapshotResolution.FrameSize.Height;
_Config.Width = _Webcam.SnapshotResolution.FrameSize.Width;
_Config.MonikerString = _Webcam.Source;
return true;
return _IsCapturing && !_Paused;
}
public SWebcamConfig GetConfig()
+15 -1
View File
@@ -46,19 +46,33 @@ namespace Vocaluxe.Lib.Webcam
interface IWebcam
{
bool Init();
void Close();
void Start();
void Pause();
void Stop();
void Close();
/// <summary>
/// Gets last captured frame. Returns true if frame was updated<br />
/// Note that this will invalidate the last frame and not return it again with this function
/// </summary>
/// <param name="frame"></param>
/// <returns></returns>
bool GetFrame(ref CTexture frame);
/// <summary>
/// Gets the last captured frame as a bitma
/// </summary>
/// <param name="frame"></param>
/// <returns>Null if no frame was captured, the bitmap otherwise</returns>
Bitmap GetBitmap();
SWebcamConfig GetConfig();
SWebcamDevice[] GetDevices();
bool IsDeviceAvailable();
bool IsCapturing();
bool Select(SWebcamConfig webcamConfig);
void DeSelect();
}
}
+30 -38
View File
@@ -154,8 +154,8 @@ namespace Vocaluxe.Screens
public override bool UpdateGame()
{
CWebcam.GetFrame(ref _WebcamTexture);
_Statics[_StaticWebcamOutput].Texture = _WebcamTexture;
if (CWebcam.GetFrame(ref _WebcamTexture))
_Statics[_StaticWebcamOutput].Texture = _WebcamTexture;
_SelectSlides[_SelectSlideVideosToBackground].Selection = (int)CConfig.VideosToBackground;
return true;
}
@@ -170,7 +170,7 @@ namespace Vocaluxe.Screens
public override void OnClose()
{
base.OnClose();
CWebcam.Close();
CWebcam.Stop();
}
public override void OnShow()
@@ -189,25 +189,20 @@ namespace Vocaluxe.Screens
{
if (devices != null && devices.Length > 0)
{
_DeviceNr = 0;
_CapabilityNr = 0;
_GetFirstConfiguredWebcamDevice(ref _DeviceNr, ref _CapabilityNr);
foreach (SWebcamDevice d in devices)
_SelectSlides[_SelectSlideWebcamDevices].AddValue(d.Name);
_SelectSlides[_SelectSlideWebcamDevices].Selection = _DeviceNr;
foreach (SCapabilities c in devices[_DeviceNr].Capabilities)
_SelectSlides[_SelectSlideWebcamCapabilities].AddValue(c.Width + " x " + c.Height + " @ " + c.Framerate + " FPS ");
_Config.MonikerString = devices[_SelectSlides[_SelectSlideWebcamDevices].Selection].MonikerString;
_Config.Width = devices[_SelectSlides[_SelectSlideWebcamDevices].Selection].Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Width;
_Config.Height = devices[_SelectSlides[_SelectSlideWebcamDevices].Selection].Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Height;
_Config.Framerate =
devices[_SelectSlides[_SelectSlideWebcamDevices].Selection].Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Framerate;
CWebcam.Close();
CWebcam.Select(CConfig.WebcamConfig);
CWebcam.Start();
ssVisible = true;
int devNr;
int capNr;
if (_GetFirstConfiguredWebcamDevice(out devNr, out capNr))
{
_SelectSlides[_SelectSlideWebcamDevices].Selection = devNr;
_OnDeviceEvent();
_SelectSlides[_SelectSlideWebcamCapabilities].Selection = capNr;
_OnCapabilitiesEvent();
ssVisible = true;
}
}
}
catch (Exception e)
@@ -239,7 +234,6 @@ namespace Vocaluxe.Screens
{
_SelectSlides[_SelectSlideWebcamCapabilities].Clear();
_DeviceNr = _SelectSlides[_SelectSlideWebcamDevices].Selection;
_CapabilityNr = 0;
SWebcamDevice d = CWebcam.GetDevices()[_DeviceNr];
for (int i = 0; i < d.Capabilities.Count; i++)
@@ -247,16 +241,8 @@ namespace Vocaluxe.Screens
_SelectSlides[_SelectSlideWebcamCapabilities].AddValue(d.Capabilities[i].Width + " x " + d.Capabilities[i].Height +
" @ " + d.Capabilities[i].Framerate + "FPS");
}
_SelectSlides[_SelectSlideWebcamCapabilities].Selection = 0;
_Config.MonikerString = d.MonikerString;
_Config.Width = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Width;
_Config.Height = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Height;
_Config.Framerate = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Framerate;
CWebcam.Close();
CWebcam.Select(_Config);
CWebcam.Start();
_CapabilityNr = -1;
_OnCapabilitiesEvent();
}
}
@@ -266,24 +252,27 @@ namespace Vocaluxe.Screens
{
_CapabilityNr = _SelectSlides[_SelectSlideWebcamCapabilities].Selection;
SWebcamDevice d = CWebcam.GetDevices()[_SelectSlides[_SelectSlideWebcamDevices].Selection];
SWebcamDevice d = CWebcam.GetDevices()[_DeviceNr];
_Config.MonikerString = d.MonikerString;
_Config.Width = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Width;
_Config.Height = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Height;
_Config.Framerate = d.Capabilities[_SelectSlides[_SelectSlideWebcamCapabilities].Selection].Framerate;
_Config.Width = d.Capabilities[_CapabilityNr].Width;
_Config.Height = d.Capabilities[_CapabilityNr].Height;
_Config.Framerate = d.Capabilities[_CapabilityNr].Framerate;
CWebcam.Close();
CWebcam.Select(_Config);
CWebcam.Start();
}
}
private void _GetFirstConfiguredWebcamDevice(ref int device, ref int capabilities)
private bool _GetFirstConfiguredWebcamDevice(out int device, out int capabilities)
{
SWebcamDevice[] devices = CWebcam.GetDevices();
if (devices == null)
return;
{
device = -1;
capabilities = -1;
return false;
}
for (int i = 0; i < devices.Length; i++)
{
@@ -296,10 +285,13 @@ namespace Vocaluxe.Screens
{
device = i;
capabilities = j;
return;
return true;
}
}
}
device = 0;
capabilities = 0;
return true;
}
}
}
+44 -26
View File
@@ -141,13 +141,13 @@ namespace Vocaluxe.Screens
}
else if (_Buttons[_ButtonDelete].Selected)
_DeleteProfile();
else if (_Buttons[_ButtonWebcam].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonWebcam].Selected)
_OnWebcam();
else if (_Buttons[_ButtonSaveSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonSaveSnapshot].Selected)
_OnSaveSnapshot();
else if (_Buttons[_ButtonDiscardSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonDiscardSnapshot].Selected)
_OnDiscardSnapshot();
else if (_Buttons[_ButtonTakeSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonTakeSnapshot].Selected)
_OnTakeSnapshot();
break;
@@ -235,13 +235,13 @@ namespace Vocaluxe.Screens
CProfiles.SetActive(_SelectSlides[_SelectSlideProfiles].ValueIndex,
(EOffOn)_SelectSlides[_SelectSlideActive].Selection);
}
else if (_Buttons[_ButtonWebcam].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonWebcam].Selected)
_OnWebcam();
else if (_Buttons[_ButtonSaveSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonSaveSnapshot].Selected)
_OnSaveSnapshot();
else if (_Buttons[_ButtonDiscardSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonDiscardSnapshot].Selected)
_OnDiscardSnapshot();
else if (_Buttons[_ButtonTakeSnapshot].Selected && CWebcam.IsDeviceAvailable())
else if (_Buttons[_ButtonTakeSnapshot].Selected)
_OnTakeSnapshot();
}
@@ -270,15 +270,16 @@ namespace Vocaluxe.Screens
int avatarID = CProfiles.GetAvatarID(_SelectSlides[_SelectSlideProfiles].ValueIndex);
_SelectSlides[_SelectSlideAvatars].SetSelectionByValueIndex(avatarID);
if (CWebcam.IsDeviceAvailable() && CWebcam.IsCapturing())
if (_Snapshot == null)
{
if (_Snapshot == null)
CWebcam.GetFrame(ref _WebcamTexture);
_Statics[_StaticAvatar].Texture = _WebcamTexture;
if (CWebcam.IsCapturing())
{
if (CWebcam.GetFrame(ref _WebcamTexture))
_Statics[_StaticAvatar].Texture = _WebcamTexture;
}
else
_Statics[_StaticAvatar].Texture = CProfiles.GetAvatarTexture(avatarID);
}
else
_Statics[_StaticAvatar].Texture = CProfiles.GetAvatarTexture(avatarID);
}
return true;
@@ -310,22 +311,36 @@ namespace Vocaluxe.Screens
private void _OnTakeSnapshot()
{
_Buttons[_ButtonSaveSnapshot].Visible = true;
_Buttons[_ButtonDiscardSnapshot].Visible = true;
_Buttons[_ButtonWebcam].Visible = false;
_Buttons[_ButtonTakeSnapshot].Visible = false;
_Snapshot = CWebcam.GetBitmap();
if (!CWebcam.IsDeviceAvailable())
{
CDraw.RemoveTexture(ref _WebcamTexture);
_Snapshot = null;
_Buttons[_ButtonSaveSnapshot].Visible = false;
_Buttons[_ButtonDiscardSnapshot].Visible = false;
_Buttons[_ButtonTakeSnapshot].Visible = false;
_Buttons[_ButtonWebcam].Visible = false;
}
else
{
CWebcam.Stop(); //Do this first to get consistent frame and bitmap
_Snapshot = CWebcam.GetBitmap();
if (CWebcam.GetFrame(ref _WebcamTexture))
_Statics[_StaticAvatar].Texture = _WebcamTexture;
_Buttons[_ButtonSaveSnapshot].Visible = true;
_Buttons[_ButtonDiscardSnapshot].Visible = true;
_Buttons[_ButtonTakeSnapshot].Visible = false;
_Buttons[_ButtonWebcam].Visible = false;
}
}
private void _OnDiscardSnapshot()
{
CWebcam.Stop();
CDraw.RemoveTexture(ref _WebcamTexture);
_Snapshot = null;
CDraw.RemoveTexture(ref _WebcamTexture);
_Buttons[_ButtonSaveSnapshot].Visible = false;
_Buttons[_ButtonDiscardSnapshot].Visible = false;
_Buttons[_ButtonTakeSnapshot].Visible = false;
_Buttons[_ButtonWebcam].Visible = true;
_Buttons[_ButtonWebcam].Visible = CWebcam.IsDeviceAvailable();
}
private void _OnSaveSnapshot()
@@ -339,13 +354,12 @@ namespace Vocaluxe.Screens
_Snapshot.Save(filename, ImageFormat.Png);
_Snapshot = null;
CWebcam.Stop();
CDraw.RemoveTexture(ref _WebcamTexture);
_Buttons[_ButtonSaveSnapshot].Visible = false;
_Buttons[_ButtonDiscardSnapshot].Visible = false;
_Buttons[_ButtonTakeSnapshot].Visible = false;
_Buttons[_ButtonWebcam].Visible = true;
_Buttons[_ButtonWebcam].Visible = CWebcam.IsDeviceAvailable();
int id = CProfiles.NewAvatar(filename);
CProfiles.SetAvatar(_SelectSlides[_SelectSlideProfiles].ValueIndex, id);
@@ -354,9 +368,13 @@ namespace Vocaluxe.Screens
private void _OnWebcam()
{
if (!CWebcam.IsDeviceAvailable())
{
_Buttons[_ButtonWebcam].Visible = false;
return;
}
_Snapshot = null;
CWebcam.Start();
CWebcam.GetFrame(ref _WebcamTexture);
_Buttons[_ButtonSaveSnapshot].Visible = false;
_Buttons[_ButtonDiscardSnapshot].Visible = false;
_Buttons[_ButtonTakeSnapshot].Visible = true;
+2 -2
View File
@@ -235,7 +235,7 @@ namespace Vocaluxe.Screens
case Keys.W:
if (CWebcam.IsDeviceAvailable())
{
_Webcam = !_Webcam;
_Webcam = !CWebcam.IsCapturing();
if (_Webcam)
CWebcam.Start();
else
@@ -756,7 +756,7 @@ namespace Vocaluxe.Screens
CParty.FinishedSinging();
if (_Webcam)
CWebcam.Close();
CWebcam.Stop();
}
private int _FindCurrentLine(CVoice voice, CSongLine[] lines, CSong song)