all repos — captcha @ 436e363f8439b0ac604f152748e2e13bbe575b53

Go package captcha implements generation and verification of image and audio CAPTCHAs.

audio.go (view raw)

  1package captcha
  2
  3import (
  4	"bytes"
  5	crand "crypto/rand"
  6	"encoding/binary"
  7	"math"
  8	"os"
  9	"rand"
 10	"io"
 11)
 12
 13const sampleRate = 8000 // Hz
 14
 15var (
 16	longestDigitSndLen int
 17	endingBeepSound    []byte
 18	reverseDigitSounds [][]byte
 19)
 20
 21func init() {
 22	for _, v := range digitSounds {
 23		if longestDigitSndLen < len(v) {
 24			longestDigitSndLen = len(v)
 25		}
 26	}
 27	endingBeepSound = changeSpeed(beepSound, 1.4)
 28	// Preallocate reversed digit sounds for background noise.
 29	reverseDigitSounds = make([][]byte, len(digitSounds))
 30	for i, v := range digitSounds {
 31		reverseDigitSounds[i] = reversedSound(v)
 32	}
 33}
 34
 35// BUG(dchest): [Not our bug] Google Chrome 10 plays unsigned 8-bit PCM WAVE
 36// audio on Mac with horrible distortions.  Issue:
 37// http://code.google.com/p/chromium/issues/detail?id=70730.
 38// This has been fixed, and version 12 will play them properly.
 39
 40type Audio struct {
 41	body *bytes.Buffer
 42}
 43
 44// NewImage returns a new audio captcha with the given digits, where each digit
 45// must be in range 0-9.
 46func NewAudio(digits []byte) *Audio {
 47	numsnd := make([][]byte, len(digits))
 48	nsdur := 0
 49	for i, n := range digits {
 50		snd := randomizedDigitSound(n)
 51		nsdur += len(snd)
 52		numsnd[i] = snd
 53	}
 54	// Random intervals between digits (including beginning).
 55	intervals := make([]int, len(digits)+1)
 56	intdur := 0
 57	for i := range intervals {
 58		dur := rnd(sampleRate, sampleRate*3) // 1 to 3 seconds
 59		intdur += dur
 60		intervals[i] = dur
 61	}
 62	// Generate background sound.
 63	bg := makeBackgroundSound(longestDigitSndLen*len(digits) + intdur)
 64	// Create buffer and write audio to it.
 65	a := new(Audio)
 66	sil := makeSilence(sampleRate / 5)
 67	bufcap := 3*len(beepSound) + 2*len(sil) + len(bg) + len(endingBeepSound)
 68	a.body = bytes.NewBuffer(make([]byte, 0, bufcap))
 69	// Write prelude, three beeps.
 70	a.body.Write(beepSound)
 71	a.body.Write(sil)
 72	a.body.Write(beepSound)
 73	a.body.Write(sil)
 74	a.body.Write(beepSound)
 75	// Write digits.
 76	pos := intervals[0]
 77	for i, v := range numsnd {
 78		mixSound(bg[pos:], v)
 79		pos += len(v) + intervals[i+1]
 80	}
 81	a.body.Write(bg)
 82	// Write ending (one beep).
 83	a.body.Write(endingBeepSound)
 84	return a
 85}
 86
 87// WriteTo writes captcha audio in WAVE format into the given io.Writer, and
 88// returns the number of bytes written and an error if any.
 89func (a *Audio) WriteTo(w io.Writer) (n int64, err os.Error) {
 90	nn, err := w.Write(waveHeader)
 91	n = int64(nn)
 92	if err != nil {
 93		return
 94	}
 95	err = binary.Write(w, binary.LittleEndian, uint32(a.body.Len()))
 96	if err != nil {
 97		return
 98	}
 99	nn += 4
100	n, err = a.body.WriteTo(w)
101	n += int64(nn)
102	return
103}
104
105// EncodedLen returns the length of WAV-encoded audio captcha.
106func (a *Audio) EncodedLen() int {
107	return len(waveHeader) + 4 + a.body.Len()
108}
109
110// mixSound mixes src into dst. Dst must have length equal to or greater than
111// src length.
112func mixSound(dst, src []byte) {
113	for i, v := range src {
114		av := int(v)
115		bv := int(dst[i])
116		if av < 128 && bv < 128 {
117			dst[i] = byte(av * bv / 128)
118		} else {
119			dst[i] = byte(2*(av+bv) - av*bv/128 - 256)
120		}
121	}
122}
123
124func setSoundLevel(a []byte, level float64) {
125	for i, v := range a {
126		av := float64(v)
127		switch {
128		case av > 128:
129			if av = (av-128)*level + 128; av < 128 {
130				av = 128
131			}
132		case av < 128:
133			if av = 128 - (128-av)*level; av > 128 {
134				av = 128
135			}
136		default:
137			continue
138		}
139		a[i] = byte(av)
140	}
141}
142
143// changeSpeed returns new PCM bytes from the bytes with the speed and pitch
144// changed to the given value that must be in range [0, x].
145func changeSpeed(a []byte, speed float64) []byte {
146	b := make([]byte, int(math.Floor(float64(len(a))*speed)))
147	var p float64
148	for _, v := range a {
149		for i := int(p); i < int(p+speed); i++ {
150			b[i] = v
151		}
152		p += speed
153	}
154	return b
155}
156
157// rndf returns a random float64 number in range [from, to].
158func rndf(from, to float64) float64 {
159	return (to-from)*rand.Float64() + from
160}
161
162func randomSpeed(a []byte) []byte {
163	pitch := rndf(0.9, 1.2)
164	return changeSpeed(a, pitch)
165}
166
167func makeSilence(length int) []byte {
168	b := make([]byte, length)
169	for i := range b {
170		b[i] = 128
171	}
172	return b
173}
174
175func makeWhiteNoise(length int, level uint8) []byte {
176	noise := make([]byte, length)
177	if _, err := io.ReadFull(crand.Reader, noise); err != nil {
178		panic("error reading from random source: " + err.String())
179	}
180	adj := 128 - level/2
181	for i, v := range noise {
182		v %= level
183		v += adj
184		noise[i] = v
185	}
186	return noise
187}
188
189func reversedSound(a []byte) []byte {
190	n := len(a)
191	b := make([]byte, n)
192	for i, v := range a {
193		b[n-1-i] = v
194	}
195	return b
196}
197
198func makeBackgroundSound(length int) []byte {
199	b := makeWhiteNoise(length, 4)
200	for i := 0; i < length/(sampleRate/10); i++ {
201		snd := reverseDigitSounds[rand.Intn(10)]
202		snd = changeSpeed(snd, rndf(0.8, 1.4))
203		place := rand.Intn(len(b) - len(snd))
204		setSoundLevel(snd, rndf(0.2, 0.3))
205		mixSound(b[place:], snd)
206	}
207	return b
208}
209
210func randomizedDigitSound(n byte) []byte {
211	s := randomSpeed(digitSounds[n])
212	setSoundLevel(s, rndf(0.7, 1.3))
213	return s
214}