Subscribe to RSS feed

splitbrain.org - electronic brain surgery since 2001

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 (3 years 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
33
Nice, function very smart and very small.
Found 1 error :

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

should be (note the letter V in uppercase) =>

$size  = unpack('Vsize',fread($fp, 4));

... without this change, my test resulting in a very short generated wav as output.

Thanks.
2008-09-05 10:39:37
Partner
34
2009-03-08 08:49:09
Sirozz
35
isn't this horribly insecure; akin to creating a visual CAPTCHA with a simple call to ImageString; i.e. no obfuscation is performed whatsoever, so all the attacker needs to do is match the waveforms to extract the letters?

Nice code though!
2009-05-01 00:45:23
Jimbo
36
follow post 30 for perfection!
2009-06-21 23:54:08
37
I have 2 wav files:

http://www.leidou.hk/wavs/stre … ton_rd.wav

http://wwwleidou.hk/wavs/stree … ood_rd.wav

I create a new blank test.php file with only the contents of the above function, and also the following to call the function:

<?php
$content = joinwavs(array('hollywood_rd.wav','johnston_rd.wav'));
header('Content-Type: audio/x-wav');
echo $content;
?>

The resulting output does not seem to work. Any ideas on what I am doing wrong? Any advice would be greatly appreciated: dru.lie@gmail.com

Thanks so much!
2009-07-22 10:03:47
Andrew Lie
38
Andrew, something seems to be wrong with your .wav files. What I did is run them through sox and use the resulting wavs which did fix the problem for me:

$> sox hollywood_rd.wav 1.wav
$> sox johnston_rd.wav 2.wav
2009-07-22 10:12:21
39
Andi, would you have any directions on how I can use sox? Is there a way I can run my wav files dynamically through sox using PHP?

On another note, I tested the code using 2 sample wav files in the above links from other comments. On Firefox, it played only a very short sample of the first sound clip. On Safari, it worked fine. Do you know if there is a fix for this?

Thanks very much Andi!
2009-07-22 10:36:05
Andrew Lie
40
This is my version to distort the captcha sound and add white noise to make it better. The wav files should be 22.1khz, 16bit, and they should be stored in sub directory wavs/. It has four components: html, player, captcha generation, and validation.

1. captcha.html
<html>
<head>
<title>Captcha</title>
<script type="text/javascript">
function reload () {
var f = document.getElementById('captcha');
f.src = f.src;
}
</script>
</head>
<body>

<iframe  id="captcha" frameborder="0"  vspace="0"  hspace="0"  marginwidth="0"  marginheight="0" width="100"  scrolling="yes"  height="100"  src="player.html"></iframe>

<input type="button" value="New Response" onclick="reload();">

<form action='validate.php' method='post''>
<input type='text' name='captcha'>
<input type='submit'>
</form>
</body>
</html>

2. player.html
<OBJECT ID="MediaPlayer" WIDTH="160" HEIGHT="120" CLASSID="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
STANDBY="Loading Windows Media Player components..." TYPE="application/x-oleobject">
<PARAM NAME="FileName" VALUE="captcha.php">
<PARAM name="autostart" VALUE="true">
<PARAM name="ShowControls" VALUE="true">
<param name="ShowStatusBar" value="false">
<PARAM name="ShowDisplay" VALUE="false">
<EMBED TYPE="application/x-mplayer2" SRC="captcha.php" NAME="MediaPlayer"
WIDTH="160" HEIGHT="120" ShowControls="1" ShowStatusBar="0" ShowDisplay="0" autostart="1"> </EMBED>
</OBJECT>

3. captcha.php
<?php
session_start();
$path = "wavs/";
$choices = "0123456789abcdefghijklmnopqrstuvwxyz";
$choices = str_split($choices);
$SecurityCode = "";
for ($i = 0; $i < 8; $i++) {
$SecurityCode .= $choices[rand(0, count($choices)-1)];
}
$_SESSION['captcha']=$SecurityCode;
$_SESSION['captchaExpire']=time();
session_write_close();              
$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, $path.$character.".wav");
}

$content = joinwavs($wav_array); //  execute the joinwavs function
header('Content-Type: audio/x-wav'); // create a header specifying audio
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($wav,'rb'); // open the wave file up.
$header = fread($fp,42);

$originalBytes=fread($fp, 4);
$size = unpack('Vsize',$originalBytes);
$tsize  += $size['size']; // add this size to the running total of data size

$fsize = filesize($wav)-46;
$data .= fread($fp,$fsize);

fclose($fp);
}

$tmp = unpack("s*",$data);
$tsize=0;
$data = "";
for ($i = 0; $i < 22050; $i++) {
$data .= pack('s*',(int)rand(-500,500));
        $tsize++;
}

for ($i = 1; $i < count($tmp); $i++) {
$tmp[$i] += $tmp[rand(1, count($tmp))]*0.25+$tmp[count($tmp)-$i]*0.25+rand(-500,500);;
$data .= pack('s*',(int)$tmp[$i]);
$tsize++;
}

for ($i = 0; $i < 22050; $i++) {
$data .= pack('s*',(int)rand(-500,500));
        $tsize++;
}

                return $header.pack('V',$tsize*2).$data;

}

?>


4. validate.php
<?php
session_start();
if ($_SESSION['captchaExpire']+60 > time()) {
if ($_SESSION['captcha'] == $_POST['captcha']) echo "You're passed.";
else echo "You're not allowed. The captia was ".$_SESSION['captcha'].", but you typed ".$_POST['captcha'];
}
else echo "Time expired.";
?>
2009-08-06 03:43:21
Chi Kim
41
For some reason, I can't play the wav file generated with the code above in IE on vista, but I can in IE on xp. Embedded player says it's not a supported media type. However, if I download the file and play it on windows media player, it works.
2009-08-06 04:55:31
Chi Kim
42
Adding random white noise is a good idea, I will maybe add that to my CAPTCHA implementation.
2009-08-06 09:22:15
43
I can't seem to get this to work, I have a bunch of wav files that contain metadata (could this be the problem?) I've uploaded them to the server, but when I goto the page, quicktime gives me a ?. What am I doing wrong?
2009-08-27 23:38:41
44
Update:
It still doesn't work, but this time when I try to play it it sounds like it's been sped up 100000 times, and in IE it doesn't work at all
2009-08-27 23:55:24
45
This worked great for me. I ran into issues passing the file through the browser in initial testing but that actually doesn't matter to me. I write the new file to the file system and it works like a charm! Thanks for this code and all the comments guys!
2009-09-24 18:11:19
Neil
46
My daughter-in-law is studying to be a court translator and was having problems with numbers.I created a PHP program that generated random numbers to practice.
The problem was she needed numbers spoken - thus your function.I used Audacity software to record all of the numbers I needed into one stream and used the "Export selection as WAV" option to create the necessary files.
1.wav, 2.wav.....
I put the array of wav files into a cookie:
$ck =implode(",", $wav);
setcookie("ck", $ck);
and because of header("Content-type: audio/x-wav"); called speaknum.php to do the talking
header("Location: speaknum.php");
2009-10-21 18:10:17
Bob Hughes
47
Hi,

Can this be used to take sample from wav file, I need a script that can input a .wav file and take a sample of like first 10 secons and save to another file like this one here http://www.sourcerally.net/Scr … -MP3-Class (its for mp3 sampling)

Thanks,
Atiq
2009-11-13 11:23:54
Atiq
CAPTCHA

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