aboutsummaryrefslogblamecommitdiffstats
path: root/mediaplugin/src/media/UMediaPlugin.pas
blob: cf25e033193548a0db363e5a8f98e1c8d24589a5 (plain) (tree)





































                                                                        
         


         




                        

                                       
                  
 
                                                                           

























                                                                                      

      
                               
                                
                               







                                         

                                         
                   

                                       











                                                                                                   
 

                                             
                   

                                       




                                                                                                                

      






                                     

                                         
                   

                                       





                                                                            
                                                                                      


                                                                                       







                                         
                                    

      
                                                                                  
 
                             
                               


                                           


                                                                


              
           
          
      




               
            
                      
                        
                      













                                              




                                           







                                                                

          
                                                                           



                                                  
                                            
     




                           



                                                                          
 















                                                                                   


                                                     

                       
     

                                


                                                                                     

                      
     





                                          


                                                                                      

                      
     





                                           


                                                                                      


                       
     








                                         


                                                           

                       
     

                                
















































































                                                                                    



                                    


























                                            

    









                                         








                                    
                                 







                           
     

                               




                                                                
                                                     




                                                        
                                         





















                                                                                  

                                           








                                                                                  





                                   

                                            
                                                               

                                                                 
                                            
                                                               


      

















                                                                                      



                            
{* 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.