{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UMediaPlugin;
interface
{$IFDEF FPC}
{$MODE DELPHI }
{$PACKENUM 4} (* use 4-byte enums *)
{$PACKRECORDS C} (* C/C++-compatible record packing *)
{$ELSE}
{$MINENUMSIZE 4} (* use 4-byte enums *)
{$ENDIF}
uses
UMusic,
ctypes;
type
PFileStream = Pointer;
PThread = Pointer;
PMutex = Pointer;
PCond = Pointer;
PMediaPluginCore = ^TMediaPluginCore;
TMediaPluginCore = record
version: cint;
log: procedure(level: cint; msg: PAnsiChar; context: PAnsiChar); cdecl;
ticksMillis: function(): cuint32; cdecl;
fileOpen: function(utf8Filename: PAnsiChar; mode: cint): PFileStream; cdecl;
fileClose: procedure(stream: PFileStream); cdecl;
fileRead: function(stream: PFileStream; buf: PCuint8; size: cint): cint64; cdecl;
fileWrite: function(stream: PFileStream; buf: PCuint8; size: cint): cint64; cdecl;
fileSeek: function(stream: PFileStream; pos: cint64; whence: cint): cint64; cdecl;
fileSize: function(stream: PFileStream): cint64; cdecl;
threadCreate: function(func: Pointer; data: Pointer): PThread; cdecl;
threadCurrentID: function(): cuint32; cdecl;
threadGetID: function(thread: PThread): cuint32; cdecl;
threadWait: procedure(thread: PThread; status: PCint); cdecl;
threadSleep: procedure(millisecs: cuint32); cdecl;
mutexCreate: function(): PMutex; cdecl;
mutexDestroy: procedure(mutex: PMutex); cdecl;
mutexLock: function(mutex: PMutex): cint; cdecl;
mutexUnlock: function(mutex: PMutex): cint; cdecl;
condCreate: function(): PCond; cdecl;
condDestroy: procedure(cond: PCond); cdecl;
condSignal: function(cond: PCond): cint; cdecl;
condBroadcast: function(cond: PCond): cint; cdecl;
condWait: function(cond: PCond; mutex: PMutex): cint; cdecl;
condWaitTimeout: function(cond: PCond; mutex: PMutex; ms: cuint32): cint; cdecl;
end;
PAudioDecodeStream = Pointer;
PAudioConvertStream = Pointer;
PVideoDecodeStream = Pointer;
PCAudioFormatInfo = ^TCAudioFormatInfo;
TCAudioFormatInfo = record
sampleRate: double;
channels: cuint8;
format: cint;
end;
PAudioDecoderInfo = ^TAudioDecoderInfo;
TAudioDecoderInfo = record
priority: cint;
init: function(): cbool; cdecl;
finalize: function(): cbool; cdecl;
open: function(filename: PAnsiChar): PAudioDecodeStream; cdecl;
close: procedure(stream: PAudioDecodeStream); cdecl;
getLength: function(stream: PAudioDecodeStream): double; cdecl;
getAudioFormatInfo: procedure(stream: PAudioDecodeStream; var info: TCAudioFormatInfo); cdecl;
getPosition: function(stream: PAudioDecodeStream): double; cdecl;
setPosition: procedure(stream: PAudioDecodeStream; time: double); cdecl;
getLoop: function(stream: PAudioDecodeStream): cbool; cdecl;
setLoop: procedure(stream: PAudioDecodeStream; enabled: cbool); cdecl;
isEOF: function(stream: PAudioDecodeStream): cbool; cdecl;
isError: function(stream: PAudioDecodeStream): cbool; cdecl;
readData: function(stream: PAudioDecodeStream; buffer: PCUint8; bufferSize: cint): cint; cdecl;
end;
PAudioConverterInfo = ^TAudioConverterInfo;
TAudioConverterInfo = record
priority: cint;
init: function(): cbool; cdecl;
finalize: function(): cbool; cdecl;
open: function(inputFormat: PCAudioFormatInfo; outputFormat: PCAudioFormatInfo): PAudioConvertStream; cdecl;
close: procedure(stream: PAudioConvertStream); cdecl;
convert: function(stream: PAudioConvertStream; input, output: PCuint8; numSamples: PCint): cint; cdecl;
getOutputBufferSize: function(stream: PAudioConvertStream; inputSize: cint): cint; cdecl;
getRatio: function(stream: PAudioConvertStream): double; cdecl;
end;
PVideoFrameInfo = ^TVideoFrameInfo;
TVideoFrameInfo = record
width: cint;
height: cint;
aspect: double;
end;
PVideoDecoderInfo = ^TVideoDecoderInfo;
TVideoDecoderInfo = record
priority: cint;
init: function(): cbool; cdecl;
finalize: function(): cbool; cdecl;
open: function(filename: PAnsiChar): PVideoDecodeStream; cdecl;
close: procedure(stream: PVideoDecodeStream); cdecl;
setLoop: procedure(stream: PVideoDecodeStream; enable: cbool); cdecl;
getLoop: function(stream: PVideoDecodeStream): cbool; cdecl;
setPosition: procedure(stream: PVideoDecodeStream; time: double); cdecl;
getPosition: function(stream: PVideoDecodeStream): double; cdecl;
getFrameInfo: procedure(stream: PVideoDecodeStream; info: PVideoFrameInfo); cdecl;
getFrame: function (stream: PVideoDecodeStream; time: clongdouble): PCuint8; cdecl;
end;
PMediaPluginInfo = ^TMediaPluginInfo;
TMediaPluginInfo = record
version: cint;
name: PAnsiChar;
initialize: function(): cbool; cdecl;
finalize: function(): cbool; cdecl;
audioDecoder: PAudioDecoderInfo;
audioConverter: PAudioConverterInfo;
videoDecoder: PVideoDecoderInfo;
end;
TPluginRegisterFunc = function(core: PMediaPluginCore): PMediaPluginInfo; cdecl;
procedure LoadMediaPlugins();
procedure UnloadMediaPlugins();
function MediaPluginCore: PMediaPluginCore;
procedure AudioFormatInfoToCStruct(
const Info: TAudioFormatInfo; var CInfo: TCAudioFormatInfo);
implementation
uses
SysUtils,
Classes,
SDL,
moduleloader,
UFilesystem,
UPath,
UPathUtils,
ULog,
UPlatform,
UAudioDecoderPlugin,
UAudioConverterPlugin,
UVideoDecoderPlugin;
var
MediaPluginCore_Instance: TMediaPluginCore;
const
DebugLogLevels: array[0 .. 5] of integer = (
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_STATUS,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
LOG_LEVEL_CRITICAL
);
function MediaPluginCore: PMediaPluginCore;
begin
Result := @MediaPluginCore_Instance;
end;
procedure AudioFormatInfoToCStruct(
const Info: TAudioFormatInfo; var CInfo: TCAudioFormatInfo);
begin
CInfo.sampleRate := Info.SampleRate;
CInfo.channels := Info.Channels;
CInfo.format := Ord(Info.Format);
end;
{* Misc *}
procedure Core_log(level: cint; msg: PAnsiChar; context: PAnsiChar); cdecl;
begin
Log.LogMsg(msg, context, DebugLogLevels[level]);
end;
function Core_ticksMillis(): cuint32; cdecl;
begin
Result := SDL_GetTicks();
end;
{* File *}
const
FILE_OPEN_MODE_READ = $01;
FILE_OPEN_MODE_WRITE = $02;
FILE_OPEN_MODE_READ_WRITE = FILE_OPEN_MODE_READ or FILE_OPEN_MODE_WRITE;
function Core_fileOpen(utf8Filename: PAnsiChar; mode: cint): PFileStream; cdecl;
var
OpenMode: word;
begin
if (mode = FILE_OPEN_MODE_READ_WRITE) then
OpenMode := fmCreate
else if (mode = FILE_OPEN_MODE_WRITE) then
OpenMode := fmCreate // TODO: fmCreate is Read+Write -> reopen with fmOpenWrite
else // (mode = FILE_OPEN_MODE_READ)
OpenMode := fmOpenRead or fmShareDenyWrite;
try
Result := TBinaryFileStream.Create(Path(utf8Filename), OpenMode);
except
Result := nil;
end;
end;
procedure Core_fileClose(stream: PFileStream); cdecl;
var
FileStream : TStream;
begin
FileStream := TStream(stream);
FileStream.Free;
end;
function Core_fileRead(stream: PFileStream; buf: PCuint8; size: cint): cint64; cdecl;
var
FileStream: TStream;
begin
FileStream := TStream(stream);
try
Result := FileStream.Read(buf^, size);
except
Result := -1;
end;
end;
function Core_fileWrite(stream: PFileStream; buf: PCuint8; size: cint): cint64; cdecl;
var
FileStream: TStream;
begin
FileStream := TStream(stream);
try
Result := FileStream.Write(buf^, size);
except
Result := -1;
end;
end;
function Core_fileSeek(stream: PFileStream; pos: cint64; whence: cint): cint64; cdecl;
var
FileStream : TStream;
Origin : TSeekOrigin;
begin
FileStream := TStream(stream);
case whence of
0 {SEEK_SET}: Origin := soBeginning;
1 {SEEK_CUR}: Origin := soCurrent;
2 {SEEK_END}: Origin := soEnd;
else
Origin := soBeginning;
end;
Result := FileStream.Seek(pos, Origin);
end;
function Core_fileSize(stream: PFileStream): cint64; cdecl;
var
FileStream : TStream;
begin
FileStream := TStream(stream);
Result := FileStream.Size;
end;
{* Thread *}
function Core_threadCreate(func: Pointer; data: Pointer): PThread; cdecl;
begin
Result := SDL_CreateThread(func, data);
end;
function Core_threadCurrentID(): cuint32; cdecl;
begin
Result := SDL_ThreadID();
end;
function Core_threadGetID(thread: PThread): cuint32; cdecl;
begin
Result := SDL_GetThreadID(PSDL_Thread(thread));
end;
procedure Core_threadWait(thread: PThread; status: PCint); cdecl;
begin
SDL_WaitThread(PSDL_Thread(thread), status^);
end;
procedure Core_threadSleep(millisecs: cuint32); cdecl;
begin
SDL_Delay(millisecs);
end;
{* Mutex *}
function Core_mutexCreate(): PMutex; cdecl;
begin
Result := PMutex(SDL_CreateMutex());
end;
procedure Core_mutexDestroy(mutex: PMutex); cdecl;
begin
SDL_DestroyMutex(PSDL_Mutex(mutex));
end;
function Core_mutexLock(mutex: PMutex): cint; cdecl;
begin
Result := SDL_mutexP(PSDL_Mutex(mutex));
end;
function Core_mutexUnlock(mutex: PMutex): cint; cdecl;
begin
Result := SDL_mutexV(PSDL_Mutex(mutex));
end;
{* Condition *}
function Core_condCreate(): PCond; cdecl;
begin
Result := PCond(SDL_CreateCond());
end;
procedure Core_condDestroy(cond: PCond); cdecl;
begin
SDL_DestroyCond(PSDL_Cond(cond));
end;
function Core_condSignal(cond: PCond): cint; cdecl;
begin
Result := SDL_CondSignal(PSDL_Cond(cond));
end;
function Core_condBroadcast(cond: PCond): cint; cdecl;
begin
Result := SDL_CondBroadcast(PSDL_Cond(cond));
end;
function Core_condWait(cond: PCond; mutex: PMutex): cint; cdecl;
begin
Result := SDL_CondWait(PSDL_Cond(cond), PSDL_Mutex(mutex));
end;
function Core_condWaitTimeout(cond: PCond; mutex: PMutex; ms: cuint32): cint; cdecl;
begin
Result := SDL_CondWaitTimeout(PSDL_Cond(cond), PSDL_Mutex(mutex), ms);
end;
procedure InitializeMediaPluginCore;
begin
with MediaPluginCore_Instance do
begin
version := 0;
log := Core_log;
ticksMillis := Core_ticksMillis;
fileOpen := Core_fileOpen;
fileClose := Core_fileClose;
fileRead := Core_fileRead;
fileWrite := Core_fileWrite;
fileSeek := Core_fileSeek;
fileSize := Core_fileSize;
threadCreate := Core_threadCreate;
threadCurrentID := Core_threadCurrentID;
threadGetID := Core_threadGetID;
threadWait := Core_threadWait;
threadSleep := Core_threadSleep;
mutexCreate := Core_mutexCreate;
mutexDestroy := Core_mutexDestroy;
mutexLock := Core_mutexLock;
mutexUnlock := Core_mutexUnlock;
condCreate := Core_condCreate;
condDestroy := Core_condDestroy;
condSignal := Core_condSignal;
condBroadcast := Core_condBroadcast;
condWait := Core_condWait;
condWaitTimeout := Core_condWaitTimeout;
end;
end;
var
MediaPlugins: TList;
type
PMediaPluginEntry = ^TMediaPluginEntry;
TMediaPluginEntry = record
Module: TModuleHandle;
Info: PMediaPluginInfo;
end;
procedure LoadMediaPlugins();
var
LibPath: IPath;
Iter: IFileIterator;
FileInfo: TFileInfo;
ModuleFile: IPath;
Module: TModuleHandle;
RegisterFunc: TPluginRegisterFunc;
PluginInfo: PMediaPluginInfo;
PluginEntry: PMediaPluginEntry;
const
{$IF Defined(MSWINDOWS)}
ModuleExt = '.dll';
{$ELSEIF Defined(DARWIN)}
ModuleExt = '.dylib';
{$ELSE} //Defined(UNIX)
ModuleExt = '.so';
{$IFEND}
begin
MediaPlugins := TList.Create;
{$IFDEF MSWINDOWS}
// add an additional dll search path for plugin dependencies
Platform.AddLibrarySearchPath(MediaPluginPath.Append('deps'));
{$ENDIF}
LibPath := MediaPluginPath.Append('*' + ModuleExt);
Iter := FileSystem.FileFind(LibPath, faAnyFile);
while (Iter.HasNext) do
begin
FileInfo := Iter.Next();
ModuleFile := MediaPluginPath.Append(FileInfo.Name);
Module := INVALID_MODULEHANDLE_VALUE;
if (not LoadModule(Module, PChar(ModuleFile.ToNative))) then
begin
Log.LogInfo('Failed to load media plugin: "' + FileInfo.Name.ToNative + '"',
'LoadMediaPlugins');
Continue;
end;
RegisterFunc := GetModuleSymbol(Module, 'Plugin_register');
if (@RegisterFunc = nil) then
begin
Log.LogError('Invalid media plugin: "' + FileInfo.Name.ToNative + '"',
'LoadMediaPlugins');
UnloadModule(Module);
Continue;
end;
PluginInfo := RegisterFunc(MediaPluginCore);
if (PluginInfo = nil) then
begin
Log.LogError('Invalid media plugin info: "' + FileInfo.Name.ToNative + '"',
'LoadMediaPlugins');
UnloadModule(Module);
Continue;
end;
if ((@PluginInfo.initialize <> nil) and
(not PluginInfo.initialize())) then
begin
Log.LogError('Failed to initialize media plugin: "' + PluginInfo.name + '"',
'LoadMediaPlugins');
UnloadModule(Module);
Continue;
end;
Log.LogStatus('Loaded media plugin: "' + PluginInfo.name + '"',
'LoadMediaPlugins');
New(PluginEntry);
PluginEntry.Module := Module;
PluginEntry.Info := PluginInfo;
MediaPlugins.Add(PluginEntry);
// register modules
if (PluginInfo.audioDecoder <> nil) then
MediaManager.Add(TAudioDecoderPlugin.Create(PluginInfo));
if (PluginInfo.audioConverter <> nil) then
MediaManager.Add(TAudioConverterPlugin.Create(PluginInfo));
if (PluginInfo.videoDecoder <> nil) then
MediaManager.Add(TVideoDecoderPlugin.Create(PluginInfo));
end;
end;
procedure UnloadMediaPlugins();
var
I: integer;
PluginEntry: PMediaPluginEntry;
begin
for I := 0 to MediaPlugins.Count - 1 do
begin
PluginEntry := MediaPlugins[I];
if ((@PluginEntry.Info.finalize <> nil) and
(not PluginEntry.Info.finalize())) then
begin
Log.LogError('Failed to finalize media plugin: "' + PluginEntry.Info.name + '"',
'UnloadMediaPlugins');
end;
UnloadModule(PluginEntry.Module);
end;
end;
initialization
InitializeMediaPluginCore;
end.