1 (* Copyright (C) Doom 2D: Forever Developers
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.
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.
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/>.
15 {$INCLUDE ../shared/a_modes.inc}
20 uses e_soundfile
, mpg123
, classes
;
23 // an MP3 loader that uses libmpg123
25 TMP3Loader
= class (TSoundLoader
)
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;
42 function LoadStream(Stream
: TStream
): Boolean;
45 TMP3LoaderFactory
= class (TSoundLoaderFactory
)
47 function MatchHeader(Data
: Pointer; Len
: LongWord): Boolean; override;
48 function MatchExtension(FName
: string): Boolean; override;
49 function GetLoader(): TSoundLoader
; override;
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;
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
74 function streamRead(h
: Pointer; buf
: Pointer; len
: csize_t
): csize_t
; cdecl; // ssize_t
80 Result
:= S
.Read(buf
^, len
);
82 Result
:= csize_t(-1);
86 (* TMP3LoaderFactory *)
89 function TMP3LoaderFactory
.MatchHeader(Data
: Pointer; Len
: LongWord): Boolean;
95 if Len
< 10 then Exit
; // way too short even without an ID3
99 // try to check for an ID3v2 header
100 if ((P
+0)^ = $49) and ((P
+1)^ = $44) and ((P
+2)^ = $33) then // 'ID3'
102 N
:= (P
+9)^ + ((P
+8)^ shl 7) + ((P
+7)^ shl 14) + ((P
+6)^ shl 21);
103 Result
:= Len
> (N
+ 10);
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
110 // bits 11-12: mpeg version, can't be 01
111 if (((P
+1)^ and $10) = 0) and (((P
+1)^ and $08) = $08) then
113 // bits 13-14: layer: can't be 00
114 if ((P
+1)^ and $06) = 0 then
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
119 // bits 20-21: samplerate index: can't be 11
120 if ((P
+2)^ and $0C) = $0C then
122 // this is probably an MP3 then
127 function TMP3LoaderFactory
.MatchExtension(FName
: string): Boolean;
131 Ext
:= GetFilenameExt(FName
);
132 Result
:= (Ext
= '.mp3') or (Ext
= '.mpeg3');
135 function TMP3LoaderFactory
.GetLoader(): TSoundLoader
;
137 Result
:= TMP3Loader
.Create();
142 function TMP3Loader
.LoadStream(Stream
: TStream
): Boolean;
145 SEnc
, SChans
: LongInt;
147 FMPG
:= mpg123_new(nil, nil);
150 e_LogWriteln('MPG123: mpg123_new() failed');
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');
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');
173 e_LogWriteln('MPG123: Load(Data) failed: ' + E
.Message);
174 if FOpen
then mpg123_close(FMPG
);
183 FFormat
.SampleRate
:= SRate
;
184 FFormat
.SampleBits
:= 16;
185 FFormat
.Channels
:= SChans
;
192 function TMP3Loader
.Load(Data
: Pointer; Len
: LongWord; Loop
: Boolean): Boolean;
198 // TODO: have to make a dupe here because Data gets deallocated after loading
199 // this is obviously very shit
201 if FBuf
= nil then Exit
;
202 Move(Data
^, FBuf
^, Len
);
204 S
:= TSFSMemoryStreamRO
.Create(FBuf
, Len
{, True});
205 Result
:= LoadStream(S
);
208 if not Result
and (S
<> nil) then
216 function TMP3Loader
.Load(FName
: string; Loop
: Boolean): Boolean;
223 S
:= openDiskFileRO(FName
);
224 Result
:= LoadStream(S
);
228 e_LogWritefln('MPG123: ERROR: could not read file `%s`: %s', [FName
, E
.Message]);
231 if not Result
and (S
<> nil) then
235 function TMP3Loader
.Finished(): Boolean;
240 function TMP3Loader
.Restart(): Boolean;
243 if FMPG
= nil then Exit
;
245 Result
:= mpg123_seek(FMPG
, 0, 0) = MPG123_OK
;
248 function TMP3Loader
.FillBuffer(Buf
: Pointer; Len
: LongWord): LongWord;
255 if FMPG
= nil then Exit
;
257 Ret
:= mpg123_read(FMPG
, Buf
, Len
, @Got
);
259 if (Ret
= MPG123_DONE
) or (Got
= 0) then
262 Ret
:= mpg123_seek(FMPG
, 0, 0) // loop
267 if (Ret
= MPG123_OK
) or FFinished
then
271 procedure TMP3Loader
.Free();
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
);
286 if mpg123_init() = MPG123_OK
then
287 e_AddSoundLoader(TMP3LoaderFactory
.Create());