Joining .WAVs with PHP

I'm currently working on a CAPTCHA plugin for DokuWiki and thought about providing audio output for users not able to see the image. This is pretty simple for CAPTCHAs – there is no need for complicated speech synthesis because you only need recordings of the 26 possible letters. But you need a way of joining those recordings on the fly…

I looked around the web for a way to concatenate multiple wave files with PHP and found an article by Phillip Perkins called Create an audio stitching tool in PHP. But at a closer look his example code was overly complicated for my simple task. I then stumbled about a very short forum thread which together with a simple format specification led to the following function:

function joinwavs($wavs){
    $fields = join('/',array( 'H8ChunkID', 'VChunkSize', 'H8Format',
                              'H8Subchunk1ID', 'VSubchunk1Size',
                              'vAudioFormat', 'vNumChannels', 'VSampleRate',
                              'VByteRate', 'vBlockAlign', 'vBitsPerSample' ));
    $data = '';
    foreach($wavs as $wav){
        $fp     = fopen($wav,'rb');
        $header = fread($fp,36);
        $info   = unpack($fields,$header);
        // read optional extra stuff
        if($info['Subchunk1Size'] > 16){
            $header .= fread($fp,($info['Subchunk1Size']-16));
        }
        // read SubChunk2ID
        $header .= fread($fp,4);
        // read Subchunk2Size
        $size  = unpack('vsize',fread($fp, 4));
        $size  = $size['size'];
        // read data
        $data .= fread($fp,$size);
    }
    return $header.pack('V',strlen($data)).$data;
}

Just fill it with a bunch of .wav files and it will return a new joined file. Send it with a audio/x-wav header and you're done.

What I need now are the audio files. I found some on the web but I can't figure out who owns them and what the license is… If anyone can help me out, mail me or leave a comment.

Tags:
php,
wav,
join,
merge
Similar posts:

 
Posted on Wednesday November the 15th, 2006 (21 months ago).

Comments

1
good job!
CAPTCHA is a very important feature
2006-11-16 03:22:13
liushk
2
hi, this feature looks (sounds) very interesting.

if you need these words in copyrightfree .wav-format, i could make a recording for you in german and international spelling alphabet. just one restriction: i would use them for my language learning friends, too :)
2006-11-16 13:14:22
Martin Bendig
3
Martin, thanks for the offer, but I already got some files produced in a professional studio.

Michael Klier and his colleague Christian Spellenberg did create the needed wav files and released them under a Creative Commons license. They are now included in the CAPTCHA plugin.
2006-11-17 11:36:04
4
How long does it take to combine the images this way? I'm wondering if it would be better to just pre-generate a bunch of them and then serve one up randomly. This way you could also do MP3 encoding as well if that takes up space and time. I'm not sure how the speed of PHP would compare to a similar compiled program. Any ideas?
2006-11-29 09:09:50
Kyle Mulka
5
The time to generate the whole audio file is nearly unnoticeable for the sizes we talk about here (using down sampled mono wavs). Pregenerating combined files is not feasible. For five characters and 26 possible letters for each it would need 26^5=11,881,376 different files. (Correct me if my math is flawed, it's early in the morning here)
2006-11-29 09:19:07
6
Hmm, how do I send the data to browser? with audio/x-wav header and echo?
2006-12-17 17:21:40
Stopp
7
Yes, use it like this:

<?php
$content = joinwavs(array('1.wav','2.wav'));
header('Content-Type: audio/x-wav');
echo $content;
?>
2006-12-18 15:38:52
8
Does it need some kind of extension? It doesn't really want to work.
2006-12-20 22:31:23
Stopp
9
in the readsubchunk2size part

$size  = unpack('vsize',fread($fp, 4));
$size  = $size['size'];


these 2 lines contradict why???
2007-01-02 04:54:20
manu
10
@Stopp: no extension is needed

@manu: they don't contradict. unpack returns an associative array, read the PHP manual.
2007-01-03 14:10:39
11
HI there, this is perfect for a project I'm working on, however I can't seem to get it to work.  I'm a bit of a php noob so this is probably something really simple.  When I use the example echo with the header statement shown above my browser displays the content of the stitched wavs, rather than playing the audio.  ie:  I get a screen full of gobbledygook.  Am I being dumb?
2007-01-12 14:21:43
Deicist
12
@Stopp: maybe you should check http://www.phon.ucl.ac.uk/home … o/play.htm
2007-01-20 01:26:19
HoTShoT
13
wow, this is really cool. I havent't used it yet but i will.
2007-02-09 23:05:14
bob
14
Great bit of code. Good work!
2007-02-12 20:32:48
greatspacecoaster
15
would it be possible to use this to manipulate a wav  file at binary level. Maybe like shuflle the song around abit?
2007-02-21 15:50:45
Dexter
16
Very halpeful for some people. Th@nks!
2007-03-28 15:59:09
17
what properties do the WAV-files need to have? i recorded WAV-files with the ubuntu recording tool but everythin i hear after joining the files is noise.
2007-04-01 23:00:07
sven
18
There is an error! The last letter is not spoken in totem-xine under ubuntu-linux 6.10. with totem-gstreamer there is no problem. Are there missing information in the file, or is the xine framework not able to handle WAV-files?
2007-04-06 01:58:29
sven
19
I tried using this method and wav stitching appears to work just fine when viewing with IE. But in FF and Safari for some reason only the first 2 audio parts (from 6) are sticked-played back. Any insites? Is this happening elsewhere also?
2007-04-14 09:08:56
Nick A.
20
Lovely work Andi.

Would you mind telling the php-challenged like me, in simple code and without the DokuWiki context of your plugin code,

how to prepare the $secretwordwavarray using $secretword and .wav in say /wavdirectory/ before we put it into

header('Content-Type: audio/x-wav');
echo joinwavs($secretwordwavarray);

I would truly appreciate your help, as it would make the captcha system I am trying out just a little more accessible.
2007-04-19 18:21:01
21
Perfect !! Perfect work my boy! Lets all continue to make this great Language Super! Thanks Andi for sharing this code..
2007-04-26 09:37:48
ruharo
22
Hmm... This a good alternative for captchas.
2007-05-16 16:02:22
23
I've tried to use this on my own captcha script and it works like a champ... only in IE.  

In FireFox I've been having a whole host of problems, not the least of which is the one mentioned earlier about it only speaking the first bit.  Seems as though FireFox doesn't like to load the entire wav before spitting it out to the browser.  Not sure what the deal is.  I've tried forcing it to sleep for a second or so before spitting it out, but it still only plays the first half-second or so.
2007-06-07 04:13:54
24
I got the same problem as Ed with Firefox.
Anyway maybe this is a usefull list for some people who wants to record it.

http://www.informationstart.com/sa.htm
2007-06-23 18:50:39
Thijs
25
nice work!  I'm posting this 2 cents from firefox and the audio works fine on your site.  I'll be back after I try it on my own server.
2007-08-30 21:09:49
26
Well I kinda got it working.  Some of my wav files work and some don't.  They all work when u play them individually but only 4 of them will sow together.  

I have the digits:  This will try to do them all

http://demo.wazzuplocal.com/te … 0123456789

They were all recorded with Audacity

get them individually from
http://demo.wazzuplocal.com/au/en/0.wav   ... 1.wav etc.  
any idea what's wrong with the bad files?
2007-08-31 23:11:37
27
Me again, I forgot to mention I am using xmms for playback on linux and quicktime on windows(firefox).  and it works even less well on windows it only says one of the digits.  on the linux box it says four of them.   This may be firefox only getting the first bit but when I try on IE7 I don't hear anything.  I cant even play the individual wav files on IE much less sow them together.  Oh well....
2007-08-31 23:25:12
28
well i tweaked the security settings on IE and put my server in the trusted zone and connected to it over https because I happen to have that going, and then it worked better than firefox on windows it said the four digits that "work."  But this is not a general solution, average joe blind guy isn't going to do that and a lot of sites would not have https.    PHOOEY
2007-08-31 23:32:50
29
OK It all works now, it was a typo in the header

Great.
2007-09-15 06:31:22
30
<?php
// I really appreciated what you had posted on your site, it was very useful! I had to make some modifications to make it work for me, I wanted to share it as well

// this code assumes that you have the .wav files for a to z and 1 to 10 sitting in a folder called captcha_wav in the same directory as this php file
// the security code variable will have to be hooked up to the session variable you are holding the captcha string



$SecurityCode = "beb9";
$localCode = str_split(strtolower($SecurityCode)); // bring the captcha string into a local array and make sure it is lower case to match the filenames.
$wav_array = array(); // make another array to hold the filenames

foreach($localCode as $character){ // for each character in the character string add it as an element to an array with the remainder of the filename, in order to get 1.wav or c.wav
array_push($wav_array, $character.".wav");
}


header('Content-Type: audio/x-wav'); // create a header specifying audio
$content = joinwavs($wav_array); //  execute the joinwavs function
echo $content; // output the binary of the new wav



function joinwavs($wavs){
   $data = ''; // the binary block of audio data
   $tsize = 0; // the running total of the file size

   foreach($wavs as $wav){

       $fp     = fopen("captcha_wav/".$wav,'rb'); // open the wave file up, you may want to put the directory up in the array creaation function to consolidate filename and filepath specification

       $header = fread($fp,50); // !!!!!!!!!! IMPORTANT !!!!!!!! Online documentation says that the header information should be 40 bits before running into the "data" delimiter.  This may be related to Windows soundpad. But I have 50 insted of 40.  Took a long time to figure this problem out.

       // read SubChunk2ID
       $wordata = fread($fp,4); // to test that you are reading the header correctly you can echo this variable.  If you get the word "data", you are fine. If you don't, then echo the binary to the screen and try to figure out where you are in relation to data and then adjust the number of bits being read out of the header accordingly

   $header .= $wordata; // add the word "data" back to the header
       
       // read Subchunk2Size
       $originalBytes=fread($fp, 4);  // this is the four bit field that comes next,
       

       
       $size  = unpack('Vsize',$originalBytes);  // get the numeric value of the data size, it is a CAPITAL V

   $tsize  += $size['size']; // add this size to the running total of data size
       
   
       // read the data, which is the remainder of the file, I came up with an arbitrary maximum here
       $data .= fread($fp,100000);

   // close the data input stream
   fclose($fp);
   }
   
   return $header.pack('V',$tsize).$data;

}




?>
2007-11-29 19:45:18
Andrew
31
It's nice in theory, but I can't get the code to work, sadly. I'll be working on one of my own though. You've laid the foundations. Thanks!
2007-12-07 00:27:16
32
hmm nice idea, but isnt sound much easier to identify for a program if it knows all the 26 single files? it could just "reverse" the splitting and compare the files with the 26 single files and you would have "broken" the captcha...
2008-03-02 21:30:05
shat
CAPTCHA

No HTML allowed. URLs will be linked with nofollow attribute. Whitespace is preserved.

 
 

Blog

Older Weblog articles are available in the Archive, subscribe to the
Full Content RSS Feed
to stay tuned. (learn more)

Subscribe to the Feed

Recent Blog Entries

 

This is the personal web site of Andreas Gohr - human being, blogger and web geek from Berlin, Germany.

This page was last updated at 2006/11/16 00:05.
Imprint/Impressum

Tagged at del.icio.us:

Bookmarks: 30

View blog reactions

Recent readers: