【问题标题】:How to implement Custom Audio Capcha in ASP.Net如何在 ASP.Net 中实现自定义音频验证码
【发布时间】:2012-05-28 10:29:27
【问题描述】:

我们使用 C#.Net 创建了自定义验证码生成器。现在我们需要升级我们的验证码以包含音频功能(听验证码)。我们的主要限制是我们不能使用任何第三方组件(Dlls)

请指导我实现这样的功能。

提前致谢。

【问题讨论】:

  • 顺便问一下,问题是如何在服务器上使字符发声,或者如何在浏览器上播放它们?给他们一个适合你的答案:stackoverflow.com/questions/10648471/…
  • 角色建立在服务器上,基于这些角色的声音将在浏览器上生成和播放。
  • 你想要什么细节?在服务器或客户端上的女巫部分?

标签: c# asp.net


【解决方案1】:

我过去做过这样的事情。需要将背景噪音(器乐)与连接的字母/字符合并。此外,我们从不同的声音中生成了使用中的录音。所有这些都增加了能够从语音中提取验证码文本的“智能代码”的复杂性。由于显而易见的原因,我还必须将最终的 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 或将其(修改)放在代码隐藏文件中。

【讨论】:

  • 感谢您的回复。这真的很有用,请告诉我是否有播放内存流中的内容的播放器,或者请告诉我如何在客户端播放流中的合并内容。
  • 首先创建一个sayit.ashx,它获取验证码文本并将其传递给对AudioCaptcha的调用。然后将 onclick="SayIt()" 添加到触发音频请求的对象并在您的 html 中添加一个空的
    。最后添加这个 javascript 代码: function SayIt(){var o=document.getElementById('voice');if(o.innerHTML=='')o.innerHTML="" else with(document.getElementById('play')){SelectionStart = 0;Play( )}} 然后,如果您重新加载验证码图像,请将 voice.innerHTML 更改为 ""
  • 谢谢。这真的很有用
【解决方案2】:

这个想法很简单。

对于您拥有的每个角色,您将其录制成音频,然后按照与显示角色相同的顺序简单地播放录制的音频。

所以对于字符 A,你拼写它并将它记录在一个 wav 文件中,当你听到它清楚而响亮地说出“alpha”时。

阅读这个答案以了解如何玩它们:How to play audio and video files in web browser?

对后面的代码进行声音编辑

如果您想将音频连接到一个文件,您可以选择一个包含所有现成功能的音频库。

我知道最好的之一是http://www.un4seen.com/低音音频库

当然因为你只需要添加两个音频也许你可以直接使用 windows 媒体库,检查它的 SDK http://msdn.microsoft.com/en-us/library/windows/desktop/dd757738(v=vs.85).aspx

Windows 媒体已准备好使用窗口内的功能,许多人认为这些功能与音频等媒体有关。

【讨论】:

  • 可能对此进行了安全调整。我需要在服务器端合并所需的 wav 文件并将其作为输出流发送回浏览器。然后我们的 Flash 播放器应该从输出流中播放。
  • @suryakiran 我已经更新了答案,并提供了有关如何执行此操作的指南。我知道我没有为您提供源代码来执行此操作,但该 SDK 的示例和帮助包含许多您自己制作的信息。还有太多的想法要涵盖,如何玩它的答案包含很多细节。剩下的唯一部分是如何在服务器上连接它们。
  • +1 - 尽管他特别要求不要使用第 3 方库,但如果没有大量工作,这听起来有点不切实际。
【解决方案3】:

我将跳过很多验证码安全性和生成细节来支持我的提议。

我在服务器端使用了语音合成器。

在您的 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 .

【讨论】:

    【解决方案4】:

    RECAPTHA 默认有这个,那你为什么要重新发明轮子呢?

    http://blog.recaptcha.net/2008/12/new-audio-recaptcha.html

    【讨论】:

    • 在我们的项目中,我们不必使用没有源代码的第三方组件来实现
    猜你喜欢
    • 2023-03-28
    • 1970-01-01
    • 2015-10-13
    • 2020-06-19
    • 2018-04-09
    • 2012-01-07
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    相关资源
    最近更新 更多