kerneltest/e32test/multimedia/t_soundwav.cpp
changeset 43 96e5fb8b040d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/multimedia/t_soundwav.cpp	Thu Dec 17 09:24:54 2009 +0200
@@ -0,0 +1,877 @@
+// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the License "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// e32test\multimedia\t_soundwav.cpp
+// Record wav to a file "t_soundwav filename [rate] [channels] [seconds]" where filename does not exist.
+// Play a wav file "t_soundwav filename" where filename exists.
+// 
+//
+
+/**
+ @file Shared chunk sound driver WAV file player and recorder utility.
+*/
+
+#include <e32test.h>
+#include <e32def.h>
+#include <e32def_private.h>
+#include "t_soundutils.h"
+#include <f32file.h>
+
+#define CHECK(aValue) {Test(aValue);}
+#define CHECK_NOERROR(aValue) { TInt v=(aValue); if(v) { Test.Printf(_L("Error value = %d\n"),v); Test(EFalse,__LINE__); }}
+#define CHECK_EQUAL(aValue1,aValue2) { TInt v1=(aValue1); TInt v2=(aValue2); if(v1!=v2) { Test.Printf(_L("Error value = %d\n"),v1); Test(EFalse,__LINE__); }}
+
+_LIT(KSndLddFileName,"ESOUNDSC.LDD");
+_LIT(KSndPddFileName,"SOUNDSC.PDD");
+
+RTest Test(_L("T_SOUNDWAV"));
+RSoundSc TxSoundDevice;
+RSoundSc RxSoundDevice;
+
+TSoundFormatsSupportedV02Buf RecordCapsBuf;
+TSoundFormatsSupportedV02Buf PlayCapsBuf;
+TCurrentSoundFormatV02Buf PlayFormatBuf;
+TCurrentSoundFormatV02Buf RecordFormatBuf;
+
+TBuf<256> CommandLine;
+RFs Fs;
+
+//
+// This is the WAV header structure used for playing and recording files
+//
+struct WAVEheader
+	{
+	char ckID[4];				// chunk id 'RIFF'
+	TUint32 ckSize;				// chunk size
+	char wave_ckID[4];			// wave chunk id 'WAVE'
+	char fmt_ckID[4];			// format chunk id 'fmt '
+	TUint32 fmt_ckSize;			// format chunk size
+	TUint16 formatTag;			// format tag currently pcm
+	TUint16 nChannels;			// number of channels
+	TUint32 nSamplesPerSec;		// sample rate in hz
+	TUint32 nAvgBytesPerSec;	// average bytes per second
+	TUint16 nBlockAlign;		// number of bytes per sample
+	TUint16 nBitsPerSample;		// number of bits in a sample
+	char data_ckID[4];			// data chunk id 'data'
+	TUint32 data_ckSize;		// length of data chunk
+	};
+	
+LOCAL_C TInt SamplesPerSecondToRate(TInt aRateInSamplesPerSecond,TSoundRate& aRate)
+	{
+	switch (aRateInSamplesPerSecond)
+		{
+		case 7350: 	aRate=ESoundRate7350Hz; break;
+		case 8000: 	aRate=ESoundRate8000Hz; break;
+		case 8820: 	aRate=ESoundRate8820Hz; break;
+		case 9600: 	aRate=ESoundRate9600Hz; break;
+		case 11025: aRate=ESoundRate11025Hz; break;
+		case 12000: aRate=ESoundRate12000Hz; break;
+		case 14700:	aRate=ESoundRate14700Hz; break;
+		case 16000: aRate=ESoundRate16000Hz; break;
+		case 22050: aRate=ESoundRate22050Hz; break;
+		case 24000: aRate=ESoundRate24000Hz; break;
+		case 29400: aRate=ESoundRate29400Hz; break;
+		case 32000: aRate=ESoundRate32000Hz; break;
+		case 44100: aRate=ESoundRate44100Hz; break;
+		case 48000: aRate=ESoundRate48000Hz; break;
+		default: return(KErrArgument);
+		};
+	return(KErrNone);	
+	}
+	
+LOCAL_C TInt RecordBufferSizeInBytes(TCurrentSoundFormatV02& aFormat)
+	{
+	switch (aFormat.iRate)
+		{
+		case ESoundRate7350Hz: return(8192);
+		case ESoundRate8000Hz: return(8192);
+		case ESoundRate8820Hz: return(12288);
+		case ESoundRate9600Hz: return(12288);
+		case ESoundRate11025Hz: return(12288);
+		case ESoundRate12000Hz: return(12288);
+		case ESoundRate14700Hz: return(12288);
+		case ESoundRate16000Hz: return(12288);
+		case ESoundRate22050Hz: return(16384);
+		case ESoundRate24000Hz: return(16384);
+		case ESoundRate29400Hz: return(24576);
+		case ESoundRate32000Hz: return(24576);
+		case ESoundRate44100Hz: return(32768);
+		case ESoundRate48000Hz: return(32768);
+		default: return(32768);
+		};	
+	}	
+	
+LOCAL_C TInt Load()
+	{
+	TInt r;
+
+	Test.Start(_L("Load sound PDD"));
+	r=User::LoadPhysicalDevice(KSndPddFileName);
+	if (r==KErrNotFound)
+		{
+		Test.End();
+		return(r);
+		}
+	CHECK(r==KErrNone || r==KErrAlreadyExists);
+	
+	Test.Next(_L("Load sound LDD"));
+	r=User::LoadLogicalDevice(KSndLddFileName);
+	CHECK(r==KErrNone || r==KErrAlreadyExists);
+
+	Test.End();
+	return(KErrNone);
+	}
+
+LOCAL_C TInt WavPlay()
+	{
+	RChunk chunk;
+	
+	// Parse the commandline and get a filename to use
+	TLex l(CommandLine);
+	TFileName thisfile=RProcess().FileName();
+	TPtrC token=l.NextToken();
+	if (token.MatchF(thisfile)==0)
+		token.Set(l.NextToken());
+
+	if (token.Length()==0)
+		{
+		// No args, skip to end
+		Test.Printf(_L("Invalid configuration\r\n"));
+		return(KErrArgument);
+		}
+		
+	Test.Next(_L("Play Wav file"));
+
+	// Assume that the argument is a WAV filename
+	TFileName wavFilename=token;
+	TInt r;
+	RFile source;
+	r = source.Open(Fs,wavFilename,EFileRead);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Open failed(%d)\r\n"), r);
+		return(r);
+		}
+
+	// Read the pcm header
+	WAVEheader header;
+	TPtr8 headerDes((TUint8 *)&header,sizeof(struct WAVEheader),sizeof(struct WAVEheader));
+	r = source.Read(headerDes);
+	if (r!=KErrNone)
+		{
+		source.Close();
+		return(r);
+		}
+	Test.Printf(_L("Header Read %d bytes\r\n"),headerDes.Size());
+
+	if (headerDes.Size() != sizeof(struct WAVEheader)) // EOF
+		{
+		Test.Printf(_L("Couldn't read a header(%d bytes)\r\n"),headerDes.Size());
+		source.Close();
+		return(KErrCorrupt);
+		}
+
+	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\n"),
+			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
+			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize);
+
+	if (header.formatTag != 1) // not pcm
+		{
+		Test.Printf(_L("Format not PCM(%d)\r\n"),header.formatTag);
+		source.Close();
+		return(KErrNotSupported);
+		}
+
+	if (header.nBitsPerSample != 16) // not 16 bit
+		{
+		Test.Printf(_L("Format not 16 bit PCM(%d bits)\r\n"),header.nBitsPerSample);
+		source.Close();
+		return(KErrNotSupported);
+		}
+		
+	TSoundRate rate;	
+	if (SamplesPerSecondToRate(header.nSamplesPerSec,rate)!=KErrNone)	
+		{
+		Test.Printf(_L("Format specifies a rate not supported(%d)\r\n"),header.nSamplesPerSec);
+		source.Close();
+		return(KErrNotSupported);
+		}
+
+	TxSoundDevice.AudioFormat(PlayFormatBuf);	// Read back the current setting which must be valid.
+	PlayFormatBuf().iChannels = header.nChannels;
+	PlayFormatBuf().iRate = rate;
+	PlayFormatBuf().iEncoding = ESoundEncoding16BitPCM;
+	
+	// Set the play buffer configuration.
+	TInt bufSize=BytesPerSecond(PlayFormatBuf())/8; 	// Large enough to hold 1/8th second of data.
+	bufSize&=~(header.nBlockAlign-1);					// Keep the buffer length a multiple of the bytes per sample (assumes 16bitPCM, 1 or 2 chans).
+	if (PlayCapsBuf().iRequestMinSize)
+		bufSize&=~(PlayCapsBuf().iRequestMinSize-1); 	// Keep the buffer length valid for the driver.
+	TTestSharedChunkBufConfig bufferConfig;
+	bufferConfig.iNumBuffers=3;
+	bufferConfig.iBufferSizeInBytes=bufSize;
+	bufferConfig.iFlags=0;	
+	PrintBufferConf(bufferConfig,Test);
+	TPckg<TTestSharedChunkBufConfig> bufferConfigBuf(bufferConfig);
+	r=TxSoundDevice.SetBufferChunkCreate(bufferConfigBuf,chunk);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Buffer configuration not supported(%d)\r\n"),r);
+		source.Close();
+		return(r);
+		}
+	TxSoundDevice.GetBufferConfig(bufferConfigBuf);			// Read back the configuration - to get the buffer offsets
+	CHECK(bufferConfig.iBufferSizeInBytes==bufSize);
+
+	// Set the audio play configuration.
+	TxSoundDevice.SetVolume(KSoundMaxVolume - (KSoundMaxVolume / 4)); // set volume to 75%
+	PrintConfig(PlayFormatBuf(),Test);
+	r=TxSoundDevice.SetAudioFormat(PlayFormatBuf);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Format not supported\r\n"));
+		source.Close();
+		chunk.Close();
+		return(r);
+		}
+	TxSoundDevice.ResetBytesTransferred();
+
+	TInt32 bytesToPlay = header.data_ckSize;
+	TTime starttime;
+	starttime.HomeTime();
+
+	TRequestStatus stat[3];
+	TPtr8* tPtr[3];
+	TInt i;
+	for (i=0;i<3;i++)
+		tPtr[i]=new TPtr8(NULL,0); 
+
+	TTime startTime;
+	startTime.HomeTime();
+	
+	// Start off by issuing a play request for each buffer (assuming that the file is long enough). Use the full size
+	// of each buffer.
+	TInt stillToRead=bytesToPlay;
+	TInt stillNotPlayed=bytesToPlay;
+	TUint flags;
+	for (i=0 ; i<3 ; i++)
+		{
+		// Setup the descriptor for reading in the data from the file.
+		tPtr[i]->Set(chunk.Base()+bufferConfig.iBufferOffsetList[i],0,bufSize); 
+		
+		// If there is still data to read to play then read this into the descriptor
+		// and then write it to the driver.
+		if (stillToRead)
+			{
+			r=source.Read(*tPtr[i],Min(stillToRead,bufSize));
+			if (r!=KErrNone)
+				{
+				Test.Printf(_L("Initial file read error(%d)\r\n"),r);
+				source.Close();
+				chunk.Close();
+				return(r);
+				}
+			stillToRead-=tPtr[i]->Length();
+			flags=(stillToRead>0)?0:KSndFlagLastSample;
+			TxSoundDevice.PlayData(stat[i],bufferConfig.iBufferOffsetList[i],tPtr[i]->Length(),flags);
+			}
+		else
+			stat[i]=KRequestPending;	
+		}	
+		
+	FOREVER
+		{
+		// Wait for any one of the outstanding play requests to complete.
+		User::WaitForAnyRequest();
+
+		TTime currentTime;
+		currentTime.HomeTime();
+		TInt64 elapsedTime = currentTime.Int64()-startTime.Int64();	// us
+		TTimeIntervalMicroSecondsBuf timePlayedBuf;
+		if(TxSoundDevice.TimePlayed(timePlayedBuf) == KErrNone)
+			{
+			// Compare TimePlayed with the actual elapsed time. They should be different, but not drift apart too badly...
+			TInt32 offset = TInt32(elapsedTime - timePlayedBuf().Int64());
+			Test.Printf(_L("\telapsedTime - TimePlayed = %d ms\n"), offset/1000);
+			}		
+	
+		// Work out which buffer this applies to
+		for (i=0 ; i<3 ; i++)
+			{
+			if (stat[i]!=KRequestPending)
+				break;
+			}
+		if (i>=3)
+			{
+			Test.Printf(_L("I/O error\r\n"));
+			source.Close();
+			chunk.Close();
+			return(KErrGeneral);
+			}
+	
+		// Check that the transfer was succesful and whether we have now played all the file.
+		if (stat[i]!=KErrNone)
+			{
+			Test.Printf(_L("Play error(%d)\r\n"),stat[i].Int());
+			source.Close();
+			chunk.Close();
+			return(stat[i].Int());
+			}
+		Test.Printf(_L("Played %d bytes(%d) - %d\r\n"),tPtr[i]->Length(),i,stat[i].Int());
+		stillNotPlayed-=tPtr[i]->Length();
+		CHECK(stillNotPlayed>=0);
+		if (!stillNotPlayed)
+			break;
+	
+		// Still more to be played so read the next part of the file into the descriptor for this
+		// buffer and then write it to the driver.
+		if (stillToRead)
+			{
+			TInt len=Min(stillToRead,bufSize);
+		
+			// If we've got to the end of the file and the driver is particular about the request length then 
+			// zero fill the entire buffer so we can play extra zeros after the last sample from the file.
+			if (len<bufSize && PlayCapsBuf().iRequestMinSize)
+				tPtr[i]->FillZ(bufSize);
+		
+			// Read the next part of the file 
+			r=source.Read(*tPtr[i],len);			// This will alter the length of the descriptor
+			if (r!=KErrNone)
+				{
+				Test.Printf(_L("File read error(%d)\r\n"),r);
+				source.Close();
+				chunk.Close();
+				return(r);
+				}
+			stillToRead-=tPtr[i]->Length();
+		
+			// If we've got to the end of the file and the driver is particular about the request length then
+			// round up the length to the next valid boundary. This is OK since we zero filled.
+			if (tPtr[i]->Length() < bufSize && PlayCapsBuf().iRequestMinSize)
+				{
+				TUint m=PlayCapsBuf().iRequestMinSize-1;
+				len=(tPtr[i]->Length() + m) & ~m;
+				}
+		
+			// Write it to the driver.
+			flags=(stillToRead>0)?0:KSndFlagLastSample;
+			TxSoundDevice.PlayData(stat[i],bufferConfig.iBufferOffsetList[i],len,flags);
+			}
+		else
+			stat[i]=KRequestPending;		
+		}
+	
+	// Delete all the variables again.	
+	for (i=0 ; i<3 ; i++)
+		delete tPtr[i];
+	
+	TTime endtime;
+	endtime.HomeTime();
+
+	Test.Printf(_L("Done playing\r\n"));
+	Test.Printf(_L("Bytes played = %d\r\n"),TxSoundDevice.BytesTransferred());
+	Test.Printf(_L("Delta time = %d\r\n"),endtime.Int64()-starttime.Int64());
+
+	chunk.Close();
+	source.Close();
+	return(KErrNone);
+	}
+
+LOCAL_C TInt WavRecord()
+	{
+	// Parse the commandline and get a filename to use
+	TLex l(CommandLine);
+	TParse destinationName;
+	if (destinationName.SetNoWild(l.NextToken(),0,0)!=KErrNone)
+		{
+		Test.Printf(_L("No arg, skipping\r\n"));
+		return(KErrArgument);
+		}
+	Test.Next(_L("Record Wav file"));
+
+	// Open the file for writing
+	TInt r;
+	RFile destination;
+	r = destination.Replace(Fs,destinationName.FullName(),EFileWrite);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Open file for write failed(%d)\n"), r);
+		return(r);
+		}		
+	Test.Printf(_L("File opened for write\r\n"));
+	
+	Test.Next(_L("Preparing to record"));
+	
+	// Get the rate
+	TLex cl(l.NextToken());
+	TUint32 tmpRate;
+	TSoundRate rate;
+	r = cl.Val(tmpRate,EDecimal);
+	if (r == KErrNone && (r=SamplesPerSecondToRate(tmpRate,rate))==KErrNone)
+		{
+		Test.Printf(_L("Parsed rate: %d\r\n"), tmpRate);
+		RecordFormatBuf().iRate = rate;
+		}
+	else
+		{
+		Test.Printf(_L("Parse rate failed(%d)\r\n"),r);
+		RecordFormatBuf().iRate = ESoundRate32000Hz;
+		}
+
+	// Get number of channels
+	TLex cl_chan(l.NextToken());
+	TUint32 tmpChannels;
+	r = cl_chan.Val(tmpChannels,EDecimal);
+	if (r == KErrNone)
+		{
+		Test.Printf(_L("Parsed %d channels\r\n"),tmpChannels);
+		RecordFormatBuf().iChannels = tmpChannels;
+		}
+	else
+		{
+		Test.Printf(_L("Parse channels failed(%d)\r\n"), r);
+		RecordFormatBuf().iChannels = 2;
+		}
+
+	RecordFormatBuf().iEncoding = ESoundEncoding16BitPCM;
+	
+	// Set the record buffer configuration.
+	RChunk chunk;
+	TTestSharedChunkBufConfig bufferConfig;
+	bufferConfig.iNumBuffers=4;
+	bufferConfig.iBufferSizeInBytes=RecordBufferSizeInBytes(RecordFormatBuf());
+	if (RecordCapsBuf().iRequestMinSize)
+		bufferConfig.iBufferSizeInBytes&=~(RecordCapsBuf().iRequestMinSize-1); 	// Keep the buffer length valid for the driver.
+	bufferConfig.iFlags=0;	
+	PrintBufferConf(bufferConfig,Test);
+	TPckg<TTestSharedChunkBufConfig> bufferConfigBuf(bufferConfig);
+	r=RxSoundDevice.SetBufferChunkCreate(bufferConfigBuf,chunk);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Buffer configuration not supported(%d)\r\n"),r);
+		return(r);
+		}
+	
+	// Set the audio record configuration.
+	RxSoundDevice.SetVolume(KSoundMaxVolume);
+	PrintConfig(RecordFormatBuf(),Test);
+	r=RxSoundDevice.SetAudioFormat(RecordFormatBuf);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Format not supported\r\n"));
+		return(r);
+		}
+
+	// Get length in seconds
+	TLex cl_seconds(l.NextToken());
+	TUint32 tmpSeconds;
+	r = cl_seconds.Val(tmpSeconds,EDecimal);
+	if (r == KErrNone)
+		{
+		Test.Printf(_L("Parsed %d seconds\r\n"),tmpSeconds);
+		}
+	else
+		{
+		Test.Printf(_L("Parse seconds failed(%d)\r\n"),r);
+		tmpSeconds=10;
+		}
+	TInt bytesToRecord = BytesPerSecond(RecordFormatBuf())*tmpSeconds;	
+		
+	Test.Next(_L("Recording..."));
+	
+	// Lay down a file header
+	WAVEheader header;
+	TPtr8 headerDes((TUint8 *)&header, sizeof(struct WAVEheader), sizeof(struct WAVEheader));
+
+	// "RIFF"
+	header.ckID[0] = 'R'; header.ckID[1] = 'I';
+	header.ckID[2] = 'F'; header.ckID[3] = 'F';
+	// "WAVE"
+	header.wave_ckID[0] = 'W'; header.wave_ckID[1] = 'A';
+	header.wave_ckID[2] = 'V'; header.wave_ckID[3] = 'E';
+	// "fmt "
+	header.fmt_ckID[0] = 'f'; header.fmt_ckID[1] = 'm';
+	header.fmt_ckID[2] = 't'; header.fmt_ckID[3] = ' ';
+	// "data"
+	header.data_ckID[0] = 'd'; header.data_ckID[1] = 'a';
+	header.data_ckID[2] = 't'; header.data_ckID[3] = 'a';
+
+	header.nChannels		= (TUint16)RecordFormatBuf().iChannels;
+	header.nSamplesPerSec	= RateInSamplesPerSecond(RecordFormatBuf().iRate);
+	header.nBitsPerSample	= 16;
+	header.nBlockAlign		= TUint16((RecordFormatBuf().iChannels == 2) ? 4 : 2);
+	header.formatTag		= 1;	// type 1 is PCM
+	header.fmt_ckSize		= 16;
+	header.nAvgBytesPerSec	= BytesPerSecond(RecordFormatBuf());
+	header.data_ckSize		= bytesToRecord;
+	header.ckSize			= bytesToRecord + sizeof(struct WAVEheader) - 8;
+
+	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\r\n"),
+			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
+			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize, sizeof(struct WAVEheader));
+
+	r = destination.Write(headerDes);
+
+	TRequestStatus stat;
+	TInt length;
+	TPtrC8 buf;
+
+	TTime startTime;
+	startTime.HomeTime();
+	
+	// Start off by issuing a record request.
+	TTime starttime;
+	starttime.HomeTime();
+	TInt bytesRecorded = 0;
+	RxSoundDevice.RecordData(stat,length);
+
+	TInt pausesToDo = 10;
+	pausesToDo = 0;
+	FOREVER
+		{
+		// Wait for the outstanding record request to complete.
+        User::After(6000);
+
+		User::WaitForAnyRequest();
+		if (stat==KRequestPending)
+			return(KErrGeneral);
+
+		TTime currentTime;
+		currentTime.HomeTime();
+		TInt64 elapsedTime = currentTime.Int64()-startTime.Int64();	// us
+		TTimeIntervalMicroSecondsBuf timeRecordedBuf;
+		if(RxSoundDevice.TimeRecorded(timeRecordedBuf) == KErrNone)
+			{
+			// Compare TimeRecorded with the actual elapsed time. They should be different, but not drift apart too badly...
+			TInt32 offset = TInt32(elapsedTime - timeRecordedBuf().Int64());
+			Test.Printf(_L("\telapsedTime - TimeRecorded = %d ms\n"), offset/1000);
+			}		
+			
+		// Check whether the record request was succesful.
+		TInt retOffset=stat.Int();
+		if (retOffset<0)
+			{
+			Test.Printf(_L("Record failed(%d)\r\n"),retOffset);
+			return(retOffset);
+			}
+		
+		// Successfully recorded another buffer so write the recorded data to the record file and release the buffer.
+		buf.Set((const TUint8*)(chunk.Base()+retOffset),length);
+		r=destination.Write(buf);
+		if (r!=KErrNone)
+			{
+			Test.Printf(_L("File write failed(%d)\r\n"),r);
+			return(r);
+			}
+		r=RxSoundDevice.ReleaseBuffer(retOffset);
+		if (r!=KErrNone)
+			{
+			Test.Printf(_L("Release buffer failed(%d)\r\n"),r);
+			return(r);
+			}
+		
+		Test.Printf(_L("Recorded %d more bytes - %d\r\n"),length,retOffset);
+
+		if((pausesToDo > 0) && (bytesRecorded > bytesToRecord/2))
+			{
+			--pausesToDo;
+			Test.Printf(_L("Pause\r\n"));
+			RxSoundDevice.Pause();
+			Test.Printf(_L("Paused, sleeping for 0.5 seconds\r\n"));
+			User::After(500*1000);
+            Test.Printf(_L("Resume\r\n"));
+			RxSoundDevice.Resume();
+			}
+		
+		// Check whether we have now recorded all the data. If more to record then queue a further request
+		bytesRecorded+=length;
+		if (bytesRecorded<bytesToRecord)
+		    {
+            Test.Printf(_L("RecordData\r\n"));
+			RxSoundDevice.RecordData(stat,length);
+		    }
+		else
+			break;
+		}
+	
+	RxSoundDevice.CancelRecordData();	// Stop the driver from recording.
+	
+	TTime endtime;
+	endtime.HomeTime();
+
+	TInt64 elapsedTime = endtime.Int64()-starttime.Int64();	// us
+	Test.Printf(_L("Delta time = %d\r\n"),I64LOW(elapsedTime));
+	Test.Printf(_L("Seconds in buffer: %d (%d)\r\n"), bytesRecorded / header.nAvgBytesPerSec, (bytesRecorded / header.nAvgBytesPerSec)*1000000);
+
+	if (I64LOW(elapsedTime) <= (bytesRecorded / header.nAvgBytesPerSec)*1000000)
+		{
+		Test.Printf(_L("Time travelling; record took less time than it should have done\r\n"));
+		return(KErrGeneral);
+		}
+	
+	chunk.Close();
+	destination.Close();
+
+	Test.Printf(_L("Record finished\r\n"));
+	return(KErrNone);
+	}
+	
+// Quick test block to write the output of the signal generator to a file.
+// You'll need to comment out the reference to playdata in writetone and link
+// the function into the command line processing (search on this function name).	
+/*
+LOCAL_C void TestWaveformGenerator()
+	{
+	
+	Test.Next(_L("Testing waveform generator"));
+	// Parse the commandline and get a filename to use
+	TLex l(CommandLine);
+	TParse destinationName;
+	if (destinationName.SetNoWild(l.NextToken(),0,0)!=KErrNone)
+		{
+		Test.Printf(_L("No arg, skipping\r\n"));
+		return;
+		}
+
+	// Open the file for writing
+	TInt r;
+	RFile destination;
+	r = destination.Replace(Fs,destinationName.FullName(),EFileWrite);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Open file for write failed(%d)\r\n"), r);
+		}
+	
+	Test.Printf(_L("File opened for write\r\n"));
+	Test.Next(_L("Preparing to record data"));
+
+	// Get the rate
+	TLex cl(l.NextToken());
+	TUint32 tmpRate;
+	TSoundRate rate;
+	r = cl.Val(tmpRate,EDecimal);
+	if (r == KErrNone && (r=SamplesPerSecondToRate(tmpRate,rate))==KErrNone)
+		{
+		Test.Printf(_L("Parsed rate: %d\r\n"), tmpRate);
+		PlayFormatBuf().iRate = rate;
+		}
+	else
+		{
+		Test.Printf(_L("Parse rate failed(%d)\r\n"),r);
+		PlayFormatBuf().iRate = ESoundRate32000Hz;
+		}
+
+	// Get number of channels
+	TLex cl_chan(l.NextToken());
+	TUint32 tmpChannels;
+	r = cl_chan.Val(tmpChannels,EDecimal);
+	if (r == KErrNone)
+		{
+		Test.Printf(_L("Parsed %d channels\r\n"),tmpChannels);
+		PlayFormatBuf().iChannels = tmpChannels;
+		}
+	else
+		{
+		Test.Printf(_L("Parse channels failed(%d)\r\n"), r);
+		PlayFormatBuf().iChannels = 2;
+		}
+
+	PlayFormatBuf().iEncoding = ESoundEncoding16BitPCM;
+	PrintConfig(PlayFormatBuf(),Test);
+	
+	TInt bufferSize=BytesPerSecond(PlayFormatBuf())/8;
+	TUint8* buffer = (TUint8*)User::Alloc(bufferSize*sizeof(TUint8));
+	if (buffer==NULL)
+		{
+		Test.Printf(_L("Out of memory\r\n"));
+		return;
+		}
+	TPtr8 bufferDes(buffer,bufferSize,bufferSize);	
+
+	Test.Next(_L("Recording..."));
+	TInt i = BytesPerSecond(PlayFormatBuf())*10/bufferSize;
+	TInt bytesToRecord = i * bufferSize;
+
+	// Lay down a file header
+	WAVEheader header;
+	TPtr8 headerDes((TUint8 *)&header, sizeof(struct WAVEheader), sizeof(struct WAVEheader));
+
+	// "RIFF"
+	header.ckID[0] = 'R'; header.ckID[1] = 'I';
+	header.ckID[2] = 'F'; header.ckID[3] = 'F';
+	// "WAVE"
+	header.wave_ckID[0] = 'W'; header.wave_ckID[1] = 'A';
+	header.wave_ckID[2] = 'V'; header.wave_ckID[3] = 'E';
+	// "fmt "
+	header.fmt_ckID[0] = 'f'; header.fmt_ckID[1] = 'm';
+	header.fmt_ckID[2] = 't'; header.fmt_ckID[3] = ' ';
+	// "data"
+	header.data_ckID[0] = 'd'; header.data_ckID[1] = 'a';
+	header.data_ckID[2] = 't'; header.data_ckID[3] = 'a';
+
+	header.nChannels		= PlayFormatBuf().iChannels;
+	header.nSamplesPerSec	= RateInSamplesPerSecond(PlayFormatBuf().iRate);
+	header.nBitsPerSample	= 16;
+	header.nBlockAlign		= 4;
+	header.formatTag		= 1;
+	header.fmt_ckSize		= 16;
+	header.nAvgBytesPerSec	= BytesPerSecond(PlayFormatBuf());
+	header.data_ckSize		= bytesToRecord;
+	header.ckSize			= bytesToRecord + sizeof(struct WAVEheader) - 8;
+
+	Test.Printf(_L("Header rate:%d channels:%d tag:%d bits:%d (%d bytes/s) align %d datalen:%d fmt_ckSize:%d ckSize:%d\r\n"),
+			header.nSamplesPerSec, header.nChannels, header.formatTag, header.nBitsPerSample,
+			header.nAvgBytesPerSec, header.nBlockAlign, header.data_ckSize, header.fmt_ckSize, header.ckSize);
+
+	r = destination.Write(headerDes);
+
+	MakeSineTable(PlayFormatBuf());
+	SetToneFrequency(440,PlayFormatBuf()); // 'A'
+
+	while(--i>0)
+		{
+		WriteTone(bufferDes,PlayFormatBuf());
+		r = destination.Write(bufferDes);
+		if (r!=KErrNone)
+			{
+			Test.Printf(_L("Write failed(%d)\r\n"),r);
+			break;
+			}
+		}
+	Test.Printf(_L("Finished\r\n"));
+
+	delete buffer;
+	destination.Close();
+	}
+*/
+LOCAL_C void TestUnloadDrivers()
+	{
+	TInt r=User::FreeLogicalDevice(KDevSoundScName);
+	Test.Printf(_L("Unloading %S.LDD - %d\r\n"),&KDevSoundScName,r);
+	CHECK_NOERROR(r);
+	
+	TName pddName(KDevSoundScName);
+	_LIT(KPddWildcardExtension,".*");
+	pddName.Append(KPddWildcardExtension);
+	TFindPhysicalDevice findPD(pddName);
+	TFullName findResult;
+	r=findPD.Next(findResult);
+	while (r==KErrNone)
+		{
+		r=User::FreePhysicalDevice(findResult);
+		Test.Printf(_L("Unloading %S.PDD - %d\r\n"),&findResult,r);
+		CHECK_NOERROR(r);
+		findPD.Find(pddName); // Reset the find handle now that we have deleted something from the container.
+		r=findPD.Next(findResult);
+		} 
+	}
+	
+TInt E32Main()
+	{
+	TInt r;
+
+	__UHEAP_MARK;
+
+	Test.Title();
+
+	Test.Start(_L("Load"));
+	if (Load()==KErrNotFound)
+		{
+		Test.Printf(_L("Shared chunk sound driver not supported - test skipped\r\n"));
+		Test.End();
+		Test.Close();
+		__UHEAP_MARKEND;
+		return(KErrNone);
+		}
+
+	__KHEAP_MARK;
+
+	Test.Next(_L("Open playback channel"));
+	r = TxSoundDevice.Open(KSoundScTxUnit0);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Open playback channel error(%d)\r\n"),r);
+		Test(0);
+		}
+	
+	Test.Next(_L("Open record channel"));
+	r = RxSoundDevice.Open(KSoundScRxUnit0);
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Open record channel error(%d)\r\n"),r);
+		Test(0);
+		}
+	
+	Test.Next(_L("Query play formats supported"));
+	TxSoundDevice.Caps(PlayCapsBuf);
+	TSoundFormatsSupportedV02 playCaps=PlayCapsBuf();
+	PrintCaps(playCaps,Test);
+
+	Test.Next(_L("Query record formats supported"));
+	RxSoundDevice.Caps(RecordCapsBuf);
+	TSoundFormatsSupportedV02 recordCaps=RecordCapsBuf();
+	PrintCaps(recordCaps,Test);
+	
+	Test.Next(_L("Connect to the file server"));
+	r = Fs.Connect();
+	if (r!=KErrNone)
+		{
+		Test.Printf(_L("Connect to the file server error(%d)\r\n"),r);
+		Test(0);
+		}
+		
+	if (User::CommandLineLength())
+		{
+		User::CommandLine(CommandLine);
+		TLex l(CommandLine);
+		TPtrC token=l.NextToken();
+
+		TInt count=0;
+		while (token.Length()!=0)
+			{
+			++count;
+			token.Set(l.NextToken());
+			}
+		Test.Printf(_L("Command line %d parameters\r\n"),count);
+
+		if (count==1)		// If 1 parameter try playing a file
+			r=WavPlay();
+		else if (count)		// If there is more than 1 parameter, try recording
+			r=WavRecord();
+		
+		//TestWaveformGenerator();
+			
+		}
+	
+	Fs.Close();
+	
+	Test.Next(_L("Close channels"));
+	RxSoundDevice.Close();
+	TxSoundDevice.Close();
+	
+	__KHEAP_MARKEND;
+
+	// Now that both the channels are closed, unload the LDD and the PDDs.
+	TestUnloadDrivers();
+
+	Test(r==KErrNone);
+
+	Test.End();
+	Test.Close();
+
+	Cleanup();
+	
+	__UHEAP_MARKEND;
+
+	return(KErrNone);
+	}