DEADSOFTWARE

2870cbed154beda7f5e21614dc6549b456c0f605
[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: clong;
155 SEnc, SChans: LongInt;
156 begin
157 FMPG := mpg123_new(nil, nil);
158 if FMPG = nil then
159 begin
160 e_LogWriteln('MPG123: mpg123_new() failed');
161 Exit;
162 end;
164 try
165 if mpg123_replace_reader_handle(FMPG, streamRead, streamLSeek, nil) <> MPG123_OK then
166 raise Exception.Create('mpg123_replace_header_handle failed');
167 if mpg123_open_handle(FMPG, Stream) <> MPG123_OK then
168 raise Exception.Create('mpg123_open_handle failed');
170 FOpen := True;
172 if mpg123_getformat(FMPG, @SRate, @SChans, @SEnc) <> MPG123_OK then
173 raise Exception.Create('mpg123_get_format failed');
174 if (SChans < 1) or (SChans > 2) or (SRate <= 0) then
175 raise Exception.Create('invalid format');
177 mpg123_format_none(FMPG);
178 if mpg123_format(FMPG, SRate, SChans, MPG123_ENC_SIGNED_16) <> MPG123_OK then
179 raise Exception.Create('mpg123_format failed');
180 except
181 on E: Exception do
182 begin
183 e_LogWriteln('MPG123: Load(Data) failed: ' + E.Message);
184 if FOpen then mpg123_close(FMPG);
185 mpg123_delete(FMPG);
186 FMPG := nil;
187 FOpen := False;
188 Exit;
189 end;
190 end;
192 FData := Stream;
193 FFormat.SampleRate := SRate;
194 FFormat.SampleBits := 16;
195 FFormat.Channels := SChans;
196 FStreaming := SStreaming;
198 Result := True;
199 end;
201 function TMP3Loader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
202 var
203 S: TStream;
204 begin
205 Result := False;
207 // TODO: have to make a dupe here because Data gets deallocated after loading
208 // this is obviously very shit
209 FBuf := GetMem(Len);
210 if FBuf = nil then Exit;
211 Move(Data^, FBuf^, Len);
213 S := TSFSMemoryStreamRO.Create(FBuf, Len{, True});
214 Result := LoadStream(S, SStreaming);
216 if not Result and (S <> nil) then
217 begin
218 S.Destroy();
219 FreeMem(FBuf);
220 FBuf := nil;
221 end;
222 end;
224 function TMP3Loader.Load(FName: string; SStreaming: Boolean): Boolean;
225 var
226 S: TStream = nil;
227 begin
228 Result := False;
230 try
231 S := openDiskFileRO(FName);
232 Result := LoadStream(S, SStreaming);
233 except
234 on E: Exception do
235 e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName, E.Message]);
236 end;
238 if not Result and (S <> nil) then
239 S.Destroy();
240 end;
242 function TMP3Loader.SetPosition(Pos: LongWord): Boolean;
243 begin
244 Result := False;
245 if FMPG = nil then Exit;
246 Result := mpg123_seek(FMPG, Pos, 0) = MPG123_OK;
247 end;
249 function TMP3Loader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
250 var
251 Ret: LongInt;
252 Got: csize_t;
253 begin
254 Result := 0;
255 Got := 0;
256 if FMPG = nil then Exit;
257 Ret := mpg123_read(FMPG, Buf, Len, @Got);
258 if FLooping and (Ret = MPG123_DONE) then
259 mpg123_seek(FMPG, 0, 0); // loop
260 Result := Got;
261 end;
263 function TMP3Loader.GetAll(var OutPtr: Pointer): LongWord;
264 begin
265 Result := 0;
266 if FMPG = nil then Exit;
267 if FStreaming then Exit;
268 // TODO
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 if FAllSamples <> nil then FreeMem(FAllSamples);
278 FOpen := False;
279 FMPG := nil;
280 FData := nil;
281 FBuf := nil;
282 FAllSamples := nil;
283 end;
285 initialization
286 if mpg123_init() = MPG123_OK then
287 e_AddSoundLoader(TMP3LoaderFactory.Create());
288 finalization
289 mpg123_exit();
290 end.