DEADSOFTWARE

6bf7dddd8b1d21e71dc34546f8b2fd2d2a1ca134
[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 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; SStreaming: Boolean): 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 := SStreaming;
188 Result := True;
189 end;
191 function TMP3Loader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
192 var
193 S: TStream;
194 begin
195 Result := False;
197 // TODO: have to make a dupe here because Data gets deallocated after loading
198 // this is obviously very shit
199 FBuf := GetMem(Len);
200 if FBuf = nil then Exit;
201 Move(Data^, FBuf^, Len);
203 S := TSFSMemoryStreamRO.Create(FBuf, Len{, True});
204 Result := LoadStream(S, SStreaming);
206 if not Result and (S <> nil) then
207 begin
208 S.Destroy();
209 FreeMem(FBuf);
210 FBuf := nil;
211 end;
212 end;
214 function TMP3Loader.Load(FName: string; SStreaming: Boolean): Boolean;
215 var
216 S: TStream = nil;
217 begin
218 Result := False;
220 try
221 S := openDiskFileRO(FName);
222 Result := LoadStream(S, SStreaming);
223 except
224 on E: Exception do
225 e_LogWritefln('MPG123: ERROR: could not read file `%s`: %s', [FName, E.Message]);
226 end;
228 if not Result and (S <> nil) then
229 S.Destroy();
230 end;
232 function TMP3Loader.SetPosition(Pos: LongWord): Boolean;
233 begin
234 Result := False;
235 if FMPG = nil then Exit;
236 Result := mpg123_seek(FMPG, Pos, 0) = MPG123_OK;
237 end;
239 function TMP3Loader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
240 var
241 Ret: LongInt;
242 Got: csize_t;
243 begin
244 Result := 0;
245 Got := 0;
246 if FMPG = nil then Exit;
247 Ret := mpg123_read(FMPG, Buf, Len, @Got);
248 if FLooping and ((Ret = MPG123_DONE) or (Got = 0)) then
249 Ret := mpg123_seek(FMPG, 0, 0); // loop
250 if Ret = MPG123_OK then
251 Result := Got;
252 end;
254 function TMP3Loader.GetAll(var OutPtr: Pointer): LongWord;
255 begin
256 Result := 0;
257 if FMPG = nil then Exit;
258 if FStreaming then Exit;
259 // TODO
260 end;
262 procedure TMP3Loader.Free();
263 begin
264 if FOpen then mpg123_close(FMPG);
265 if FMPG <> nil then mpg123_delete(FMPG);
266 if FData <> nil then FData.Destroy();
267 if FBuf <> nil then FreeMem(FBuf);
268 if FAllSamples <> nil then FreeMem(FAllSamples);
269 FOpen := False;
270 FMPG := nil;
271 FData := nil;
272 FBuf := nil;
273 FAllSamples := nil;
274 end;
276 initialization
277 if mpg123_init() = MPG123_OK then
278 e_AddSoundLoader(TMP3LoaderFactory.Create());
279 finalization
280 mpg123_exit();
281 end.