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