2870cbed154beda7f5e21614dc6549b456c0f605
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, either version 3 of the License, or
6 * (at your option) any later version.
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.
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/>.
16 {$INCLUDE ../shared/a_modes.inc}
21 uses e_soundfile
, mpg123
, classes
;
24 // an MP3 loader that uses libmpg123
26 TMP3Loader
= class (TSoundLoader
)
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;
42 function LoadStream(Stream
: TStream
; SStreaming
: Boolean): Boolean;
45 TMP3LoaderFactory
= class (TSoundLoaderFactory
)
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;
53 FMPG
: pmpg123_handle
; // tester context
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;
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
78 function streamRead(h
: Pointer; buf
: Pointer; len
: csize_t
): csize_t
; cdecl; // ssize_t
84 Result
:= S
.Read(buf
^, len
);
86 Result
:= csize_t(-1);
90 (* TMP3LoaderFactory *)
92 constructor TMP3LoaderFactory
.Create();
94 FMPG
:= mpg123_new(nil, nil);
96 mpg123_replace_reader_handle(FMPG
, streamRead
, streamLSeek
, nil);
99 destructor TMP3LoaderFactory
.Destroy();
101 if FMPG
<> nil then mpg123_delete(FMPG
);
104 function TMP3LoaderFactory
.MatchHeader(Data
: Pointer; Len
: LongWord): Boolean;
106 ID3
: array [0..9] of Byte;
108 S
: TSFSMemoryStreamRO
;
109 Info
: mpg123_frameinfo
;
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
118 HeaderLen
:= ID3
[9] + (ID3
[8] shl 7) + (ID3
[7] shl 14) + (ID3
[6] shl 21);
119 Result
:= Len
> (HeaderLen
+ 10);
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
130 Result
:= mpg123_info(FMPG
, @Info
) = MPG123_OK
;
137 function TMP3LoaderFactory
.MatchExtension(FName
: string): Boolean;
141 Ext
:= GetFilenameExt(FName
);
142 Result
:= (Ext
= '.mp3') or (Ext
= '.mpeg3');
145 function TMP3LoaderFactory
.GetLoader(): TSoundLoader
;
147 Result
:= TMP3Loader
.Create();
152 function TMP3Loader
.LoadStream(Stream
: TStream
; SStreaming
: Boolean): Boolean;
155 SEnc
, SChans
: LongInt;
157 FMPG
:= mpg123_new(nil, nil);
160 e_LogWriteln('MPG123: mpg123_new() failed');
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');
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');
183 e_LogWriteln('MPG123: Load(Data) failed: ' + E
.Message);
184 if FOpen
then mpg123_close(FMPG
);
193 FFormat
.SampleRate
:= SRate
;
194 FFormat
.SampleBits
:= 16;
195 FFormat
.Channels
:= SChans
;
196 FStreaming
:= SStreaming
;
201 function TMP3Loader
.Load(Data
: Pointer; Len
: LongWord; SStreaming
: Boolean): Boolean;
207 // TODO: have to make a dupe here because Data gets deallocated after loading
208 // this is obviously very shit
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
224 function TMP3Loader
.Load(FName
: string; SStreaming
: Boolean): Boolean;
231 S
:= openDiskFileRO(FName
);
232 Result
:= LoadStream(S
, SStreaming
);
235 e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName
, E
.Message]);
238 if not Result
and (S
<> nil) then
242 function TMP3Loader
.SetPosition(Pos
: LongWord): Boolean;
245 if FMPG
= nil then Exit
;
246 Result
:= mpg123_seek(FMPG
, Pos
, 0) = MPG123_OK
;
249 function TMP3Loader
.FillBuffer(Buf
: Pointer; Len
: LongWord): LongWord;
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
263 function TMP3Loader
.GetAll(var OutPtr
: Pointer): LongWord;
266 if FMPG
= nil then Exit
;
267 if FStreaming
then Exit
;
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
);
277 if FAllSamples
<> nil then FreeMem(FAllSamples
);
286 if mpg123_init() = MPG123_OK
then
287 e_AddSoundLoader(TMP3LoaderFactory
.Create());