5f60a864bb726d8ed1a1ac9507e8e54de746301d
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;
154 SRate
, SEnc
, SChans
: LongInt;
156 FMPG
:= mpg123_new(nil, nil);
159 e_LogWriteln('MPG123: mpg123_new() failed');
164 if mpg123_replace_reader_handle(FMPG
, streamRead
, streamLSeek
, nil) <> MPG123_OK
then
165 raise Exception
.Create('mpg123_replace_header_handle failed');
166 if mpg123_open_handle(FMPG
, Stream
) <> MPG123_OK
then
167 raise Exception
.Create('mpg123_open_handle failed');
171 if mpg123_getformat(FMPG
, @SRate
, @SChans
, @SEnc
) <> MPG123_OK
then
172 raise Exception
.Create('mpg123_get_format failed');
173 if (SChans
< 1) or (SChans
> 2) or (SRate
<= 0) then
174 raise Exception
.Create('invalid format');
176 mpg123_format_none(FMPG
);
177 if mpg123_format(FMPG
, SRate
, SChans
, MPG123_ENC_SIGNED_16
) <> MPG123_OK
then
178 raise Exception
.Create('mpg123_format failed');
182 e_LogWriteln('MPG123: Load(Data) failed: ' + E
.Message);
183 if FOpen
then mpg123_close(FMPG
);
192 FFormat
.SampleRate
:= SRate
;
193 FFormat
.SampleBits
:= 16;
194 FFormat
.Channels
:= SChans
;
195 FStreaming
:= SStreaming
;
200 function TMP3Loader
.Load(Data
: Pointer; Len
: LongWord; SStreaming
: Boolean): Boolean;
206 // TODO: have to make a dupe here because Data gets deallocated after loading
207 // this is obviously very shit
209 if FBuf
= nil then Exit
;
210 Move(Data
^, FBuf
^, Len
);
212 S
:= TSFSMemoryStreamRO
.Create(FBuf
, Len
{, True});
213 Result
:= LoadStream(S
, SStreaming
);
215 if not Result
and (S
<> nil) then
223 function TMP3Loader
.Load(FName
: string; SStreaming
: Boolean): Boolean;
230 S
:= openDiskFileRO(FName
);
231 Result
:= LoadStream(S
, SStreaming
);
234 e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName
, E
.Message]);
237 if not Result
and (S
<> nil) then
241 function TMP3Loader
.SetPosition(Pos
: LongWord): Boolean;
244 if FMPG
= nil then Exit
;
245 Result
:= mpg123_seek(FMPG
, Pos
, 0) = MPG123_OK
;
248 function TMP3Loader
.FillBuffer(Buf
: Pointer; Len
: LongWord): LongWord;
255 if FMPG
= nil then Exit
;
256 Ret
:= mpg123_read(FMPG
, Buf
, Len
, @Got
);
257 if FLooping
and (Ret
= MPG123_DONE
) then
258 mpg123_seek(FMPG
, 0, 0); // loop
262 function TMP3Loader
.GetAll(var OutPtr
: Pointer): LongWord;
265 if FMPG
= nil then Exit
;
266 if FStreaming
then Exit
;
270 procedure TMP3Loader
.Free();
272 if FOpen
then mpg123_close(FMPG
);
273 if FMPG
<> nil then mpg123_delete(FMPG
);
274 if FData
<> nil then FData
.Destroy();
275 if FBuf
<> nil then FreeMem(FBuf
);
276 if FAllSamples
<> nil then FreeMem(FAllSamples
);
285 if mpg123_init() = MPG123_OK
then
286 e_AddSoundLoader(TMP3LoaderFactory
.Create());