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; SStreaming
: Boolean): Boolean; override; overload
;
28 function Load(FName
: string; SStreaming
: Boolean): Boolean; override; overload
;
29 function SetPosition(Pos
: LongWord): Boolean; override;
30 function FillBuffer(Buf
: Pointer; Len
: LongWord): LongWord; override;
31 function GetAll(var OutPtr
: Pointer): LongWord; override;
32 procedure Free(); override;
41 function LoadStream(Stream
: TStream
; SStreaming
: Boolean): Boolean;
44 TMP3LoaderFactory
= class (TSoundLoaderFactory
)
46 function MatchHeader(Data
: Pointer; Len
: LongWord): Boolean; override;
47 function MatchExtension(FName
: string): Boolean; override;
48 function GetLoader(): TSoundLoader
; override;
53 uses sysutils
, utils
, e_sound
, e_log
, ctypes
, xstreams
;
55 (* Reader functions for mpg123_replace_reader_handle *)
57 function streamLSeek(h
: Pointer; off
: coff_t
; whence
: cint
): coff_t
; cdecl;
64 0: Result
:= s
.Seek(off
, soBeginning
); // SEEK_SET
65 1: Result
:= s
.Seek(off
, soCurrent
); // SEEK_CUR
66 2: Result
:= s
.Seek(off
, soEnd
); // SEEK_END
73 function streamRead(h
: Pointer; buf
: Pointer; len
: csize_t
): csize_t
; cdecl; // ssize_t
79 Result
:= S
.Read(buf
^, len
);
81 Result
:= csize_t(-1);
85 (* TMP3LoaderFactory *)
88 function TMP3LoaderFactory
.MatchHeader(Data
: Pointer; Len
: LongWord): Boolean;
94 if Len
< 10 then Exit
; // way too short even without an ID3
98 // try to check for an ID3v2 header
99 if ((P
+0)^ = $49) and ((P
+1)^ = $44) and ((P
+2)^ = $33) then // 'ID3'
101 N
:= (P
+9)^ + ((P
+8)^ shl 7) + ((P
+7)^ shl 14) + ((P
+6)^ shl 21);
102 Result
:= Len
> (N
+ 10);
106 // try to read the frame sync word, bits 0-10 should be 1
107 if ((P
+0)^ = $FF) and (((P
+1)^ and $E0) = $E0) then
109 // bits 11-12: mpeg version, can't be 01
110 if (((P
+1)^ and $10) = 0) and (((P
+1)^ and $08) = $08) then
112 // bits 13-14: layer: can't be 00
113 if ((P
+1)^ and $06) = 0 then
115 // bits 16-19: bitrate index: can't be 1111 or 0000
116 if (((P
+2)^ and $F0) = 0) or (((P
+2)^ and $F0) = $F0) then
118 // bits 20-21: samplerate index: can't be 11
119 if ((P
+2)^ and $0C) = $0C then
121 // this is probably an MP3 then
126 function TMP3LoaderFactory
.MatchExtension(FName
: string): Boolean;
130 Ext
:= GetFilenameExt(FName
);
131 Result
:= (Ext
= '.mp3') or (Ext
= '.mpeg3');
134 function TMP3LoaderFactory
.GetLoader(): TSoundLoader
;
136 Result
:= TMP3Loader
.Create();
141 function TMP3Loader
.LoadStream(Stream
: TStream
; SStreaming
: Boolean): Boolean;
144 SEnc
, SChans
: LongInt;
146 FMPG
:= mpg123_new(nil, nil);
149 e_LogWriteln('MPG123: mpg123_new() failed');
154 if mpg123_replace_reader_handle(FMPG
, streamRead
, streamLSeek
, nil) <> MPG123_OK
then
155 raise Exception
.Create('mpg123_replace_header_handle failed');
156 if mpg123_open_handle(FMPG
, Stream
) <> MPG123_OK
then
157 raise Exception
.Create('mpg123_open_handle failed');
161 if mpg123_getformat(FMPG
, @SRate
, @SChans
, @SEnc
) <> MPG123_OK
then
162 raise Exception
.Create('mpg123_get_format failed');
163 if (SChans
< 1) or (SChans
> 2) or (SRate
<= 0) then
164 raise Exception
.Create('invalid format');
166 mpg123_format_none(FMPG
);
167 if mpg123_format(FMPG
, SRate
, SChans
, MPG123_ENC_SIGNED_16
) <> MPG123_OK
then
168 raise Exception
.Create('mpg123_format failed');
172 e_LogWriteln('MPG123: Load(Data) failed: ' + E
.Message);
173 if FOpen
then mpg123_close(FMPG
);
182 FFormat
.SampleRate
:= SRate
;
183 FFormat
.SampleBits
:= 16;
184 FFormat
.Channels
:= SChans
;
185 FStreaming
:= SStreaming
;
190 function TMP3Loader
.Load(Data
: Pointer; Len
: LongWord; SStreaming
: Boolean): Boolean;
196 // TODO: have to make a dupe here because Data gets deallocated after loading
197 // this is obviously very shit
199 if FBuf
= nil then Exit
;
200 Move(Data
^, FBuf
^, Len
);
202 S
:= TSFSMemoryStreamRO
.Create(FBuf
, Len
{, True});
203 Result
:= LoadStream(S
, SStreaming
);
205 if not Result
and (S
<> nil) then
213 function TMP3Loader
.Load(FName
: string; SStreaming
: Boolean): Boolean;
220 S
:= openDiskFileRO(FName
);
221 Result
:= LoadStream(S
, SStreaming
);
224 e_LogWritefln('MPG123: ERROR: could not read file `%s`: %s', [FName
, E
.Message]);
227 if not Result
and (S
<> nil) then
231 function TMP3Loader
.SetPosition(Pos
: LongWord): Boolean;
234 if FMPG
= nil then Exit
;
235 Result
:= mpg123_seek(FMPG
, Pos
, 0) = MPG123_OK
;
238 function TMP3Loader
.FillBuffer(Buf
: Pointer; Len
: LongWord): LongWord;
245 if FMPG
= nil then Exit
;
246 Ret
:= mpg123_read(FMPG
, Buf
, Len
, @Got
);
247 if FLooping
and ((Ret
= MPG123_DONE
) or (Got
= 0)) then
248 Ret
:= mpg123_seek(FMPG
, 0, 0); // loop
249 if Ret
= MPG123_OK
then
253 function TMP3Loader
.GetAll(var OutPtr
: Pointer): LongWord;
256 if FMPG
= nil then Exit
;
257 if FStreaming
then Exit
;
261 procedure TMP3Loader
.Free();
263 if FOpen
then mpg123_close(FMPG
);
264 if FMPG
<> nil then mpg123_delete(FMPG
);
265 if FData
<> nil then FData
.Destroy();
266 if FBuf
<> nil then FreeMem(FBuf
);
267 if FAllSamples
<> nil then FreeMem(FAllSamples
);
276 if mpg123_init() = MPG123_OK
then
277 e_AddSoundLoader(TMP3LoaderFactory
.Create());