DEADSOFTWARE

simplify TSoundLoader interface
[d2df-sdl.git] / src / engine / e_soundfile_mp3.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit e_soundfile_mp3;
18 interface
20 uses e_soundfile, mpg123, classes;
22 type
23 // an MP3 loader that uses libmpg123
25 TMP3Loader = class (TSoundLoader)
26 public
27 function Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean; override; overload;
28 function Load(FName: string; Loop: Boolean): Boolean; override; overload;
29 function Finished(): Boolean; override;
30 function Restart(): Boolean; override;
31 function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override;
32 procedure Free(); override;
34 private
35 FMPG: pmpg123_handle;
36 FData: TStream;
37 FBuf: Pointer;
38 FOpen: Boolean;
39 FFinished: Boolean;
40 FLooping: Boolean;
42 function LoadStream(Stream: TStream): Boolean;
43 end;
45 TMP3LoaderFactory = class (TSoundLoaderFactory)
46 public
47 function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override;
48 function MatchExtension(FName: string): Boolean; override;
49 function GetLoader(): TSoundLoader; override;
50 end;
52 implementation
54 uses sysutils, utils, e_sound, e_log, ctypes, xstreams;
56 (* Reader functions for mpg123_replace_reader_handle *)
58 function streamLSeek(h: Pointer; off: coff_t; whence: cint): coff_t; cdecl;
59 var
60 S: TStream;
61 begin
62 S:= TStream(h);
63 try
64 case whence of
65 0: Result := s.Seek(off, soBeginning); // SEEK_SET
66 1: Result := s.Seek(off, soCurrent); // SEEK_CUR
67 2: Result := s.Seek(off, soEnd); // SEEK_END
68 end;
69 except
70 Result := -1;
71 end;
72 end;
74 function streamRead(h: Pointer; buf: Pointer; len: csize_t): csize_t; cdecl; // ssize_t
75 var
76 S: TStream;
77 begin
78 S:= TStream(h);
79 try
80 Result := S.Read(buf^, len);
81 except
82 Result := csize_t(-1);
83 end;
84 end;
86 (* TMP3LoaderFactory *)
89 function TMP3LoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;
90 var
91 P: PByte;
92 N: LongInt;
93 begin
94 Result := False;
95 if Len < 10 then Exit; // way too short even without an ID3
97 P := PByte(Data);
99 // try to check for an ID3v2 header
100 if ((P+0)^ = $49) and ((P+1)^ = $44) and ((P+2)^ = $33) then // 'ID3'
101 begin
102 N := (P+9)^ + ((P+8)^ shl 7) + ((P+7)^ shl 14) + ((P+6)^ shl 21);
103 Result := Len > (N + 10);
104 if Result then Exit;
105 end;
107 // try to read the frame sync word, bits 0-10 should be 1
108 if ((P+0)^ = $FF) and (((P+1)^ and $E0) = $E0) then
109 begin
110 // bits 11-12: mpeg version, can't be 01
111 if (((P+1)^ and $10) = 0) and (((P+1)^ and $08) = $08) then
112 Exit;
113 // bits 13-14: layer: can't be 00
114 if ((P+1)^ and $06) = 0 then
115 Exit;
116 // bits 16-19: bitrate index: can't be 1111 or 0000
117 if (((P+2)^ and $F0) = 0) or (((P+2)^ and $F0) = $F0) then
118 Exit;
119 // bits 20-21: samplerate index: can't be 11
120 if ((P+2)^ and $0C) = $0C then
121 Exit;
122 // this is probably an MP3 then
123 Result := True;
124 end;
125 end;
127 function TMP3LoaderFactory.MatchExtension(FName: string): Boolean;
128 var
129 Ext: string;
130 begin
131 Ext := GetFilenameExt(FName);
132 Result := (Ext = '.mp3') or (Ext = '.mpeg3');
133 end;
135 function TMP3LoaderFactory.GetLoader(): TSoundLoader;
136 begin
137 Result := TMP3Loader.Create();
138 end;
140 (* TMP3Loader *)
142 function TMP3Loader.LoadStream(Stream: TStream): Boolean;
143 var
144 SRate: clong;
145 SEnc, SChans: LongInt;
146 begin
147 FMPG := mpg123_new(nil, nil);
148 if FMPG = nil then
149 begin
150 e_LogWriteln('MPG123: mpg123_new() failed');
151 Exit;
152 end;
154 try
155 if mpg123_replace_reader_handle(FMPG, streamRead, streamLSeek, nil) <> MPG123_OK then
156 raise Exception.Create('mpg123_replace_header_handle failed');
157 if mpg123_open_handle(FMPG, Stream) <> MPG123_OK then
158 raise Exception.Create('mpg123_open_handle failed');
160 FOpen := True;
162 if mpg123_getformat(FMPG, @SRate, @SChans, @SEnc) <> MPG123_OK then
163 raise Exception.Create('mpg123_get_format failed');
164 if (SChans < 1) or (SChans > 2) or (SRate <= 0) then
165 raise Exception.Create('invalid format');
167 mpg123_format_none(FMPG);
168 if mpg123_format(FMPG, SRate, SChans, MPG123_ENC_SIGNED_16) <> MPG123_OK then
169 raise Exception.Create('mpg123_format failed');
170 except
171 on E: Exception do
172 begin
173 e_LogWriteln('MPG123: Load(Data) failed: ' + E.Message);
174 if FOpen then mpg123_close(FMPG);
175 mpg123_delete(FMPG);
176 FMPG := nil;
177 FOpen := False;
178 Exit;
179 end;
180 end;
182 FData := Stream;
183 FFormat.SampleRate := SRate;
184 FFormat.SampleBits := 16;
185 FFormat.Channels := SChans;
186 FStreaming := True;
187 FFinished := False;
189 Result := True;
190 end;
192 function TMP3Loader.Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean;
193 var
194 S: TStream;
195 begin
196 Result := False;
198 // TODO: have to make a dupe here because Data gets deallocated after loading
199 // this is obviously very shit
200 FBuf := GetMem(Len);
201 if FBuf = nil then Exit;
202 Move(Data^, FBuf^, Len);
204 S := TSFSMemoryStreamRO.Create(FBuf, Len{, True});
205 Result := LoadStream(S);
206 FLooping := Loop;
208 if not Result and (S <> nil) then
209 begin
210 S.Destroy();
211 FreeMem(FBuf);
212 FBuf := nil;
213 end;
214 end;
216 function TMP3Loader.Load(FName: string; Loop: Boolean): Boolean;
217 var
218 S: TStream = nil;
219 begin
220 Result := False;
222 try
223 S := openDiskFileRO(FName);
224 Result := LoadStream(S);
225 FLooping := Loop;
226 except
227 on E: Exception do
228 e_LogWritefln('MPG123: ERROR: could not read file `%s`: %s', [FName, E.Message]);
229 end;
231 if not Result and (S <> nil) then
232 S.Destroy();
233 end;
235 function TMP3Loader.Finished(): Boolean;
236 begin
237 Result := FFinished;
238 end;
240 function TMP3Loader.Restart(): Boolean;
241 begin
242 Result := False;
243 if FMPG = nil then Exit;
244 FFinished := False;
245 Result := mpg123_seek(FMPG, 0, 0) = MPG123_OK;
246 end;
248 function TMP3Loader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
249 var
250 Ret: LongInt;
251 Got: csize_t;
252 begin
253 Result := 0;
254 Got := 0;
255 if FMPG = nil then Exit;
257 Ret := mpg123_read(FMPG, Buf, Len, @Got);
259 if (Ret = MPG123_DONE) or (Got = 0) then
260 begin
261 if FLooping then
262 Ret := mpg123_seek(FMPG, 0, 0) // loop
263 else
264 FFinished := True;
265 end;
267 if (Ret = MPG123_OK) or FFinished then
268 Result := Got;
269 end;
271 procedure TMP3Loader.Free();
272 begin
273 if FOpen then mpg123_close(FMPG);
274 if FMPG <> nil then mpg123_delete(FMPG);
275 if FData <> nil then FData.Destroy();
276 if FBuf <> nil then FreeMem(FBuf);
277 FOpen := False;
278 FFinished := False;
279 FLooping := False;
280 FMPG := nil;
281 FData := nil;
282 FBuf := nil;
283 end;
285 initialization
286 if mpg123_init() = MPG123_OK then
287 e_AddSoundLoader(TMP3LoaderFactory.Create());
288 finalization
289 mpg123_exit();
290 end.