摘要:silverlight从4.0开始支持摄像头和麦克风,可以说这一个功能的加入弥补了silverlight媒体方面的不足,毕竟flash很早就具有这一功能。从下面的内容你可以看到在silverlight中使用camera和microphone是如此的简单。
主要内容:
1.视频捕获
2.视频截图
3.音频捕获
一、视频捕获
silverlight的视频捕获只需要简单几步即可做到:获得VideoCaptureDevice对象,声明CaptureSource对象,将VideoCaptureDevice赋值给CaptureSource的VideoCaptureDevice属性,然后将CaptureSource交给VideoBrush即可。有了VideoBrush以后如何操作就随你了。
CaptureSource提供了操作视频和音频的很多方法(后面关于音频的捕获同样需要使用它),关于视频捕获的启动、停止等操作均是由CaptureSource来控制的。而视频捕获在启动操作之前建议对设备访问权限进行判读,如果不允许访问可以通过CaptureDeviceConfiguration.RequestDeviceAccess()方法对用户发出请求。下面还是来看代码吧(Camera是对摄像头操作的简单封装,代码中还包括后面会用到的内容,不过都有注释,其他内容下面也会说到):
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using Cmj.MyWeb.MySilverlight.SilverlightImage;
namespace Cmj.MyWeb.MySilverlight.SilverlightMeida
{
public class Camera
{
private CaptureSource _cSource = null;
private VideoCaptureDevice _vcDevice = null;
public Camera()
{
_cSource = new CaptureSource() ;
_cSource.Stop();
_vcDevice = GetVideoCapureDevice();
}
/// <summary>
/// 设置当前视频捕获设备
/// </summary>
/// <param name="vcDevice"></param>
public void SetVideoCaptureDevice(VideoCaptureDevice vcDevice)
{
_vcDevice = vcDevice;
this._cSource.VideoCaptureDevice = _vcDevice;
}
/// <summary>
/// 取得所有可用视频捕捉设备
/// </summary>
/// <returns></returns>
public ReadOnlyCollection<VideoCaptureDevice> GetAvailableDevice()
{
return CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();
}
/// <summary>
/// 获得VideoBrush对象
/// </summary>
/// <returns></returns>
public VideoBrush GetVideoBrush()
{
VideoBrush vBrush = new VideoBrush();
if (_vcDevice != null)
{
_cSource.VideoCaptureDevice = _vcDevice;
vBrush.SetSource(_cSource);//注意,不能在这里直接开启摄像头,必须等到所有设置准备就绪
}
else
{
Console.WriteLine("尚未找到捕捉设备!请确保设备正确安装!");
}
return vBrush;
}
/// <summary>
/// 以Rectangle形式取得视频
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public Rectangle GetVideo(int width,int height)
{
Rectangle rctg = new Rectangle();
rctg.Width = width;
rctg.Height = height;
rctg.Fill = GetVideoBrush();
return rctg;
}
/// <summary>
/// 取得水平翻转的视频捕获
/// </summary>
/// <param name="with"></param>
/// <param name="height"></param>
/// <returns></returns>
public Rectangle GetVideoWithFlipHorizontal(int with, int height)
{
Rectangle rctg = GetVideo(with, height);
MatrixTransform mTransform = new MatrixTransform();
mTransform.Matrix = new Matrix(-1, 0, 0, 1, with, 0);
rctg.RenderTransform = mTransform;
return rctg;
}
/// <summary>
/// 开启视频捕获
/// </summary>
public void StartCapture()
{
if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
{
_cSource.Start();
}
}
/// <summary>
/// 停止捕获
/// </summary>
public void StopCapture()
{
_cSource.Stop();
}
/// <summary>
/// 设置视频格式
/// </summary>
/// <param name="quality"></param>
public void SetVideoQuality(byte quality)
{
_vcDevice.DesiredFormat=_vcDevice.SupportedFormats[quality];
}
/// <summary>
/// 取得默认的视频捕获设备
/// </summary>
/// <returns></returns>
public VideoCaptureDevice GetVideoCapureDevice()
{
return CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
}
private Image _screnshotImage = null;
/// <summary>
/// 截取原始视频内容
/// </summary>
/// <param name="image">显示截图的Image对象</param>
public void GetScreenshot(Image image)
{
if (_cSource.State == CaptureState.Started)
{
_screnshotImage = image;
_cSource.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(_cSource_CaptureImageCompleted);
_cSource.CaptureImageAsync();
}
}
void _cSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
WriteableBitmap wBitmap = e.Result;
_screnshotImage.Source = wBitmap;
}
/// <summary>
/// 使用WritableBitmap方式截图
/// </summary>
/// <param name="uIElement">视频所在对象</param>
/// <returns></returns>
public WriteableBitmap GetScreenshotByUIElement(UIElement uIElement)
{
WriteableBitmap wBitmap = new WriteableBitmap(uIElement, null);
return wBitmap;
}
/// <summary>
/// 以png格式保存截图(注意目前只支持第二种截图方式(使用WritableBitmap方式截图))
/// </summary>
/// <param name="uIElement"></param>
/// <param name="filePath"></param>
public void SaveScreenshot(UIElement uIElement,string filePath)
{
GetScreenshotByUIElement(uIElement).SaveToPNG();
}
}
}
来看一下运行效果:
为了更清楚地体验silverlight 视频和音频捕获效果我简单的做了一个界面,当然它包含的内容不仅仅是上面提到的,有些内容是下面要讨论的,不过不用担心我将整个界面的说明以图片形式展示出来:
在界面左上方是视频设备列表,这个是展开的效果:
要想得到所有可用设备只需要使用CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices()便可以获得所有可用视频设备(音频设备与之类似)。
注意:上面在调用CaptureDeviceConfiguration.RequestDeviceAccess()进行视频请求时会弹出下面的界面,如果你不想每次都请求只要勾选"Remember my answer"。如果不小心勾选了并且点击了"No"怎么办呢?可以通过右键"Silverlight"在下图列表中删除即可。
二、视频截图
很多视频软件都有截图功能,并且添加很多特效将其保存到本地,例如dell官方有一款Dell Webcam Central就可以使用摄像头拍照、制作大头贴、录制视频等。下面是这款应用的截图,看起来还是很有意思:
这里不妨试一下silverlight的截图功能。在silverlight中要实现截图功能一般有两种方式:调用CaptureSource的CaptureImageAsync()方法和利用WriteableBitmap对象直接取得视频所在容器的截图。二者不同的地方在于第一种方式截取的是原始视频图像,而第二种方式截取的则是容器内所有可见内容。换句话说即使你在视频容器中放入一个TextBlock,截图时也会将TextBlock中的文本截取下来,而第一种方式则不会。
在体验截图效果之前在这里插入一个小主题,在上面的截图中你或许已经注意到UI的左下方有一组"特效选项",没错,这里将带您一起实现一个类似于Dell Webcam Central的特效功能,由于此功能刚好可以说明两种截图的不同效果因此先来看一下如何实现视频(图片)特效。实现视频特效功能方法比较简单,就是设置控件的Effect属性,但是关键是要实现这些效果要花费一番功夫。不过不用担心,这里有一个强大的特效库http://wpffx.codeplex.com/,而且是完全开源的,里面除了提供silverlight特效组件还有用于wpf的特效。有了它要制作特效简直易如反掌,这里简单列举几类:
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Effects;
using ShaderEffectLibrary;
namespace Cmj.MyWeb.MySilverlight.SilverlightEffect
{
public class Shadercs
{
/// <summary>
/// 反色特效
/// </summary>
public static InvertColorEffect InvertColor()
{
InvertColorEffect ice= new InvertColorEffect();
return ice;
}
/// <summary>
/// 黑白特效
/// </summary>
public static MonochromeEffect Monochrome(Color filterColor)
{
MonochromeEffect me= new MonochromeEffect();
me.FilterColor = filterColor;
return me;
}
/// <summary>
/// 漩涡特效
/// </summary>
public static SwirlEffect Swirl(double swirlStrength)
{
SwirlEffect we = new SwirlEffect();
we.SwirlStrength = swirlStrength;
return we;
}
/// <summary>
/// 色调
/// </summary>
public static ColorToneEffect ColorTone(double tone,Color lightColor)
{
ColorToneEffect cte = new ColorToneEffect();
cte.Toned=tone;
cte.LightColor = lightColor;
return cte;
}
/// <summary>
/// 修饰
/// </summary>
public static EmbossedEffect Embossed(double amount,double width)
{
EmbossedEffect ee= new EmbossedEffect();
ee.Amount = amount;
ee.Width = width;
return ee;
}
}
}
有了它只要在点击"特效选项"中的每个Image时设置不同的Effect即可,当然这里要保留原视频,因此第一个Image组件只需要使用原有Effect即可。下面是几种效果下的截图,当然wpffx还有很多的特效,这里不再赘余(为了方便截图,下面直接使用虚拟摄像头,本人不再献丑了O(∩_∩)O~)。
效果不错,回到之前的截图问题,下面是两种截图方式截图比较(两中截图均是在添加了Invert特效后),可以清晰的看出对于采用第一种方式的截图根本没有添加特效,而第二种方式截图却完整保留了特效,这也正验证了上面关于两种方式截图的区别。
截图相关代码在上面Camera类中。
截图功能已经实现了,但是还无法保存到本地。文件的保存事实上最重要的就是encode问题,无论是采用第一种方式还是第二种方式来截图,最终得到的都是WriteableBitmap,而这个对象是不存在任何save方法的,因此需要自己实现编码问题。不过这里你仍然不需要担心,已经有朋友对此进行封装并且开源,这就是ImageTools,地址:http://imagetools.codeplex.com/,其中有对于png编码操作(当然还有其他开源类库,这里采用ImageTools)。有了这个开源控件,只需要对WriteableBitmap进行简单扩展即可:
using System.IO;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using ImageTools;
using ImageTools.IO.Png;
namespace Cmj.MyWeb.MySilverlight.SilverlightImage
{
public static class BitmapExtensions
{
public static void SaveToPNG(this WriteableBitmap bitmap)//扩展WriteableBitmap类添加SaveToPNG方法
{
if (bitmap != null)
{
SaveFileDialog sfd = new SaveFileDialog
{
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*",
DefaultExt = ".png",
FilterIndex = 1
};
if ((bool)sfd.ShowDialog())
{
var img = bitmap.ToImage();
var encoder = new PngEncoder();
using (Stream stream = sfd.OpenFile())
{
encoder.Encode(img, stream);
stream.Close();
}
}
}
}
}
}
扩展之后WriteableBitmap就拥有了SaveToPNG方法。在点击保存时只需要将截图区域中Image控件的Source属性转化为WriteableBitmap对象调用SaveToPNG
方法(在此之前注意先将扩展方法所在命名空间引入)。
使用效果:
三、音频捕获
有了上面视频捕获的操作,音频捕获就变得更简单了,不同之处仅仅就是VideoCaptureDevice变成了AudioCaptureDevice而已,只不过这次设置的是CaptureSource的AudioCaptureDevice属性而已。
同视频捕获不同,音频操作没办法直接看到捕获效果,因此不妨将音频保存到本地。想要保存音频第一个问题就是如何得到音频数据?此刻就需要请出AudioSink类,它公开了音频设备捕获方法,只需要实现并且覆盖这些方法就可接收音频信息,当然这其中就包括录音数据。AudioSink公开了四个虚拟回调方法,供调用者重写,下面是msdn对于AudioSink重写的说明:
为了使用捕获图形中的原始音频,必须重写 OnSamples 并在将 sampleData 转换为目标格式的音频接收器中提供实现。然后可以使用从 BitsPerSample、Channels 和 SamplesPerSecond 获取的数字将位流转换为 PCM 音频格式。转换逻辑可能使用一系列嵌套循环和适当的数据类型(如 8 位的 Byte 和 32 位的 Int32)获取离散示例。
如果提供了 OnSamples 的有意义重写,应该同时提供有意义的 OnFormatChange 重写。在第一次连接设备与实际捕获这两个时刻之间,此接收器的 AudioFormat 信息可能会改变。处理 OnFormatChange 是了解运行时上的真实格式信息的唯一方法。
可以在更改通知时使用 OnCaptureStarted 和 OnCaptureStopped,以向用户指示当前正在捕获音频或捕获已停止。还可以将通知用作标记,以在捕获仍在进行时阻止应用程序的某些其他操作。
前一段简而言之就是在OnSamples中获取原始音频,而后一段是什么意思呢?不妨看看下面msdn对于OnFormatChange的备注:
接收器的 AudioFormat 可以不同于在 AudioCaptureDevice.DesiredFormat 中请求的格式。
当在接收器中启动音频捕获时,针对该接收器的第一次捕获,系统将至少调用一次 OnFormatChange。因此,可以将 OnFormatChange 用作音频格式的最终确定,而不使用 DesiredFormat 或 SupportedFormats。
AudioCaptureDevice 属性旨确定应用程序逻辑是否可以根据其格式支持特定设备的捕获和示例转换。(如果 GetAvailableAudioCaptureDevices 方法报告在当前默认设备不受支持的情况下可能起作用的替代方案,您还可以提示用户更改其配置 UI 中的设备。)
来自音频设备(包括获取捕获的初始格式)的格式更改通常也会与您的 OnSamples 实现相关。
从这段描述中可以得知音频的最终格式可以由OnFormatChange来确定。
接下来就来继承并重写AudioSink吧(Microphone是对麦克风操作的封装):
using System;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Windows.Controls;
using System.IO;
namespace Cmj.MyWeb.MySilverlight.SilverlightMeida
{
public class Microphone:AudioSink
{
public MemoryStream Stream{get;private set;}
public AudioFormat Format { get; private set; }
private AudioCaptureDevice _acDevice = null;
private CaptureSource _cSource = null;
public Microphone()
{
_cSource = new CaptureSource();
_cSource.Stop();
_acDevice = GetAudioDevice();
_cSource.AudioCaptureDevice = _acDevice;
this.CaptureSource = _cSource;
}
/// <summary>
/// 设置当前音频设备
/// </summary>
/// <param name="acDevice"></param>
public void SetAudioCaptureDevice(AudioCaptureDevice acDevice)
{
_acDevice = acDevice;
_cSource.AudioCaptureDevice = _acDevice;
}
/// <summary>
/// 取得所有可用音频捕获设备
/// </summary>
/// <returns></returns>
public ReadOnlyCollection<AudioCaptureDevice> GetAvailableDevice()
{
return CaptureDeviceConfiguration.GetAvailableAudioCaptureDevices();
}
/// <summary>
/// 获得默认音频设备
/// </summary>
/// <returns></returns>
public AudioCaptureDevice GetAudioDevice()
{
return CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
}
/// <summary>
/// 开始音频捕获
/// </summary>
public void StartCapture()
{
if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
{
_cSource.Start();
}
}
/// <summary>
/// 停止音频捕获
/// </summary>
public void StopCapture()
{
_cSource.Stop();
}
/***************************************下面是对应AudioSink方法的覆盖****************************************/
/// <summary>
/// 开始捕获
/// </summary>
protected override void OnCaptureStarted()
{
Stream = new MemoryStream();
}
/// <summary>
/// 停止捕获
/// </summary>
protected override void OnCaptureStopped()
{
}
/// <summary>
/// 音频格式更改
/// 第一次捕获时会调用一次,此后便确定格式
/// </summary>
/// <param name="audioFormat"></param>
protected override void OnFormatChange(AudioFormat audioFormat)
{
Format = audioFormat;
}
/// <summary>
/// 捕获完音频
/// </summary>
/// <param name="sampleTimeInHundredNanoseconds"></param>
/// <param name="sampleDurationInHundredNanoseconds"></param>
/// <param name="sampleData"></param>
protected override void OnSamples(long sampleTimeInHundredNanoseconds, long sampleDurationInHundredNanoseconds, byte[] sampleData)
{
Stream.Write(sampleData, 0, sampleData.Length);
}
/***************************************下面是音频保存相关操作****************************************/
/// <summary>
/// 添加WAV header
/// </summary>
/// <param name="audioByteLength"></param>
/// <param name="audioFormat"></param>
/// <returns></returns>
public 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);
bwOutput.Write("RIFF".ToCharArray());
bwOutput.Write((uint)(rawData.Length + 36));
bwOutput.Write("WAVE".ToCharArray());
bwOutput.Write("fmt ".ToCharArray());
bwOutput.Write((uint)0x10);
bwOutput.Write((ushort)0x01);
bwOutput.Write((ushort)audioFormat.Channels);
bwOutput.Write((uint)audioFormat.SamplesPerSecond);
bwOutput.Write((uint)(audioFormat.BitsPerSample * audioFormat.SamplesPerSecond * audioFormat.Channels / 8));
bwOutput.Write((ushort)(audioFormat.BitsPerSample * audioFormat.Channels / 8));
bwOutput.Write((ushort)audioFormat.BitsPerSample);
bwOutput.Write("data".ToCharArray());
bwOutput.Write((uint)rawData.Length);
long originalRawDataStreamPosition = rawData.Position;
rawData.Seek(0, SeekOrigin.Begin);
byte[] buffer = new byte[4096];
int read;
while ((read = rawData.Read(buffer, 0, 4096)) > 0)
{
bwOutput.Write(buffer, 0, read);
}
rawData.Seek(originalRawDataStreamPosition, SeekOrigin.Begin);
}
/// <summary>
/// 音频保存
/// </summary>
public void SaveToWav()
{
SaveFileDialog sfDialog = new SaveFileDialog();
sfDialog.Filter = "音频(*.wav)|*.wav";
if (sfDialog.ShowDialog()==true)
{
using (Stream stream = sfDialog.OpenFile())
{
SavePcmToWav(this.Stream, stream, this.Format);
}
}
}
}
}
将原始音频存放到MemoryStream中,然后在OnFormatChange中获得音频格式。这样一来音频捕获时将会执行OnSamples将其写入MemoryStream中,由于AudioCaptureDevice可以捕获得到PCM数据,接下来只要给捕获的音频数据加上WAV头就可以了。
相对视频编码来说音频编码会简单许多,而且从占用空间大小上音频也会小很多(这也是将其临时存储到MemoryStream中的原因),上面Microphone.cs中有关于音频头添加和保持的具体代码,关于WAV更多编码信息可以访问http://technology.niagarac.on.ca/courses/ctec1631/WavFileFormat.html(目前好像只有pcm的wave编码)。
添加过WAV头之后接下来只需要将MemoryStream中的内容编码并写入本地即可,具体代码见上面Microphone中SaveToWav方法。
下面是演示效果:
好了,今天主要跟大家一起学习了silverlight中视频和音频捕获基本内容,但是或许有人会问音频可以保存了、截图也可以保存了,那么视频呢?如何使用silverlight录制视频呢?没错,这是接下来要跟大家一起学习的内容,下一篇博客我会跟大家一起学习如何使用silverlight进行视频录制。
![]() | 本作品采用知识共享署名 2.5 中国大陆许可协议进行许可,欢迎转载,演绎或用于商业目的。但转载请注明来自崔江涛(KenshinCui),并包含相关链接。 |