DEADSOFTWARE

5f60a864bb726d8ed1a1ac9507e8e54de746301d
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit e_soundfile_mp3;
19 interface
21 uses e_soundfile, mpg123, classes;
23 type
24 // an MP3 loader that uses libmpg123
26 TMP3Loader = class (TSoundLoader)
27 public
28 function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload;
29 function Load(FName: string; SStreaming: Boolean): Boolean; override; overload;
30 function SetPosition(Pos: LongWord): Boolean; override;
31 function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override;
32 function GetAll(var OutPtr: Pointer): LongWord; override;
33 procedure Free(); override;
35 private
36 FMPG: pmpg123_handle;
37 FData: TStream;
38 FBuf: Pointer;
39 FAllSamples: Pointer;
40 FOpen: Boolean;
42 function LoadStream(Stream: TStream; SStreaming: Boolean): Boolean;
43 end;
45 TMP3LoaderFactory = class (TSoundLoaderFactory)
46 public
47 constructor Create();
48 destructor Destroy(); override;
49 function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override;
50 function MatchExtension(FName: string): Boolean; override;
51 function GetLoader(): TSoundLoader; override;
52 private
53 FMPG: pmpg123_handle; // tester context
54 end;
56 implementation
58 uses sysutils, utils, e_sound, e_log, ctypes, xstreams;
60 (* Reader functions for mpg123_replace_reader_handle *)
62 function streamLSeek(h: Pointer; off: coff_t; whence: cint): coff_t; cdecl;
63 var
64 S: TStream;
65 begin
66 S:= TStream(h);
67 try
68 case whence of
69 0: Result := s.Seek(off, soBeginning); // SEEK_SET
70 1: Result := s.Seek(off, soCurrent); // SEEK_CUR
71 2: Result := s.Seek(off, soEnd); // SEEK_END
72 end;
73 except
74 Result := -1;
75 end;
76 end;
78 function streamRead(h: Pointer; buf: Pointer; len: csize_t): csize_t; cdecl; // ssize_t
79 var
80 S: TStream;
81 begin
82 S:= TStream(h);
83 try
84 Result := S.Read(buf^, len);
85 except
86 Result := csize_t(-1);
87 end;
88 end;
90 (* TMP3LoaderFactory *)
92 constructor TMP3LoaderFactory.Create();
93 begin
94 FMPG := mpg123_new(nil, nil);
95 if FMPG <> nil then
96 mpg123_replace_reader_handle(FMPG, streamRead, streamLSeek, nil);
97 end;
99 destructor TMP3LoaderFactory.Destroy();
100 begin
101 if FMPG <> nil then mpg123_delete(FMPG);
102 end;
104 function TMP3LoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;
105 var
106 ID3: array [0..9] of Byte;
107 HeaderLen: LongInt;
108 S: TSFSMemoryStreamRO;
109 Info: mpg123_frameinfo;
110 begin
111 Result := False;
112 if Len < 10 then Exit;
114 // try and check for an ID3 header
115 Move(Data^, ID3, 10);
116 if (ID3[0] = Ord('I')) and (ID3[1] = Ord('D')) and (ID3[2] = Ord('3')) then
117 begin
118 HeaderLen := ID3[9] + (ID3[8] shl 7) + (ID3[7] shl 14) + (ID3[6] shl 21);
119 Result := Len > (HeaderLen + 10);
120 if Result then Exit;
121 end;
123 // if there isn't one, employ heavier shit
124 if FMPG = nil then Exit;
126 S := TSFSMemoryStreamRO.Create(Data, Len);
128 if mpg123_open_handle(FMPG, S) = MPG123_OK then
129 begin
130 Result := mpg123_info(FMPG, @Info) = MPG123_OK;
131 mpg123_close(FMPG);
132 end;
134 S.Destroy();
135 end;
137 function TMP3LoaderFactory.MatchExtension(FName: string): Boolean;
138 var
139 Ext: string;
140 begin
141 Ext := GetFilenameExt(FName);
142 Result := (Ext = '.mp3') or (Ext = '.mpeg3');
143 end;
145 function TMP3LoaderFactory.GetLoader(): TSoundLoader;
146 begin
147 Result := TMP3Loader.Create();
148 end;
150 (* TMP3Loader *)
152 function TMP3Loader.LoadStream(Stream: TStream; SStreaming: Boolean): Boolean;
153 var
154 SRate, SEnc, SChans: LongInt;
155 begin
156 FMPG := mpg123_new(nil, nil);
157 if FMPG = nil then
158 begin
159 e_LogWriteln('MPG123: mpg123_new() failed');
160 Exit;
161 end;
163 try
164 if mpg123_replace_reader_handle(FMPG, streamRead, streamLSeek, nil) <> MPG123_OK then
165 raise Exception.Create('mpg123_replace_header_handle failed');
166 if mpg123_open_handle(FMPG, Stream) <> MPG123_OK then
167 raise Exception.Create('mpg123_open_handle failed');
169 FOpen := True;
171 if mpg123_getformat(FMPG, @SRate, @SChans, @SEnc) <> MPG123_OK then
172 raise Exception.Create('mpg123_get_format failed');
173 if (SChans < 1) or (SChans > 2) or (SRate <= 0) then
174 raise Exception.Create('invalid format');
176 mpg123_format_none(FMPG);
177 if mpg123_format(FMPG, SRate, SChans, MPG123_ENC_SIGNED_16) <> MPG123_OK then
178 raise Exception.Create('mpg123_format failed');
179 except
180 on E: Exception do
181 begin
182 e_LogWriteln('MPG123: Load(Data) failed: ' + E.Message);
183 if FOpen then mpg123_close(FMPG);
184 mpg123_delete(FMPG);
185 FMPG := nil;
186 FOpen := False;
187 Exit;
188 end;
189 end;
191 FData := Stream;
192 FFormat.SampleRate := SRate;
193 FFormat.SampleBits := 16;
194 FFormat.Channels := SChans;
195 FStreaming := SStreaming;
197 Result := True;
198 end;
200 function TMP3Loader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
201 var
202 S: TStream;
203 begin
204 Result := False;
206 // TODO: have to make a dupe here because Data gets deallocated after loading
207 // this is obviously very shit
208 FBuf := GetMem(Len);
209 if FBuf = nil then Exit;
210 Move(Data^, FBuf^, Len);
212 S := TSFSMemoryStreamRO.Create(FBuf, Len{, True});
213 Result := LoadStream(S, SStreaming);
215 if not Result and (S <> nil) then
216 begin
217 S.Destroy();
218 FreeMem(FBuf);
219 FBuf := nil;
220 end;
221 end;
223 function TMP3Loader.Load(FName: string; SStreaming: Boolean): Boolean;
224 var
225 S: TStream = nil;
226 begin
227 Result := False;
229 try
230 S := openDiskFileRO(FName);
231 Result := LoadStream(S, SStreaming);
232 except
233 on E: Exception do
234 e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName, E.Message]);
235 end;
237 if not Result and (S <> nil) then
238 S.Destroy();
239 end;
241 function TMP3Loader.SetPosition(Pos: LongWord): Boolean;
242 begin
243 Result := False;
244 if FMPG = nil then Exit;
245 Result := mpg123_seek(FMPG, Pos, 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;
256 Ret := mpg123_read(FMPG, Buf, Len, @Got);
257 if FLooping and (Ret = MPG123_DONE) then
258 mpg123_seek(FMPG, 0, 0); // loop
259 Result := Got;
260 end;
262 function TMP3Loader.GetAll(var OutPtr: Pointer): LongWord;
263 begin
264 Result := 0;
265 if FMPG = nil then Exit;
266 if FStreaming then Exit;
267 // TODO
268 end;
270 procedure TMP3Loader.Free();
271 begin
272 if FOpen then mpg123_close(FMPG);
273 if FMPG <> nil then mpg123_delete(FMPG);
274 if FData <> nil then FData.Destroy();
275 if FBuf <> nil then FreeMem(FBuf);
276 if FAllSamples <> nil then FreeMem(FAllSamples);
277 FOpen := False;
278 FMPG := nil;
279 FData := nil;
280 FBuf := nil;
281 FAllSamples := nil;
282 end;
284 initialization
285 if mpg123_init() = MPG123_OK then
286 e_AddSoundLoader(TMP3LoaderFactory.Create());
287 finalization
288 mpg123_exit();
289 end.