1 (* WavOpenAL - OpenAL wave playing example
2 
3  Copyright (c) 2010 Dmitry Boyarintsev
4 
5  This software is provided 'as-is', without any express or implied
6  warranty. In no event will the authors be held liable for any damages
7  arising from the use of this software.
8 
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12 
13     1. The origin of this software must not be misrepresented; you must not
14     claim that you wrote the original software. If you use this software
15     in a product, an acknowledgment in the product documentation would be
16     appreciated but is not required.
17 
18     2. Altered source versions must be plainly marked as such, and must not be
19     misrepresented as being the original software.
20 
21     3. This notice may not be removed or altered from any source
22     distribution.
23 
24   WaveOpenAL is based on MadOpenAL playing sample.
25   Yhe wavopenal program accepts a single .wav file name as a parameter and
26   plays it using openal until the end.
27 
28 *)
29 program wavopenal;
30 
31 {$mode objfpc}
32 
33 uses
34   classes, sysutils, openal;
35 
36 // WAVE UTILS
37 
38 type
39   TRiffHeader = packed record
40     ID      : array [0..3] of char;
41     Size    : LongWord;
42     Format  : array [0..3] of char;
43   end;
44 
45   TWaveFormat = packed record
46     ID            : array [0..3] of char;
47     Size          : LongWord;
48     Format        : Word;
49     Channels      : Word;
50     SampleRate    : LongWord;
51     ByteRate      : LongWord;
52     BlockAlign    : Word;
53     BitsPerSample : Word;
54   end;
55 
56   TDataChunk = packed record
57     Id      : array [0..3] of char;
58     Size    : LongWord;
59   end;
60 
61 type
62 
63   { TWaveReader }
64 
65   TWaveReader = class(TObject)
66   private
67     loaded    : Boolean;
68     chunkdata : TDataChunk;
69     chunkpos  : Int64;
70     pos       : Int64;
71     eof       : Boolean;
72     fStream   : TStream;
73 
74   public
75     fmt   : TWaveFormat;
LoadFromStreamnull76     function LoadFromStream(AStream: TStream): Boolean;
ReadBufnull77     function ReadBuf(var Buffer; BufferSize: Integer): Integer;
78   end;
79 
80 const
81   ID_RIFF = 'RIFF';
82   ID_WAVE = 'WAVE';
83   ID_fmt  = 'fmt ';
84   ID_data = 'data';
85 
86 { TWaveReader }
87 
TWaveReader.LoadFromStreamnull88 function TWaveReader.LoadFromStream(AStream:TStream):Boolean;
89 var
90   riff  : TRiffHeader;
91 begin
92   fStream:=AStream;
93   loaded:=True;
94   try
95     Result:=fStream.Read(riff, sizeof(riff))=sizeof(riff);
96     riff.Size:=LEtoN(riff.Size);
97     Result:=Result and (riff.ID=ID_RIFF) and (riff.Format=ID_WAVE);
98     if not Result then Exit;
99 
100     Result:=fStream.Read(fmt, sizeof(fmt))=sizeof(fmt);
101     fmt.Size:=LEtoN(fmt.Size);
102     fmt.Format:=LEtoN(fmt.Format);
103     fmt.Channels:=LEtoN(fmt.Channels);
104     fmt.SampleRate:=LEtoN(fmt.SampleRate);
105     fmt.ByteRate:=LEtoN(fmt.ByteRate);
106     fmt.BlockAlign:=LEtoN(fmt.BlockAlign);
107     fmt.BitsPerSample:=LEtoN(fmt.BitsPerSample);
108 
109     Result:=fmt.ID=ID_fmt;
110     pos:=-1;
111   except
112     Result:=False;
113     Exit;
114   end;
115 end;
116 
Minnull117 function Min(a,b: Integer): Integer;
118 begin
119   if a<b then Result:=a
120   else Result:=b;
121 end;
122 
ReadBufnull123 function TWaveReader.ReadBuf(var Buffer;BufferSize:Integer):Integer;
124 var
125   sz  : Integer;
126   p   : PByteArray;
127   i   : Integer;
128 begin
129   FillChar(Buffer, BufferSize, 0);
130   Result:=0;
131   // all data read
132   if eof then Exit;
133 
134   p:=@Buffer;
135   i:=0;
136   while (not eof) and (i<bufferSize) do begin
137     if chunkpos>=chunkdata.Size then begin
138       if pos<0 then
139         fstream.Position:=sizeof(TRiffHeader)+Int64(fmt.Size)+sizeof(TDataChunk)
140      else
141         fstream.Position:=pos+chunkdata.size+SizeOf(chunkdata);
142 
143       eof:=pos>=fStream.Size;
144       if not eof then begin
145         pos:=fStream.Position;
146         sz:=fstream.Read(chunkdata, sizeof(chunkdata));
147         chunkdata.Size:=LEtoN(chunkdata.Size);
148         if (sz<>sizeof(chunkdata)) or (chunkdata.Id<>ID_data) then
149           chunkpos:=chunkdata.Size
150         else
151           chunkpos:=0;
152       end;
153     end else begin
154       sz:=Min(BufferSize, chunkdata.Size-chunkpos);
155       fStream.Position:=pos+sizeof(chunkdata)+chunkpos;
156       sz:=fStream.Read(p[i], sz);
157       if sz<0 then Exit;
158       inc(chunkpos, sz);
159       inc(i, sz);
160     end;
161   end;
162   Result:=i;
163 end;
164 
165 // ------------------------ OPEN AL ----------------------
166 
167 var
168   source     : TStream;
169   codec_bs   : Longword;
170 
171 // openal
172 const
173   // Note: if you lower the al_bufcount, then you have to modify the al_polltime also!
174   al_bufcount           = 4;
175   al_polltime           = 100;
176 
177 var
178   al_device   : PALCdevice;
179   al_context  : PALCcontext;
180   al_source   : ALuint;
181   al_format   : Integer;
182   al_buffers  : array[0..al_bufcount-1] of ALuint;
183   al_bufsize  : Longword;
184   al_readbuf  : Pointer;
185   al_rate     : Longword;
186 
187   wave       : TWaveReader;
188 
189 procedure alPlay;
190 var
191   i: Integer;
192 begin
193   alSourceStop(al_source);
194   alSourceRewind(al_source);
195   alSourcei(al_source, AL_BUFFER, 0);
196 
197   for i := 0 to al_bufcount - 1 do
198   begin
199     if wave.ReadBuf(al_readbuf^, al_bufsize) = 0 then
200       Break;
201 
202     alBufferData(al_buffers[i], al_format, al_readbuf, al_bufsize, al_rate);
203     alSourceQueueBuffers(al_source, 1, @al_buffers[i]);
204   end;
205 
206   // Under windows, AL_LOOPING = AL_TRUE breaks queueing, no idea why
207   alSourcei(al_source, AL_LOOPING, AL_FALSE);
208   alSourcePlay(al_source);
209 end;
210 
211 procedure alStop;
212 begin
213   alSourceStop(al_source);
214   alSourceRewind(al_source);
215   alSourcei(al_source, AL_BUFFER, 0);
216 end;
217 
alProcessnull218 function alProcess: Boolean;
219 var
220   processed : ALint;
221   buffer    : ALuint;
222   sz        : Integer;
223 begin
224   alGetSourcei(al_source, AL_BUFFERS_PROCESSED, processed);
225   while (processed > 0) and (processed <= al_bufcount) do
226   begin
227     Write('.');
228 
229     alSourceUnqueueBuffers(al_source, 1, @buffer);
230 
231     sz:=wave.ReadBuf(al_readbuf^, al_bufsize);
232     if sz <= 0 then
233     begin
234       Exit(False);
235     end;
236 
237     alBufferData(buffer, al_format, al_readbuf, sz, al_rate);
238     alSourceQueueBuffers(al_source, 1, @buffer);
239 
240     Dec(processed);
241   end;
242 
243   Result := True;
244 end;
245 
246 
247 var
248   Filename: String;
249   queued  : Integer;
250   done    : Boolean;
251 begin
252   // define codec
253   if (ParamCount<=0) or not FileExists(ParamStr(1)) then begin
254     writeln('please specify .wav file name');
255     Exit;
256   end;
257   FileName:=ParamStr(1);
258 
259   source := TFileStream.Create(Filename, fmOpenRead);
260 
261   // inittialize codec
262   wave:=TWaveReader.Create;
263   if not wave.LoadFromStream(source) then begin
264     writeln('unable to read WAVE format');
265     Exit;
266   end;
267   if wave.fmt.Format<>1 then begin
268     writeln('WAVE file is using compression. Cannot play sorry. Please provide uncompressed .wav');
269     Exit;
270   end;
271   if wave.fmt.Channels=2 then begin
272     if wave.fmt.BitsPerSample=8 then al_format:=AL_FORMAT_STEREO8
273     else al_format:=AL_FORMAT_STEREO16
274   end else begin
275     if wave.fmt.BitsPerSample=8 then al_format:=AL_FORMAT_MONO8
276     else al_format:=AL_FORMAT_MONO16
277   end;
278 
279   codec_bs:=2*wave.fmt.Channels;
280 
281   //al_bufsize := 20000 - (20000 mod codec_bs);
282   al_bufsize := 20000 - (20000 mod codec_bs);
283   al_rate:=wave.fmt.SampleRate;
284   WriteLn('Blocksize    : ', codec_bs);
285   WriteLn('Rate         : ', wave.fmt.SampleRate);
286   WriteLn('Channels     : ', wave.fmt.Channels);
287   WriteLn('OpenAL Buffers     : ', al_bufcount);
288   WriteLn('OpenAL Buffer Size : ', al_bufsize);
289 
290   // init openal
291   al_device := alcOpenDevice(nil);
292   al_context := alcCreateContext(al_device, nil);
293   alcMakeContextCurrent(al_context);
294 
295   alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
296   alGenSources(1, @al_source);
297   alGenBuffers(al_bufcount, @al_buffers);
298 
299   GetMem(al_readbuf, al_bufsize);
300 
301 
302   // play loop
303   alPlay;
304 
305   done:=False;
306   queued:=0;
307   repeat
308     if alProcess then begin
309       alGetSourcei(al_source, AL_BUFFERS_QUEUED, queued);
310       done:=queued=0;
311     end;
312     Sleep(al_polltime);
313   until done;
314 
315   alStop;
316 
317   // finalize openal
318   alDeleteSources(1, @al_source);
319   alDeleteBuffers(al_bufcount, @al_buffers);
320   alcDestroyContext(al_context);
321   alcCloseDevice(al_device);
322   FreeMem(al_readbuf);
323 
324 
325   // finalize codec
326   wave.Free;
327 
328   // close file
329   source.Free;
330 end.
331