我们使用 C#.Net 创建了一个自定义验证码生成器。现在我们需要升级我们的验证码以包含音频功能(听验证码)。我们的主要限制是我们不能使用任何第三方组件(Dlls)
请指导我实现这样的功能。
提前致谢。
我过去做过这样的事情。需要将背景噪音(器乐)与连接的字母/字符合并。此外,我们从不同的声音中生成了使用中的录音。所有这些都增加了能够从语音中提取验证码文本的“智能代码”的复杂性。由于显而易见的原因,我还必须将最终的 WAVE 转换为 MP3(使用 lame_enc.dll)。向您解释如何做到这一点并不容易,因此我包含了该工作所需的初稿版本,省略了 MP3 代码,因为它使用了第三方 DLL。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Reflection;
namespace suryakiran
{
internal static partial class Extensions
{
internal static byte[] AudioCaptcha(this string text)
{
String en = "abcdefghijkoprstvx0123456789", Location = string.Concat(System.Web.HttpContext.Current.Request.ServerVariables["APPL_PHYSICAL_PATH"].ToString(), @"\bin\wav\");
Int32 dataLength = 0, length = 0, sampleRate = 0, plus = 37500, p = 0;
Int16 bitsPerSample = 0, channels = 0;
Byte[] music, wav;
Random r = new Random();
p = r.Next(1, 4000000);
p += (p % 150) + 44;
Byte[] rb = new Byte[9 * plus];
// read music
using (FileStream fs = new FileStream(String.Format(Location + @"z{0}.wav", (r.Next() % 12) + 1), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
wav = new Byte[44];
fs.Read(wav, 0, 44);
fs.Position = (long)p;
fs.Read(rb, 0, rb.Length);
}
// make music
using (MemoryStream ms = new MemoryStream())
{
channels = BitConverter.ToInt16(wav, 22); sampleRate = BitConverter.ToInt32(wav, 24); bitsPerSample = BitConverter.ToInt16(wav, 34);
length = rb.Length; dataLength = rb.Length;
ms.Write(new Byte[44], 0, 44); ms.Write(rb, 0, rb.Length);
ms.Position = 0;
using (BinaryWriter bw = new BinaryWriter(ms))
{
bw.Write(new char[4] { 'R', 'I', 'F', 'F' }); bw.Write(length);
bw.Write(new char[8] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' });
bw.Write((Int32)16); bw.Write((Int16)1);
bw.Write(channels); bw.Write(sampleRate);
bw.Write((Int32)(sampleRate * ((bitsPerSample * channels) / 8)));
bw.Write((Int16)((bitsPerSample * channels) / 8));
bw.Write(bitsPerSample); bw.Write(new char[4] { 'd', 'a', 't', 'a' }); bw.Write(dataLength);
music = ms.ToArray();
p = 0;
}
}
using (MemoryStream final = new MemoryStream())
{
final.Write(music, 0, 44);
// make voice
using (MemoryStream msvoice = new MemoryStream())
{
msvoice.Write(new Byte[plus / 2], 0, plus / 2);
length += plus; dataLength += plus / 2; p += plus / 2;
for (var i = 0; i < text.Length; i++)
{
String fn = String.Format(Location + @"{0}\{1}.wav", (r.Next() % 3), en.Substring(en.IndexOf(text.Substring(i, 1)), 1)).Replace("?", "qm");
wav = File.ReadAllBytes(fn);
Int32 size = BitConverter.ToInt32(wav, 4);
{
msvoice.Write(new Byte[plus / 2], 0, plus / 2);
length += plus; dataLength += plus / 2; p += plus / 2;
}
msvoice.Write(wav, 44, wav.Length - 44);
length += size; dataLength += size - 36;
}
msvoice.Position = 0;
MemoryStream msmusic = new MemoryStream();
msmusic.Write(music, 0, music.Length);
msmusic.Position = 44;
//merge;
while (final.Length < msmusic.Length)
final.WriteByte((byte)(msvoice.ReadByte() - msmusic.ReadByte()));
return final.ToArray();
}
}
}
internal static Byte[] VisualCaptcha(this String source)
{
try
{
Random r = new Random();
Int32 w = 250, h = 75;
String family = "Arial Rounded MT Bold";
using (var bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb))
{
Int32 m = 0, nm = 0;
Color tc;
using (var g = Graphics.FromImage(bmp))
{
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.SmoothingMode = SmoothingMode.HighQuality;
g.Clear(Color.White);
SizeF size;
m = r.Next() % 9 + 1;
nm = r.Next() % 3;
tc = Color.FromArgb(255, 255, 255);
size = g.MeasureString(source, new Font(family, h * 1.2f, FontStyle.Bold), new SizeF(w * 1F, h * 1F));
using (var brush = new LinearGradientBrush(new Rectangle(0, 0, w, h), Color.Black, Color.Black, 45, false))
{
ColorBlend blend = new ColorBlend(6);
for (var i = 0; i < 6; i++) { blend.Positions[i] = i * (1 / 5F); blend.Colors[i] = r.RandomColor(255, 64, 128); }
brush.InterpolationColors = blend;
for (int wave = 0; wave < 2; wave++)
{
Int32 min = (15 + wave * 20);
PointF[] pt = new PointF[] { new PointF(16f, (float)r.Next(min, min + 10)), new PointF(240f, (float)r.Next(min + 10, min + 20)) };
List<PointF> PointList = new List<PointF>();
float curDist = 0, distance = 0;
for (int i = 0; i < pt.Length - 1; i++)
{
PointF ptA = pt[i], ptB = pt[i + 1];
float deltaX = ptB.X - ptA.X, deltaY = ptB.Y - ptA.Y;
curDist = 0;
distance = (float)Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
while (curDist < distance)
{
curDist++;
float offsetX = (float)((double)curDist / (double)distance * (double)deltaX);
float offsetY = (float)((double)curDist / (double)distance * (double)deltaY);
PointList.Add(new PointF(ptA.X + offsetX, ptA.Y + offsetY));
}
}
for (int i = 0; i < PointList.Count - 24; i = i + 24)
{
float x1 = PointList[i].X, y1 = PointList[i].Y, x2 = PointList[i + 24].X, y2 = PointList[i + 24].Y;
float angle = (float)((Math.Atan2(y2 - y1, x2 - x1) * 180 / 3.14159265));
g.TranslateTransform(x1, y1);
g.RotateTransform(angle);
Int32 pm = r.Next() % 2 + 1;
Point[] p1 = new Point[] { new Point(0, 0), new Point(3, -3 * pm), new Point(6, -4 * pm), new Point(9, -3 * pm), new Point(12, 0), new Point(15, 3 * pm), new Point(18, 4 * pm), new Point(21, 3 * pm), new Point(24, 0) };
using (var path = new GraphicsPath()) g.DrawLines(new Pen(brush, 2f), p1);
g.RotateTransform(-angle);
g.TranslateTransform(-x1, -y1);
}
}
using (var path = new GraphicsPath())
{
PointF[] points = new PointF[] { };
if (m == 1 || m == 2 || m == 3) // star trek inverse
{
path.AddString(source, new FontFamily(family), 1, h * 0.75F, new PointF((w - size.Width) / 2F, (h * 0.9F - size.Height) / 2F), StringFormat.GenericTypographic);
points = new PointF[] { new PointF(0, 0), new PointF(w, 0), new PointF(w * 0.2F, h), new PointF(w * 0.8F, h) };
}
else if (m == 4 || m == 5) // star trek
{
path.AddString(source, new FontFamily(family), 1, h * 0.75F, new PointF((w - size.Width) / 2F, (h * 1.2F - size.Height) / 2F + 2F), StringFormat.GenericTypographic);
points = new PointF[] { new PointF(w * 0.2F, 0), new PointF(w * 0.8F, 0), new PointF(0, h), new PointF(w, h) };
}
else if (m == 6 || m == 7) // grow from left
{
path.AddString(source, new FontFamily(family), 1, h * 0.75F, new PointF((w * 1.15F - size.Width) / 2F, (h - size.Height) / 2F), StringFormat.GenericTypographic);
points = new PointF[] { new PointF(0, h * 0.25F), new PointF(w, 0), new PointF(0, h * 0.75F), new PointF(w, h) };
}
else if (m == 8 || m == 9) // grow from right
{
path.AddString(source, new FontFamily(family), 1, h * 0.75F, new PointF((w * 0.85F - size.Width) / 2F, (h - size.Height) / 2F), StringFormat.GenericTypographic);
points = new PointF[] { new PointF(w * 0.1F, 0), new PointF(w * 0.9F, h * 0.25F), new PointF(w * 0.1F, h), new PointF(w * 0.9F, h * 0.75F) };
}
path.Warp(points, new RectangleF(0, 0, w, h));
g.FillPath(Brushes.White, path);
g.DrawPath(new Pen(brush, 2F), path);
}
}
}
using (var thumb = new Bitmap(128, 40, PixelFormat.Format32bppArgb))
{
using (var g = Graphics.FromImage(thumb))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
Rectangle tr = new Rectangle(0, 0, thumb.Width, thumb.Height);
g.DrawImage(bmp, tr);
g.DrawRectangle(new Pen(Brushes.White), new Rectangle(0, 0, 127, 39));
}
using (var ms = new MemoryStream())
{
((Image)thumb).Save(ms, ImageFormat.Png);
return ms.ToArray();
}
}
}
}
catch { return null; }
}
private static Color RandomColor(this Random rnd, Int32 alpha, Int32 min, Int32 max)
{
return Color.FromArgb(alpha, rnd.Next(min, max), rnd.Next(min, max), rnd.Next(min, max));
}
}
}
对于视觉和音频生成,代码独立于验证码生成(源文本)。WAVE 格式的每个字母音频文件大约为 30KB,但每个音乐文件至少为 8MB,这为我提供了随机起始位置的广泛范围(避免 - 经常 - 重复模式)。您可以将代码编译为 dll 或将其(修改)放在代码隐藏文件中。
这个想法很简单。
对于您拥有的每个角色,您都将其录制成音频,然后按照与显示角色相同的顺序简单地播放录制的音频。
因此,对于字符 A,您将其拼写并记录在一个 wav 文件中,当您听到它清晰而响亮地说出“alpha”时。
阅读此答案以了解如何播放它们:如何在网络浏览器中播放音频和视频文件?
如果您想将音频连接到一个文件,您可以选择一个包含所有现成功能的音频库来进行此混音。
我知道最好的之一是 http://www.un4seen.com/ 上的低音音频库
当然,因为您只需要添加两个音频,也许您可以直接使用windows 媒体库,检查它的 SDK http://msdn.microsoft.com/en-us/library/windows/desktop/dd757738(v =vs.85).aspx
Windows 媒体已准备好使用 Windows 内的功能,这些功能与音频等媒体有关。
我将跳过很多验证码安全性和生成细节来支持我的建议。
我在服务器端使用了语音合成器。
在您的 Web 应用程序项目中,添加对System.Speech.dll的引用
我创建了两个页面:
Start.aspx -> 用户将看到的页面。有了这两个元素:
<span id="lblCaptchaText" runat="server"></span>
<embed height="50px" width="100px" src="AudioPlayer.aspx" />
后面的代码应该是这样的(简化)
public partial class Start : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// You generate your captcha somehow
string captchaString = "E F G 6 7";
// Let's store the captcha in a place so that our wave generator can find it.
Session.Add("captcha", captchaString);
// Display the captcha in the page (this should actually be a blurry non-human readable image)
lblCaptchaText.InnerText = captchaString;
}
}
AudioPlayer.aspx -> 生成波形输出给用户的页面。HTML 方面没有任何内容。
public partial class AudioPlayer : System.Web.UI.Page
{
protected override void OnPreRender(EventArgs e)
{
Response.ContentType = "audio/wav";
MemoryStream mstream = GetAudio(Session["captcha"] as string);
mstream.Position = 0;
mstream.CopyTo(Response.OutputStream);
Response.End();
}
public static MemoryStream GetAudio(string input)
{
MemoryStream mem = new MemoryStream();
Thread t = new Thread(new ThreadStart(() =>
{
SpeechSynthesizer synth = new SpeechSynthesizer();
synth.Rate = -5;
synth.SetOutputToWaveStream(mem);
synth.Speak(input);
}));
t.Start();
t.Join();
return mem;
}
}
编译您的网站并运行 Start.aspx,您将听到您的验证码。
使用 Microsoft Speech SDK、性能、资源管理、可扩展性等时需要考虑很多事情。好消息是,他们的语音识别引擎和语音合成器具有称为 Microsoft Speech Server 的服务器“风格”。
RECAPTHA 默认有这个,那么你为什么要重新发明轮子呢?