From f44a93685ae651a08b51a2688a1c8d4c9ae2d28e Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Fri, 8 May 2009 15:00:25 +0000
Subject: checkin of trunk-copy as base for cmake test

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@1716 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 cmake/src/media/UAudioConverter.pas          |  483 +++++++++++
 cmake/src/media/UAudioCore_Bass.pas          |  148 ++++
 cmake/src/media/UAudioCore_Portaudio.pas     |  281 +++++++
 cmake/src/media/UAudioDecoder_Bass.pas       |  267 ++++++
 cmake/src/media/UAudioDecoder_FFmpeg.pas     | 1138 +++++++++++++++++++++++++
 cmake/src/media/UAudioInput_Bass.pas         |  505 +++++++++++
 cmake/src/media/UAudioInput_Portaudio.pas    |  495 +++++++++++
 cmake/src/media/UAudioPlaybackBase.pas       |  317 +++++++
 cmake/src/media/UAudioPlayback_Bass.pas      |  756 +++++++++++++++++
 cmake/src/media/UAudioPlayback_Portaudio.pas |  385 +++++++++
 cmake/src/media/UAudioPlayback_SDL.pas       |  182 ++++
 cmake/src/media/UAudioPlayback_SoftMixer.pas | 1154 ++++++++++++++++++++++++++
 cmake/src/media/UMediaCore_FFmpeg.pas        |  430 ++++++++++
 cmake/src/media/UMediaCore_SDL.pas           |   63 ++
 cmake/src/media/UMedia_dummy.pas             |  268 ++++++
 cmake/src/media/UVideo.pas                   |  954 +++++++++++++++++++++
 cmake/src/media/UVisualizer.pas              |  551 ++++++++++++
 17 files changed, 8377 insertions(+)
 create mode 100644 cmake/src/media/UAudioConverter.pas
 create mode 100644 cmake/src/media/UAudioCore_Bass.pas
 create mode 100644 cmake/src/media/UAudioCore_Portaudio.pas
 create mode 100644 cmake/src/media/UAudioDecoder_Bass.pas
 create mode 100644 cmake/src/media/UAudioDecoder_FFmpeg.pas
 create mode 100644 cmake/src/media/UAudioInput_Bass.pas
 create mode 100644 cmake/src/media/UAudioInput_Portaudio.pas
 create mode 100644 cmake/src/media/UAudioPlaybackBase.pas
 create mode 100644 cmake/src/media/UAudioPlayback_Bass.pas
 create mode 100644 cmake/src/media/UAudioPlayback_Portaudio.pas
 create mode 100644 cmake/src/media/UAudioPlayback_SDL.pas
 create mode 100644 cmake/src/media/UAudioPlayback_SoftMixer.pas
 create mode 100644 cmake/src/media/UMediaCore_FFmpeg.pas
 create mode 100644 cmake/src/media/UMediaCore_SDL.pas
 create mode 100644 cmake/src/media/UMedia_dummy.pas
 create mode 100644 cmake/src/media/UVideo.pas
 create mode 100644 cmake/src/media/UVisualizer.pas

(limited to 'cmake/src/media')

diff --git a/cmake/src/media/UAudioConverter.pas b/cmake/src/media/UAudioConverter.pas
new file mode 100644
index 00000000..657b80dd
--- /dev/null
+++ b/cmake/src/media/UAudioConverter.pas
@@ -0,0 +1,483 @@
+{* 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 UAudioConverter;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UMusic,
+  ULog,
+  ctypes,
+  {$IFDEF UseSRCResample}
+  samplerate,
+  {$ENDIF}
+  {$IFDEF UseFFmpegResample}
+  avcodec,
+  {$ENDIF}
+  UMediaCore_SDL,
+  sdl,
+  SysUtils,
+  Math;
+
+type
+  {*
+   * Notes:
+   *  - 44.1kHz to 48kHz conversion or vice versa is not supported
+   *    by SDL 1.2 (will be introduced in 1.3).
+   *    No conversion takes place in this cases.
+   *    This is because SDL just converts differences in powers of 2.
+   *    So the result might not be that accurate.
+   *    This IS audible (voice to high/low) and it needs good synchronization
+   *    with the video or the lyrics timer.
+   *  - float<->int16 conversion is not supported (will be part of 1.3) and
+   *    SDL (<1.3) is not capable of handling floats at all.
+   *  -> Using FFmpeg or libsamplerate for resampling is preferred.
+   *     Use SDL for channel and format conversion only.
+   *}
+  TAudioConverter_SDL = class(TAudioConverter)
+    private
+      cvt: TSDL_AudioCVT;
+    public
+      function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override;
+      destructor Destroy(); override;
+
+      function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override;
+      function GetOutputBufferSize(InputSize: integer): integer; override;
+      function GetRatio(): double; override;
+  end;
+
+  {$IFDEF UseFFmpegResample}
+  // Note: FFmpeg seems to be using "kaiser windowed sinc" for resampling, so
+  // the quality should be good.
+  TAudioConverter_FFmpeg = class(TAudioConverter)
+    private
+      // TODO: use SDL for multi-channel->stereo and format conversion
+      ResampleContext: PReSampleContext;
+      Ratio: double;
+    public
+      function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override;
+      destructor Destroy(); override;
+
+      function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override;
+      function GetOutputBufferSize(InputSize: integer): integer; override;
+      function GetRatio(): double; override;
+  end;
+  {$ENDIF}
+
+  {$IFDEF UseSRCResample}
+  TAudioConverter_SRC = class(TAudioConverter)
+    private
+      ConverterState: PSRC_STATE;
+      ConversionData: SRC_DATA;
+      FormatConverter: TAudioConverter;
+    public
+      function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override;
+      destructor Destroy(); override;
+
+      function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override;
+      function GetOutputBufferSize(InputSize: integer): integer; override;
+      function GetRatio(): double; override;
+  end;
+
+  // Note: SRC (=libsamplerate) provides several converters with different quality
+  // speed trade-offs. The SINC-types are slow but offer best quality.
+  // The SRC_SINC_* converters are too slow for realtime conversion,
+  // (SRC_SINC_FASTEST is approx. ten times slower than SRC_LINEAR) resulting
+  // in audible clicks and pops.
+  // SRC_LINEAR is very fast and should have a better quality than SRC_ZERO_ORDER_HOLD
+  // because it interpolates the samples. Normal "non-audiophile" users should not
+  // be able to hear a difference between the SINC_* ones and LINEAR. Especially
+  // if people sing along with the song.
+  // But FFmpeg might offer a better quality/speed ratio than SRC_LINEAR.
+  const
+    SRC_CONVERTER_TYPE = SRC_LINEAR; 
+  {$ENDIF}
+
+implementation
+
+function TAudioConverter_SDL.Init(srcFormatInfo: TAudioFormatInfo; dstFormatInfo: TAudioFormatInfo): boolean;
+var
+  srcFormat: UInt16;
+  dstFormat: UInt16;
+begin
+  inherited Init(SrcFormatInfo, DstFormatInfo);
+
+  Result := false;
+
+  if (not ConvertAudioFormatToSDL(srcFormatInfo.Format, srcFormat) or
+      not ConvertAudioFormatToSDL(dstFormatInfo.Format, dstFormat)) then
+  begin
+    Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
+    Exit;
+  end;
+
+  if (SDL_BuildAudioCVT(@cvt,
+    srcFormat, srcFormatInfo.Channels, Round(srcFormatInfo.SampleRate),
+    dstFormat, dstFormatInfo.Channels, Round(dstFormatInfo.SampleRate)) = -1) then
+  begin
+    Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+destructor TAudioConverter_SDL.Destroy();
+begin
+  // nothing to be done here
+  inherited;
+end;
+
+(*
+ * Returns the size of the output buffer. This might be bigger than the actual
+ * size of resampled audio data.
+ *)
+function TAudioConverter_SDL.GetOutputBufferSize(InputSize: integer): integer;
+begin
+  // Note: len_ratio must not be used here. Even if the len_ratio is 1.0, len_mult might be 2.
+  // Example: 44.1kHz/mono to 22.05kHz/stereo -> len_ratio=1, len_mult=2
+  Result := InputSize * cvt.len_mult;
+end;
+
+function TAudioConverter_SDL.GetRatio(): double;
+begin
+  Result := cvt.len_ratio;
+end;
+
+function TAudioConverter_SDL.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer;
+begin
+  Result := -1;
+
+  if (InputSize <= 0) then
+  begin
+    // avoid div-by-zero problems
+    if (InputSize = 0) then
+      Result := 0;
+    Exit;
+  end;
+
+  // OutputBuffer is always bigger than or equal to InputBuffer
+  Move(InputBuffer[0], OutputBuffer[0], InputSize);
+  cvt.buf := PUint8(OutputBuffer);
+  cvt.len := InputSize;
+  if (SDL_ConvertAudio(@cvt) = -1) then
+    Exit;
+
+  Result := cvt.len_cvt;
+end;
+
+
+{$IFDEF UseFFmpegResample}
+
+function TAudioConverter_FFmpeg.Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean;
+begin
+  inherited Init(SrcFormatInfo, DstFormatInfo);
+
+  Result := false;
+
+  // Note: ffmpeg does not support resampling for more than 2 input channels
+
+  if (srcFormatInfo.Format <> asfS16) then
+  begin
+    Log.LogError('Unsupported format', 'TAudioConverter_FFmpeg.Init');
+    Exit;
+  end;
+
+  // TODO: use SDL here
+  if (srcFormatInfo.Format <> dstFormatInfo.Format) then
+  begin
+    Log.LogError('Incompatible formats', 'TAudioConverter_FFmpeg.Init');
+    Exit;
+  end;
+
+  ResampleContext := audio_resample_init(
+      dstFormatInfo.Channels, srcFormatInfo.Channels,
+      Round(dstFormatInfo.SampleRate), Round(srcFormatInfo.SampleRate));
+  if (ResampleContext = nil) then
+  begin
+    Log.LogError('audio_resample_init() failed', 'TAudioConverter_FFmpeg.Init');
+    Exit;
+  end;
+
+  // calculate ratio
+  Ratio := (dstFormatInfo.Channels / srcFormatInfo.Channels) *
+           (dstFormatInfo.SampleRate / srcFormatInfo.SampleRate);
+
+  Result := true;
+end;
+
+destructor TAudioConverter_FFmpeg.Destroy();
+begin
+  if (ResampleContext <> nil) then
+    audio_resample_close(ResampleContext);
+  inherited;
+end;
+
+function TAudioConverter_FFmpeg.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer;
+var
+  InputSampleCount: integer;
+  OutputSampleCount: integer;
+begin
+  Result := -1;
+
+  if (InputSize <= 0) then
+  begin
+    // avoid div-by-zero in audio_resample()
+    if (InputSize = 0) then
+      Result := 0;
+    Exit;
+  end;
+
+  InputSampleCount := InputSize div SrcFormatInfo.FrameSize;
+  OutputSampleCount := audio_resample(
+      ResampleContext, PSmallInt(OutputBuffer), PSmallInt(InputBuffer),
+      InputSampleCount);
+  if (OutputSampleCount = -1) then
+  begin
+    Log.LogError('audio_resample() failed', 'TAudioConverter_FFmpeg.Convert');
+    Exit;
+  end;
+  Result := OutputSampleCount * DstFormatInfo.FrameSize;
+end;
+
+function TAudioConverter_FFmpeg.GetOutputBufferSize(InputSize: integer): integer;
+begin
+  Result := Ceil(InputSize * GetRatio());
+end;
+
+function TAudioConverter_FFmpeg.GetRatio(): double;
+begin
+  Result := Ratio;
+end;
+
+{$ENDIF}
+
+
+{$IFDEF UseSRCResample}
+
+function TAudioConverter_SRC.Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean;
+var
+  error: integer;
+  TempSrcFormatInfo: TAudioFormatInfo;
+  TempDstFormatInfo: TAudioFormatInfo;
+begin
+  inherited Init(SrcFormatInfo, DstFormatInfo);
+
+  Result := false;
+
+  FormatConverter := nil;
+
+  // SRC does not handle channel or format conversion
+  if ((SrcFormatInfo.Channels <> DstFormatInfo.Channels) or
+      not (SrcFormatInfo.Format in [asfS16, asfFloat])) then
+  begin
+    // SDL can not convert to float, so we have to convert to SInt16 first
+    TempSrcFormatInfo := TAudioFormatInfo.Create(
+        SrcFormatInfo.Channels, SrcFormatInfo.SampleRate, SrcFormatInfo.Format);
+    TempDstFormatInfo := TAudioFormatInfo.Create(
+        DstFormatInfo.Channels, SrcFormatInfo.SampleRate, asfS16);
+
+    // init format/channel conversion
+    FormatConverter := TAudioConverter_SDL.Create();
+    if (not FormatConverter.Init(TempSrcFormatInfo, TempDstFormatInfo)) then
+    begin
+      Log.LogError('Unsupported input format', 'TAudioConverter_SRC.Init');
+      FormatConverter.Free;
+      // exit after the format-info is freed
+    end;
+
+    // this info was copied so we do not need it anymore 
+    TempSrcFormatInfo.Free;
+    TempDstFormatInfo.Free;
+
+    // leave if the format is not supported
+    if (not assigned(FormatConverter)) then
+      Exit;
+
+    // adjust our copy of the input audio-format for SRC conversion
+    Self.SrcFormatInfo.Channels := DstFormatInfo.Channels;
+    Self.SrcFormatInfo.Format := asfS16;
+  end;
+
+  if ((DstFormatInfo.Format <> asfS16) and
+      (DstFormatInfo.Format <> asfFloat)) then
+  begin
+    Log.LogError('Unsupported output format', 'TAudioConverter_SRC.Init');
+    Exit;
+  end;
+
+  ConversionData.src_ratio := DstFormatInfo.SampleRate / SrcFormatInfo.SampleRate;
+  if (src_is_valid_ratio(ConversionData.src_ratio) = 0) then
+  begin
+    Log.LogError('Invalid samplerate ratio', 'TAudioConverter_SRC.Init');
+    Exit;
+  end;
+
+  ConverterState := src_new(SRC_CONVERTER_TYPE, DstFormatInfo.Channels, @error);
+  if (ConverterState = nil) then
+  begin
+    Log.LogError('src_new() failed: ' + src_strerror(error), 'TAudioConverter_SRC.Init');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+destructor TAudioConverter_SRC.Destroy();
+begin
+  if (ConverterState <> nil) then
+    src_delete(ConverterState);
+  FormatConverter.Free;
+  inherited;
+end;
+
+function TAudioConverter_SRC.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer;
+var
+  FloatInputBuffer: PSingle;
+  FloatOutputBuffer: PSingle;
+  TempBuffer: PByteArray;
+  TempSize: integer;
+  NumSamples: integer;
+  OutputSize: integer;
+  error: integer;
+begin
+  Result := -1;
+
+  TempBuffer := nil;
+
+  // format conversion with external converter (to correct number of channels and format)
+  if (assigned(FormatConverter)) then
+  begin
+    TempSize := FormatConverter.GetOutputBufferSize(InputSize);
+    GetMem(TempBuffer, TempSize);
+    InputSize := FormatConverter.Convert(InputBuffer, TempBuffer, InputSize);
+    InputBuffer := TempBuffer;
+  end;
+
+  if (InputSize <= 0) then
+  begin
+    // avoid div-by-zero problems
+    if (InputSize = 0) then
+      Result := 0;
+    if (TempBuffer <> nil) then
+      FreeMem(TempBuffer);
+    Exit;
+  end;
+
+  if (SrcFormatInfo.Format = asfFloat) then
+  begin
+    FloatInputBuffer := PSingle(InputBuffer);
+  end else begin
+    NumSamples := InputSize div AudioSampleSize[SrcFormatInfo.Format];
+    GetMem(FloatInputBuffer, NumSamples * SizeOf(Single));
+    src_short_to_float_array(PCshort(InputBuffer), PCfloat(FloatInputBuffer), NumSamples);
+  end;
+
+  // calculate approx. output size
+  OutputSize := Ceil(InputSize * ConversionData.src_ratio);
+
+  if (DstFormatInfo.Format = asfFloat) then
+  begin
+    FloatOutputBuffer := PSingle(OutputBuffer);
+  end else begin
+    NumSamples := OutputSize div AudioSampleSize[DstFormatInfo.Format];
+    GetMem(FloatOutputBuffer, NumSamples * SizeOf(Single));
+  end;
+
+  with ConversionData do
+  begin
+    data_in := PCFloat(FloatInputBuffer);
+    input_frames := InputSize div SrcFormatInfo.FrameSize;
+    data_out := PCFloat(FloatOutputBuffer);
+    output_frames := OutputSize div DstFormatInfo.FrameSize;
+    // TODO: set this to 1 at end of file-playback
+    end_of_input := 0;
+  end;
+
+  error := src_process(ConverterState, @ConversionData);
+  if (error <> 0) then
+  begin
+    Log.LogError(src_strerror(error), 'TAudioConverter_SRC.Convert');
+    if (SrcFormatInfo.Format <> asfFloat) then
+      FreeMem(FloatInputBuffer);
+    if (DstFormatInfo.Format <> asfFloat) then
+      FreeMem(FloatOutputBuffer);
+    if (TempBuffer <> nil) then
+      FreeMem(TempBuffer);
+    Exit;
+  end;
+
+  if (SrcFormatInfo.Format <> asfFloat) then
+    FreeMem(FloatInputBuffer);
+
+  if (DstFormatInfo.Format <> asfFloat) then
+  begin
+    NumSamples := ConversionData.output_frames_gen * DstFormatInfo.Channels;
+    src_float_to_short_array(PCfloat(FloatOutputBuffer), PCshort(OutputBuffer), NumSamples);
+    FreeMem(FloatOutputBuffer);
+  end;
+
+  // free format conversion buffer if used
+  if (TempBuffer <> nil) then
+    FreeMem(TempBuffer);
+
+  if (assigned(FormatConverter)) then
+    InputSize := ConversionData.input_frames_used * FormatConverter.SrcFormatInfo.FrameSize
+  else
+    InputSize := ConversionData.input_frames_used * SrcFormatInfo.FrameSize;
+
+  // set result to output size according to SRC
+  Result := ConversionData.output_frames_gen * DstFormatInfo.FrameSize;
+end;
+
+function TAudioConverter_SRC.GetOutputBufferSize(InputSize: integer): integer;
+begin
+  Result := Ceil(InputSize * GetRatio());
+end;
+
+function TAudioConverter_SRC.GetRatio(): double;
+begin
+  // if we need additional channel/format conversion, use this ratio
+  if (assigned(FormatConverter)) then
+    Result := FormatConverter.GetRatio()
+  else
+    Result := 1.0;
+
+  // now the SRC ratio (Note: the format might change from SInt16 to float)
+  Result := Result *
+            ConversionData.src_ratio *
+            (DstFormatInfo.FrameSize / SrcFormatInfo.FrameSize);
+end;
+
+{$ENDIF}
+
+end.
\ No newline at end of file
diff --git a/cmake/src/media/UAudioCore_Bass.pas b/cmake/src/media/UAudioCore_Bass.pas
new file mode 100644
index 00000000..12623dc1
--- /dev/null
+++ b/cmake/src/media/UAudioCore_Bass.pas
@@ -0,0 +1,148 @@
+{* 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 UAudioCore_Bass;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  Classes,
+  SysUtils,
+  UMusic,
+  bass;     // (Note: DWORD is defined here)
+
+type
+  TAudioCore_Bass = class
+    public
+      constructor Create();
+      class function GetInstance(): TAudioCore_Bass;
+      function ErrorGetString(): string; overload;
+      function ErrorGetString(errCode: integer): string; overload;
+      function ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
+      function ConvertBASSFlagsToAudioFormat(Flags: DWORD; out Format: TAudioSampleFormat): boolean;
+  end;
+
+implementation
+
+uses
+  UMain,
+  ULog;
+
+var
+  Instance: TAudioCore_Bass;
+
+constructor TAudioCore_Bass.Create();
+begin
+  inherited;
+end;
+
+class function TAudioCore_Bass.GetInstance(): TAudioCore_Bass;
+begin
+  if (not Assigned(Instance)) then
+    Instance := TAudioCore_Bass.Create();
+  Result := Instance;
+end;
+
+function TAudioCore_Bass.ErrorGetString(): string;
+begin
+  Result := ErrorGetString(BASS_ErrorGetCode());
+end;
+
+function TAudioCore_Bass.ErrorGetString(errCode: integer): string;
+begin
+  case errCode of
+    BASS_OK:             result := 'No error';
+    BASS_ERROR_MEM:      result := 'Insufficient memory';
+    BASS_ERROR_FILEOPEN: result := 'File could not be opened';
+    BASS_ERROR_DRIVER:   result := 'Device driver not available';
+    BASS_ERROR_BUFLOST:  result := 'Buffer lost';
+    BASS_ERROR_HANDLE:   result := 'Invalid Handle';
+    BASS_ERROR_FORMAT:   result := 'Sample-Format not supported';
+    BASS_ERROR_POSITION: result := 'Illegal position';
+    BASS_ERROR_INIT:     result := 'BASS_Init has not been successfully called';
+    BASS_ERROR_START:    result := 'Paused/stopped';
+    BASS_ERROR_ALREADY:  result := 'Already created/used';
+    BASS_ERROR_NOCHAN:   result := 'No free channels';
+    BASS_ERROR_ILLTYPE:  result := 'Type is invalid';
+    BASS_ERROR_ILLPARAM: result := 'Illegal parameter';
+    BASS_ERROR_NO3D:     result := 'No 3D support';
+    BASS_ERROR_NOEAX:    result := 'No EAX support';
+    BASS_ERROR_DEVICE:   result := 'Invalid device number';
+    BASS_ERROR_NOPLAY:   result := 'Channel not playing';
+    BASS_ERROR_FREQ:     result := 'Freq out of range';
+    BASS_ERROR_NOTFILE:  result := 'Not a file stream';
+    BASS_ERROR_NOHW:     result := 'No hardware support';
+    BASS_ERROR_EMPTY:    result := 'Is empty';
+    BASS_ERROR_NONET:    result := 'Network unavailable';
+    BASS_ERROR_CREATE:   result := 'Creation error';
+    BASS_ERROR_NOFX:     result := 'DX8 effects unavailable';
+    BASS_ERROR_NOTAVAIL: result := 'Not available';
+    BASS_ERROR_DECODE:   result := 'Is a decoding channel';
+    BASS_ERROR_DX:       result := 'Insufficient version of DirectX';
+    BASS_ERROR_TIMEOUT:  result := 'Timeout';
+    BASS_ERROR_FILEFORM: result := 'File-Format not recognised/supported';
+    BASS_ERROR_SPEAKER:  result := 'Requested speaker(s) not support';
+    BASS_ERROR_VERSION:  result := 'Version error';
+    BASS_ERROR_CODEC:    result := 'Codec not available/supported';
+    BASS_ERROR_ENDED:    result := 'The channel/file has ended';
+    BASS_ERROR_UNKNOWN:  result := 'Unknown error';
+    else                 result := 'Unknown error';
+  end;
+end;
+
+function TAudioCore_Bass.ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
+begin
+  case Format of
+    asfS16:   Flags := 0;
+    asfFloat: Flags := BASS_SAMPLE_FLOAT;
+    asfU8:    Flags := BASS_SAMPLE_8BITS;
+    else begin
+      Result := false;
+      Exit;
+    end;
+  end;
+
+  Result := true;
+end;
+
+function TAudioCore_Bass.ConvertBASSFlagsToAudioFormat(Flags: DWORD; out Format: TAudioSampleFormat): boolean;
+begin
+  if ((Flags and BASS_SAMPLE_FLOAT) <> 0) then
+    Format := asfFloat
+  else if ((Flags and BASS_SAMPLE_8BITS) <> 0) then
+    Format := asfU8
+  else
+    Format := asfS16;
+
+  Result := true;
+end;
+
+end.
diff --git a/cmake/src/media/UAudioCore_Portaudio.pas b/cmake/src/media/UAudioCore_Portaudio.pas
new file mode 100644
index 00000000..25ceae3c
--- /dev/null
+++ b/cmake/src/media/UAudioCore_Portaudio.pas
@@ -0,0 +1,281 @@
+{* 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 UAudioCore_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I ../switches.inc}
+
+uses
+  Classes,
+  SysUtils,
+  portaudio;
+
+type
+  TAudioCore_Portaudio = class
+    public
+      constructor Create();
+      class function GetInstance(): TAudioCore_Portaudio;
+      function GetPreferredApiIndex(): TPaHostApiIndex;
+      function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+  end;
+
+implementation
+
+uses
+  ULog;
+
+{*
+ * The default API used by Portaudio is the least common denominator
+ * and might lack efficiency. In addition it might not even work.
+ * We use an array named ApiPreferenceOrder with which we define the order of
+ * preferred APIs to use. The first API-type in the list is tried first.
+ * If it is not available the next one is tried and so on ...
+ * If none of the preferred APIs was found the default API (detected by
+ * portaudio) is used.
+ *
+ * Pascal does not permit zero-length static arrays, so you must use paDefaultApi
+ * as an array's only member if you do not have any preferences.
+ * You can also append paDefaultApi to a non-zero length preferences array but
+ * this is optional because the default API is always used as a fallback.
+ *}
+const
+  paDefaultApi = -1;
+const
+  ApiPreferenceOrder:
+{$IF Defined(MSWINDOWS)}
+    // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
+    // Note2: Windows Default-API is MME, but DirectSound is faster
+    array[0..0] of TPaHostApiTypeId = ( paDirectSound );
+{$ELSEIF Defined(DARWIN)}
+    array[0..0] of TPaHostApiTypeId = ( paDefaultApi ); // paCoreAudio
+{$ELSEIF Defined(UNIX)}
+    // Note: Portmixer has no mixer support for JACK at the moment
+    array[0..2] of TPaHostApiTypeId = ( paALSA, paJACK, paOSS );
+{$ELSE}
+    array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
+{$IFEND}
+
+
+{ TAudioInput_Portaudio }
+
+var
+  Instance: TAudioCore_Portaudio;
+
+constructor TAudioCore_Portaudio.Create();
+begin
+  inherited;
+end;
+
+class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio;
+begin
+  if not assigned(Instance) then
+    Instance := TAudioCore_Portaudio.Create();
+  Result := Instance;
+end;
+
+function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
+var
+  i: integer;
+  apiIndex: TPaHostApiIndex;
+  apiInfo:  PPaHostApiInfo;
+begin
+  result := -1;
+
+  // select preferred sound-API
+  for i:= 0 to High(ApiPreferenceOrder) do
+  begin
+    if(ApiPreferenceOrder[i] <> paDefaultApi) then
+    begin
+      // check if API is available
+      apiIndex := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
+      if(apiIndex >= 0) then
+      begin
+        // we found an API but we must check if it works
+        // (on linux portaudio might detect OSS but does not provide
+        // any devices if ALSA is enabled)
+        apiInfo := Pa_GetHostApiInfo(apiIndex);
+        if (apiInfo^.deviceCount > 0) then
+        begin
+          Result := apiIndex;
+          break;
+        end;
+      end;
+    end;
+  end;
+
+  // None of the preferred APIs is available -> use default
+  if(result < 0) then
+  begin
+    result := Pa_GetDefaultHostApi();
+  end;
+end;
+
+{*
+ * Portaudio test callback used by TestDevice().
+ *}
+function TestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl;
+begin
+  // this callback is called only once
+  result := paAbort;
+end;
+
+(*
+ * Tests if the callback works. Some devices can be opened without
+ * an error but the callback is never called. Calling Pa_StopStream() on such
+ * a stream freezes USDX then. Probably because the callback-thread is deadlocked
+ * due to some bug in portaudio. The blocking Pa_ReadStream() and Pa_WriteStream()
+ * block forever too and though can't be used for testing.
+ *
+ * To avoid freezing Pa_AbortStream (or Pa_CloseStream which calls Pa_AbortStream)
+ * can be used to force the stream to stop. But for some reason this stops debugging
+ * in gdb with a "no process found" message.
+ *
+ * Because freezing devices are non-working devices we test the devices here to
+ * be able to exclude them from the device-selection list.
+ *
+ * Portaudio does not provide any test to check this error case (probably because
+ * it should not even occur). So we have to open the device, start the stream and
+ * check if the callback is called (the stream is stopped if the callback is called
+ * for the first time, so we can poll until the stream is stopped).
+ *
+ * Another error that occurs is that some devices (even the default device) might
+ * work at the beginning but stop after a few calls (maybe 50) of the callback.
+ * For me this problem occurs with the default output-device. The "dmix" or "front"
+ * device must be selected instead. Another problem is that (due to a bug in
+ * portaudio or ALSA) the "front" device is not detected every time portaudio
+ * is started. Sometimes it needs two or more restarts.
+ *
+ * There is no reasonable way to test for these errors. For the first error-case
+ * we could test if the callback is called 50 times but this can take a second
+ * for each device and it can fail in the 51st or even 100th callback call then.
+ *
+ * The second error-case cannot be tested at all. How should we now that one
+ * device is missing if portaudio is not even able to detect it.
+ * We could start and terminate Portaudio for several times and see if the device
+ * count changes but this is ugly.
+ *
+ * Conclusion: We are not able to autodetect a working device with
+ *   portaudio (at least not with the newest v19_20071207) at the moment.
+ *   So we have to provide the possibility to manually select an output device
+ *   in the UltraStar options if we want to use portaudio instead of SDL.
+ *)
+function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+var
+  stream: PPaStream;
+  err: TPaError;
+  cbWorks: boolean;
+  cbPolls: integer;
+  i: integer;
+const
+  altSampleRates: array[0..1] of Double = (44100, 48000); // alternative sample-rates
+begin
+  Result := false;
+
+  if (sampleRate <= 0) then
+    sampleRate := 44100;
+
+  // check if device supports our input-format
+  err := Pa_IsFormatSupported(inParams, outParams, sampleRate);
+  if(err <> paNoError) then
+  begin
+    // we cannot fix the error -> exit
+    if (err <> paInvalidSampleRate) then
+      Exit;
+
+    // try alternative sample-rates to the detected one
+    sampleRate := 0;
+    for i := 0 to High(altSampleRates) do
+    begin
+      // do not check the detected sample-rate twice
+      if (altSampleRates[i] = sampleRate) then
+        continue;
+      // check alternative
+      err := Pa_IsFormatSupported(inParams, outParams, altSampleRates[i]);
+      if (err = paNoError) then
+      begin
+        // sample-rate works
+        sampleRate := altSampleRates[i];
+        break;
+      end;
+    end;
+    // no working sample-rate found
+    if (sampleRate = 0) then
+      Exit;
+  end;
+
+  // FIXME: for some reason gdb stops after a call of Pa_AbortStream()
+  // which is implicitely called by Pa_CloseStream().
+  // gdb's stops with the message: "ptrace: no process found".
+  // Probably because the callback-thread is killed what confuses gdb.
+  {$IF Defined(Debug) and Defined(Linux)}
+  cbWorks := true;
+  {$ELSE}
+  // open device for testing
+  err := Pa_OpenStream(stream, inParams, outParams, sampleRate,
+          paFramesPerBufferUnspecified,
+          paNoFlag, @TestCallback, nil);
+  if(err <> paNoError) then
+  begin
+    exit;
+  end;
+
+  // start the callback
+  err := Pa_StartStream(stream);
+  if(err <> paNoError) then
+  begin
+    Pa_CloseStream(stream);
+    exit;
+  end;
+
+  cbWorks := false;
+  // check if the callback was called (poll for max. 200ms)
+  for cbPolls := 1 to 20 do
+  begin
+    // if the test-callback was called it should be aborted now
+    if (Pa_IsStreamActive(stream) = 0) then
+    begin
+      cbWorks := true;
+      break;
+    end;
+    // not yet aborted, wait and try (poll) again
+    Pa_Sleep(10);
+  end;
+
+  // finally abort the stream
+  Pa_CloseStream(stream);
+  {$IFEND}
+  
+  Result := cbWorks;
+end;
+
+end.
diff --git a/cmake/src/media/UAudioDecoder_Bass.pas b/cmake/src/media/UAudioDecoder_Bass.pas
new file mode 100644
index 00000000..6bbdaeaa
--- /dev/null
+++ b/cmake/src/media/UAudioDecoder_Bass.pas
@@ -0,0 +1,267 @@
+{* 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 UAudioDecoder_Bass;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+implementation
+
+uses
+  Classes,
+  SysUtils,
+  UMain,
+  UMusic,
+  UAudioCore_Bass,
+  ULog,
+  bass;
+
+type
+  TBassDecodeStream = class(TAudioDecodeStream)
+    private
+      Handle: HSTREAM;
+      FormatInfo : TAudioFormatInfo;
+      Error: boolean;
+    public
+      constructor Create(Handle: HSTREAM);
+      destructor Destroy(); override;
+
+      procedure Close();                     override;
+
+      function GetLength(): real;            override;
+      function GetAudioFormatInfo(): TAudioFormatInfo; override;
+      function GetPosition: real;            override;
+      procedure SetPosition(Time: real);     override;
+      function GetLoop(): boolean;           override;
+      procedure SetLoop(Enabled: boolean);   override;
+      function IsEOF(): boolean;             override;
+      function IsError(): boolean;           override;
+
+      function ReadData(Buffer: PByteArray; BufSize: integer): integer; override;
+  end;
+
+type
+  TAudioDecoder_Bass = class( TInterfacedObject, IAudioDecoder )
+    public
+      function GetName: string;
+
+      function InitializeDecoder(): boolean;
+      function FinalizeDecoder(): boolean;
+      function Open(const Filename: string): TAudioDecodeStream;
+  end;
+
+var
+  BassCore: TAudioCore_Bass;
+
+
+{ TBassDecodeStream }
+
+constructor TBassDecodeStream.Create(Handle: HSTREAM);
+var
+  ChannelInfo: BASS_CHANNELINFO;
+  Format: TAudioSampleFormat;
+begin
+  inherited Create();
+  Self.Handle := Handle;
+
+  // setup format info
+  if (not BASS_ChannelGetInfo(Handle, ChannelInfo)) then
+  begin
+    raise Exception.Create('Failed to open decode-stream');
+  end;
+  BassCore.ConvertBASSFlagsToAudioFormat(ChannelInfo.flags, Format);
+  FormatInfo := TAudioFormatInfo.Create(ChannelInfo.chans, ChannelInfo.freq, format);
+
+  Error := false;
+end;
+
+destructor TBassDecodeStream.Destroy();
+begin
+  Close();
+  inherited;
+end;
+
+procedure TBassDecodeStream.Close();
+begin
+  if (Handle <> 0) then
+  begin
+    BASS_StreamFree(Handle);
+    Handle := 0;
+  end;
+  PerformOnClose();
+  FreeAndNil(FormatInfo);
+  Error := false;
+end;
+
+function TBassDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+  Result := FormatInfo;
+end;
+
+function TBassDecodeStream.GetLength(): real;
+var
+  bytes: QWORD;
+begin
+  bytes  := BASS_ChannelGetLength(Handle, BASS_POS_BYTE);
+  Result := BASS_ChannelBytes2Seconds(Handle, bytes);
+end;
+
+function TBassDecodeStream.GetPosition: real;
+var
+  bytes: QWORD;
+begin
+  bytes  := BASS_ChannelGetPosition(Handle, BASS_POS_BYTE);
+  Result := BASS_ChannelBytes2Seconds(Handle, bytes);
+end;
+
+procedure TBassDecodeStream.SetPosition(Time: real);
+var
+  bytes: QWORD;
+begin
+  bytes := BASS_ChannelSeconds2Bytes(Handle, Time);
+  BASS_ChannelSetPosition(Handle, bytes, BASS_POS_BYTE);
+end;
+
+function TBassDecodeStream.GetLoop(): boolean;
+var
+  flags: DWORD;
+begin
+  // retrieve channel flags
+  flags := BASS_ChannelFlags(Handle, 0, 0);
+  if (flags = DWORD(-1)) then
+  begin
+    Log.LogError('BASS_ChannelFlags: ' + BassCore.ErrorGetString(), 'TBassDecodeStream.GetLoop');
+    Result := false;
+    Exit;
+  end;
+  Result := (flags and BASS_SAMPLE_LOOP) <> 0;
+end;
+
+procedure TBassDecodeStream.SetLoop(Enabled: boolean);
+var
+  flags: DWORD;
+begin
+  // set/unset loop-flag
+  if (Enabled) then
+    flags := BASS_SAMPLE_LOOP
+  else
+    flags := 0;
+
+  // set new flag-bits
+  if (BASS_ChannelFlags(Handle, flags, BASS_SAMPLE_LOOP) = DWORD(-1)) then
+  begin
+    Log.LogError('BASS_ChannelFlags: ' + BassCore.ErrorGetString(), 'TBassDecodeStream.SetLoop');
+    Exit;
+  end;
+end;
+
+function TBassDecodeStream.IsEOF(): boolean;
+begin
+  Result := (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_STOPPED);
+end;
+
+function TBassDecodeStream.IsError(): boolean;
+begin
+  Result := Error;
+end;
+
+function TBassDecodeStream.ReadData(Buffer: PByteArray; BufSize: integer): integer;
+begin
+  Result := BASS_ChannelGetData(Handle, Buffer, BufSize);
+  // check error state (do not handle EOF as error)
+  if ((Result = -1) and (BASS_ErrorGetCode() <> BASS_ERROR_ENDED)) then
+    Error := true
+  else
+    Error := false;
+end;
+
+
+{ TAudioDecoder_Bass }
+
+function TAudioDecoder_Bass.GetName: String;
+begin
+  result := 'BASS_Decoder';
+end;
+
+function TAudioDecoder_Bass.InitializeDecoder(): boolean;
+begin
+  BassCore := TAudioCore_Bass.GetInstance();
+  Result := true;
+end;
+
+function TAudioDecoder_Bass.FinalizeDecoder(): boolean;
+begin
+  Result := true;
+end;
+
+function TAudioDecoder_Bass.Open(const Filename: string): TAudioDecodeStream;
+var
+  Stream: HSTREAM;
+  ChannelInfo: BASS_CHANNELINFO;
+  FileExt: string;
+begin
+  Result := nil;
+
+  // check if BASS was initialized
+  // in case the decoder is not used with BASS playback, init the NO_SOUND device
+  if ((integer(BASS_GetDevice) = -1) and (BASS_ErrorGetCode() = BASS_ERROR_INIT)) then
+    BASS_Init(0, 44100, 0, 0, nil);
+
+  // TODO: use BASS_STREAM_PRESCAN for accurate seeking in VBR-files?
+  //       disadvantage: seeking will slow down.
+  Stream := BASS_StreamCreateFile(False, PAnsiChar(Filename), 0, 0, BASS_STREAM_DECODE);
+  if (Stream = 0) then
+  begin
+    //Log.LogError(BassCore.ErrorGetString(), 'TAudioDecoder_Bass.Open');
+    Exit;
+  end;
+
+  // check if BASS opened some erroneously recognized file-formats
+  if BASS_ChannelGetInfo(Stream, channelInfo) then
+  begin
+    fileExt := ExtractFileExt(Filename);
+    // BASS opens FLV-files (maybe others too) although it cannot handle them.
+    // Setting BASS_CONFIG_VERIFY to the max. value (100000) does not help.
+    if ((fileExt = '.flv') and (channelInfo.ctype = BASS_CTYPE_STREAM_MP1)) then
+    begin
+      BASS_StreamFree(Stream);
+      Exit;
+    end;
+  end;
+
+  Result := TBassDecodeStream.Create(Stream);
+end;
+
+
+initialization
+  MediaManager.Add(TAudioDecoder_Bass.Create);
+
+end.
diff --git a/cmake/src/media/UAudioDecoder_FFmpeg.pas b/cmake/src/media/UAudioDecoder_FFmpeg.pas
new file mode 100644
index 00000000..f62cb92c
--- /dev/null
+++ b/cmake/src/media/UAudioDecoder_FFmpeg.pas
@@ -0,0 +1,1138 @@
+{* 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 UAudioDecoder_FFmpeg;
+
+(*******************************************************************************
+ *
+ * This unit is primarily based upon -
+ *   http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
+ *
+ *   and tutorial03.c
+ *
+ *   http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
+ *
+ *******************************************************************************)
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+// show FFmpeg specific debug output
+{.$DEFINE DebugFFmpegDecode}
+
+// FFmpeg is very verbose and shows a bunch of errors.
+// Those errors (they can be considered as warnings by us) can be ignored
+// as they do not give any useful information.
+// There is no solution to fix this except for turning them off.
+{.$DEFINE EnableFFmpegErrorOutput}
+
+implementation
+
+uses
+  Classes,
+  Math,
+  UMusic,
+  UIni,
+  UMain,
+  avcodec,
+  avformat,
+  avutil,
+  avio,
+  mathematics, // used for av_rescale_q
+  rational,
+  SDL,
+  SysUtils,
+  UMediaCore_FFmpeg,
+  ULog,
+  UCommon,
+  UConfig;
+
+const
+  MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
+
+const
+  // TODO: The factor 3/2 might not be necessary as we do not need extra
+  // space for synchronizing as in the tutorial.
+  AUDIO_BUFFER_SIZE = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) div 2;
+
+type
+  TFFmpegDecodeStream = class(TAudioDecodeStream)
+    private
+      StateLock:   PSDL_Mutex;
+
+      EOFState:   boolean; // end-of-stream flag (locked by StateLock)
+      ErrorState: boolean; // error flag (locked by StateLock)
+
+      QuitRequest: boolean; // (locked by StateLock)
+      ParserIdleCond: PSDL_Cond;
+
+      // parser pause/resume data
+      ParserLocked:            boolean;
+      ParserPauseRequestCount: integer;
+      ParserUnlockedCond:      PSDL_Cond;
+      ParserResumeCond:        PSDL_Cond;
+
+      SeekRequest: boolean; // (locked by StateLock)
+      SeekFlags:   integer; // (locked by StateLock)
+      SeekPos:     double;    // stream position to seek for (in secs) (locked by StateLock)
+      SeekFlush:   boolean;   // true if the buffers should be flushed after seeking (locked by StateLock)
+      SeekFinishedCond: PSDL_Cond;
+
+      Loop: boolean; // (locked by StateLock)
+
+      ParseThread: PSDL_Thread;
+      PacketQueue: TPacketQueue;
+
+      FormatInfo: TAudioFormatInfo;
+
+      // FFmpeg specific data
+      FormatCtx: PAVFormatContext;
+      CodecCtx:  PAVCodecContext;
+      Codec:     PAVCodec;
+
+      AudioStreamIndex: integer;
+      AudioStream: PAVStream;
+      AudioStreamPos: double; // stream position in seconds (locked by DecoderLock)
+
+      // decoder pause/resume data
+      DecoderLocked:            boolean;
+      DecoderPauseRequestCount: integer;
+      DecoderUnlockedCond:      PSDL_Cond;
+      DecoderResumeCond:        PSDL_Cond;
+
+      // state-vars for DecodeFrame (locked by DecoderLock)
+      AudioPaket:        TAVPacket;
+      AudioPaketData:    PByteArray;
+      AudioPaketSize:    integer;
+      AudioPaketSilence: integer; // number of bytes of silence to return
+
+      // state-vars for AudioCallback (locked by DecoderLock)
+      AudioBufferPos:  integer;
+      AudioBufferSize: integer;
+      AudioBuffer:     PByteArray;
+
+      Filename: string;
+
+      procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean);
+      procedure SetEOF(State: boolean);   {$IFDEF HasInline}inline;{$ENDIF}
+      procedure SetError(State: boolean); {$IFDEF HasInline}inline;{$ENDIF}
+      function IsSeeking(): boolean;
+      function IsQuit(): boolean;
+
+      procedure Reset();
+
+      procedure Parse();
+      function ParseLoop(): boolean;
+      procedure PauseParser();
+      procedure ResumeParser();
+
+      function DecodeFrame(Buffer: PByteArray; BufferSize: integer): integer;
+      procedure FlushCodecBuffers();
+      procedure PauseDecoder();
+      procedure ResumeDecoder();
+    public
+      constructor Create();
+      destructor Destroy(); override;
+
+      function Open(const Filename: string): boolean;
+      procedure Close();                     override;
+
+      function GetLength(): real;            override;
+      function GetAudioFormatInfo(): TAudioFormatInfo; override;
+      function GetPosition: real;            override;
+      procedure SetPosition(Time: real);     override;
+      function GetLoop(): boolean;           override;
+      procedure SetLoop(Enabled: boolean);   override;
+      function IsEOF(): boolean;             override;
+      function IsError(): boolean;           override;
+
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer; override;
+  end;
+
+type
+  TAudioDecoder_FFmpeg = class(TInterfacedObject, IAudioDecoder)
+    public
+      function GetName: string;
+
+      function InitializeDecoder(): boolean;
+      function FinalizeDecoder(): boolean;
+      function Open(const Filename: string): TAudioDecodeStream;
+  end;
+
+var
+  FFmpegCore: TMediaCore_FFmpeg;
+
+function ParseThreadMain(Data: Pointer): integer; cdecl; forward;
+
+
+{ TFFmpegDecodeStream }
+
+constructor TFFmpegDecodeStream.Create();
+begin
+  inherited Create();
+
+  StateLock := SDL_CreateMutex();
+  ParserUnlockedCond := SDL_CreateCond();
+  ParserResumeCond := SDL_CreateCond();
+  ParserIdleCond := SDL_CreateCond();
+  SeekFinishedCond := SDL_CreateCond();
+  DecoderUnlockedCond := SDL_CreateCond();
+  DecoderResumeCond := SDL_CreateCond();
+
+  // according to the documentation of avcodec_decode_audio(2), sample-data
+  // should be aligned on a 16 byte boundary. Otherwise internal calls
+  // (e.g. to SSE or Altivec operations) might fail or lack performance on some
+  // CPUs. Although GetMem() in Delphi and FPC seems to use a 16 byte or higher
+  // alignment for buffers of this size (alignment depends on the size of the
+  // requested buffer), we will set the alignment explicitly as the minimum
+  // alignment used by Delphi and FPC is on an 8 byte boundary.
+  // 
+  // Note: AudioBuffer was previously defined as a field of type TAudioBuffer
+  // (array[0..AUDIO_BUFFER_SIZE-1] of byte) and hence statically allocated.
+  // Fields of records are aligned different to memory allocated with GetMem(),
+  // aligning depending on the type but will be at least 2 bytes.
+  // AudioBuffer was not aligned to a 16 byte boundary. The {$ALIGN x} directive
+  // was not applicable as Delphi in contrast to FPC provides at most 8 byte
+  // alignment ({$ALIGN 16} is not supported) by this directive.
+  AudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16);
+
+  Reset();
+end;
+
+procedure TFFmpegDecodeStream.Reset();
+begin
+  ParseThread := nil;
+
+  EOFState := false;
+  ErrorState := false;
+  Loop := false;
+  QuitRequest := false;
+
+  AudioPaketData := nil;
+  AudioPaketSize := 0;
+  AudioPaketSilence := 0;
+
+  AudioBufferPos := 0;
+  AudioBufferSize := 0;
+
+  ParserLocked := false;
+  ParserPauseRequestCount := 0;
+  DecoderLocked := false;
+  DecoderPauseRequestCount := 0;
+
+  FillChar(AudioPaket, SizeOf(TAVPacket), 0);
+end;
+
+{*
+ * Frees the decode-stream data.
+ *}
+destructor TFFmpegDecodeStream.Destroy();
+begin
+  Close();
+
+  SDL_DestroyMutex(StateLock);
+  SDL_DestroyCond(ParserUnlockedCond);
+  SDL_DestroyCond(ParserResumeCond);
+  SDL_DestroyCond(ParserIdleCond);
+  SDL_DestroyCond(SeekFinishedCond);
+  SDL_DestroyCond(DecoderUnlockedCond);
+  SDL_DestroyCond(DecoderResumeCond);
+
+  FreeAlignedMem(AudioBuffer);
+
+  inherited;
+end;
+
+function TFFmpegDecodeStream.Open(const Filename: string): boolean;
+var
+  SampleFormat: TAudioSampleFormat;
+  AVResult: integer;
+begin
+  Result := false;
+
+  Close();
+  Reset();
+
+  if (not FileExists(Filename)) then
+  begin
+    Log.LogError('Audio-file does not exist: "' + Filename + '"', 'UAudio_FFmpeg');
+    Exit;
+  end;
+
+  Self.Filename := Filename;
+
+  // open audio file
+  if (av_open_input_file(FormatCtx, PAnsiChar(Filename), nil, 0, nil) <> 0) then
+  begin
+    Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg');
+    Exit;
+  end;
+
+  // generate PTS values if they do not exist
+  FormatCtx^.flags := FormatCtx^.flags or AVFMT_FLAG_GENPTS;
+
+  // retrieve stream information
+  if (av_find_stream_info(FormatCtx) < 0) then
+  begin
+    Log.LogError('av_find_stream_info failed: "' + Filename + '"', 'UAudio_FFmpeg');
+    Close();
+    Exit;
+  end;
+
+  // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
+  FormatCtx^.pb.eof_reached := 0;
+
+  {$IFDEF DebugFFmpegDecode}
+  dump_format(FormatCtx, 0, PAnsiChar(Filename), 0);
+  {$ENDIF}
+
+  AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx);
+  if (AudioStreamIndex < 0) then
+  begin
+    Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename + '"', 'UAudio_FFmpeg');
+    Close();
+    Exit;
+  end;
+
+  //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg');
+
+  AudioStream := FormatCtx.streams[AudioStreamIndex];
+  CodecCtx := AudioStream^.codec;
+
+  // TODO: should we use this or not? Should we allow 5.1 channel audio?
+  (*
+  {$IF LIBAVCODEC_VERSION >= 51042000}
+  if (CodecCtx^.channels > 0) then
+    CodecCtx^.request_channels := Min(2, CodecCtx^.channels)
+  else
+    CodecCtx^.request_channels := 2;
+  {$IFEND}
+  *)
+
+  Codec := avcodec_find_decoder(CodecCtx^.codec_id);
+  if (Codec = nil) then
+  begin
+    Log.LogError('Unsupported codec!', 'UAudio_FFmpeg');
+    CodecCtx := nil;
+    Close();
+    Exit;
+  end;
+
+  // set debug options
+  CodecCtx^.debug_mv := 0;
+  CodecCtx^.debug := 0;
+
+  // detect bug-workarounds automatically
+  CodecCtx^.workaround_bugs := FF_BUG_AUTODETECT;
+  // error resilience strategy (careful/compliant/agressive/very_aggressive)
+  //CodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+  // allow non spec compliant speedup tricks.
+  //CodecCtx^.flags2 := CodecCtx^.flags2 or CODEC_FLAG2_FAST;
+
+  // Note: avcodec_open() and avcodec_close() are not thread-safe and will
+  // fail if called concurrently by different threads.
+  FFmpegCore.LockAVCodec();
+  try
+    AVResult := avcodec_open(CodecCtx, Codec);
+  finally
+    FFmpegCore.UnlockAVCodec();
+  end;
+  if (AVResult < 0) then
+  begin
+    Log.LogError('avcodec_open failed!', 'UAudio_FFmpeg');
+    Close();
+    Exit;
+  end;
+
+  // now initialize the audio-format
+
+  if (not FFmpegCore.ConvertFFmpegToAudioFormat(CodecCtx^.sample_fmt, SampleFormat)) then
+  begin
+    // try standard format
+    SampleFormat := asfS16;
+  end;
+
+  FormatInfo := TAudioFormatInfo.Create(
+    CodecCtx^.channels,
+    CodecCtx^.sample_rate,
+    SampleFormat
+  );
+
+
+  PacketQueue := TPacketQueue.Create();
+
+  // finally start the decode thread
+  ParseThread := SDL_CreateThread(@ParseThreadMain, Self);
+
+  Result := true;
+end;
+
+procedure TFFmpegDecodeStream.Close();
+var
+  ThreadResult: integer;
+begin
+  // wake threads waiting for packet-queue data
+  // Note: normally, there are no waiting threads. If there were waiting
+  // ones, they would block the audio-callback thread.
+  if (assigned(PacketQueue)) then
+    PacketQueue.Abort();
+
+  // send quit request (to parse-thread etc)
+  SDL_mutexP(StateLock);
+  QuitRequest := true;
+  SDL_CondBroadcast(ParserIdleCond);
+  SDL_mutexV(StateLock);
+
+  // abort parse-thread
+  if (ParseThread <> nil) then
+  begin
+    // and wait until it terminates
+    SDL_WaitThread(ParseThread, ThreadResult);
+    ParseThread := nil;
+  end;
+
+  // Close the codec
+  if (CodecCtx <> nil) then
+  begin
+    // avcodec_close() is not thread-safe
+    FFmpegCore.LockAVCodec();
+    try
+      avcodec_close(CodecCtx);
+    finally
+      FFmpegCore.UnlockAVCodec();
+    end;
+    CodecCtx := nil;
+  end;
+
+  // Close the video file
+  if (FormatCtx <> nil) then
+  begin
+    av_close_input_file(FormatCtx);
+    FormatCtx := nil;
+  end;
+
+  PerformOnClose();
+  
+  FreeAndNil(PacketQueue);
+  FreeAndNil(FormatInfo);
+end;
+
+function TFFmpegDecodeStream.GetLength(): real;
+begin
+  // do not forget to consider the start_time value here 
+  Result := (FormatCtx^.start_time + FormatCtx^.duration) / AV_TIME_BASE;
+end;
+
+function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+  Result := FormatInfo;
+end;
+
+function TFFmpegDecodeStream.IsEOF(): boolean;
+begin
+  SDL_mutexP(StateLock);
+  Result := EOFState;
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.SetEOF(State: boolean);
+begin
+  SDL_mutexP(StateLock);
+  EOFState := State;
+  SDL_mutexV(StateLock);
+end;
+
+function TFFmpegDecodeStream.IsError(): boolean;
+begin
+  SDL_mutexP(StateLock);
+  Result := ErrorState;
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.SetError(State: boolean);
+begin
+  SDL_mutexP(StateLock);
+  ErrorState := State;
+  SDL_mutexV(StateLock);
+end;
+
+function TFFmpegDecodeStream.IsSeeking(): boolean;
+begin
+  SDL_mutexP(StateLock);
+  Result := SeekRequest;
+  SDL_mutexV(StateLock);
+end;
+
+function TFFmpegDecodeStream.IsQuit(): boolean;
+begin
+  SDL_mutexP(StateLock);
+  Result := QuitRequest;
+  SDL_mutexV(StateLock);
+end;
+
+function TFFmpegDecodeStream.GetPosition(): real;
+var
+  BufferSizeSec: double;
+begin
+  PauseDecoder();
+
+  // ReadData() does not return all of the buffer retrieved by DecodeFrame().
+  // Determine the size of the unused part of the decode-buffer.
+  BufferSizeSec := (AudioBufferSize - AudioBufferPos) /
+                   FormatInfo.BytesPerSec;
+
+  // subtract the size of unused buffer-data from the audio clock.
+  Result := AudioStreamPos - BufferSizeSec;
+
+  ResumeDecoder();
+end;
+
+procedure TFFmpegDecodeStream.SetPosition(Time: real);
+begin
+  SetPositionIntern(Time, true, true);
+end;
+
+function TFFmpegDecodeStream.GetLoop(): boolean;
+begin
+  SDL_mutexP(StateLock);
+  Result := Loop;
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean);
+begin
+  SDL_mutexP(StateLock);
+  Loop := Enabled;
+  SDL_mutexV(StateLock);
+end;
+
+
+(********************************************
+ * Parser section
+ ********************************************)
+
+procedure TFFmpegDecodeStream.PauseParser();
+begin
+  if (SDL_ThreadID() = ParseThread.threadid) then
+    Exit;
+    
+  SDL_mutexP(StateLock);
+  Inc(ParserPauseRequestCount);
+  while (ParserLocked) do
+    SDL_CondWait(ParserUnlockedCond, StateLock);
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.ResumeParser();
+begin
+  if (SDL_ThreadID() = ParseThread.threadid) then
+    Exit;
+
+  SDL_mutexP(StateLock);
+  Dec(ParserPauseRequestCount);
+  SDL_CondSignal(ParserResumeCond);
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean);
+begin
+  // - Pause the parser first to prevent it from putting obsolete packages
+  //   into the queue after the queue was flushed and before seeking is done.
+  //   Otherwise we will hear fragments of old data, if the stream was seeked
+  //   in stopped mode and resumed afterwards (applies to non-blocking mode only).
+  // - Pause the decoder to avoid race-condition that might occur otherwise.
+  // - Last lock the state lock because we are manipulating some shared state-vars.
+  PauseParser();
+  PauseDecoder();
+  SDL_mutexP(StateLock);
+
+  // configure seek parameters
+  SeekPos := Time;
+  SeekFlush := Flush;
+  SeekFlags := AVSEEK_FLAG_ANY;
+  SeekRequest := true;
+
+  // Note: the BACKWARD-flag seeks to the first position <= the position
+  // searched for. Otherwise e.g. position 0 might not be seeked correct.
+  // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
+  // following. In streams with few key-frames (like many flv-files) the next
+  // key-frame after 0 might be 5secs ahead.
+  if (Time < AudioStreamPos) then
+    SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD;
+
+  EOFState := false;
+  ErrorState := false;
+
+  // send a reuse signal in case the parser was stopped (e.g. because of an EOF)
+  SDL_CondSignal(ParserIdleCond);
+
+  SDL_mutexV(StateLock);
+  ResumeDecoder();
+  ResumeParser();
+
+  // in blocking mode, wait until seeking is done
+  if (Blocking) then
+  begin
+    SDL_mutexP(StateLock);
+    while (SeekRequest) do
+      SDL_CondWait(SeekFinishedCond, StateLock);
+    SDL_mutexV(StateLock);
+  end;
+end;
+
+function ParseThreadMain(Data: Pointer): integer; cdecl;
+var
+  Stream: TFFmpegDecodeStream;
+begin
+  Stream := TFFmpegDecodeStream(Data);
+  if (Stream <> nil) then
+    Stream.Parse();
+  Result := 0;
+end;
+
+procedure TFFmpegDecodeStream.Parse();
+begin
+  // reuse thread as long as the stream is not terminated
+  while (ParseLoop()) do
+  begin
+    // wait for reuse or destruction of stream
+    SDL_mutexP(StateLock);
+    while (not (SeekRequest or QuitRequest)) do
+      SDL_CondWait(ParserIdleCond, StateLock);
+    SDL_mutexV(StateLock);
+  end;
+end;
+
+(**
+ * Parser main loop.
+ * Will not return until parsing of the stream is finished.
+ * Reasons for the parser to return are:
+ * - the end-of-file is reached
+ * - an error occured
+ * - the stream was quited (received a quit-request)
+ * Returns true if the stream can be resumed or false if the stream has to
+ * be terminated.
+ *)
+function TFFmpegDecodeStream.ParseLoop(): boolean;
+var
+  Packet: TAVPacket;
+  SeekTarget: int64;
+  ByteIOCtx: PByteIOContext;
+  ErrorCode: integer;
+  StartSilence: double;       // duration of silence at start of stream
+  StartSilencePtr: PDouble;  // pointer for the EMPTY status packet 
+
+  // Note: pthreads wakes threads waiting on a mutex in the order of their
+  // priority and not in FIFO order. SDL does not provide any option to
+  // control priorities. This might (and already did) starve threads waiting
+  // on the mutex (e.g. SetPosition) making usdx look like it was froozen.
+  // Instead of simply locking the critical section we set a ParserLocked flag
+  // instead and give priority to the threads requesting the parser to pause.
+  procedure LockParser();
+  begin
+    SDL_mutexP(StateLock);
+    while (ParserPauseRequestCount > 0) do
+      SDL_CondWait(ParserResumeCond, StateLock);
+    ParserLocked := true;
+    SDL_mutexV(StateLock);
+  end;
+
+  procedure UnlockParser();
+  begin
+    SDL_mutexP(StateLock);
+    ParserLocked := false;
+    SDL_CondBroadcast(ParserUnlockedCond);
+    SDL_mutexV(StateLock);
+  end;
+
+begin
+  Result := true;
+
+  while (true) do
+  begin
+    LockParser();
+    try
+
+      if (IsQuit()) then
+      begin
+        Result := false;
+        Exit;
+      end;
+
+      // handle seek-request (Note: no need to lock SeekRequest here)
+      if (SeekRequest) then
+      begin
+        // first try: seek on the audio stream
+        SeekTarget := Round(SeekPos / av_q2d(AudioStream^.time_base));
+        StartSilence := 0;
+        if (SeekTarget < AudioStream^.start_time) then
+          StartSilence := (AudioStream^.start_time - SeekTarget) * av_q2d(AudioStream^.time_base);
+        ErrorCode := av_seek_frame(FormatCtx, AudioStreamIndex, SeekTarget, SeekFlags);
+
+        if (ErrorCode < 0) then
+        begin
+          // second try: seek on the default stream (necessary for flv-videos and some ogg-files)
+          SeekTarget := Round(SeekPos * AV_TIME_BASE);
+          StartSilence := 0;
+          if (SeekTarget < FormatCtx^.start_time) then
+            StartSilence := (FormatCtx^.start_time - SeekTarget) / AV_TIME_BASE;
+          ErrorCode := av_seek_frame(FormatCtx, -1, SeekTarget, SeekFlags);
+        end;
+
+        // pause decoder and lock state (keep the lock-order to avoid deadlocks).
+        // Note that the decoder does not block in the packet-queue in seeking state,
+        // so locking the decoder here does not cause a dead-lock.
+        PauseDecoder();
+        SDL_mutexP(StateLock);
+        try
+          if (ErrorCode < 0) then
+          begin
+            // seeking failed
+            ErrorState := true;
+            Log.LogStatus('Seek Error in "'+FormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg');
+          end
+          else
+          begin
+            if (SeekFlush) then
+            begin
+              // flush queue (we will send a Flush-Packet when seeking is finished)
+              PacketQueue.Flush();
+
+              // flush the decode buffers
+              AudioBufferSize := 0;
+              AudioBufferPos := 0;
+              AudioPaketSize := 0;
+              AudioPaketSilence := 0;
+              FlushCodecBuffers();
+              
+              // Set preliminary stream position. The position will be set to
+              // the correct value as soon as the first packet is decoded.
+              AudioStreamPos := SeekPos;
+            end
+            else
+            begin
+              // request avcodec buffer flush
+              PacketQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil);
+            end;
+
+            // fill the gap between position 0 and start_time with silence
+            // but not if we are in loop mode
+            if ((StartSilence > 0) and (not Loop)) then
+            begin
+              GetMem(StartSilencePtr, SizeOf(StartSilence));
+              StartSilencePtr^ := StartSilence;
+              PacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr);
+            end;
+          end;
+
+          SeekRequest := false;
+          SDL_CondBroadcast(SeekFinishedCond);
+        finally
+          SDL_mutexV(StateLock);
+          ResumeDecoder();
+        end;
+      end;
+
+      if (PacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then
+      begin
+        SDL_Delay(10);
+        Continue;
+      end;
+
+      if (av_read_frame(FormatCtx, Packet) < 0) then
+      begin
+        // failed to read a frame, check reason
+        {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
+        ByteIOCtx := FormatCtx^.pb;
+        {$ELSE}
+        ByteIOCtx := @FormatCtx^.pb;
+        {$IFEND}
+
+        // check for end-of-file (eof is not an error)
+        if (url_feof(ByteIOCtx) <> 0) then
+        begin
+          if (GetLoop()) then
+          begin
+            // rewind stream (but do not flush)
+            SetPositionIntern(0, false, false);
+            Continue;
+          end
+          else
+          begin
+            // signal end-of-file
+            PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil);
+            Exit;
+          end;
+        end;
+
+        // check for errors
+        if (url_ferror(ByteIOCtx) <> 0) then
+        begin
+          // an error occured -> abort and wait for repositioning or termination
+          PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil);
+          Exit;
+        end;
+
+        // no error -> wait for user input
+        SDL_Delay(100);
+        Continue;
+      end;
+
+      if (Packet.stream_index = AudioStreamIndex) then
+        PacketQueue.Put(@Packet)
+      else
+        av_free_packet(@Packet);
+
+    finally
+      UnlockParser();
+    end;
+  end;
+end;
+
+
+(********************************************
+ * Decoder section
+ ********************************************)
+
+procedure TFFmpegDecodeStream.PauseDecoder();
+begin
+  SDL_mutexP(StateLock);
+  Inc(DecoderPauseRequestCount);
+  while (DecoderLocked) do
+    SDL_CondWait(DecoderUnlockedCond, StateLock);
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.ResumeDecoder();
+begin
+  SDL_mutexP(StateLock);
+  Dec(DecoderPauseRequestCount);
+  SDL_CondSignal(DecoderResumeCond);
+  SDL_mutexV(StateLock);
+end;
+
+procedure TFFmpegDecodeStream.FlushCodecBuffers();
+begin
+  // if no flush operation is specified, avcodec_flush_buffers will not do anything.
+  if (@CodecCtx.codec.flush <> nil) then
+  begin
+    // flush buffers used by avcodec_decode_audio, etc.
+    avcodec_flush_buffers(CodecCtx);
+  end
+  else
+  begin
+    // we need a Workaround to avoid plopping noise with ogg-vorbis and
+    // mp3 (in older versions of FFmpeg).
+    // We will just reopen the codec.
+    FFmpegCore.LockAVCodec();
+    try
+      avcodec_close(CodecCtx);
+      avcodec_open(CodecCtx, Codec);
+    finally
+      FFmpegCore.UnlockAVCodec();
+    end;
+  end;
+end;
+
+function TFFmpegDecodeStream.DecodeFrame(Buffer: PByteArray; BufferSize: integer): integer;
+var
+  PaketDecodedSize: integer; // size of packet data used for decoding
+  DataSize: integer;         // size of output data decoded by FFmpeg
+  BlockQueue: boolean;
+  SilenceDuration: double;
+  {$IFDEF DebugFFmpegDecode}
+  TmpPos: double;
+  {$ENDIF}
+begin
+  Result := -1;
+
+  if (EOF) then
+    Exit;
+
+  while(true) do
+  begin
+    // for titles with start_time > 0 we have to generate silence
+    // until we reach the pts of the first data packet.
+    if (AudioPaketSilence > 0) then
+    begin
+      DataSize := Min(AudioPaketSilence, BufferSize);
+      FillChar(Buffer[0], DataSize, 0);
+      Dec(AudioPaketSilence, DataSize);
+      AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec; 
+      Result := DataSize;
+      Exit;
+    end;
+
+    // read packet data
+    while (AudioPaketSize > 0) do
+    begin
+      DataSize := BufferSize;
+
+      {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
+      PaketDecodedSize := avcodec_decode_audio2(CodecCtx, PSmallint(Buffer),
+                  DataSize, AudioPaketData, AudioPaketSize);
+      {$ELSE}
+      PaketDecodedSize := avcodec_decode_audio(CodecCtx, PSmallint(Buffer),
+                  DataSize, AudioPaketData, AudioPaketSize);
+      {$IFEND}
+
+      if(PaketDecodedSize < 0) then
+      begin
+        // if error, skip frame
+        {$IFDEF DebugFFmpegDecode}
+        DebugWriteln('Skip audio frame');
+        {$ENDIF}
+        AudioPaketSize := 0;
+        Break;
+      end;
+
+      Inc(AudioPaketData, PaketDecodedSize);
+      Dec(AudioPaketSize, PaketDecodedSize);
+
+      // check if avcodec_decode_audio returned data, otherwise fetch more frames
+      if (DataSize <= 0) then
+        Continue;
+
+      // update stream position by the amount of fetched data
+      AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec;
+      
+      // we have data, return it and come back for more later
+      Result := DataSize;
+      Exit;
+    end;
+
+    // free old packet data
+    if (AudioPaket.data <> nil) then
+      av_free_packet(@AudioPaket);
+
+    // do not block queue on seeking (to avoid deadlocks on the DecoderLock)
+    if (IsSeeking()) then
+      BlockQueue := false
+    else
+      BlockQueue := true;
+
+    // request a new packet and block if none available.
+    // If this fails, the queue was aborted.
+    if (PacketQueue.Get(AudioPaket, BlockQueue) <= 0) then
+      Exit;
+
+    // handle Status-packet
+    if (PAnsiChar(AudioPaket.data) = STATUS_PACKET) then
+    begin
+      AudioPaket.data := nil;
+      AudioPaketData := nil;
+      AudioPaketSize := 0;
+
+      case (AudioPaket.flags) of
+        PKT_STATUS_FLAG_FLUSH:
+        begin
+          // just used if SetPositionIntern was called without the flush flag.
+          FlushCodecBuffers;
+        end;
+        PKT_STATUS_FLAG_EOF: // end-of-file
+        begin
+          // ignore EOF while seeking
+          if (not IsSeeking()) then
+            SetEOF(true);
+          // buffer contains no data -> result = -1
+          Exit;
+        end;
+        PKT_STATUS_FLAG_ERROR:
+        begin
+          SetError(true);
+          Log.LogStatus('I/O Error', 'TFFmpegDecodeStream.DecodeFrame');
+          Exit;
+        end;
+        PKT_STATUS_FLAG_EMPTY:
+        begin
+          SilenceDuration := PDouble(PacketQueue.GetStatusInfo(AudioPaket))^;
+          AudioPaketSilence := Round(SilenceDuration * FormatInfo.SampleRate) * FormatInfo.FrameSize;
+          PacketQueue.FreeStatusInfo(AudioPaket);
+        end
+        else
+        begin
+          Log.LogStatus('Unknown status', 'TFFmpegDecodeStream.DecodeFrame');
+        end;
+      end;
+
+      Continue;
+    end;
+
+    AudioPaketData := AudioPaket.data;
+    AudioPaketSize := AudioPaket.size;
+
+    // if available, update the stream position to the presentation time of this package
+    if(AudioPaket.pts <> AV_NOPTS_VALUE) then
+    begin
+      {$IFDEF DebugFFmpegDecode}
+      TmpPos := AudioStreamPos;
+      {$ENDIF}
+      AudioStreamPos := av_q2d(AudioStream^.time_base) * AudioPaket.pts;
+      {$IFDEF DebugFFmpegDecode}
+      DebugWriteln('Timestamp: ' + floattostrf(AudioStreamPos, ffFixed, 15, 3) + ' ' +
+                   '(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' +
+                   'Diff: ' + floattostrf(AudioStreamPos-TmpPos, ffFixed, 15, 3));
+      {$ENDIF}
+    end;
+  end;
+end;
+
+function TFFmpegDecodeStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+var
+  CopyByteCount:   integer; // number of bytes to copy
+  RemainByteCount: integer; // number of bytes left (remain) to read
+  BufferPos: integer;
+
+  // prioritize pause requests
+  procedure LockDecoder();
+  begin
+    SDL_mutexP(StateLock);
+    while (DecoderPauseRequestCount > 0) do
+      SDL_CondWait(DecoderResumeCond, StateLock);
+    DecoderLocked := true;
+    SDL_mutexV(StateLock);
+  end;
+
+  procedure UnlockDecoder();
+  begin
+    SDL_mutexP(StateLock);
+    DecoderLocked := false;
+    SDL_CondBroadcast(DecoderUnlockedCond);
+    SDL_mutexV(StateLock);
+  end;
+
+begin
+  Result := -1;
+
+  // set number of bytes to copy to the output buffer
+  BufferPos := 0;
+
+  LockDecoder();
+  try
+    // leave if end-of-file is reached
+    if (EOF) then
+      Exit;
+
+    // copy data to output buffer
+    while (BufferPos < BufferSize) do
+    begin
+      // check if we need more data
+      if (AudioBufferPos >= AudioBufferSize) then
+      begin
+        AudioBufferPos := 0;
+
+        // we have already sent all our data; get more
+        AudioBufferSize := DecodeFrame(AudioBuffer, AUDIO_BUFFER_SIZE);
+
+        // check for errors or EOF
+        if(AudioBufferSize < 0) then
+        begin
+          Result := BufferPos;
+          Exit;
+        end;
+      end;
+
+      // calc number of new bytes in the decode-buffer
+      CopyByteCount := AudioBufferSize - AudioBufferPos;
+      // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
+      RemainByteCount := BufferSize - BufferPos;
+      if (CopyByteCount > RemainByteCount) then
+        CopyByteCount := RemainByteCount;
+
+      Move(AudioBuffer[AudioBufferPos], Buffer[BufferPos], CopyByteCount);
+
+      Inc(BufferPos,      CopyByteCount);
+      Inc(AudioBufferPos, CopyByteCount);
+    end;
+  finally
+    UnlockDecoder();
+  end;
+
+  Result := BufferSize;
+end;
+
+
+{ TAudioDecoder_FFmpeg }
+
+function TAudioDecoder_FFmpeg.GetName: String;
+begin
+  Result := 'FFmpeg_Decoder';
+end;
+
+function TAudioDecoder_FFmpeg.InitializeDecoder: boolean;
+begin
+  //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFmpeg');
+  FFmpegCore := TMediaCore_FFmpeg.GetInstance();
+  av_register_all();
+
+  // Do not show uninformative error messages by default.
+  // FFmpeg prints all error-infos on the console by default what
+  // is very confusing as the playback of the files is correct.
+  // We consider these errors to be internal to FFMpeg. They can be fixed
+  // by the FFmpeg guys only and do not provide any useful information in
+  // respect to USDX.
+  {$IFNDEF EnableFFmpegErrorOutput}
+    {$IF LIBAVUTIL_VERSION_MAJOR >= 50}
+    av_log_set_level(AV_LOG_FATAL);
+    {$ELSE}
+    // FATAL and ERROR share one log-level, so we have to use QUIET
+    av_log_set_level(AV_LOG_QUIET);
+    {$IFEND}
+  {$ENDIF}
+
+  Result := true;
+end;
+
+function TAudioDecoder_FFmpeg.FinalizeDecoder(): boolean;
+begin
+  Result := true;
+end;
+
+function TAudioDecoder_FFmpeg.Open(const Filename: string): TAudioDecodeStream;
+var
+  Stream: TFFmpegDecodeStream;
+begin
+  Result := nil;
+
+  Stream := TFFmpegDecodeStream.Create();
+  if (not Stream.Open(Filename)) then
+  begin
+    Stream.Free;
+    Exit;
+  end;
+
+  Result := Stream;
+end;
+
+
+initialization
+  MediaManager.Add(TAudioDecoder_FFmpeg.Create);
+
+end.
diff --git a/cmake/src/media/UAudioInput_Bass.pas b/cmake/src/media/UAudioInput_Bass.pas
new file mode 100644
index 00000000..ad6c3818
--- /dev/null
+++ b/cmake/src/media/UAudioInput_Bass.pas
@@ -0,0 +1,505 @@
+{* 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 UAudioInput_Bass;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  Classes,
+  SysUtils,
+  URecord,
+  UMusic;
+
+implementation
+
+uses
+  UMain,
+  UIni,
+  ULog,
+  UAudioCore_Bass,
+  UCommon,    // (Note: for MakeLong on non-windows platforms)
+  {$IFDEF MSWINDOWS}
+  Windows,    // (Note: for MakeLong)
+  {$ENDIF}
+  bass;       // (Note: DWORD is redefined here -> insert after Windows-unit)
+
+type
+  TAudioInput_Bass = class(TAudioInputBase)
+    private
+      function EnumDevices(): boolean;
+    public
+      function GetName: String; override;
+      function InitializeRecord: boolean; override;
+      function FinalizeRecord: boolean; override;
+  end;
+
+  TBassInputDevice = class(TAudioInputDevice)
+    private
+      RecordStream: HSTREAM;
+      BassDeviceID: DWORD; // DeviceID used by BASS
+      SingleIn: boolean;
+
+      function SetInputSource(SourceIndex: integer): boolean;
+      function GetInputSource(): integer;
+    public
+      function Open(): boolean;
+      function Close(): boolean;
+      function Start(): boolean; override;
+      function Stop(): boolean;  override;
+
+      function GetVolume(): single;        override;
+      procedure SetVolume(Volume: single); override;
+  end;
+
+var
+  BassCore: TAudioCore_Bass;
+
+
+{ Global }
+
+{*
+ * Bass input capture callback.
+ * Params:
+ *   stream - BASS input stream
+ *   buffer - buffer of captured samples
+ *   len - size of buffer in bytes
+ *   user - players associated with left/right channels
+ *}
+function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
+    len: integer; inputDevice: Pointer): boolean; {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
+begin
+  AudioInputProcessor.HandleMicrophoneData(buffer, len, inputDevice);
+  Result := true;
+end;
+
+
+{ TBassInputDevice }
+
+function TBassInputDevice.GetInputSource(): integer;
+var
+  SourceCnt: integer;
+  i: integer;
+  flags: DWORD;
+begin
+  // get input-source config (subtract virtual device to get BASS indices)
+  SourceCnt := Length(Source)-1;
+
+  // find source
+  Result := -1;
+  for i := 0 to SourceCnt-1 do
+  begin
+    // get input settings
+    flags := BASS_RecordGetInput(i, PSingle(nil)^);
+    if (flags = DWORD(-1)) then
+    begin
+      Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.GetInputSource');
+      Exit;
+    end;
+
+    // check if current source is selected
+    if ((flags and BASS_INPUT_OFF) = 0) then
+    begin
+      // selected source found
+      Result := i;
+      Exit;
+    end;
+  end;
+end;
+
+function TBassInputDevice.SetInputSource(SourceIndex: integer): boolean;
+var
+  SourceCnt: integer;
+  i: integer;
+  flags: DWORD;
+begin
+  Result := false;
+
+  // check for invalid source index
+  if (SourceIndex < 0) then
+    Exit;
+
+  // get input-source config (subtract virtual device to get BASS indices)
+  SourceCnt := Length(Source)-1;
+
+  // turn on selected source (turns off the others for single-in devices)
+  if (not BASS_RecordSetInput(SourceIndex, BASS_INPUT_ON, -1)) then
+  begin
+    Log.LogError('BASS_RecordSetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Start');
+    Exit;
+  end;
+
+  // turn off all other sources (not needed for single-in devices)
+  if (not SingleIn) then
+  begin
+    for i := 0 to SourceCnt-1 do
+    begin
+      if (i = SourceIndex) then
+        continue;
+      // get input settings
+      flags := BASS_RecordGetInput(i, PSingle(nil)^);
+      if (flags = DWORD(-1)) then
+      begin
+        Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.GetInputSource');
+        Exit;
+      end;
+      // deselect source if selected
+      if ((flags and BASS_INPUT_OFF) = 0) then
+        BASS_RecordSetInput(i, BASS_INPUT_OFF, -1);
+    end;
+  end;
+
+  Result := true;
+end;
+
+function TBassInputDevice.Open(): boolean;
+var
+  FormatFlags: DWORD;
+  SourceIndex: integer;
+const
+  latency = 20; // 20ms callback period (= latency)
+begin
+  Result := false;
+
+  if (not BASS_RecordInit(BassDeviceID)) then
+  begin
+    Log.LogError('BASS_RecordInit['+Name+']: ' +
+                 BassCore.ErrorGetString(), 'TBassInputDevice.Open');
+    Exit;
+  end;
+
+  if (not BassCore.ConvertAudioFormatToBASSFlags(AudioFormat.Format, FormatFlags)) then
+  begin
+    Log.LogError('Unhandled sample-format', 'TBassInputDevice.Open');
+    Exit;
+  end;
+
+  // start capturing in paused state
+  RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels,
+                    MakeLong(FormatFlags or BASS_RECORD_PAUSE, latency),
+                    @MicrophoneCallback, Self);
+  if (RecordStream = 0) then
+  begin
+    Log.LogError('BASS_RecordStart: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Open');
+    BASS_RecordFree;
+    Exit;
+  end;
+
+  // save current source selection and select new source
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // nothing to do if default source is used
+    SourceRestore := -1;
+  end
+  else
+  begin
+    // store current source-index and select new source
+    SourceRestore := GetInputSource();
+    SetInputSource(SourceIndex);
+  end;
+
+  Result := true;
+end;
+
+{* Start input-capturing on this device. *}
+function TBassInputDevice.Start(): boolean;
+begin
+  Result := false;
+
+  // recording already started -> stop first
+  if (RecordStream <> 0) then
+    Stop();
+
+  // TODO: Do not open the device here (takes too much time).
+  if not Open() then
+    Exit;
+
+  if (not BASS_ChannelPlay(RecordStream, true)) then
+  begin
+    Log.LogError('BASS_ChannelPlay: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Start');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+{* Stop input-capturing on this device. *}
+function TBassInputDevice.Stop(): boolean;
+begin
+  Result := false;
+
+  if (RecordStream = 0) then
+    Exit;
+  if (not BASS_RecordSetDevice(BassDeviceID)) then
+    Exit;
+
+  if (not BASS_ChannelStop(RecordStream)) then
+  begin
+    Log.LogError('BASS_ChannelStop: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Stop');
+  end;
+
+  // TODO: Do not close the device here (takes too much time).
+  Result := Close();
+end;
+
+function TBassInputDevice.Close(): boolean;
+begin
+  // restore source selection
+  if (SourceRestore >= 0) then
+  begin
+    SetInputSource(SourceRestore);
+  end;
+
+  // free data
+  if (not BASS_RecordFree()) then
+  begin
+    Log.LogError('BASS_RecordFree: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Close');
+    Result := false;
+  end
+  else
+  begin
+    Result := true;
+  end;
+
+  RecordStream := 0;
+end;
+
+function TBassInputDevice.GetVolume(): single;
+var
+  SourceIndex: integer;
+  lVolume: Single;
+begin
+  Result := 0;
+
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // if default source used find selected source
+    SourceIndex := GetInputSource();
+    if (SourceIndex = -1) then
+      Exit;
+  end;
+
+  if (BASS_RecordGetInput(SourceIndex, lVolume) = DWORD(-1)) then
+  begin
+    Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString() , 'TBassInputDevice.GetVolume');
+    Exit;
+  end;
+  Result := lVolume;
+end;
+
+procedure TBassInputDevice.SetVolume(Volume: single);
+var
+  SourceIndex: integer;
+begin
+  SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+  if (SourceIndex = -1) then
+  begin
+    // if default source used find selected source
+    SourceIndex := GetInputSource();
+    if (SourceIndex = -1) then
+      Exit;
+  end;
+
+  // clip volume to valid range
+  if (Volume > 1.0) then
+    Volume := 1.0
+  else if (Volume < 0) then
+    Volume := 0;
+
+  if (not BASS_RecordSetInput(SourceIndex, 0, Volume)) then
+  begin
+    Log.LogError('BASS_RecordSetInput: ' + BassCore.ErrorGetString() , 'TBassInputDevice.SetVolume');
+  end;
+end;
+
+
+{ TAudioInput_Bass }
+
+function  TAudioInput_Bass.GetName: String;
+begin
+  result := 'BASS_Input';
+end;
+
+function TAudioInput_Bass.EnumDevices(): boolean;
+var
+  Descr:      PChar;
+  SourceName: PChar;
+  Flags:      integer;
+  BassDeviceID: integer;
+  BassDevice:   TBassInputDevice;
+  DeviceIndex:  integer;
+  DeviceInfo: BASS_DEVICEINFO;
+  SourceIndex:  integer;
+  RecordInfo: BASS_RECORDINFO;
+  SelectedSourceIndex: integer;
+begin
+  result := false;
+
+  DeviceIndex := 0;
+  BassDeviceID := 0;
+  SetLength(AudioInputProcessor.DeviceList, 0);
+
+  // checks for recording devices and puts them into an array
+  while true do
+  begin
+    if (not BASS_RecordGetDeviceInfo(BassDeviceID, DeviceInfo)) then
+      break;
+
+    // try to initialize the device
+    if not BASS_RecordInit(BassDeviceID) then
+    begin
+      Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
+                    'TAudioInput_Bass.InitializeRecord');
+    end
+    else
+    begin
+      SetLength(AudioInputProcessor.DeviceList, DeviceIndex+1);
+
+      // TODO: free object on termination
+      BassDevice := TBassInputDevice.Create();
+      AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice;
+
+      Descr := DeviceInfo.name;
+
+      BassDevice.BassDeviceID := BassDeviceID;
+      BassDevice.Name := UnifyDeviceName(Descr, DeviceIndex);
+
+      // zero info-struct as some fields might not be set (e.g. freq is just set on Vista and MacOSX)
+      FillChar(RecordInfo, SizeOf(RecordInfo), 0);
+      // retrieve recording device info
+      BASS_RecordGetInfo(RecordInfo);
+
+      // check if BASS has capture-freq. info
+      if (RecordInfo.freq > 0) then
+      begin
+        // use current input sample rate (available only on Windows Vista and OSX).
+        // Recording at this rate will give the best quality and performance, as no resampling is required.
+        // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
+        BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
+      end
+      else
+      begin
+        // BASS does not provide an explizit input channel count (except BASS_RECORDINFO.formats)
+        // but it doesn't fail if we use stereo input on a mono device
+        // -> use stereo by default
+        BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
+      end;
+
+      // get info if multiple input-sources can be selected at once
+      BassDevice.SingleIn := RecordInfo.singlein;
+
+      // init list for capture buffers per channel
+      SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
+
+      BassDevice.MicSource := -1;
+      BassDevice.SourceRestore := -1;
+
+      // add a virtual default source (will not change mixer-settings)
+      SetLength(BassDevice.Source, 1);
+      BassDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
+
+      // add real input sources
+      SourceIndex := 1;
+
+      // process each input
+      while true do
+      begin
+        SourceName := BASS_RecordGetInputName(SourceIndex-1);
+
+        {$IFDEF DARWIN}
+        // Under MacOSX the SingStar Mics have an empty InputName.
+        // So, we have to add a hard coded Workaround for this problem
+        // FIXME: - Do we need this anymore? Doesn't the (new) default source already solve this problem?
+        //        - Normally a nil return value of BASS_RecordGetInputName() means end-of-list, so maybe
+        //          BASS is not able to detect any mic-sources (the default source will work then).
+        //        - Does BASS_RecordGetInfo() return true or false? If it returns true in this case
+        //          we could use this value to check if the device exists.
+        //          Please check that, eddie.
+        //          If it returns false, then the source is not detected and it does not make sense to add a second
+        //          fake device here.
+        //          What about BASS_RecordGetInput()? Does it return a value <> -1?
+        //        - Does it even work at all with this fake source-index, now that input switching works?
+        //          This info was not used before (sources were never switched), so it did not matter what source-index was used.
+        //          But now BASS_RecordSetInput() will probably fail.
+        if ((SourceName = nil) and (SourceIndex = 1) and (Pos('USBMIC Serial#', Descr) > 0)) then
+          SourceName := 'Microphone'
+        {$ENDIF}
+        
+        if (SourceName = nil) then
+          break;
+
+        SetLength(BassDevice.Source, Length(BassDevice.Source)+1);
+        BassDevice.Source[SourceIndex].Name := SourceName;
+
+        // get input-source info
+        Flags := BASS_RecordGetInput(SourceIndex, PSingle(nil)^);
+        if (Flags <> -1) then
+        begin
+          // is the current source a mic-source?
+          if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then
+            BassDevice.MicSource := SourceIndex;
+        end;
+
+        Inc(SourceIndex);
+      end;
+
+      // FIXME: this call hangs in FPC (windows) every 2nd time USDX is called.
+      //   Maybe because the sound-device was not released properly?
+      BASS_RecordFree;
+
+      Inc(DeviceIndex);
+    end;
+
+    Inc(BassDeviceID);
+  end;
+
+  result := true;
+end;
+
+function TAudioInput_Bass.InitializeRecord(): boolean;
+begin
+  BassCore := TAudioCore_Bass.GetInstance();
+  Result := EnumDevices();
+end;
+
+function TAudioInput_Bass.FinalizeRecord(): boolean;
+begin
+  CaptureStop;
+  Result := inherited FinalizeRecord;
+end;
+
+
+initialization
+  MediaManager.Add(TAudioInput_Bass.Create);
+
+end.
diff --git a/cmake/src/media/UAudioInput_Portaudio.pas b/cmake/src/media/UAudioInput_Portaudio.pas
new file mode 100644
index 00000000..31d2882b
--- /dev/null
+++ b/cmake/src/media/UAudioInput_Portaudio.pas
@@ -0,0 +1,495 @@
+{* 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 UAudioInput_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I ../switches.inc}
+
+uses
+  Classes,
+  SysUtils,
+  UMusic;
+
+implementation
+
+uses
+  {$IFDEF UsePortmixer}
+  portmixer,
+  {$ENDIF}
+  portaudio,
+  UAudioCore_Portaudio,
+  URecord,
+  UIni,
+  ULog,
+  UMain;
+
+type
+  TAudioInput_Portaudio = class(TAudioInputBase)
+    private
+      AudioCore: TAudioCore_Portaudio;
+      function EnumDevices(): boolean;
+    public
+      function GetName: String; override;
+      function InitializeRecord: boolean; override;
+      function FinalizeRecord: boolean; override;
+  end;
+
+  TPortaudioInputDevice = class(TAudioInputDevice)
+    private
+      RecordStream: PPaStream;
+      {$IFDEF UsePortmixer}
+      Mixer: PPxMixer;
+      {$ENDIF}
+      PaDeviceIndex:  TPaDeviceIndex;
+    public
+      function Open(): boolean;
+      function Close(): boolean;
+      function Start(): boolean; override;
+      function Stop(): boolean;  override;
+
+      function GetVolume(): single;        override;
+      procedure SetVolume(Volume: single); override;
+  end;
+
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl; forward;
+
+function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl; forward;
+
+
+{ TPortaudioInputDevice }
+
+function TPortaudioInputDevice.Open(): boolean;
+var
+  Error:       TPaError;
+  inputParams: TPaStreamParameters;
+  deviceInfo:  PPaDeviceInfo;
+begin
+  Result := false;
+
+  // get input latency info
+  deviceInfo := Pa_GetDeviceInfo(PaDeviceIndex);
+
+  // set input stream parameters
+  with inputParams do
+  begin
+    device := PaDeviceIndex;
+    channelCount := AudioFormat.Channels;
+    sampleFormat := paInt16;
+    suggestedLatency := deviceInfo^.defaultLowInputLatency;
+    hostApiSpecificStreamInfo := nil;
+  end;
+
+  //Log.LogStatus(deviceInfo^.name, 'Portaudio');
+  //Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
+
+  // open input stream
+  Error := Pa_OpenStream(RecordStream, @inputParams, nil,
+      AudioFormat.SampleRate,
+      paFramesPerBufferUnspecified, paNoFlag,
+      @MicrophoneCallback, Pointer(Self));
+  if(Error <> paNoError) then
+  begin
+    Log.LogError('Error opening stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
+    Exit;
+  end;
+
+  {$IFDEF UsePortmixer}
+    // open default mixer
+    Mixer := Px_OpenMixer(RecordStream, 0);
+    if (Mixer = nil) then
+    begin
+      Log.LogError('Error opening mixer: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
+    end
+    else
+    begin
+      // save current source selection and select new source
+      SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+      if (SourceIndex = -1) then
+      begin
+        // nothing to do if default source is used
+        SourceRestore := -1;
+      end
+      else
+      begin
+        // store current source-index and select new source
+        SourceRestore := Px_GetCurrentInputSource(Mixer); // -1 in error case
+        Px_SetCurrentInputSource(Mixer, SourceIndex);
+      end;
+    end;
+  {$ENDIF}
+
+  Result := true;
+end;
+
+function TPortaudioInputDevice.Start(): boolean;
+var
+  Error:       TPaError;
+begin
+  Result := false;
+
+  // recording already started -> stop first
+  if (RecordStream <> nil) then
+    Stop();
+
+  // TODO: Do not open the device here (takes too much time).
+  if (not Open()) then
+    Exit;
+
+  // start capture
+  Error := Pa_StartStream(RecordStream);
+  if(Error <> paNoError) then
+  begin
+    Log.LogError('Error starting stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Start');
+    Close();
+    RecordStream := nil;
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+function TPortaudioInputDevice.Stop(): boolean;
+var
+  Error: TPaError;
+begin
+  Result := false;
+
+  if (RecordStream = nil) then
+    Exit;
+
+  // Note: do NOT call Pa_StopStream here!
+  // It gets stuck on devices with non-working callback as Pa_StopStream
+  // waits until all buffers have been handled (which never occurs in that case).
+  Error := Pa_AbortStream(RecordStream);
+  if (Error <> paNoError) then
+  begin
+    Log.LogError('Pa_AbortStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Stop');
+  end;
+
+  Result := Close();
+end;
+
+function TPortaudioInputDevice.Close(): boolean;
+var
+  Error: TPaError;
+begin
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+    begin
+      // restore source selection
+      if (SourceRestore >= 0) then
+      begin
+        Px_SetCurrentInputSource(Mixer, SourceRestore);
+      end;
+
+      // close mixer
+      Px_CloseMixer(Mixer);
+      Mixer := nil;
+    end;
+  {$ENDIF}
+
+  Error := Pa_CloseStream(RecordStream);
+  if (Error <> paNoError) then
+  begin
+    Log.LogError('Pa_CloseStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Close');
+    Result := false;
+  end
+  else
+  begin
+    Result := true;
+  end;
+
+  RecordStream := nil;
+end;
+
+function TPortaudioInputDevice.GetVolume(): single;
+begin
+  Result := 0;
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+      Result := Px_GetInputVolume(Mixer);
+  {$ENDIF}
+end;
+
+procedure TPortaudioInputDevice.SetVolume(Volume: single);
+begin
+  {$IFDEF UsePortmixer}
+    if (Mixer <> nil) then
+    begin
+      // clip to valid range
+      if (Volume > 1.0) then
+        Volume := 1.0
+      else if (Volume < 0) then
+        Volume := 0;
+      Px_SetInputVolume(Mixer, Volume);
+    end;
+  {$ENDIF}
+end;
+
+
+{ TAudioInput_Portaudio }
+
+function TAudioInput_Portaudio.GetName: String;
+begin
+  result := 'Portaudio';
+end;
+
+function TAudioInput_Portaudio.EnumDevices(): boolean;
+var
+  i:           integer;
+  paApiIndex:  TPaHostApiIndex;
+  paApiInfo:   PPaHostApiInfo;
+  deviceName:  string;
+  deviceIndex: TPaDeviceIndex;
+  deviceInfo:  PPaDeviceInfo;
+  channelCnt:  integer;
+  SC:          integer; // soundcard
+  err:         TPaError;
+  errMsg:      string;
+  paDevice:    TPortaudioInputDevice;
+  inputParams: TPaStreamParameters;
+  stream:      PPaStream;
+  streamInfo:  PPaStreamInfo;
+  sampleRate:  double;
+  latency:     TPaTime;
+  {$IFDEF UsePortmixer}
+  mixer:       PPxMixer;
+  sourceCnt:   integer;
+  sourceIndex: integer;
+  sourceName:  string;
+  {$ENDIF}
+begin
+  Result := false;
+
+  // choose the best available Audio-API
+  paApiIndex := AudioCore.GetPreferredApiIndex();
+  if(paApiIndex = -1) then
+  begin
+    Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.EnumDevices');
+    Exit;
+  end;
+
+  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+
+  SC := 0;
+
+  // init array-size to max. input-devices count
+  SetLength(AudioInputProcessor.DeviceList, paApiInfo^.deviceCount);
+  for i:= 0 to High(AudioInputProcessor.DeviceList) do
+  begin
+    // convert API-specific device-index to global index
+    deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+    deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+    channelCnt := deviceInfo^.maxInputChannels;
+
+    // current device is no input device -> skip
+    if (channelCnt <= 0) then
+      continue;
+
+    // portaudio returns a channel-count of 128 for some devices
+    // (e.g. the "default"-device), so we have to detect those
+    // fantasy channel counts.
+    if (channelCnt > 8) then
+      channelCnt := 2;
+
+    paDevice := TPortaudioInputDevice.Create();
+    AudioInputProcessor.DeviceList[SC] := paDevice;
+
+    // retrieve device-name
+    deviceName := deviceInfo^.name;
+    paDevice.Name := deviceName;
+    paDevice.PaDeviceIndex := deviceIndex;
+
+    sampleRate := deviceInfo^.defaultSampleRate;
+
+    // on vista and xp the defaultLowInputLatency may be set to 0 but it works.
+    // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?)
+    latency := deviceInfo^.defaultLowInputLatency;
+
+    // setup desired input parameters
+    // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+    //       not be set correctly in OSS)
+    with inputParams do
+    begin
+      device := deviceIndex;
+      channelCount := channelCnt;
+      sampleFormat := paInt16;
+      suggestedLatency := latency;
+      hostApiSpecificStreamInfo := nil;
+    end;
+
+    // check souncard and adjust sample-rate
+    if (not AudioCore.TestDevice(@inputParams, nil, sampleRate)) then
+    begin
+      // ignore device if it does not work
+      Log.LogError('Device "'+paDevice.Name+'" does not work',
+                   'TAudioInput_Portaudio.EnumDevices');
+      paDevice.Free();
+      continue;
+    end;
+
+    // open device for further info
+    err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+        paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
+    if(err <> paNoError) then
+    begin
+      // unable to open device -> skip
+      errMsg := Pa_GetErrorText(err);
+      Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+                   'TAudioInput_Portaudio.EnumDevices');
+      paDevice.Free();
+      continue;
+    end;
+
+    // adjust sample-rate (might be changed by portaudio)
+    streamInfo := Pa_GetStreamInfo(stream);
+    if (streamInfo <> nil) then
+    begin
+      if (sampleRate <> streamInfo^.sampleRate) then
+      begin
+        Log.LogStatus('Portaudio changed Samplerate from ' + FloatToStr(sampleRate) +
+            ' to ' + FloatToStr(streamInfo^.sampleRate),
+            'TAudioInput_Portaudio.InitializeRecord');
+        sampleRate := streamInfo^.sampleRate;
+      end;
+    end;
+    
+    // create audio-format info and resize capture-buffer array
+    paDevice.AudioFormat := TAudioFormatInfo.Create(
+        channelCnt,
+        sampleRate,
+        asfS16
+    );
+    SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
+
+    Log.LogStatus('InputDevice "'+paDevice.Name+'"@' +
+        IntToStr(paDevice.AudioFormat.Channels)+'x'+
+        FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
+        FloatTostr(inputParams.suggestedLatency)+'sec)' ,
+        'Portaudio.EnumDevices');
+
+    // portaudio does not provide a source-type check
+    paDevice.MicSource := -1;
+    paDevice.SourceRestore := -1;
+
+    // add a virtual default source (will not change mixer-settings)
+    SetLength(paDevice.Source, 1);
+    paDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
+
+    {$IFDEF UsePortmixer}
+      // use default mixer
+      mixer := Px_OpenMixer(stream, 0);
+
+      // get input count
+      sourceCnt := Px_GetNumInputSources(mixer);
+      SetLength(paDevice.Source, sourceCnt+1);
+
+      // get input names
+      for sourceIndex := 1 to sourceCnt do
+      begin
+        sourceName := Px_GetInputSourceName(mixer, sourceIndex-1);
+        paDevice.Source[sourceIndex].Name := sourceName;
+      end;
+
+      Px_CloseMixer(mixer);
+    {$ENDIF}
+
+    // close test-stream
+    Pa_CloseStream(stream);
+
+    Inc(SC);
+  end;
+
+  // adjust size to actual input-device count
+  SetLength(AudioInputProcessor.DeviceList, SC);
+
+  Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio');
+
+  Result := true;
+end;
+
+function TAudioInput_Portaudio.InitializeRecord(): boolean;
+var
+  err: TPaError;
+begin
+  AudioCore := TAudioCore_Portaudio.GetInstance();
+
+  // initialize portaudio
+  err := Pa_Initialize();
+  if(err <> paNoError) then
+  begin
+    Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+    Result := false;
+    Exit;
+  end;
+
+  Result := EnumDevices();
+end;
+
+function TAudioInput_Portaudio.FinalizeRecord: boolean;
+begin
+  CaptureStop;
+  Pa_Terminate();
+  Result := inherited FinalizeRecord();
+end;
+
+{*
+ * Portaudio input capture callback.
+ *}
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl;
+begin
+  AudioInputProcessor.HandleMicrophoneData(input, frameCount*4, inputDevice);
+  result := paContinue;
+end;
+
+{*
+ * Portaudio test capture callback.
+ *}
+function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl;
+begin
+  // this callback is called only once
+  result := paAbort;
+end;
+
+
+initialization
+  MediaManager.add(TAudioInput_Portaudio.Create);
+
+end.
diff --git a/cmake/src/media/UAudioPlaybackBase.pas b/cmake/src/media/UAudioPlaybackBase.pas
new file mode 100644
index 00000000..7d143fdc
--- /dev/null
+++ b/cmake/src/media/UAudioPlaybackBase.pas
@@ -0,0 +1,317 @@
+{* 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 UAudioPlaybackBase;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UMusic;
+
+type
+  TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback)
+    protected
+      OutputDeviceList: TAudioOutputDeviceList;
+      MusicStream: TAudioPlaybackStream;
+      function CreatePlaybackStream(): TAudioPlaybackStream; virtual; abstract;
+      procedure ClearOutputDeviceList();
+      function GetLatency(): double; virtual; abstract;
+
+      // open sound or music stream (used by Open() and OpenSound())
+      function OpenStream(const Filename: string): TAudioPlaybackStream;
+      function OpenDecodeStream(const Filename: string): TAudioDecodeStream;
+    public
+      function GetName: string; virtual; abstract;
+
+      function Open(const Filename: string): boolean; // true if succeed
+      procedure Close;
+
+      procedure Play;
+      procedure Pause;
+      procedure Stop;
+      procedure FadeIn(Time: real; TargetVolume: single);
+
+      procedure SetSyncSource(SyncSource: TSyncSource);
+
+      procedure SetPosition(Time: real);
+      function  GetPosition: real;
+
+      function InitializePlayback: boolean; virtual; abstract;
+      function FinalizePlayback: boolean; virtual;
+
+      //function SetOutputDevice(Device: TAudioOutputDevice): boolean;
+      function GetOutputDeviceList(): TAudioOutputDeviceList;
+
+      procedure SetAppVolume(Volume: single); virtual; abstract;
+      procedure SetVolume(Volume: single);
+      procedure SetLoop(Enabled: boolean);
+
+      procedure Rewind;
+      function  Finished: boolean;
+      function  Length: real;
+
+      // Sounds
+      function OpenSound(const Filename: string): TAudioPlaybackStream;
+      procedure PlaySound(Stream: TAudioPlaybackStream);
+      procedure StopSound(Stream: TAudioPlaybackStream);
+
+      // Equalizer
+      procedure GetFFTData(var Data: TFFTData);
+
+      // Interface for Visualizer
+      function GetPCMData(var Data: TPCMData): Cardinal;
+
+      function CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; virtual; abstract;
+  end;
+
+
+implementation
+
+uses
+  ULog,
+  SysUtils;
+
+{ TAudioPlaybackBase }
+
+function TAudioPlaybackBase.FinalizePlayback: boolean;
+begin
+  FreeAndNil(MusicStream);
+  ClearOutputDeviceList();
+  Result := true;
+end;
+
+function TAudioPlaybackBase.Open(const Filename: string): boolean;
+begin
+  // free old MusicStream
+  MusicStream.Free;
+
+  MusicStream := OpenStream(Filename);
+  if not assigned(MusicStream) then
+  begin
+    Result := false;
+    Exit;
+  end;
+
+  //MusicStream.AddSoundEffect(TVoiceRemoval.Create());
+
+  Result := true;
+end;
+
+procedure TAudioPlaybackBase.Close;
+begin
+  FreeAndNil(MusicStream);
+end;
+
+function TAudioPlaybackBase.OpenDecodeStream(const Filename: String): TAudioDecodeStream;
+var
+  i: integer;
+begin
+  for i := 0 to AudioDecoders.Count-1 do
+  begin
+    Result := IAudioDecoder(AudioDecoders[i]).Open(Filename);
+    if (assigned(Result)) then
+    begin
+      Log.LogInfo('Using decoder ' + IAudioDecoder(AudioDecoders[i]).GetName() +
+        ' for "' + Filename + '"', 'TAudioPlaybackBase.OpenDecodeStream');
+      Exit;
+    end;
+  end;
+  Result := nil;
+end;
+
+procedure OnClosePlaybackStream(Stream: TAudioProcessingStream);
+var
+  PlaybackStream: TAudioPlaybackStream;
+  SourceStream: TAudioSourceStream;
+begin
+  PlaybackStream := TAudioPlaybackStream(Stream);
+  SourceStream := PlaybackStream.GetSourceStream();
+  SourceStream.Free;
+end;
+
+function TAudioPlaybackBase.OpenStream(const Filename: string): TAudioPlaybackStream;
+var
+  PlaybackStream: TAudioPlaybackStream;
+  DecodeStream: TAudioDecodeStream;
+begin
+  Result := nil;
+
+  //Log.LogStatus('Loading Sound: "' + Filename + '"', 'TAudioPlayback_Bass.OpenStream');
+
+  DecodeStream := OpenDecodeStream(Filename);
+  if (not assigned(DecodeStream)) then
+  begin
+    Log.LogStatus('Could not open "' + Filename + '"', 'TAudioPlayback_Bass.OpenStream');
+    Exit;
+  end;
+
+  // create a matching playback-stream for the decoder
+  PlaybackStream := CreatePlaybackStream();
+  if (not PlaybackStream.Open(DecodeStream)) then
+  begin
+    FreeAndNil(PlaybackStream);
+    FreeAndNil(DecodeStream);
+    Exit;
+  end;
+
+  PlaybackStream.AddOnCloseHandler(OnClosePlaybackStream);
+
+  Result := PlaybackStream;
+end;
+
+procedure TAudioPlaybackBase.Play;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Play();
+end;
+
+procedure TAudioPlaybackBase.Pause;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Pause();
+end;
+
+procedure TAudioPlaybackBase.Stop;
+begin
+  if assigned(MusicStream) then
+    MusicStream.Stop();
+end;
+
+function TAudioPlaybackBase.Length: real;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.Length
+  else
+    Result := 0;
+end;
+
+function TAudioPlaybackBase.GetPosition: real;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.Position
+  else
+    Result := 0;
+end;
+
+procedure TAudioPlaybackBase.SetPosition(Time: real);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Position := Time;
+end;
+
+procedure TAudioPlaybackBase.SetSyncSource(SyncSource: TSyncSource);
+begin
+  if assigned(MusicStream) then
+    MusicStream.SetSyncSource(SyncSource);
+end;
+
+procedure TAudioPlaybackBase.Rewind;
+begin
+  SetPosition(0);
+end;
+
+function TAudioPlaybackBase.Finished: boolean;
+begin
+  if assigned(MusicStream) then
+    Result := (MusicStream.Status = ssStopped)
+  else
+    Result := true;
+end;
+
+procedure TAudioPlaybackBase.SetVolume(Volume: single);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Volume := Volume;
+end;
+
+procedure TAudioPlaybackBase.FadeIn(Time: real; TargetVolume: single);
+begin
+  if assigned(MusicStream) then
+    MusicStream.FadeIn(Time, TargetVolume);
+end;
+
+procedure TAudioPlaybackBase.SetLoop(Enabled: boolean);
+begin
+  if assigned(MusicStream) then
+    MusicStream.Loop := Enabled;
+end;
+
+// Equalizer
+procedure TAudioPlaybackBase.GetFFTData(var data: TFFTData);
+begin
+  if assigned(MusicStream) then
+    MusicStream.GetFFTData(data);
+end;
+
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TAudioPlaybackBase.GetPCMData(var data: TPCMData): Cardinal;
+begin
+  if assigned(MusicStream) then
+    Result := MusicStream.GetPCMData(data)
+  else
+    Result := 0;
+end;
+
+function TAudioPlaybackBase.OpenSound(const Filename: string): TAudioPlaybackStream;
+begin
+  Result := OpenStream(Filename);
+end;
+
+procedure TAudioPlaybackBase.PlaySound(stream: TAudioPlaybackStream);
+begin
+  if assigned(stream) then
+    stream.Play();
+end;
+
+procedure TAudioPlaybackBase.StopSound(stream: TAudioPlaybackStream);
+begin
+  if assigned(stream) then
+    stream.Stop();
+end;
+
+procedure TAudioPlaybackBase.ClearOutputDeviceList();
+var
+  DeviceIndex: integer;
+begin
+  for DeviceIndex := 0 to High(OutputDeviceList) do
+    OutputDeviceList[DeviceIndex].Free();
+  SetLength(OutputDeviceList, 0);
+end;
+
+function TAudioPlaybackBase.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+  Result := OutputDeviceList;
+end;
+
+end.
diff --git a/cmake/src/media/UAudioPlayback_Bass.pas b/cmake/src/media/UAudioPlayback_Bass.pas
new file mode 100644
index 00000000..923c1d7b
--- /dev/null
+++ b/cmake/src/media/UAudioPlayback_Bass.pas
@@ -0,0 +1,756 @@
+{* 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 UAudioPlayback_Bass;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+implementation
+
+uses
+  Classes,
+  Math,
+  UIni,
+  UMain,
+  UMusic,
+  UAudioPlaybackBase,
+  UAudioCore_Bass,
+  ULog,
+  sdl,
+  bass,
+  SysUtils;
+
+type
+  PHDSP = ^HDSP;
+
+type
+  TBassPlaybackStream = class(TAudioPlaybackStream)
+    private
+      Handle: HSTREAM;
+      NeedsRewind: boolean;
+      PausedSeek: boolean; // true if a seek was performed in pause state
+
+      procedure Reset();
+      function IsEOF(): boolean;
+    protected
+      function GetLatency(): double;        override;
+      function GetLoop(): boolean;          override;
+      procedure SetLoop(Enabled: boolean);  override;
+      function GetLength(): real;           override;
+      function GetStatus(): TStreamStatus;  override;
+      function GetVolume(): single;         override;
+      procedure SetVolume(Volume: single);  override;
+      function GetPosition: real;           override;
+      procedure SetPosition(Time: real);    override;
+    public
+      constructor Create();
+      destructor Destroy(); override;
+
+      function Open(SourceStream: TAudioSourceStream): boolean; override;
+      procedure Close();                    override;
+
+      procedure Play();                     override;
+      procedure Pause();                    override;
+      procedure Stop();                     override;
+      procedure FadeIn(Time: real; TargetVolume: single); override;
+
+      procedure AddSoundEffect(Effect: TSoundEffect);    override;
+      procedure RemoveSoundEffect(Effect: TSoundEffect); override;
+
+      procedure GetFFTData(var Data: TFFTData);           override;
+      function  GetPCMData(var Data: TPCMData): Cardinal; override;
+
+      function GetAudioFormatInfo(): TAudioFormatInfo; override;
+
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+
+      property EOF: boolean READ IsEOF;
+  end;
+
+const
+  MAX_VOICE_DELAY = 0.020; // 20ms
+
+type
+  TBassVoiceStream = class(TAudioVoiceStream)
+    private
+      Handle: HSTREAM;
+    public
+      function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override;
+      procedure Close(); override;
+
+      procedure WriteData(Buffer: PByteArray; BufferSize: integer); override;
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer; override;
+      function IsEOF(): boolean; override;
+      function IsError(): boolean; override;
+  end;
+
+type
+  TAudioPlayback_Bass = class(TAudioPlaybackBase)
+    private
+      function EnumDevices(): boolean;
+    protected
+      function GetLatency(): double; override;
+      function CreatePlaybackStream(): TAudioPlaybackStream; override;
+    public
+      function GetName: String; override;
+      function InitializePlayback(): boolean; override;
+      function FinalizePlayback: boolean; override;
+      procedure SetAppVolume(Volume: single); override;
+      function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; override;
+  end;
+
+  TBassOutputDevice = class(TAudioOutputDevice)
+    private
+      BassDeviceID: DWORD; // DeviceID used by BASS
+  end;
+
+var
+  BassCore: TAudioCore_Bass;
+
+
+{ TBassPlaybackStream }
+
+function PlaybackStreamHandler(handle: HSTREAM; buffer: Pointer; length: DWORD; user: Pointer): DWORD;
+{$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
+var
+  PlaybackStream: TBassPlaybackStream;
+  BytesRead: integer;
+begin
+  PlaybackStream := TBassPlaybackStream(user);
+  if (not assigned (PlaybackStream)) then
+  begin
+    Result := BASS_STREAMPROC_END;
+    Exit;
+  end;
+
+  BytesRead := PlaybackStream.ReadData(buffer, length);
+  // check for errors
+  if (BytesRead < 0) then
+    Result := BASS_STREAMPROC_END
+  // check for EOF
+  else if (PlaybackStream.EOF) then
+    Result := BytesRead or BASS_STREAMPROC_END
+  // no error/EOF
+  else
+    Result := BytesRead;
+end;
+
+function TBassPlaybackStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+var
+  AdjustedSize: integer;
+  RequestedSourceSize, SourceSize: integer;
+  SkipCount: integer;
+  SourceFormatInfo: TAudioFormatInfo;
+  FrameSize: integer;
+  PadFrame: PByteArray;
+  //Info: BASS_INFO;
+  //Latency: double;
+begin
+  Result := -1;
+
+  if (not assigned(SourceStream)) then
+    Exit;
+
+  // sanity check
+  if (BufferSize = 0) then
+  begin
+    Result := 0;
+    Exit;
+  end;
+
+  SourceFormatInfo := SourceStream.GetAudioFormatInfo();
+  FrameSize := SourceFormatInfo.FrameSize;
+
+  // check how much data to fetch to be in synch
+  AdjustedSize := Synchronize(BufferSize, SourceFormatInfo);
+
+  // skip data if we are too far behind
+  SkipCount := AdjustedSize - BufferSize;
+  while (SkipCount > 0) do
+  begin
+    RequestedSourceSize := Min(SkipCount, BufferSize);
+    SourceSize := SourceStream.ReadData(Buffer, RequestedSourceSize);
+    // if an error or EOF occured stop skipping and handle error/EOF with the next ReadData()
+    if (SourceSize <= 0) then
+      break;
+    Dec(SkipCount, SourceSize);
+  end;
+
+  // get source data (e.g. from a decoder)
+  RequestedSourceSize := Min(AdjustedSize, BufferSize);
+  SourceSize := SourceStream.ReadData(Buffer, RequestedSourceSize);
+  if (SourceSize < 0) then
+    Exit;
+
+  // set preliminary result
+  Result := SourceSize;
+
+  // if we are to far ahead, fill output-buffer with last frame of source data
+  // Note that AdjustedSize is used instead of SourceSize as the SourceSize might
+  // be less than expected because of errors etc.
+  if (AdjustedSize < BufferSize) then
+  begin
+    // use either the last frame for padding or fill with zero
+    if (SourceSize >= FrameSize) then
+      PadFrame := @Buffer[SourceSize-FrameSize]
+    else
+      PadFrame := nil;
+
+    FillBufferWithFrame(@Buffer[SourceSize], BufferSize - SourceSize,
+                        PadFrame, FrameSize);
+    Result := BufferSize;
+  end;
+end;
+
+constructor TBassPlaybackStream.Create();
+begin
+  inherited;
+  Reset();
+end;
+
+destructor TBassPlaybackStream.Destroy();
+begin
+  Close();
+  inherited;
+end;
+
+function TBassPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean;
+var
+  FormatInfo: TAudioFormatInfo;
+  FormatFlags: DWORD;
+begin
+  Result := false;
+
+  // close previous stream and reset state
+  Reset();
+
+  // sanity check if stream is valid
+  if not assigned(SourceStream) then
+    Exit;
+
+  Self.SourceStream := SourceStream;
+  FormatInfo := SourceStream.GetAudioFormatInfo();
+  if (not BassCore.ConvertAudioFormatToBASSFlags(FormatInfo.Format, FormatFlags)) then
+  begin
+    Log.LogError('Unhandled sample-format', 'TBassPlaybackStream.Open');
+    Exit;
+  end;
+
+  // create matching playback stream
+  Handle := BASS_StreamCreate(Round(FormatInfo.SampleRate), FormatInfo.Channels, formatFlags,
+      @PlaybackStreamHandler, Self);
+  if (Handle = 0) then
+  begin
+    Log.LogError('BASS_StreamCreate failed: ' + BassCore.ErrorGetString(BASS_ErrorGetCode()),
+                 'TBassPlaybackStream.Open');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+procedure TBassPlaybackStream.Close();
+begin
+  // stop and free stream
+  if (Handle <> 0) then
+  begin
+    Bass_StreamFree(Handle);
+    Handle := 0;
+  end;
+
+  // Note: PerformOnClose must be called before SourceStream is invalidated
+  PerformOnClose();
+  // unset source-stream
+  SourceStream := nil;
+end;
+
+procedure TBassPlaybackStream.Reset();
+begin
+  Close();
+  NeedsRewind := false;
+  PausedSeek := false;
+end;
+
+procedure TBassPlaybackStream.Play();
+var
+  NeedsFlush: boolean;
+begin
+  if (not assigned(SourceStream)) then
+    Exit;
+
+  NeedsFlush := true;
+
+  if (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_PAUSED) then
+  begin
+    // only paused (and not seeked while paused) streams are not flushed
+    if (not PausedSeek) then
+      NeedsFlush := false;
+    // paused streams do not need a rewind
+    NeedsRewind := false;
+  end;
+
+  // rewind if necessary. Cases that require no rewind are:
+  // - stream was created and never played
+  // - stream was paused and is resumed now
+  // - stream was stopped and set to a new position already
+  if (NeedsRewind) then
+    SourceStream.Position := 0;
+
+  NeedsRewind := true;
+  PausedSeek := false;
+
+  // start playing and flush buffers on rewind
+  BASS_ChannelPlay(Handle, NeedsFlush);
+end;
+
+procedure TBassPlaybackStream.FadeIn(Time: real; TargetVolume: single);
+begin
+  // start stream
+  Play();
+  // start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime
+  BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume, Trunc(Time * 1000));
+end;
+
+procedure TBassPlaybackStream.Pause();
+begin
+  BASS_ChannelPause(Handle);
+end;
+
+procedure TBassPlaybackStream.Stop();
+begin
+  BASS_ChannelStop(Handle);
+end;
+
+function TBassPlaybackStream.IsEOF(): boolean;
+begin
+  if (assigned(SourceStream)) then
+    Result := SourceStream.EOF
+  else
+    Result := true;
+end;
+
+function TBassPlaybackStream.GetLatency(): double;
+begin
+  // TODO: should we consider output latency for synching (needs BASS_DEVICE_LATENCY)?
+  //if (BASS_GetInfo(Info)) then
+  //  Latency := Info.latency / 1000
+  //else
+  //  Latency := 0;
+  Result := 0; 
+end;
+
+function TBassPlaybackStream.GetVolume(): single;
+var
+  lVolume: single;
+begin
+  if (not BASS_ChannelGetAttribute(Handle, BASS_ATTRIB_VOL, lVolume)) then
+  begin
+    Log.LogError('BASS_ChannelGetAttribute: ' + BassCore.ErrorGetString(),
+      'TBassPlaybackStream.GetVolume');
+    Result := 0;
+    Exit;
+  end;
+  Result := Round(lVolume);
+end;
+
+procedure TBassPlaybackStream.SetVolume(Volume: single);
+begin
+  // clamp volume
+  if Volume < 0 then
+    Volume := 0;
+  if Volume > 1.0 then
+    Volume := 1.0;
+  // set volume
+  BASS_ChannelSetAttribute(Handle, BASS_ATTRIB_VOL, Volume);
+end;
+
+function TBassPlaybackStream.GetPosition: real;
+var
+  BufferPosByte: QWORD;
+  BufferPosSec: double;
+begin
+  if assigned(SourceStream) then
+  begin
+    BufferPosByte := BASS_ChannelGetData(Handle, nil, BASS_DATA_AVAILABLE);
+    BufferPosSec := BASS_ChannelBytes2Seconds(Handle, BufferPosByte);
+    // decrease the decoding position by the amount buffered (and hence not played)
+    // in the BASS playback stream.
+    Result := SourceStream.Position - BufferPosSec;
+  end
+  else
+  begin
+    Result := -1;
+  end;
+end;
+
+procedure TBassPlaybackStream.SetPosition(Time: real);
+var
+  ChannelState: DWORD;
+begin
+  if assigned(SourceStream) then
+  begin
+    ChannelState := BASS_ChannelIsActive(Handle);
+    if (ChannelState = BASS_ACTIVE_STOPPED) then
+    begin
+      // if the stream is stopped, do not rewind when the stream is played next time
+      NeedsRewind := false
+    end
+    else if (ChannelState = BASS_ACTIVE_PAUSED) then
+    begin
+      // buffers must be flushed if in paused state but there is no
+      // BASS_ChannelFlush() function so we have to use BASS_ChannelPlay() called in Play().
+      PausedSeek := true;
+    end;
+
+    // set new position
+    SourceStream.Position := Time;
+  end;
+end;
+
+function TBassPlaybackStream.GetLength(): real;
+begin
+  if assigned(SourceStream) then
+    Result := SourceStream.Length
+  else
+    Result := -1;
+end;
+
+function TBassPlaybackStream.GetStatus(): TStreamStatus;
+var
+  State: DWORD;
+begin
+  State := BASS_ChannelIsActive(Handle);
+  case State of
+    BASS_ACTIVE_PLAYING,
+    BASS_ACTIVE_STALLED:
+      Result := ssPlaying;
+    BASS_ACTIVE_PAUSED:
+      Result := ssPaused;
+    BASS_ACTIVE_STOPPED:
+      Result := ssStopped;
+    else
+    begin
+      Log.LogError('Unknown status', 'TBassPlaybackStream.GetStatus');
+      Result := ssStopped;
+    end;
+  end;
+end;
+
+function TBassPlaybackStream.GetLoop(): boolean;
+begin
+  if assigned(SourceStream) then
+    Result := SourceStream.Loop
+  else
+    Result := false;
+end;
+
+procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
+begin
+  if assigned(SourceStream) then
+    SourceStream.Loop := Enabled;
+end;
+
+procedure DSPProcHandler(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: Pointer);
+{$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
+var
+  Effect: TSoundEffect;
+begin
+  Effect := TSoundEffect(user);
+  if assigned(Effect) then
+    Effect.Callback(buffer, length);
+end;
+
+procedure TBassPlaybackStream.AddSoundEffect(Effect: TSoundEffect);
+var
+  DspHandle: HDSP;
+begin
+  if assigned(Effect.engineData) then
+  begin
+    Log.LogError('TSoundEffect.engineData already set', 'TBassPlaybackStream.AddSoundEffect');
+    Exit;
+  end;
+
+  DspHandle := BASS_ChannelSetDSP(Handle, @DSPProcHandler, Effect, 0);
+  if (DspHandle = 0) then
+  begin
+    Log.LogError(BassCore.ErrorGetString(), 'TBassPlaybackStream.AddSoundEffect');
+    Exit;
+  end;
+
+  GetMem(Effect.EngineData, SizeOf(HDSP));
+  PHDSP(Effect.EngineData)^ := DspHandle;
+end;
+
+procedure TBassPlaybackStream.RemoveSoundEffect(Effect: TSoundEffect);
+begin
+  if not assigned(Effect.EngineData) then
+  begin
+    Log.LogError('TSoundEffect.engineData invalid', 'TBassPlaybackStream.RemoveSoundEffect');
+    Exit;
+  end;
+
+  if not BASS_ChannelRemoveDSP(Handle, PHDSP(Effect.EngineData)^) then
+  begin
+    Log.LogError(BassCore.ErrorGetString(), 'TBassPlaybackStream.RemoveSoundEffect');
+    Exit;
+  end;
+
+  FreeMem(Effect.EngineData);
+  Effect.EngineData := nil;
+end;
+
+procedure TBassPlaybackStream.GetFFTData(var Data: TFFTData);
+begin
+  // get FFT channel data (Mono, FFT512 -> 256 values)
+  BASS_ChannelGetData(Handle, @Data, BASS_DATA_FFT512);
+end;
+
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TBassPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal;
+var
+  Info: BASS_CHANNELINFO;
+  nBytes: DWORD;
+begin
+  Result := 0;
+
+  FillChar(Data, SizeOf(TPCMData), 0);
+
+  // no support for non-stereo files at the moment
+  BASS_ChannelGetInfo(Handle, Info);
+  if (Info.chans <> 2) then
+    Exit;
+
+  nBytes := BASS_ChannelGetData(Handle, @Data, SizeOf(TPCMData));
+  if(nBytes <= 0) then
+    Result := 0
+  else
+    Result := nBytes div SizeOf(TPCMStereoSample);
+end;
+
+function TBassPlaybackStream.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+  if assigned(SourceStream) then
+    Result := SourceStream.GetAudioFormatInfo()
+  else
+    Result := nil;
+end;
+
+
+{ TBassVoiceStream }
+
+function TBassVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
+var
+  Flags: DWORD;
+begin
+  Result := false;
+
+  Close();
+
+  if (not inherited Open(ChannelMap, FormatInfo)) then
+    Exit;
+
+  // get channel flags
+  BassCore.ConvertAudioFormatToBASSFlags(FormatInfo.Format, Flags);
+
+  (*
+  // distribute the mics equally to both speakers
+  if ((ChannelMap and CHANNELMAP_LEFT) <> 0) then
+    Flags := Flags or BASS_SPEAKER_FRONTLEFT;
+  if ((ChannelMap and CHANNELMAP_RIGHT) <> 0) then
+    Flags := Flags or BASS_SPEAKER_FRONTRIGHT;
+  *)
+  
+  // create the channel
+  Handle := BASS_StreamCreate(Round(FormatInfo.SampleRate), 1, Flags, STREAMPROC_PUSH, nil);
+
+  // start the channel
+  BASS_ChannelPlay(Handle, true);
+
+  Result := true;
+end;
+
+procedure TBassVoiceStream.Close();
+begin
+  if (Handle <> 0) then
+  begin
+    BASS_ChannelStop(Handle);
+    BASS_StreamFree(Handle);
+  end;
+  inherited Close();
+end;
+
+procedure TBassVoiceStream.WriteData(Buffer: PByteArray; BufferSize: integer);
+var QueueSize: DWORD;
+begin
+  if ((Handle <> 0) and (BufferSize > 0)) then
+  begin
+    // query the queue size (normally 0)
+    QueueSize := BASS_StreamPutData(Handle, nil, 0);
+    // flush the buffer if the delay would be too high
+    if (QueueSize > MAX_VOICE_DELAY * FormatInfo.BytesPerSec) then
+      BASS_ChannelPlay(Handle, true);
+    // send new data to playback buffer
+    BASS_StreamPutData(Handle, Buffer, BufferSize);
+  end;
+end;
+
+// Note: we do not need the read-function for the BASS implementation
+function TBassVoiceStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+begin
+  Result := -1;
+end;
+
+function TBassVoiceStream.IsEOF(): boolean;
+begin
+  Result := false;
+end;
+
+function TBassVoiceStream.IsError(): boolean;
+begin
+  Result := false;
+end;
+
+
+{ TAudioPlayback_Bass }
+
+function TAudioPlayback_Bass.GetName: String;
+begin
+  Result := 'BASS_Playback';
+end;
+
+function TAudioPlayback_Bass.EnumDevices(): boolean;
+var
+  BassDeviceID: DWORD;
+  DeviceIndex: integer;
+  Device: TBassOutputDevice;
+  DeviceInfo: BASS_DEVICEINFO;
+begin
+  Result := true;
+
+  ClearOutputDeviceList();
+
+  // skip "no sound"-device (ID = 0)
+  BassDeviceID := 1;
+
+  while (true) do
+  begin
+    // check for device
+    if (not BASS_GetDeviceInfo(BassDeviceID, DeviceInfo)) then
+      Break;
+
+    // set device info
+    Device := TBassOutputDevice.Create();
+    Device.Name := DeviceInfo.name;
+    Device.BassDeviceID := BassDeviceID;
+
+    // add device to list
+    SetLength(OutputDeviceList, BassDeviceID);
+    OutputDeviceList[BassDeviceID-1] := Device;
+
+    Inc(BassDeviceID);
+  end;
+end;
+
+function TAudioPlayback_Bass.InitializePlayback(): boolean;
+begin
+  result := false;
+
+  BassCore := TAudioCore_Bass.GetInstance();
+
+  EnumDevices();
+
+  //Log.BenchmarkStart(4);
+  //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
+
+  // TODO: use BASS_DEVICE_LATENCY to determine the latency
+  if not BASS_Init(-1, 44100, 0, 0, nil) then
+  begin
+    Log.LogError('Could not initialize BASS', 'TAudioPlayback_Bass.InitializePlayback');
+    Exit;
+  end;
+
+  //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+
+  // config playing buffer
+  //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
+  //BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+
+  result := true;
+end;
+
+function TAudioPlayback_Bass.FinalizePlayback(): boolean;
+begin
+  Close;
+  BASS_Free;
+  inherited FinalizePlayback();
+  Result := true;
+end;
+
+function TAudioPlayback_Bass.CreatePlaybackStream(): TAudioPlaybackStream;
+begin
+  Result := TBassPlaybackStream.Create();
+end;
+
+procedure TAudioPlayback_Bass.SetAppVolume(Volume: single);
+begin
+  // set volume for this application (ranges from 0..10000 since BASS 2.4)
+  BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Round(Volume*10000));
+end;
+
+function TAudioPlayback_Bass.CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
+var
+  VoiceStream: TAudioVoiceStream;
+begin
+  Result := nil;
+
+  VoiceStream := TBassVoiceStream.Create();
+  if (not VoiceStream.Open(ChannelMap, FormatInfo)) then
+  begin
+    VoiceStream.Free;
+    Exit;
+  end;
+
+  Result := VoiceStream;
+end;
+
+function TAudioPlayback_Bass.GetLatency(): double;
+begin
+  Result := 0;
+end;
+
+
+initialization
+  MediaManager.Add(TAudioPlayback_Bass.Create);
+
+end.
diff --git a/cmake/src/media/UAudioPlayback_Portaudio.pas b/cmake/src/media/UAudioPlayback_Portaudio.pas
new file mode 100644
index 00000000..ddbd03d6
--- /dev/null
+++ b/cmake/src/media/UAudioPlayback_Portaudio.pas
@@ -0,0 +1,385 @@
+{* 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 UAudioPlayback_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  Classes,
+  SysUtils,
+  UMusic;
+
+implementation
+
+uses
+  portaudio,
+  UAudioCore_Portaudio,
+  UAudioPlayback_SoftMixer,
+  ULog,
+  UIni,
+  UMain;
+
+type
+  TAudioPlayback_Portaudio = class(TAudioPlayback_SoftMixer)
+    private
+      paStream:  PPaStream;
+      AudioCore: TAudioCore_Portaudio;
+      Latency: double;
+      function OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
+      function EnumDevices(): boolean;
+    protected
+      function InitializeAudioPlaybackEngine(): boolean; override;
+      function StartAudioPlaybackEngine(): boolean;      override;
+      procedure StopAudioPlaybackEngine();               override;
+      function FinalizeAudioPlaybackEngine(): boolean;   override;
+      function GetLatency(): double;                     override;
+    public
+      function GetName: String;                          override;
+  end;
+
+  TPortaudioOutputDevice = class(TAudioOutputDevice)
+    private
+      PaDeviceIndex:  TPaDeviceIndex;
+  end;
+
+
+{ TAudioPlayback_Portaudio }
+
+function PortaudioAudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
+    timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+    userData: Pointer): Integer; cdecl;
+var
+  Engine: TAudioPlayback_Portaudio;
+begin
+  Engine := TAudioPlayback_Portaudio(userData);
+  // update latency
+  Engine.Latency := timeInfo.outputBufferDacTime - timeInfo.currentTime;
+  // call superclass callback
+  Engine.AudioCallback(output, frameCount * Engine.FormatInfo.FrameSize);
+  Result := paContinue;
+end;
+
+function TAudioPlayback_Portaudio.GetName: String;
+begin
+  Result := 'Portaudio_Playback';
+end;
+
+function TAudioPlayback_Portaudio.OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
+var
+  DeviceInfo : PPaDeviceInfo;
+  SampleRate : double;
+  OutParams  : TPaStreamParameters;
+  StreamInfo : PPaStreamInfo;
+  err        : TPaError;
+begin
+  Result := false;
+
+  DeviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+  Log.LogInfo('Audio-Output Device: ' + DeviceInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
+
+  SampleRate := DeviceInfo^.defaultSampleRate;
+
+  with OutParams do
+  begin
+    device := deviceIndex;
+    channelCount := 2;
+    sampleFormat := paInt16;
+    suggestedLatency := DeviceInfo^.defaultLowOutputLatency;
+    hostApiSpecificStreamInfo := nil;
+  end;
+
+  // check souncard and adjust sample-rate
+  if not AudioCore.TestDevice(nil, @OutParams, SampleRate) then
+  begin
+    Log.LogStatus('TestDevice failed!', 'TAudioPlayback_Portaudio.OpenDevice');
+    Exit;
+  end;
+
+  // open output stream
+  err := Pa_OpenStream(paStream, nil, @OutParams, SampleRate,
+          paFramesPerBufferUnspecified,
+          paNoFlag, @PortaudioAudioCallback, Self);
+  if(err <> paNoError) then
+  begin
+    Log.LogStatus(Pa_GetErrorText(err), 'TAudioPlayback_Portaudio.OpenDevice');
+    paStream := nil;
+    Exit;
+  end;
+
+  // get estimated latency (will be updated with real latency in the callback)
+  StreamInfo := Pa_GetStreamInfo(paStream);
+  if (StreamInfo <> nil) then
+    Latency := StreamInfo^.outputLatency
+  else
+    Latency := 0;
+
+  FormatInfo := TAudioFormatInfo.Create(
+    OutParams.channelCount,
+    SampleRate,
+    asfS16 // FIXME: is paInt16 system-dependant or -independant?
+  );
+
+  Result := true;
+end;
+
+function TAudioPlayback_Portaudio.EnumDevices(): boolean;
+var
+  i:           integer;
+  paApiIndex:  TPaHostApiIndex;
+  paApiInfo:   PPaHostApiInfo;
+  deviceName:  string;
+  deviceIndex: TPaDeviceIndex;
+  deviceInfo:  PPaDeviceInfo;
+  channelCnt:  integer;
+  SC:          integer; // soundcard
+  err:         TPaError;
+  errMsg:      string;
+  paDevice:    TPortaudioOutputDevice;
+  outputParams: TPaStreamParameters;
+  stream:      PPaStream;
+  streamInfo:  PPaStreamInfo;
+  sampleRate:  double;
+  latency:     TPaTime;
+  cbPolls: integer;
+  cbWorks: boolean;
+begin
+  Result := false;
+
+(*
+  // choose the best available Audio-API
+  paApiIndex := AudioCore.GetPreferredApiIndex();
+  if(paApiIndex = -1) then
+  begin
+    Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.EnumDevices');
+    Exit;
+  end;
+
+  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+
+  SC := 0;
+
+  // init array-size to max. output-devices count
+  SetLength(OutputDeviceList, paApiInfo^.deviceCount);
+  for i:= 0 to High(OutputDeviceList) do
+  begin
+    // convert API-specific device-index to global index
+    deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+    deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+    channelCnt := deviceInfo^.maxOutputChannels;
+
+    // current device is no output device -> skip
+    if (channelCnt <= 0) then
+      continue;
+
+    // portaudio returns a channel-count of 128 for some devices
+    // (e.g. the "default"-device), so we have to detect those
+    // fantasy channel counts.
+    if (channelCnt > 8) then
+      channelCnt := 2;
+
+    paDevice := TPortaudioOutputDevice.Create();
+    OutputDeviceList[SC] := paDevice;
+
+    // retrieve device-name
+    deviceName := deviceInfo^.name;
+    paDevice.Name := deviceName;
+    paDevice.PaDeviceIndex := deviceIndex;
+
+    if (deviceInfo^.defaultSampleRate > 0) then
+      sampleRate := deviceInfo^.defaultSampleRate
+    else
+      sampleRate := 44100;
+
+    // on vista and xp the defaultLowInputLatency may be set to 0 but it works.
+    // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?)
+    latency := deviceInfo^.defaultLowInputLatency;
+
+    // setup desired output parameters
+    // TODO: retry with input-latency set to 20ms (defaultLowOutputLatency might
+    //       not be set correctly in OSS)
+    with outputParams do
+    begin
+      device := deviceIndex;
+      channelCount := channelCnt;
+      sampleFormat := paInt16;
+      suggestedLatency := latency;
+      hostApiSpecificStreamInfo := nil;
+    end;
+
+    // check if mic-callback works (might not be called on some devices)
+    if (not TAudioCore_Portaudio.TestDevice(nil, @outputParams, sampleRate)) then
+    begin
+      // ignore device if callback did not work
+      Log.LogError('Device "'+paDevice.Name+'" does not respond',
+                   'TAudioPlayback_Portaudio.InitializeRecord');
+      paDevice.Free();
+      continue;
+    end;
+
+    // open device for further info
+    err := Pa_OpenStream(stream, nil, @outputParams, sampleRate,
+        paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
+    if(err <> paNoError) then
+    begin
+      // unable to open device -> skip
+      errMsg := Pa_GetErrorText(err);
+      Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+                   'TAudioPlayback_Portaudio.InitializeRecord');
+      paDevice.Free();
+      continue;
+    end;
+
+    // adjust sample-rate (might be changed by portaudio)
+    streamInfo := Pa_GetStreamInfo(stream);
+    if (streamInfo <> nil) then
+    begin
+      if (sampleRate <> streamInfo^.sampleRate) then
+      begin
+        Log.LogStatus('Portaudio changed Samplerate from ' + FloatToStr(sampleRate) +
+            ' to ' + FloatToStr(streamInfo^.sampleRate),
+            'TAudioInput_Portaudio.InitializeRecord');
+        sampleRate := streamInfo^.sampleRate;
+      end;
+    end;
+
+    // create audio-format info and resize capture-buffer array
+    paDevice.AudioFormat := TAudioFormatInfo.Create(
+        channelCnt,
+        sampleRate,
+        asfS16
+    );
+    SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
+
+    Log.LogStatus('OutputDevice "'+paDevice.Name+'"@' +
+        IntToStr(paDevice.AudioFormat.Channels)+'x'+
+        FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
+        FloatTostr(outputParams.suggestedLatency)+'sec)' ,
+        'TAudioInput_Portaudio.InitializeRecord');
+
+    // close test-stream
+    Pa_CloseStream(stream);
+
+    Inc(SC);
+  end;
+
+  // adjust size to actual input-device count
+  SetLength(OutputDeviceList, SC);
+
+  Log.LogStatus('#Output-Devices: ' + inttostr(SC), 'Portaudio');
+*)
+
+  Result := true;
+end;
+
+function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+var
+  paApiIndex      : TPaHostApiIndex;
+  paApiInfo       : PPaHostApiInfo;
+  paOutDevice     : TPaDeviceIndex;
+  err: TPaError;
+begin
+  Result := false;
+
+  AudioCore := TAudioCore_Portaudio.GetInstance();
+
+  // initialize portaudio
+  err := Pa_Initialize();
+  if(err <> paNoError) then
+  begin
+    Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+    Exit;
+  end;
+
+  paApiIndex := AudioCore.GetPreferredApiIndex();
+  if(paApiIndex = -1) then
+  begin
+    Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
+    Exit;
+  end;
+
+  EnumDevices();
+
+  paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+  Log.LogInfo('Audio-Output API-Type: ' + paApiInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
+
+  paOutDevice := paApiInfo^.defaultOutputDevice;
+  if (not OpenDevice(paOutDevice)) then
+  begin
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+function TAudioPlayback_Portaudio.StartAudioPlaybackEngine(): boolean;
+var
+  err: TPaError;
+begin
+  Result := false;
+
+  if (paStream = nil) then
+    Exit;
+
+  err := Pa_StartStream(paStream);
+  if(err <> paNoError) then
+  begin
+    Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
+    Exit;
+  end;
+
+  Result := true;
+end;
+
+procedure TAudioPlayback_Portaudio.StopAudioPlaybackEngine();
+begin
+  if (paStream <> nil) then
+    Pa_StopStream(paStream);
+end;
+
+function TAudioPlayback_Portaudio.FinalizeAudioPlaybackEngine(): boolean;
+begin
+  Pa_Terminate();
+  Result := true;
+end;
+
+function TAudioPlayback_Portaudio.GetLatency(): double;
+begin
+  Result := Latency;
+end;
+
+
+initialization
+  MediaManager.Add(TAudioPlayback_Portaudio.Create);
+
+end.
diff --git a/cmake/src/media/UAudioPlayback_SDL.pas b/cmake/src/media/UAudioPlayback_SDL.pas
new file mode 100644
index 00000000..8403ef03
--- /dev/null
+++ b/cmake/src/media/UAudioPlayback_SDL.pas
@@ -0,0 +1,182 @@
+{* 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 UAudioPlayback_SDL;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+implementation
+
+uses
+  Classes,
+  sdl,
+  SysUtils,
+  UAudioPlayback_SoftMixer,
+  UMusic,
+  ULog,
+  UIni,
+  UMain;
+
+type
+  TAudioPlayback_SDL = class(TAudioPlayback_SoftMixer)
+    private
+      Latency: double;
+      function EnumDevices(): boolean;
+    protected
+      function InitializeAudioPlaybackEngine(): boolean; override;
+      function StartAudioPlaybackEngine(): boolean;      override;
+      procedure StopAudioPlaybackEngine();               override;
+      function FinalizeAudioPlaybackEngine(): boolean;   override;
+      function GetLatency(): double;                     override;
+    public
+      function GetName: String;                          override;
+      procedure MixBuffers(dst, src: PByteArray; size: Cardinal; volume: Single); override;
+  end;
+
+  
+{ TAudioPlayback_SDL }
+
+procedure SDLAudioCallback(userdata: Pointer; stream: PByteArray; len: integer); cdecl;
+var
+  Engine: TAudioPlayback_SDL;
+begin
+  Engine := TAudioPlayback_SDL(userdata);
+  Engine.AudioCallback(stream, len);
+end;
+
+function TAudioPlayback_SDL.GetName: String;
+begin
+  Result := 'SDL_Playback';
+end;
+
+function TAudioPlayback_SDL.EnumDevices(): boolean;
+begin
+  // Note: SDL does not provide Device-Selection capabilities (will be introduced in 1.3)
+  ClearOutputDeviceList();
+  SetLength(OutputDeviceList, 1);
+  OutputDeviceList[0] := TAudioOutputDevice.Create();
+  OutputDeviceList[0].Name := '[SDL Default-Device]';
+  Result := true;
+end;
+
+function TAudioPlayback_SDL.InitializeAudioPlaybackEngine(): boolean;
+var
+  DesiredAudioSpec, ObtainedAudioSpec: TSDL_AudioSpec;
+  SampleBufferSize: integer;
+begin
+  Result := false;
+
+  EnumDevices();
+
+  if (SDL_InitSubSystem(SDL_INIT_AUDIO) = -1) then
+  begin
+    Log.LogError('SDL_InitSubSystem failed!', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
+    Exit;
+  end;
+
+  SampleBufferSize := IAudioOutputBufferSizeVals[Ini.AudioOutputBufferSizeIndex];
+  if (SampleBufferSize <= 0) then
+  begin
+    // Automatic setting default
+    // FIXME: too much glitches with 1024 samples
+    SampleBufferSize := 2048; //1024;
+  end;
+
+  FillChar(DesiredAudioSpec, SizeOf(DesiredAudioSpec), 0);
+  with DesiredAudioSpec do
+  begin
+    freq := 44100;
+    format := AUDIO_S16SYS;
+    channels := 2;
+    samples := SampleBufferSize;
+    callback := @SDLAudioCallback;
+    userdata := Self;
+  end;
+
+  // Note: always use the "obtained" parameter, otherwise SDL might try to convert
+  // the samples itself if the desired format is not available. This might lead
+  // to problems if for example ALSA does not support 44100Hz and proposes 48000Hz.
+  // Without the obtained parameter, SDL would try to convert 44.1kHz to 48kHz with
+  // its crappy (non working) converter resulting in a wrong (too high) pitch.
+  if(SDL_OpenAudio(@DesiredAudioSpec, @ObtainedAudioSpec) = -1) then
+  begin
+    Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
+    Exit;
+  end;
+
+  FormatInfo := TAudioFormatInfo.Create(
+    ObtainedAudioSpec.channels,
+    ObtainedAudioSpec.freq,
+    asfS16
+  );
+
+  // Note: SDL does not provide info of the internal buffer state.
+  // So we use the average buffer-size.
+  Latency := (ObtainedAudioSpec.samples/2) / FormatInfo.SampleRate;
+
+  Log.LogStatus('Opened audio device', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
+
+  Result := true;
+end;
+
+function TAudioPlayback_SDL.StartAudioPlaybackEngine(): boolean;
+begin
+  SDL_PauseAudio(0);
+  Result := true;
+end;
+
+procedure TAudioPlayback_SDL.StopAudioPlaybackEngine();
+begin
+  SDL_PauseAudio(1);
+end;
+
+function TAudioPlayback_SDL.FinalizeAudioPlaybackEngine(): boolean;
+begin
+  SDL_CloseAudio();
+  SDL_QuitSubSystem(SDL_INIT_AUDIO);
+  Result := true;
+end;
+
+function TAudioPlayback_SDL.GetLatency(): double;
+begin
+  Result := Latency;
+end;
+
+procedure TAudioPlayback_SDL.MixBuffers(dst, src: PByteArray; size: Cardinal; volume: Single);
+begin
+  SDL_MixAudio(PUInt8(dst), PUInt8(src), size, Round(volume * SDL_MIX_MAXVOLUME));
+end;
+
+
+initialization
+  MediaManager.add(TAudioPlayback_SDL.Create);
+
+end.
diff --git a/cmake/src/media/UAudioPlayback_SoftMixer.pas b/cmake/src/media/UAudioPlayback_SoftMixer.pas
new file mode 100644
index 00000000..c87e461d
--- /dev/null
+++ b/cmake/src/media/UAudioPlayback_SoftMixer.pas
@@ -0,0 +1,1154 @@
+{* 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 UAudioPlayback_SoftMixer;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  Classes,
+  sdl,
+  SysUtils,
+  URingBuffer,
+  UMusic,
+  UAudioPlaybackBase;
+
+type
+  TAudioPlayback_SoftMixer = class;
+
+  TGenericPlaybackStream = class(TAudioPlaybackStream)
+    private
+      Engine: TAudioPlayback_SoftMixer;
+
+      SampleBuffer:      PByteArray;
+      SampleBufferSize:  integer;
+      SampleBufferCount: integer; // number of available bytes in SampleBuffer
+      SampleBufferPos:   integer;
+
+      SourceBuffer:      PByteArray;
+      SourceBufferSize:  integer;
+      SourceBufferCount: integer; // number of available bytes in SourceBuffer
+
+      Converter: TAudioConverter;
+      Status:   TStreamStatus;
+      InternalLock: PSDL_Mutex;
+      SoundEffects: TList;
+      fVolume: single;
+
+      FadeInStartTime, FadeInTime: cardinal;
+      FadeInStartVolume, FadeInTargetVolume: single;
+
+      NeedsRewind: boolean;
+
+      procedure Reset();
+
+      procedure ApplySoundEffects(Buffer: PByteArray; BufferSize: integer);
+      function InitFormatConversion(): boolean;
+      procedure FlushBuffers();
+
+      procedure LockSampleBuffer(); {$IFDEF HasInline}inline;{$ENDIF}
+      procedure UnlockSampleBuffer(); {$IFDEF HasInline}inline;{$ENDIF}
+    protected
+      function GetLatency(): double;        override;
+      function GetStatus(): TStreamStatus;  override;
+      function GetVolume(): single;         override;
+      procedure SetVolume(Volume: single);  override;
+      function GetLength(): real;           override;
+      function GetLoop(): boolean;          override;
+      procedure SetLoop(Enabled: boolean);  override;
+      function GetPosition: real;           override;
+      procedure SetPosition(Time: real);    override;
+    public
+      constructor Create(Engine: TAudioPlayback_SoftMixer);
+      destructor Destroy(); override;
+
+      function Open(SourceStream: TAudioSourceStream): boolean; override;
+      procedure Close();                    override;
+
+      procedure Play();                     override;
+      procedure Pause();                    override;
+      procedure Stop();                     override;
+      procedure FadeIn(Time: real; TargetVolume: single); override;
+
+      function GetAudioFormatInfo(): TAudioFormatInfo; override;
+
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+
+      function GetPCMData(var Data: TPCMData): Cardinal; override;
+      procedure GetFFTData(var Data: TFFTData);          override;
+
+      procedure AddSoundEffect(Effect: TSoundEffect);    override;
+      procedure RemoveSoundEffect(Effect: TSoundEffect); override;
+  end;
+
+  TAudioMixerStream = class
+    private
+      Engine: TAudioPlayback_SoftMixer;
+
+      ActiveStreams: TList;
+      MixerBuffer: PByteArray;
+      InternalLock: PSDL_Mutex;
+
+      AppVolume: single;
+
+      procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
+      procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
+
+      function GetVolume(): single;
+      procedure SetVolume(Volume: single);
+    public
+      constructor Create(Engine: TAudioPlayback_SoftMixer);
+      destructor Destroy(); override;
+      procedure AddStream(Stream: TAudioPlaybackStream);
+      procedure RemoveStream(Stream: TAudioPlaybackStream);
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+
+      property Volume: single read GetVolume write SetVolume;
+  end;
+
+  TAudioPlayback_SoftMixer = class(TAudioPlaybackBase)
+    private
+      MixerStream: TAudioMixerStream;
+    protected
+      FormatInfo: TAudioFormatInfo;
+
+      function InitializeAudioPlaybackEngine(): boolean; virtual; abstract;
+      function StartAudioPlaybackEngine(): boolean;      virtual; abstract;
+      procedure StopAudioPlaybackEngine();               virtual; abstract;
+      function FinalizeAudioPlaybackEngine(): boolean;   virtual; abstract;
+      procedure AudioCallback(Buffer: PByteArray; Size: integer); {$IFDEF HasInline}inline;{$ENDIF}
+
+      function CreatePlaybackStream(): TAudioPlaybackStream; override;
+    public
+      function GetName: String; override; abstract;
+      function InitializePlayback(): boolean; override;
+      function FinalizePlayback: boolean; override;
+
+      procedure SetAppVolume(Volume: single); override;
+
+      function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; override;
+
+      function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
+      function GetAudioFormatInfo(): TAudioFormatInfo;
+
+      procedure MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single); virtual;
+  end;
+
+type
+  TGenericVoiceStream = class(TAudioVoiceStream)
+    private
+      VoiceBuffer: TRingBuffer;
+      BufferLock: PSDL_Mutex;
+      PlaybackStream: TGenericPlaybackStream;
+      Engine: TAudioPlayback_SoftMixer;
+    public
+      constructor Create(Engine: TAudioPlayback_SoftMixer);
+
+      function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override;
+      procedure Close(); override;
+      procedure WriteData(Buffer: PByteArray; BufferSize: integer); override;
+      function ReadData(Buffer: PByteArray; BufferSize: integer): integer; override;
+      function IsEOF(): boolean; override;
+      function IsError(): boolean; override;
+  end;
+
+const
+  SOURCE_BUFFER_FRAMES = 4096;
+
+const
+  MAX_VOICE_DELAY = 0.500; // 20ms
+
+implementation
+
+uses
+  Math,
+  ULog,
+  UIni,
+  UFFT,
+  UAudioConverter,
+  UMain;
+
+{ TAudioMixerStream }
+
+constructor TAudioMixerStream.Create(Engine: TAudioPlayback_SoftMixer);
+begin
+  inherited Create();
+
+  Self.Engine := Engine;
+
+  ActiveStreams := TList.Create;
+  InternalLock := SDL_CreateMutex();
+  AppVolume := 1.0;
+end;
+
+destructor TAudioMixerStream.Destroy();
+begin
+  if assigned(MixerBuffer) then
+    Freemem(MixerBuffer);
+  ActiveStreams.Free;
+  SDL_DestroyMutex(InternalLock);
+  inherited;
+end;
+
+procedure TAudioMixerStream.Lock();
+begin
+  SDL_mutexP(InternalLock);
+end;
+
+procedure TAudioMixerStream.Unlock();
+begin
+  SDL_mutexV(InternalLock);
+end;
+
+function TAudioMixerStream.GetVolume(): single;
+begin
+  Lock();
+  Result := AppVolume;
+  Unlock();
+end;
+
+procedure TAudioMixerStream.SetVolume(Volume: single);
+begin
+  Lock();
+  AppVolume := Volume;
+  Unlock();
+end;
+
+procedure TAudioMixerStream.AddStream(Stream: TAudioPlaybackStream);
+begin
+  if not assigned(Stream) then
+    Exit;
+
+  Lock();
+  // check if stream is already in list to avoid duplicates
+  if (ActiveStreams.IndexOf(Pointer(Stream)) = -1) then
+    ActiveStreams.Add(Pointer(Stream));
+  Unlock();
+end;
+
+(*
+ * Sets the entry of stream in the ActiveStreams-List to nil
+ * but does not remove it from the list (Count is not changed!).
+ * Otherwise iterations over the elements might fail due to a
+ * changed Count-property.
+ * Call ActiveStreams.Pack() to remove the nil-pointers
+ * or check for nil-pointers when accessing ActiveStreams.
+ *)
+procedure TAudioMixerStream.RemoveStream(Stream: TAudioPlaybackStream);
+var
+  Index: integer;
+begin
+  Lock();
+  Index := activeStreams.IndexOf(Pointer(Stream));
+  if (Index <> -1) then
+  begin
+    // remove entry but do not decrease count-property
+    ActiveStreams[Index] := nil;
+  end;
+  Unlock();
+end;
+
+function TAudioMixerStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+var
+  i: integer;
+  Size: integer;
+  Stream: TGenericPlaybackStream;
+  NeedsPacking: boolean;
+begin
+  Result := BufferSize;
+
+  // zero target-buffer (silence)
+  FillChar(Buffer^, BufferSize, 0);
+
+  // resize mixer-buffer if necessary
+  ReallocMem(MixerBuffer, BufferSize);
+  if not assigned(MixerBuffer) then
+    Exit;
+
+  Lock();
+
+  NeedsPacking := false;
+
+  // mix streams to one stream
+  for i := 0 to ActiveStreams.Count-1 do
+  begin
+    if (ActiveStreams[i] = nil) then
+    begin
+      NeedsPacking := true;
+      continue;
+    end;
+
+    Stream := TGenericPlaybackStream(ActiveStreams[i]);
+    // fetch data from current stream
+    Size := Stream.ReadData(MixerBuffer, BufferSize);
+    if (Size > 0) then
+    begin
+      // mix stream-data with mixer-buffer
+      // Note: use Self.appVolume instead of Self.Volume to prevent recursive locking
+      Engine.MixBuffers(Buffer, MixerBuffer, Size, AppVolume * Stream.Volume);
+    end;
+  end;
+
+  // remove nil-pointers from list
+  if (NeedsPacking) then
+  begin
+    ActiveStreams.Pack();
+  end;
+
+  Unlock();
+end;
+
+
+{ TGenericPlaybackStream }
+
+constructor TGenericPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer);
+begin
+  inherited Create();
+  Self.Engine := Engine;
+  InternalLock := SDL_CreateMutex();
+  SoundEffects := TList.Create;
+  Status := ssStopped;
+  Reset();
+end;
+
+destructor TGenericPlaybackStream.Destroy();
+begin
+  Close();
+  SDL_DestroyMutex(InternalLock);
+  FreeAndNil(SoundEffects);
+  inherited;
+end;
+
+procedure TGenericPlaybackStream.Reset();
+begin
+  SourceStream := nil;
+
+  FreeAndNil(Converter);
+
+  FreeMem(SampleBuffer);
+  SampleBuffer := nil;
+  SampleBufferPos := 0;
+  SampleBufferSize := 0;
+  SampleBufferCount := 0;
+
+  FreeMem(SourceBuffer);
+  SourceBuffer := nil;
+  SourceBufferSize := 0;
+  SourceBufferCount := 0;
+
+  NeedsRewind := false;
+
+  fVolume := 0;
+  SoundEffects.Clear;
+  FadeInTime := 0;
+end;
+
+function TGenericPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean;
+begin
+  Result := false;
+
+  Close();
+
+  if (not assigned(SourceStream)) then
+    Exit;
+  Self.SourceStream := SourceStream;
+
+  if (not InitFormatConversion()) then
+  begin
+    // reset decode-stream so it will not be freed on destruction
+    Self.SourceStream := nil;
+    Exit;
+  end;
+
+  SourceBufferSize := SOURCE_BUFFER_FRAMES * SourceStream.GetAudioFormatInfo().FrameSize;
+  GetMem(SourceBuffer, SourceBufferSize);
+  fVolume := 1.0;
+
+  Result := true;
+end;
+
+procedure TGenericPlaybackStream.Close();
+begin
+  // stop audio-callback on this stream
+  Stop();
+
+  // Note: PerformOnClose must be called before SourceStream is invalidated
+  PerformOnClose();
+  // and free data
+  Reset();
+end;
+
+procedure TGenericPlaybackStream.LockSampleBuffer();
+begin
+  SDL_mutexP(InternalLock);
+end;
+
+procedure TGenericPlaybackStream.UnlockSampleBuffer();
+begin
+  SDL_mutexV(InternalLock);
+end;
+
+function TGenericPlaybackStream.InitFormatConversion(): boolean;
+var
+  SrcFormatInfo: TAudioFormatInfo;
+  DstFormatInfo: TAudioFormatInfo;
+begin
+  Result := false;
+
+  SrcFormatInfo := SourceStream.GetAudioFormatInfo();
+  DstFormatInfo := GetAudioFormatInfo();
+
+  // TODO: selection should not be done here, use a factory (TAudioConverterFactory) instead 
+  {$IF Defined(UseFFmpegResample)}
+  Converter := TAudioConverter_FFmpeg.Create();
+  {$ELSEIF Defined(UseSRCResample)}
+  Converter := TAudioConverter_SRC.Create();
+  {$ELSE}
+  Converter := TAudioConverter_SDL.Create();
+  {$IFEND}
+
+  Result := Converter.Init(SrcFormatInfo, DstFormatInfo);
+end;
+
+procedure TGenericPlaybackStream.Play();
+var
+  Mixer: TAudioMixerStream;
+begin
+  // only paused streams are not flushed
+  if (Status = ssPaused) then
+    NeedsRewind := false;
+
+  // rewind if necessary. Cases that require no rewind are:
+  // - stream was created and never played
+  // - stream was paused and is resumed now
+  // - stream was stopped and set to a new position already
+  if (NeedsRewind) then
+    SetPosition(0);
+
+  // update status
+  Status := ssPlaying;
+
+  NeedsRewind := true;
+
+  // add this stream to the mixer
+  Mixer := Engine.GetMixer();
+  if (Mixer <> nil) then
+    Mixer.AddStream(Self);
+end;
+
+procedure TGenericPlaybackStream.FadeIn(Time: real; TargetVolume: single);
+begin
+  FadeInTime := Trunc(Time * 1000);
+  FadeInStartTime := SDL_GetTicks();
+  FadeInStartVolume := fVolume;
+  FadeInTargetVolume := TargetVolume;
+  Play();
+end;
+
+procedure TGenericPlaybackStream.Pause();
+var
+  Mixer: TAudioMixerStream;
+begin
+  if (Status <> ssPlaying) then
+    Exit;
+
+  Status := ssPaused;
+
+  Mixer := Engine.GetMixer();
+  if (Mixer <> nil) then
+    Mixer.RemoveStream(Self);
+end;
+
+procedure TGenericPlaybackStream.Stop();
+var
+  Mixer: TAudioMixerStream;
+begin
+  if (Status = ssStopped) then
+    Exit;
+
+  Status := ssStopped;
+
+  Mixer := Engine.GetMixer();
+  if (Mixer <> nil) then
+    Mixer.RemoveStream(Self);
+end;
+
+function TGenericPlaybackStream.GetLoop(): boolean;
+begin
+  if assigned(SourceStream) then
+    Result := SourceStream.Loop
+  else
+    Result := false;
+end;
+
+procedure TGenericPlaybackStream.SetLoop(Enabled: boolean);
+begin
+  if assigned(SourceStream) then
+    SourceStream.Loop := Enabled;
+end;
+
+function TGenericPlaybackStream.GetLength(): real;
+begin
+  if assigned(SourceStream) then
+    Result := SourceStream.Length
+  else
+    Result := -1;
+end;
+
+function TGenericPlaybackStream.GetLatency(): double;
+begin
+  Result := Engine.GetLatency();
+end;
+
+function TGenericPlaybackStream.GetStatus(): TStreamStatus;
+begin
+  Result := Status;
+end;
+
+function TGenericPlaybackStream.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+  Result := Engine.GetAudioFormatInfo();
+end;
+
+procedure TGenericPlaybackStream.FlushBuffers();
+begin
+  SampleBufferCount := 0;
+  SampleBufferPos := 0;
+  SourceBufferCount := 0;
+end;
+
+procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PByteArray; BufferSize: integer);
+var
+  i: integer;
+begin
+  for i := 0 to SoundEffects.Count-1 do
+  begin
+    if (SoundEffects[i] <> nil) then
+    begin
+      TSoundEffect(SoundEffects[i]).Callback(Buffer, BufferSize);
+    end;
+  end;
+end;
+
+function TGenericPlaybackStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+var
+  ConversionInputCount: integer;
+  ConversionOutputSize: integer;   // max. number of converted data (= buffer size)
+  ConversionOutputCount: integer;  // actual number of converted data
+  SourceSize: integer;
+  NeededSampleBufferSize: integer;
+  BytesNeeded: integer;
+  SourceFormatInfo, OutputFormatInfo: TAudioFormatInfo;
+  SourceFrameSize, OutputFrameSize: integer;
+  SkipOutputCount: integer;  // number of output-data bytes to skip
+  SkipSourceCount: integer;  // number of source-data bytes to skip
+  FillCount: integer;  // number of bytes to fill with padding data
+  CopyCount: integer;
+  PadFrame: PByteArray;
+begin
+  Result := -1;
+
+  // sanity check for the source-stream
+  if (not assigned(SourceStream)) then
+    Exit;
+  
+  SkipOutputCount := 0;
+  SkipSourceCount := 0;
+  FillCount := 0;
+
+  SourceFormatInfo := SourceStream.GetAudioFormatInfo();
+  SourceFrameSize := SourceFormatInfo.FrameSize;
+  OutputFormatInfo := GetAudioFormatInfo();
+  OutputFrameSize := OutputFormatInfo.FrameSize;
+
+  // synchronize (adjust buffer size)
+  BytesNeeded := Synchronize(BufferSize, OutputFormatInfo);
+  if (BytesNeeded > BufferSize) then
+  begin
+    SkipOutputCount := BytesNeeded - BufferSize;
+    BytesNeeded := BufferSize;
+  end
+  else if (BytesNeeded < BufferSize) then
+  begin
+    FillCount := BufferSize - BytesNeeded;
+  end;
+
+  // lock access to sample-buffer
+  LockSampleBuffer();
+  try
+
+    // skip sample-buffer data
+    SampleBufferPos := SampleBufferPos + SkipOutputCount;
+    // size of available bytes in SampleBuffer after skipping
+    SampleBufferCount := SampleBufferCount - SampleBufferPos;
+    // update byte skip-count
+    SkipOutputCount := -SampleBufferCount;
+
+    // now that we skipped all buffered data from the last pass, we have to skip
+    // data directly after fetching it from the source-stream.
+    if (SkipOutputCount > 0) then
+    begin
+      SampleBufferCount := 0;
+      // convert skip-count to source-format units and resize to a multiple of
+      // the source frame-size.
+      SkipSourceCount := Round((SkipOutputCount * OutputFormatInfo.GetRatio(SourceFormatInfo)) /
+                               SourceFrameSize) * SourceFrameSize;
+      SkipOutputCount := 0;
+    end;
+
+    // copy data to front of buffer
+    if ((SampleBufferCount > 0) and (SampleBufferPos > 0)) then
+      Move(SampleBuffer[SampleBufferPos], SampleBuffer[0], SampleBufferCount);
+    SampleBufferPos := 0;
+
+    // resize buffer to a reasonable size
+    if (BufferSize > SampleBufferCount) then
+    begin
+      // Note: use BufferSize instead of BytesNeeded to minimize the need for resizing
+      SampleBufferSize := BufferSize;
+      ReallocMem(SampleBuffer, SampleBufferSize);
+      if (not assigned(SampleBuffer)) then
+        Exit;
+    end;
+
+    // fill sample-buffer (fetch and convert one block of source data per loop)
+    while (SampleBufferCount < BytesNeeded) do
+    begin
+      // move remaining source data from the previous pass to front of buffer
+      if (SourceBufferCount > 0) then
+      begin
+        Move(SourceBuffer[SourceBufferSize-SourceBufferCount],
+             SourceBuffer[0],
+             SourceBufferCount);
+      end;
+
+      SourceSize := SourceStream.ReadData(
+          @SourceBuffer[SourceBufferCount], SourceBufferSize-SourceBufferCount);
+      // break on error (-1) or if no data is available (0), e.g. while seeking
+      if (SourceSize <= 0) then
+      begin
+        // if we do not have data -> exit
+        if (SourceBufferCount = 0) then
+        begin
+          FlushBuffers();
+          Exit;
+        end;
+        // if we have some data, stop retrieving data from the source stream
+        // and use the data we have so far
+        Break;
+      end;
+
+      SourceBufferCount := SourceBufferCount + SourceSize;
+
+      // end-of-file reached -> stop playback
+      if (SourceStream.EOF) then
+      begin
+        if (Loop) then
+          SourceStream.Position := 0
+        else
+          Stop();
+      end;
+
+      if (SkipSourceCount > 0) then
+      begin
+        // skip data and update source buffer count
+        SourceBufferCount := SourceBufferCount - SkipSourceCount;
+        SkipSourceCount := -SourceBufferCount;
+        // continue with next pass if we skipped all data
+        if (SourceBufferCount <= 0) then
+        begin
+          SourceBufferCount := 0;
+          Continue;
+        end;
+      end;
+
+      // calc buffer size (might be bigger than actual resampled byte count)
+      ConversionOutputSize := Converter.GetOutputBufferSize(SourceBufferCount);
+      NeededSampleBufferSize := SampleBufferCount + ConversionOutputSize;
+
+      // resize buffer if necessary
+      if (SampleBufferSize < NeededSampleBufferSize) then
+      begin
+        SampleBufferSize := NeededSampleBufferSize;
+        ReallocMem(SampleBuffer, SampleBufferSize);
+        if (not assigned(SampleBuffer)) then
+        begin
+          FlushBuffers();
+          Exit;
+        end;
+      end;
+
+      // resample source data (Note: ConversionInputCount might be adjusted by Convert())
+      ConversionInputCount := SourceBufferCount;
+      ConversionOutputCount := Converter.Convert(
+          SourceBuffer, @SampleBuffer[SampleBufferCount], ConversionInputCount);
+      if (ConversionOutputCount = -1) then
+      begin
+        FlushBuffers();
+        Exit;
+      end;
+
+      // adjust sample- and source-buffer count by the number of converted bytes
+      SampleBufferCount := SampleBufferCount + ConversionOutputCount;
+      SourceBufferCount := SourceBufferCount - ConversionInputCount;
+    end;
+
+    // apply effects
+    ApplySoundEffects(SampleBuffer, SampleBufferCount);
+
+    // copy data to result buffer
+    CopyCount := Min(BytesNeeded, SampleBufferCount);
+    Move(SampleBuffer[0], Buffer[BufferSize - BytesNeeded], CopyCount);
+    Dec(BytesNeeded, CopyCount);
+    SampleBufferPos := CopyCount;
+
+  // release buffer lock
+  finally
+    UnlockSampleBuffer();
+  end;
+
+  // pad the buffer with the last frame if we are to fast
+  if (FillCount > 0) then
+  begin
+    if (CopyCount >= OutputFrameSize) then
+      PadFrame := @Buffer[CopyCount-OutputFrameSize]
+    else
+      PadFrame := nil;
+    FillBufferWithFrame(@Buffer[CopyCount], FillCount,
+                        PadFrame, OutputFrameSize);
+  end;
+
+  // BytesNeeded now contains the number of remaining bytes we were not able to fetch
+  Result := BufferSize - BytesNeeded;
+end;
+
+function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal;
+var
+  ByteCount: integer;
+begin
+  Result := 0;
+
+  // just SInt16 stereo support for now
+  if ((Engine.GetAudioFormatInfo().Format <> asfS16) or
+      (Engine.GetAudioFormatInfo().Channels <> 2)) then
+  begin
+    Exit;
+  end;
+
+  // zero memory
+  FillChar(Data, SizeOf(Data), 0);
+
+  // TODO: At the moment just the first samples of the SampleBuffer
+  // are returned, even if there is newer data in the upper samples.
+
+  LockSampleBuffer();
+  ByteCount := Min(SizeOf(Data), SampleBufferCount);
+  if (ByteCount > 0) then
+  begin
+    Move(SampleBuffer[0], Data, ByteCount);
+  end;
+  UnlockSampleBuffer();
+  
+  Result := ByteCount div SizeOf(TPCMStereoSample);
+end;
+
+procedure TGenericPlaybackStream.GetFFTData(var Data: TFFTData);
+var
+  i: integer;
+  Frames: integer;
+  DataIn: PSingleArray;
+  AudioFormat: TAudioFormatInfo;
+begin
+  // only works with SInt16 and Float values at the moment
+  AudioFormat := GetAudioFormatInfo();
+
+  DataIn := AllocMem(FFTSize * SizeOf(Single));
+  if (DataIn = nil) then
+    Exit;
+
+  LockSampleBuffer();
+  // TODO: We just use the first Frames frames, the others are ignored.
+  Frames := Min(FFTSize, SampleBufferCount div AudioFormat.FrameSize);
+  // use only first channel and convert data to float-values
+  case AudioFormat.Format of
+    asfS16:
+    begin
+      for i := 0 to Frames-1 do
+        DataIn[i] := PSmallInt(@SampleBuffer[i*AudioFormat.FrameSize])^ / -Low(SmallInt);
+    end;
+    asfFloat:
+    begin
+      for i := 0 to Frames-1 do
+        DataIn[i] := PSingle(@SampleBuffer[i*AudioFormat.FrameSize])^;
+    end;
+  end;
+  UnlockSampleBuffer();
+
+  WindowFunc(fwfHanning, FFTSize, DataIn);
+  PowerSpectrum(FFTSize, DataIn, @Data);
+  FreeMem(DataIn);
+
+  // resize data to a 0..1 range
+  for i := 0 to High(TFFTData) do
+  begin
+    Data[i] := Sqrt(Data[i]) / 100;
+  end;
+end;
+
+procedure TGenericPlaybackStream.AddSoundEffect(Effect: TSoundEffect);
+begin
+  if (not assigned(Effect)) then
+    Exit;
+
+  LockSampleBuffer();
+  // check if effect is already in list to avoid duplicates
+  if (SoundEffects.IndexOf(Pointer(Effect)) = -1) then
+    SoundEffects.Add(Pointer(Effect));
+  UnlockSampleBuffer();
+end;
+
+procedure TGenericPlaybackStream.RemoveSoundEffect(Effect: TSoundEffect);
+begin
+  LockSampleBuffer();
+  SoundEffects.Remove(Effect);
+  UnlockSampleBuffer();
+end;
+
+function TGenericPlaybackStream.GetPosition: real;
+var
+  BufferedTime: double;
+begin
+  if assigned(SourceStream) then
+  begin
+    LockSampleBuffer();
+
+    // calc the time of source data that is buffered (in the SampleBuffer and SourceBuffer)
+    // but not yet outputed
+    BufferedTime := (SampleBufferCount - SampleBufferPos) / Engine.FormatInfo.BytesPerSec +
+                    SourceBufferCount / SourceStream.GetAudioFormatInfo().BytesPerSec;
+    // and subtract it from the source position
+    Result := SourceStream.Position - BufferedTime;
+
+    UnlockSampleBuffer();
+  end
+  else
+  begin
+    Result := -1;
+  end;
+end;
+
+procedure TGenericPlaybackStream.SetPosition(Time: real);
+begin
+  if assigned(SourceStream) then
+  begin
+    LockSampleBuffer();
+
+    SourceStream.Position := Time;
+    if (Status = ssStopped) then
+      NeedsRewind := false;
+    // do not use outdated data
+    FlushBuffers();
+
+    AvgSyncDiff := -1;
+    
+    UnlockSampleBuffer();
+  end;
+end;
+
+function TGenericPlaybackStream.GetVolume(): single;
+var
+  FadeAmount: Single;
+begin
+  LockSampleBuffer();
+  // adjust volume if fading is enabled
+  if (FadeInTime > 0) then
+  begin
+    FadeAmount := (SDL_GetTicks() - FadeInStartTime) / FadeInTime;
+    // check if fade-target is reached
+    if (FadeAmount >= 1) then
+    begin
+      // target reached -> stop fading
+      FadeInTime := 0;
+      fVolume := FadeInTargetVolume;
+    end
+    else
+    begin
+      // fading in progress
+      fVolume := FadeAmount*FadeInTargetVolume + (1-FadeAmount)*FadeInStartVolume;
+    end;
+  end;
+  // return current volume
+  Result := fVolume;
+  UnlockSampleBuffer();
+end;
+
+procedure TGenericPlaybackStream.SetVolume(Volume: single);
+begin
+  LockSampleBuffer();
+  // stop fading
+  FadeInTime := 0;
+  // clamp volume
+  if (Volume > 1.0) then
+    fVolume := 1.0
+  else if (Volume < 0) then
+    fVolume := 0
+  else
+    fVolume := Volume;
+  UnlockSampleBuffer();
+end;
+
+
+{ TGenericVoiceStream }
+
+constructor TGenericVoiceStream.Create(Engine: TAudioPlayback_SoftMixer);
+begin
+  inherited Create();
+  Self.Engine := Engine;
+end;
+
+function TGenericVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
+var
+  BufferSize: integer;
+begin
+  Result := false;
+
+  Close();
+
+  if (not inherited Open(ChannelMap, FormatInfo)) then
+    Exit;
+
+  // Note:
+  // - use Self.FormatInfo instead of FormatInfo as the latter one might have a
+  //   channel size of 2.
+  // - the buffer-size must be a multiple of the FrameSize
+  BufferSize := (Ceil(MAX_VOICE_DELAY * Self.FormatInfo.BytesPerSec) div Self.FormatInfo.FrameSize) *
+                Self.FormatInfo.FrameSize;
+  VoiceBuffer := TRingBuffer.Create(BufferSize);
+
+  BufferLock := SDL_CreateMutex();
+
+
+  // create a matching playback stream for the voice-stream
+  PlaybackStream := TGenericPlaybackStream.Create(Engine);
+  // link voice- and playback-stream
+  if (not PlaybackStream.Open(Self)) then
+  begin
+    PlaybackStream.Free;
+    Exit;
+  end;
+
+  // start voice passthrough
+  PlaybackStream.Play();
+
+  Result := true;
+end;
+
+procedure TGenericVoiceStream.Close();
+begin
+  // stop and free the playback stream
+  FreeAndNil(PlaybackStream);
+
+  // free data
+  FreeAndNil(VoiceBuffer);
+  if (BufferLock <> nil) then
+    SDL_DestroyMutex(BufferLock);
+
+  inherited Close();
+end;
+
+procedure TGenericVoiceStream.WriteData(Buffer: PByteArray; BufferSize: integer);
+begin
+  // lock access to buffer
+  SDL_mutexP(BufferLock);
+  try
+    if (VoiceBuffer = nil) then
+      Exit;
+    VoiceBuffer.Write(Buffer, BufferSize);
+  finally
+    SDL_mutexV(BufferLock);
+  end;
+end;
+
+function TGenericVoiceStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer;
+begin
+  Result := -1;
+
+  // lock access to buffer
+  SDL_mutexP(BufferLock);
+  try
+    if (VoiceBuffer = nil) then
+      Exit;
+    Result := VoiceBuffer.Read(Buffer, BufferSize);
+  finally
+    SDL_mutexV(BufferLock);
+  end;
+end;
+
+function TGenericVoiceStream.IsEOF(): boolean;
+begin
+  SDL_mutexP(BufferLock);
+  Result := (VoiceBuffer = nil);
+  SDL_mutexV(BufferLock);
+end;
+
+function TGenericVoiceStream.IsError(): boolean;
+begin
+  Result := false;
+end;
+
+
+{ TAudioPlayback_SoftMixer }
+
+function TAudioPlayback_SoftMixer.InitializePlayback: boolean;
+begin
+  Result := false;
+
+  //Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer');
+
+  if(not InitializeAudioPlaybackEngine()) then
+    Exit;
+
+  MixerStream := TAudioMixerStream.Create(Self);
+
+  if(not StartAudioPlaybackEngine()) then
+    Exit;
+
+  Result := true;
+end;
+
+function TAudioPlayback_SoftMixer.FinalizePlayback: boolean;
+begin
+  Close;
+  StopAudioPlaybackEngine();
+
+  FreeAndNil(MixerStream);
+  FreeAndNil(FormatInfo);
+
+  FinalizeAudioPlaybackEngine();
+  inherited FinalizePlayback;
+  Result := true;
+end;
+
+procedure TAudioPlayback_SoftMixer.AudioCallback(Buffer: PByteArray; Size: integer);
+begin
+  MixerStream.ReadData(Buffer, Size);
+end;
+
+function TAudioPlayback_SoftMixer.GetMixer(): TAudioMixerStream;
+begin
+  Result := MixerStream;
+end;
+
+function TAudioPlayback_SoftMixer.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+  Result := FormatInfo;
+end;
+
+function TAudioPlayback_SoftMixer.CreatePlaybackStream(): TAudioPlaybackStream;
+begin
+  Result := TGenericPlaybackStream.Create(Self);
+end;
+
+function TAudioPlayback_SoftMixer.CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
+var
+  VoiceStream: TGenericVoiceStream;
+begin
+  Result := nil;
+
+  // create a voice stream
+  VoiceStream := TGenericVoiceStream.Create(Self);
+  if (not VoiceStream.Open(ChannelMap, FormatInfo)) then
+  begin
+    VoiceStream.Free;
+    Exit;
+  end;
+
+  Result := VoiceStream;
+end;
+
+procedure TAudioPlayback_SoftMixer.SetAppVolume(Volume: single);
+begin
+  // sets volume only for this application
+  MixerStream.Volume := Volume;
+end;
+
+procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single);
+var
+  SampleIndex: Cardinal;
+  SampleInt: Integer;
+  SampleFlt: Single;
+begin
+  SampleIndex := 0;
+  case FormatInfo.Format of
+    asfS16:
+    begin
+      while (SampleIndex < Size) do
+      begin
+        // apply volume and sum with previous mixer value
+        SampleInt := PSmallInt(@DstBuffer[SampleIndex])^ +
+                     Round(PSmallInt(@SrcBuffer[SampleIndex])^ * Volume);
+        // clip result
+        if (SampleInt > High(SmallInt)) then
+          SampleInt := High(SmallInt)
+        else if (SampleInt < Low(SmallInt)) then
+          SampleInt := Low(SmallInt);
+        // assign result
+        PSmallInt(@DstBuffer[SampleIndex])^ := SampleInt;
+        // increase index by one sample
+        Inc(SampleIndex, SizeOf(SmallInt));
+      end;
+    end;
+    asfFloat:
+    begin
+      while (SampleIndex < Size) do
+      begin
+        // apply volume and sum with previous mixer value
+        SampleFlt := PSingle(@DstBuffer[SampleIndex])^ +
+                     PSingle(@SrcBuffer[SampleIndex])^ * Volume;
+        // clip result
+        if (SampleFlt > 1.0) then
+          SampleFlt := 1.0
+        else if (SampleFlt < -1.0) then
+          SampleFlt := -1.0;
+        // assign result
+        PSingle(@DstBuffer[SampleIndex])^ := SampleFlt;
+        // increase index by one sample
+        Inc(SampleIndex, SizeOf(Single));
+      end;
+    end;
+    else
+    begin
+      Log.LogError('Incompatible format', 'TAudioMixerStream.MixAudio');
+    end;
+  end;
+end;
+
+end.
diff --git a/cmake/src/media/UMediaCore_FFmpeg.pas b/cmake/src/media/UMediaCore_FFmpeg.pas
new file mode 100644
index 00000000..9ad19a5b
--- /dev/null
+++ b/cmake/src/media/UMediaCore_FFmpeg.pas
@@ -0,0 +1,430 @@
+{* 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 UMediaCore_FFmpeg;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UMusic,
+  avcodec,
+  avformat,
+  avutil,
+  ULog,
+  sdl;
+
+type
+  PPacketQueue = ^TPacketQueue;
+  TPacketQueue = class
+    private
+      FirstListEntry: PAVPacketList;
+      LastListEntry:  PAVPacketList;
+      PacketCount: integer;
+      Mutex:     PSDL_Mutex;
+      Condition: PSDL_Cond;
+      Size: integer;
+      AbortRequest: boolean;
+    public
+      constructor Create();
+      destructor Destroy(); override;
+
+      function Put(Packet : PAVPacket): integer;
+      function PutStatus(StatusFlag: integer; StatusInfo: Pointer): integer;
+      procedure FreeStatusInfo(var Packet: TAVPacket);
+      function GetStatusInfo(var Packet: TAVPacket): Pointer;
+      function Get(var Packet: TAVPacket; Blocking: boolean): integer;
+      function GetSize(): integer;
+      procedure Flush();
+      procedure Abort();
+      function IsAborted(): boolean;
+  end;
+
+const
+  STATUS_PACKET: PChar = 'STATUS_PACKET';
+const
+  PKT_STATUS_FLAG_EOF     = 1; // signal end-of-file
+  PKT_STATUS_FLAG_FLUSH   = 2; // request the decoder to flush its avcodec decode buffers
+  PKT_STATUS_FLAG_ERROR   = 3; // signal an error state
+  PKT_STATUS_FLAG_EMPTY   = 4; // request the decoder to output empty data (silence or black frames)
+
+type
+  TMediaCore_FFmpeg = class
+    private
+      AVCodecLock: PSDL_Mutex;
+    public
+      constructor Create();
+      destructor Destroy(); override;
+      class function GetInstance(): TMediaCore_FFmpeg;
+
+      function GetErrorString(ErrorNum: integer): string;
+      function FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer ): boolean;
+      function FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer;
+      function ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean;
+      procedure LockAVCodec();
+      procedure UnlockAVCodec();
+  end;
+
+implementation
+
+uses
+  SysUtils;
+
+var
+  Instance: TMediaCore_FFmpeg;
+
+constructor TMediaCore_FFmpeg.Create();
+begin
+  inherited;
+  AVCodecLock := SDL_CreateMutex();
+end;
+
+destructor TMediaCore_FFmpeg.Destroy();
+begin
+  SDL_DestroyMutex(AVCodecLock);
+  inherited;
+end;
+
+class function TMediaCore_FFmpeg.GetInstance(): TMediaCore_FFmpeg;
+begin
+  if (not Assigned(Instance)) then
+    Instance := TMediaCore_FFmpeg.Create();
+  Result := Instance;
+end;
+
+procedure TMediaCore_FFmpeg.LockAVCodec();
+begin
+  SDL_mutexP(AVCodecLock);
+end;
+
+procedure TMediaCore_FFmpeg.UnlockAVCodec();
+begin
+  SDL_mutexV(AVCodecLock);
+end;
+
+function TMediaCore_FFmpeg.GetErrorString(ErrorNum: integer): string;
+begin
+  case ErrorNum of
+    AVERROR_IO:           Result := 'AVERROR_IO';
+    AVERROR_NUMEXPECTED:  Result := 'AVERROR_NUMEXPECTED';
+    AVERROR_INVALIDDATA:  Result := 'AVERROR_INVALIDDATA';
+    AVERROR_NOMEM:        Result := 'AVERROR_NOMEM';
+    AVERROR_NOFMT:        Result := 'AVERROR_NOFMT';
+    AVERROR_NOTSUPP:      Result := 'AVERROR_NOTSUPP';
+    AVERROR_NOENT:        Result := 'AVERROR_NOENT';
+    AVERROR_PATCHWELCOME: Result := 'AVERROR_PATCHWELCOME';
+    else                  Result := 'AVERROR_#'+inttostr(ErrorNum);
+  end;
+end;
+
+{
+  @param(FormatCtx is a PAVFormatContext returned from av_open_input_file )
+  @param(FirstVideoStream is an OUT value of type integer, this is the index of the video stream)
+  @param(FirstAudioStream is an OUT value of type integer, this is the index of the audio stream)
+  @returns(@true on success, @false otherwise)
+}
+function TMediaCore_FFmpeg.FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer): boolean;
+var
+  i: integer;
+  Stream: PAVStream;
+begin
+  // find the first video stream
+  FirstAudioStream := -1;
+  FirstVideoStream := -1;
+
+  for i := 0 to FormatCtx.nb_streams-1 do
+  begin
+    Stream := FormatCtx.streams[i];
+
+    if (Stream.codec.codec_type = CODEC_TYPE_VIDEO) and
+       (FirstVideoStream < 0) then
+    begin
+      FirstVideoStream := i;
+    end;
+
+    if (Stream.codec.codec_type = CODEC_TYPE_AUDIO) and
+       (FirstAudioStream < 0) then
+    begin
+      FirstAudioStream := i;
+    end;
+  end;
+
+  // return true if either an audio- or video-stream was found
+  Result := (FirstAudioStream > -1) or
+            (FirstVideoStream > -1) ;
+end;
+
+function TMediaCore_FFmpeg.FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer;
+var
+  i: integer;
+  StreamIndex: integer;
+  Stream: PAVStream;
+begin
+  // find the first audio stream
+  StreamIndex := -1;
+
+  for i := 0 to FormatCtx^.nb_streams-1 do
+  begin
+    Stream := FormatCtx^.streams[i];
+
+    if (Stream.codec^.codec_type = CODEC_TYPE_AUDIO) then
+    begin
+      StreamIndex := i;
+      Break;
+    end;
+  end;
+
+  Result := StreamIndex;
+end;
+
+function TMediaCore_FFmpeg.ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean;
+begin
+  case FFmpegFormat of
+    SAMPLE_FMT_U8:  Format := asfU8;
+    SAMPLE_FMT_S16: Format := asfS16;
+    SAMPLE_FMT_S32: Format := asfS32;
+    SAMPLE_FMT_FLT: Format := asfFloat;
+    SAMPLE_FMT_DBL: Format := asfDouble;
+    else begin
+      Result := false;
+      Exit;
+    end;
+  end;
+  Result := true;
+end;
+
+{ TPacketQueue }
+
+constructor TPacketQueue.Create();
+begin
+  inherited;
+
+  FirstListEntry := nil;
+  LastListEntry  := nil;
+  PacketCount := 0;
+  Size := 0;
+
+  Mutex := SDL_CreateMutex();
+  Condition := SDL_CreateCond();
+end;
+
+destructor TPacketQueue.Destroy();
+begin
+  Flush();
+  SDL_DestroyMutex(Mutex);
+  SDL_DestroyCond(Condition);
+  inherited;
+end;
+
+procedure TPacketQueue.Abort();
+begin
+  SDL_LockMutex(Mutex);
+
+  AbortRequest := true;
+
+  SDL_CondBroadcast(Condition);
+  SDL_UnlockMutex(Mutex);
+end;
+
+function TPacketQueue.IsAborted(): boolean;
+begin
+  SDL_LockMutex(Mutex);
+  Result := AbortRequest;
+  SDL_UnlockMutex(Mutex);
+end;
+
+function TPacketQueue.Put(Packet : PAVPacket): integer;
+var
+  CurrentListEntry : PAVPacketList;
+begin
+  Result := -1;
+
+  if (Packet = nil) then
+    Exit;
+
+  if (PChar(Packet^.data) <> STATUS_PACKET) then
+  begin
+    if (av_dup_packet(Packet) < 0) then
+      Exit;
+  end;
+
+  CurrentListEntry := av_malloc(SizeOf(TAVPacketList));
+  if (CurrentListEntry = nil) then
+    Exit;
+
+  CurrentListEntry^.pkt  := Packet^;
+  CurrentListEntry^.next := nil;
+
+  SDL_LockMutex(Mutex);
+  try
+    if (LastListEntry = nil) then
+      FirstListEntry := CurrentListEntry
+    else
+      LastListEntry^.next := CurrentListEntry;
+
+    LastListEntry := CurrentListEntry;
+    Inc(PacketCount);
+
+    Size := Size + CurrentListEntry^.pkt.size;
+    SDL_CondSignal(Condition);
+  finally
+    SDL_UnlockMutex(Mutex);
+  end;
+
+  Result := 0;
+end;
+
+(**
+ * Adds a status packet (EOF, Flush, etc.) to the end of the queue.
+ * StatusInfo can be used to pass additional information to the decoder.
+ * Only assign nil or a valid pointer to data allocated with Getmem() to
+ * StatusInfo because the pointer will be disposed with Freemem() on a call
+ * to Flush(). If the packet is removed from the queue it is the decoder's
+ * responsibility to free the StatusInfo data with FreeStatusInfo().
+ *)
+function TPacketQueue.PutStatus(StatusFlag: integer; StatusInfo: Pointer): integer;
+var
+  TempPacket: PAVPacket;
+begin
+  // create temp. package
+  TempPacket := av_malloc(SizeOf(TAVPacket));
+  if (TempPacket = nil) then
+  begin
+    Result := -1;
+    Exit;
+  end;
+  // init package
+  av_init_packet(TempPacket^);
+  TempPacket^.data  := Pointer(STATUS_PACKET);
+  TempPacket^.flags := StatusFlag;
+  TempPacket^.priv  := StatusInfo;
+  // put a copy of the package into the queue
+  Result := Put(TempPacket);
+  // data has been copied -> delete temp. package
+  av_free(TempPacket);
+end;
+
+procedure TPacketQueue.FreeStatusInfo(var Packet: TAVPacket);
+begin
+  if (Packet.priv <> nil) then
+    FreeMem(Packet.priv);
+end;
+
+function TPacketQueue.GetStatusInfo(var Packet: TAVPacket): Pointer;
+begin
+  Result := Packet.priv;
+end;
+
+function TPacketQueue.Get(var Packet: TAVPacket; Blocking: boolean): integer;
+var
+  CurrentListEntry: PAVPacketList;
+const
+  WAIT_TIMEOUT = 10; // timeout in ms
+begin
+  Result := -1;
+
+  SDL_LockMutex(Mutex);
+  try
+    while (true) do
+    begin
+      if (AbortRequest) then
+        Exit;
+
+      CurrentListEntry := FirstListEntry;
+      if (CurrentListEntry <> nil) then
+      begin
+        FirstListEntry := CurrentListEntry^.next;
+        if (FirstListEntry = nil) then
+          LastListEntry := nil;
+        Dec(PacketCount);
+
+        Size := Size - CurrentListEntry^.pkt.size;
+        Packet := CurrentListEntry^.pkt;
+        av_free(CurrentListEntry);
+
+        Result := 1;
+        Break;
+      end
+      else if (not Blocking) then
+      begin
+        Result := 0;
+        Break;
+      end
+      else
+      begin
+        // block until a new package arrives,
+        // but do not wait till infinity to avoid deadlocks
+        if (SDL_CondWaitTimeout(Condition, Mutex, WAIT_TIMEOUT) = SDL_MUTEX_TIMEDOUT) then
+        begin
+          Result := 0;
+          Break;
+        end;
+      end;
+    end;
+  finally
+    SDL_UnlockMutex(Mutex);
+  end;
+end;
+
+function TPacketQueue.GetSize(): integer;
+begin
+  SDL_LockMutex(Mutex);
+  Result := Size;
+  SDL_UnlockMutex(Mutex);
+end;
+
+procedure TPacketQueue.Flush();
+var
+  CurrentListEntry, TempListEntry: PAVPacketList;
+begin
+  SDL_LockMutex(Mutex);
+
+  CurrentListEntry := FirstListEntry;
+  while(CurrentListEntry <> nil) do
+  begin
+    TempListEntry := CurrentListEntry^.next;
+    // free status data
+    if (PChar(CurrentListEntry^.pkt.data) = STATUS_PACKET) then
+      FreeStatusInfo(CurrentListEntry^.pkt);
+    // free packet data
+    av_free_packet(@CurrentListEntry^.pkt);
+    // Note: param must be a pointer to a pointer!
+    av_freep(@CurrentListEntry);
+    CurrentListEntry := TempListEntry;
+  end;
+  LastListEntry := nil;
+  FirstListEntry := nil;
+  PacketCount := 0;
+  Size := 0;
+
+  SDL_UnlockMutex(Mutex);
+end;
+
+end.
diff --git a/cmake/src/media/UMediaCore_SDL.pas b/cmake/src/media/UMediaCore_SDL.pas
new file mode 100644
index 00000000..74c75e16
--- /dev/null
+++ b/cmake/src/media/UMediaCore_SDL.pas
@@ -0,0 +1,63 @@
+{* 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 UMediaCore_SDL;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  UMusic,
+  sdl;
+
+function ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
+
+implementation
+
+function ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
+begin
+  case Format of
+    asfU8:     SDLFormat := AUDIO_U8;
+    asfS8:     SDLFormat := AUDIO_S8;
+    asfU16LSB: SDLFormat := AUDIO_U16LSB;
+    asfS16LSB: SDLFormat := AUDIO_S16LSB;
+    asfU16MSB: SDLFormat := AUDIO_U16MSB;
+    asfS16MSB: SDLFormat := AUDIO_S16MSB;
+    asfU16:    SDLFormat := AUDIO_U16;
+    asfS16:    SDLFormat := AUDIO_S16;
+    else begin
+      Result := false;
+      Exit;
+    end;
+  end;
+  Result := true;
+end;
+
+end.
diff --git a/cmake/src/media/UMedia_dummy.pas b/cmake/src/media/UMedia_dummy.pas
new file mode 100644
index 00000000..7558dd0b
--- /dev/null
+++ b/cmake/src/media/UMedia_dummy.pas
@@ -0,0 +1,268 @@
+{* 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 UMedia_dummy;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+implementation
+
+uses
+     SysUtils,
+     math,
+     UMusic;
+
+type
+    TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
+    private
+      DummyOutputDeviceList: TAudioOutputDeviceList;
+    public
+      constructor Create();
+      function  GetName: string;
+
+      function Init(): boolean;
+      function Finalize(): boolean;
+
+      function  Open(const aFileName : string): boolean; // true if succeed
+      procedure Close;
+
+      procedure Play;
+      procedure Pause;
+      procedure Stop;
+
+      procedure SetPosition(Time: real);
+      function  GetPosition: real;
+
+      procedure SetSyncSource(SyncSource: TSyncSource);
+
+      procedure GetFrame(Time: Extended);
+      procedure DrawGL(Screen: integer);
+
+      // IAudioInput
+      function InitializeRecord: boolean;
+      function FinalizeRecord: boolean;
+      procedure CaptureStart;
+      procedure CaptureStop;
+      procedure GetFFTData(var data: TFFTData);
+      function GetPCMData(var data: TPCMData): Cardinal;
+
+      // IAudioPlayback
+      function InitializePlayback: boolean;
+      function FinalizePlayback: boolean;
+
+      function GetOutputDeviceList(): TAudioOutputDeviceList;
+      procedure FadeIn(Time: real; TargetVolume: single);
+      procedure SetAppVolume(Volume: single);
+      procedure SetVolume(Volume: single);
+      procedure SetLoop(Enabled: boolean);
+      procedure Rewind;
+
+      function Finished: boolean;
+      function Length: real;
+
+      function OpenSound(const Filename: string): TAudioPlaybackStream;
+      procedure CloseSound(var PlaybackStream: TAudioPlaybackStream);
+      procedure PlaySound(stream: TAudioPlaybackStream);
+      procedure StopSound(stream: TAudioPlaybackStream);
+
+      function CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
+      procedure CloseVoiceStream(var VoiceStream: TAudioVoiceStream);
+    end;
+
+function  TMedia_dummy.GetName: string;
+begin
+  Result := 'dummy';
+end;
+
+procedure TMedia_dummy.GetFrame(Time: Extended);
+begin
+end;
+
+procedure TMedia_dummy.DrawGL(Screen: integer);
+begin
+end;
+
+constructor TMedia_dummy.Create();
+begin
+  inherited;
+end;
+
+function TMedia_dummy.Init(): boolean;
+begin
+  Result := true;
+end;
+
+function TMedia_dummy.Finalize(): boolean;
+begin
+  Result := true;
+end;
+
+function TMedia_dummy.Open(const aFileName : string): boolean; // true if succeed
+begin
+  Result := false;
+end;
+
+procedure TMedia_dummy.Close;
+begin
+end;
+
+procedure TMedia_dummy.Play;
+begin
+end;
+
+procedure TMedia_dummy.Pause;
+begin
+end;
+
+procedure TMedia_dummy.Stop;
+begin
+end;
+
+procedure TMedia_dummy.SetPosition(Time: real);
+begin
+end;
+
+function  TMedia_dummy.GetPosition: real;
+begin
+  Result := 0;
+end;
+
+procedure TMedia_dummy.SetSyncSource(SyncSource: TSyncSource);
+begin
+end;
+
+// IAudioInput
+function TMedia_dummy.InitializeRecord: boolean;
+begin
+  Result := true;
+end;
+
+function TMedia_dummy.FinalizeRecord: boolean;
+begin
+  Result := true;
+end;
+
+procedure TMedia_dummy.CaptureStart;
+begin
+end;
+
+procedure TMedia_dummy.CaptureStop;
+begin
+end;
+
+procedure TMedia_dummy.GetFFTData(var data: TFFTData);
+begin
+end;
+
+function  TMedia_dummy.GetPCMData(var data: TPCMData): Cardinal;
+begin
+  Result := 0;
+end;
+
+// IAudioPlayback
+function TMedia_dummy.InitializePlayback: boolean;
+begin
+  SetLength(DummyOutputDeviceList, 1);
+  DummyOutputDeviceList[0] := TAudioOutputDevice.Create();
+  DummyOutputDeviceList[0].Name := '[Dummy Device]';
+  Result := true;
+end;
+
+function TMedia_dummy.FinalizePlayback: boolean;
+begin
+  Result := true;
+end;
+
+function TMedia_dummy.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+  Result := DummyOutputDeviceList;
+end;
+
+procedure TMedia_dummy.SetAppVolume(Volume: single);
+begin
+end;
+
+procedure TMedia_dummy.SetVolume(Volume: single);
+begin
+end;
+
+procedure TMedia_dummy.SetLoop(Enabled: boolean);
+begin
+end;
+
+procedure TMedia_dummy.FadeIn(Time: real; TargetVolume: single);
+begin
+end;
+
+procedure TMedia_dummy.Rewind;
+begin
+end;
+
+function TMedia_dummy.Finished: boolean;
+begin
+  Result := false;
+end;
+
+function TMedia_dummy.Length: real;
+begin
+  Result := 60;
+end;
+
+function TMedia_dummy.OpenSound(const Filename: string): TAudioPlaybackStream;
+begin
+ Result := nil;
+end;
+
+procedure TMedia_dummy.CloseSound(var PlaybackStream: TAudioPlaybackStream);
+begin
+end;
+
+procedure TMedia_dummy.PlaySound(stream: TAudioPlaybackStream);
+begin
+end;
+
+procedure TMedia_dummy.StopSound(stream: TAudioPlaybackStream);
+begin
+end;
+
+function TMedia_dummy.CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
+begin
+  Result := nil;
+end;
+
+procedure TMedia_dummy.CloseVoiceStream(var VoiceStream: TAudioVoiceStream);
+begin
+end;
+
+initialization
+  MediaManager.Add(TMedia_dummy.Create);
+
+end.
diff --git a/cmake/src/media/UVideo.pas b/cmake/src/media/UVideo.pas
new file mode 100644
index 00000000..35f8ab4d
--- /dev/null
+++ b/cmake/src/media/UVideo.pas
@@ -0,0 +1,954 @@
+{* 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 UVideo;
+
+{*
+ * based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/)
+ *}
+
+// uncomment if you want to see the debug stuff
+{.$define DebugDisplay}
+{.$define DebugFrames}
+{.$define VideoBenchmark}
+{.$define Info}
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+// use BGR-format for accelerated colorspace conversion with swscale
+{$IFDEF UseSWScale}
+  {$DEFINE PIXEL_FMT_BGR}
+{$ENDIF}
+
+type
+  {**
+   * vacStretch: Stretch to screen width and height
+   *   - ignores aspect
+   *   + no borders
+   *   + no image data loss
+   * vacCrop: Stretch to screen width or height, crop the other dimension
+   *   + keeps aspect
+   *   + no borders
+   *   - frame borders are cropped (image data loss)
+   * vacLetterBox: Stretch to screen width, add bars at or crop top and bottom
+   *   + keeps aspect
+   *   - borders at top and bottom
+   *   o top/bottom is cropped if width < height (unusual)
+   *}
+  TAspectCorrection = (acoStretch, acoCrop, acoLetterBox);
+
+
+implementation
+
+uses
+  SDL,
+  textgl,
+  avcodec,
+  avformat,
+  avutil,
+  avio,
+  rational,
+  {$IFDEF UseSWScale}
+  swscale,
+  {$ENDIF}
+  UMediaCore_FFmpeg,
+  math,
+  gl,
+  glext,
+  SysUtils,
+  UCommon,
+  UConfig,
+  ULog,
+  UMusic,
+  UGraphicClasses,
+  UGraphic;
+
+const
+{$IFDEF PIXEL_FMT_BGR}
+  PIXEL_FMT_OPENGL = GL_BGR;
+  PIXEL_FMT_FFMPEG = PIX_FMT_BGR24;
+{$ELSE}
+  PIXEL_FMT_OPENGL = GL_RGB;
+  PIXEL_FMT_FFMPEG = PIX_FMT_RGB24;
+{$ENDIF}
+
+type
+  TRectCoords = record
+    Left, Right:  double;
+    Upper, Lower: double;
+  end;
+
+  TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback )
+  private
+    fOpened: boolean;     //**< stream successfully opened
+    fPaused: boolean;     //**< stream paused
+    fInitialized: boolean;
+    fEOF: boolean;        //**< end-of-file state
+
+    fLoop: boolean;       //**< looping enabled
+
+    fStream:        PAVStream;
+    fStreamIndex :  integer;
+    fFormatContext: PAVFormatContext;
+    fCodecContext:  PAVCodecContext;
+    fCodec:         PAVCodec;
+
+    fAVFrame:     PAVFrame;
+    fAVFrameRGB:  PAVFrame;
+
+    fFrameBuffer: PByte;  //**< stores a FFmpeg video frame
+    fFrameTex:    GLuint; //**< OpenGL texture for FrameBuffer
+    fFrameTexValid: boolean; //**< if true, fFrameTex contains the current frame
+    fTexWidth, fTexHeight: cardinal;
+
+    {$IFDEF UseSWScale}
+    fSwScaleContext: PSwsContext;
+    {$ENDIF}
+
+    fAspect: real;        //**< width/height ratio
+    fAspectCorrection: TAspectCorrection;
+    
+    fTimeBase: extended;  //**< FFmpeg time base per time unit
+    fTime:     extended;  //**< video time position (absolute)
+    fLoopTime: extended;  //**< start time of the current loop
+
+    procedure Reset();
+    function DecodeFrame(): boolean;
+    procedure SynchronizeTime(Frame: PAVFrame; var pts: double);
+
+    procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords);
+    
+    procedure ShowDebugInfo();
+
+  public
+    function    GetName: String;
+
+    function    Init(): boolean;
+    function    Finalize: boolean;
+
+    function    Open(const aFileName : string): boolean; // true if succeed
+    procedure   Close;
+
+    procedure   Play;
+    procedure   Pause;
+    procedure   Stop;
+
+    procedure   SetPosition(Time: real);
+    function    GetPosition: real;
+
+    procedure   GetFrame(Time: Extended);
+    procedure   DrawGL(Screen: integer);
+  end;
+
+var
+  FFmpegCore: TMediaCore_FFmpeg;
+
+  
+// These are called whenever we allocate a frame buffer.
+// We use this to store the global_pts in a frame at the time it is allocated.
+function PtsGetBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame): integer; cdecl;
+var
+  pts: Pint64;
+  VideoPktPts: Pint64;
+begin
+  Result := avcodec_default_get_buffer(CodecCtx, Frame);
+  VideoPktPts := CodecCtx^.opaque;
+  if (VideoPktPts <> nil) then
+  begin
+    // Note: we must copy the pts instead of passing a pointer, because the packet
+    // (and with it the pts) might change before a frame is returned by av_decode_video.
+    pts := av_malloc(sizeof(int64));
+    pts^ := VideoPktPts^;
+    Frame^.opaque := pts;
+  end;
+end;
+
+procedure PtsReleaseBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame); cdecl;
+begin
+  if (Frame <> nil) then
+    av_freep(@Frame^.opaque);
+  avcodec_default_release_buffer(CodecCtx, Frame);
+end;
+
+
+{*------------------------------------------------------------------------------
+ * TVideoPlayback_ffmpeg
+ *------------------------------------------------------------------------------}
+
+function  TVideoPlayback_FFmpeg.GetName: String;
+begin
+  result := 'FFmpeg_Video';
+end;
+
+function TVideoPlayback_FFmpeg.Init(): boolean;
+begin
+  Result := true;
+
+  if (fInitialized) then
+    Exit;
+  fInitialized := true;
+
+  FFmpegCore := TMediaCore_FFmpeg.GetInstance();
+
+  Reset();
+  av_register_all();
+  glGenTextures(1, PGLuint(@fFrameTex));
+end;
+
+function TVideoPlayback_FFmpeg.Finalize(): boolean;
+begin
+  Close();
+  glDeleteTextures(1, PGLuint(@fFrameTex));
+  Result := true;
+end;
+
+procedure TVideoPlayback_FFmpeg.Reset();
+begin
+  // close previously opened video
+  Close();
+
+  fOpened       := False;
+  fPaused       := False;
+  fTimeBase      := 0;
+  fTime          := 0;
+  fStream := nil;
+  fStreamIndex := -1;
+  fFrameTexValid := false;
+
+  fEOF := false;
+
+  // TODO: do we really want this by default?
+  fLoop := true;
+  fLoopTime := 0;
+  
+  fAspectCorrection := acoCrop;
+end;
+
+function TVideoPlayback_FFmpeg.Open(const aFileName : string): boolean; // true if succeed
+var
+  errnum: Integer;
+  AudioStreamIndex: integer;
+begin
+  Result := false;
+
+  Reset();
+
+  errnum := av_open_input_file(fFormatContext, PChar(aFileName), nil, 0, nil);
+  if (errnum <> 0) then
+  begin
+    Log.LogError('Failed to open file "'+aFileName+'" ('+FFmpegCore.GetErrorString(errnum)+')');
+    Exit;
+  end;
+
+  // update video info
+  if (av_find_stream_info(fFormatContext) < 0) then
+  begin
+    Log.LogError('No stream info found', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+  Log.LogInfo('VideoStreamIndex : ' + inttostr(fStreamIndex), 'TVideoPlayback_ffmpeg.Open');
+
+  // find video stream
+  FFmpegCore.FindStreamIDs(fFormatContext, fStreamIndex, AudioStreamIndex);
+  if (fStreamIndex < 0) then
+  begin
+    Log.LogError('No video stream found', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+
+  fStream := fFormatContext^.streams[fStreamIndex];
+  fCodecContext := fStream^.codec;
+
+  fCodec := avcodec_find_decoder(fCodecContext^.codec_id);
+  if (fCodec = nil) then
+  begin
+    Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+
+  // set debug options
+  fCodecContext^.debug_mv := 0;
+  fCodecContext^.debug := 0;
+
+  // detect bug-workarounds automatically
+  fCodecContext^.workaround_bugs := FF_BUG_AUTODETECT;
+  // error resilience strategy (careful/compliant/agressive/very_aggressive)
+  //fCodecContext^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+  // allow non spec compliant speedup tricks.
+  //fCodecContext^.flags2 := fCodecContext^.flags2 or CODEC_FLAG2_FAST;
+
+  // Note: avcodec_open() and avcodec_close() are not thread-safe and will
+  // fail if called concurrently by different threads.
+  FFmpegCore.LockAVCodec();
+  try
+    errnum := avcodec_open(fCodecContext, fCodec);
+  finally
+    FFmpegCore.UnlockAVCodec();
+  end;
+  if (errnum < 0) then
+  begin
+    Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+
+  // register custom callbacks for pts-determination
+  fCodecContext^.get_buffer := PtsGetBuffer;
+  fCodecContext^.release_buffer := PtsReleaseBuffer;
+
+  {$ifdef DebugDisplay}
+  DebugWriteln('Found a matching Codec: '+ fCodecContext^.Codec.Name + sLineBreak +
+    sLineBreak +
+    '  Width = '+inttostr(fCodecContext^.width) +
+    ', Height='+inttostr(fCodecContext^.height) + sLineBreak +
+    '  Aspect    : '+inttostr(fCodecContext^.sample_aspect_ratio.num) + '/' +
+                     inttostr(fCodecContext^.sample_aspect_ratio.den) + sLineBreak +
+    '  Framerate : '+inttostr(fCodecContext^.time_base.num) + '/' +
+                     inttostr(fCodecContext^.time_base.den));
+  {$endif}
+
+  // allocate space for decoded frame and rgb frame
+  fAVFrame := avcodec_alloc_frame();
+  fAVFrameRGB := avcodec_alloc_frame();
+  fFrameBuffer := av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG,
+      fCodecContext^.width, fCodecContext^.height));
+
+  if ((fAVFrame = nil) or (fAVFrameRGB = nil) or (fFrameBuffer = nil)) then
+  begin
+    Log.LogError('Failed to allocate buffers', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+
+  // TODO: pad data for OpenGL to GL_UNPACK_ALIGNMENT
+  // (otherwise video will be distorted if width/height is not a multiple of the alignment)
+  errnum := avpicture_fill(PAVPicture(fAVFrameRGB), fFrameBuffer, PIXEL_FMT_FFMPEG,
+      fCodecContext^.width, fCodecContext^.height);
+  if (errnum < 0) then
+  begin
+    Log.LogError('avpicture_fill failed: ' + FFmpegCore.GetErrorString(errnum), 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+
+  // calculate some information for video display
+  fAspect := av_q2d(fCodecContext^.sample_aspect_ratio);
+  if (fAspect = 0) then
+    fAspect := fCodecContext^.width /
+               fCodecContext^.height
+  else
+    fAspect := fAspect * fCodecContext^.width /
+                         fCodecContext^.height;
+
+  fTimeBase := 1/av_q2d(fStream^.r_frame_rate);
+
+  // hack to get reasonable timebase (for divx and others)
+  if (fTimeBase < 0.02) then // 0.02 <-> 50 fps
+  begin
+    fTimeBase := av_q2d(fStream^.r_frame_rate);
+    while (fTimeBase > 50) do
+      fTimeBase := fTimeBase/10;
+    fTimeBase := 1/fTimeBase;
+  end;
+
+  Log.LogInfo('VideoTimeBase: ' + floattostr(fTimeBase), 'TVideoPlayback_ffmpeg.Open');
+  Log.LogInfo('Framerate: '+inttostr(floor(1/fTimeBase))+'fps', 'TVideoPlayback_ffmpeg.Open');
+
+  {$IFDEF UseSWScale}
+  // if available get a SWScale-context -> faster than the deprecated img_convert().
+  // SWScale has accelerated support for PIX_FMT_RGB32/PIX_FMT_BGR24/PIX_FMT_BGR565/PIX_FMT_BGR555.
+  // Note: PIX_FMT_RGB32 is a BGR- and not an RGB-format (maybe a bug)!!!
+  // The BGR565-formats (GL_UNSIGNED_SHORT_5_6_5) is way too slow because of its
+  // bad OpenGL support. The BGR formats have MMX(2) implementations but no speed-up
+  // could be observed in comparison to the RGB versions.
+  fSwScaleContext := sws_getContext(
+      fCodecContext^.width, fCodecContext^.height,
+      fCodecContext^.pix_fmt,
+      fCodecContext^.width, fCodecContext^.height,
+      PIXEL_FMT_FFMPEG,
+      SWS_FAST_BILINEAR, nil, nil, nil);
+  if (fSwScaleContext = nil) then
+  begin
+    Log.LogError('Failed to get swscale context', 'TVideoPlayback_ffmpeg.Open');
+    Close();
+    Exit;
+  end;
+  {$ENDIF}
+
+  fTexWidth   := Round(Power(2, Ceil(Log2(fCodecContext^.width))));
+  fTexHeight  := Round(Power(2, Ceil(Log2(fCodecContext^.height))));
+
+  // we retrieve a texture just once with glTexImage2D and update it with glTexSubImage2D later.
+  // Benefits: glTexSubImage2D is faster and supports non-power-of-two widths/height.
+  glBindTexture(GL_TEXTURE_2D, fFrameTex);
+  glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+  glTexImage2D(GL_TEXTURE_2D, 0, 3, fTexWidth, fTexHeight, 0,
+      PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, nil);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+  fOpened := True;
+  Result := true;
+end;
+
+procedure TVideoPlayback_FFmpeg.Close;
+begin
+  if (fFrameBuffer <> nil) then
+    av_free(fFrameBuffer);
+  if (fAVFrameRGB <> nil) then
+    av_free(fAVFrameRGB);
+  if (fAVFrame <> nil) then
+    av_free(fAVFrame);
+
+  fAVFrame     := nil;
+  fAVFrameRGB  := nil;
+  fFrameBuffer := nil;
+    
+  if (fCodecContext <> nil) then
+  begin
+    // avcodec_close() is not thread-safe
+    FFmpegCore.LockAVCodec();
+    try
+      avcodec_close(fCodecContext);
+    finally
+      FFmpegCore.UnlockAVCodec();
+    end;
+  end;
+
+  if (fFormatContext <> nil) then
+    av_close_input_file(fFormatContext);
+
+  fCodecContext  := nil;
+  fFormatContext := nil;
+
+  fOpened := False;
+end;
+
+procedure TVideoPlayback_FFmpeg.SynchronizeTime(Frame: PAVFrame; var pts: double);
+var
+  FrameDelay: double;
+begin
+  if (pts <> 0) then
+  begin
+    // if we have pts, set video clock to it
+    fTime := pts;
+  end else
+  begin
+    // if we aren't given a pts, set it to the clock
+    pts := fTime;
+  end;
+  // update the video clock
+  FrameDelay := av_q2d(fCodecContext^.time_base);
+  // if we are repeating a frame, adjust clock accordingly
+  FrameDelay := FrameDelay + Frame^.repeat_pict * (FrameDelay * 0.5);
+  fTime := fTime + FrameDelay;
+end;
+
+{**
+ * Decode a new frame from the video stream.
+ * The decoded frame is stored in fAVFrame. fTime is updated to the new frame's
+ * time.
+ * @param pts will be updated to the presentation time of the decoded frame. 
+ * returns true if a frame could be decoded. False if an error or EOF occured.
+ *}
+function TVideoPlayback_FFmpeg.DecodeFrame(): boolean;
+var
+  FrameFinished: Integer;
+  VideoPktPts: int64;
+  pbIOCtx: PByteIOContext;
+  errnum: integer;
+  AVPacket: TAVPacket;
+  pts: double;
+begin
+  Result := false;
+  FrameFinished := 0;
+
+  if fEOF then
+    Exit;
+
+  // read packets until we have a finished frame (or there are no more packets)
+  while (FrameFinished = 0) do
+  begin
+    errnum := av_read_frame(fFormatContext, AVPacket);
+    if (errnum < 0) then
+    begin
+      // failed to read a frame, check reason
+
+      {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
+      pbIOCtx := fFormatContext^.pb;
+      {$ELSE}
+      pbIOCtx := @fFormatContext^.pb;
+      {$IFEND}
+
+      // check for end-of-file (EOF is not an error)
+      if (url_feof(pbIOCtx) <> 0) then
+      begin
+        fEOF := true;
+        Exit;
+      end;
+
+      // check for errors
+      if (url_ferror(pbIOCtx) <> 0) then
+        Exit;
+
+      // url_feof() does not detect an EOF for some mov-files (e.g. deluxe.mov)
+      // so we have to do it this way.
+      if ((fFormatContext^.file_size <> 0) and
+          (pbIOCtx^.pos >= fFormatContext^.file_size)) then
+      begin
+        fEOF := true;
+        Exit;
+      end;
+
+      // no error -> wait for user input
+{
+      SDL_Delay(100);  // initial version, left for documentation
+      continue;
+}
+
+      // Patch by Hawkear:
+      // Why should this function loop in an endless loop if there is an error?
+      // This runs in the main thread, so it halts the whole program
+      // Therefore, it is better to exit when an error occurs
+      Exit;
+
+    end;
+
+    // if we got a packet from the video stream, then decode it
+    if (AVPacket.stream_index = fStreamIndex) then
+    begin
+      // save pts to be stored in pFrame in first call of PtsGetBuffer()
+      VideoPktPts := AVPacket.pts;
+      fCodecContext^.opaque := @VideoPktPts;
+
+      // decode packet
+      avcodec_decode_video(fCodecContext, fAVFrame,
+          frameFinished, AVPacket.data, AVPacket.size);
+
+      // reset opaque data
+      fCodecContext^.opaque := nil;
+
+      // update pts
+      if (AVPacket.dts <> AV_NOPTS_VALUE) then
+      begin
+        pts := AVPacket.dts;
+      end
+      else if ((fAVFrame^.opaque <> nil) and
+               (Pint64(fAVFrame^.opaque)^ <> AV_NOPTS_VALUE)) then
+      begin
+        pts := Pint64(fAVFrame^.opaque)^;
+      end
+      else
+      begin
+        pts := 0;
+      end;
+      pts := pts * av_q2d(fStream^.time_base);
+
+      // synchronize time on each complete frame
+      if (frameFinished <> 0) then
+        SynchronizeTime(fAVFrame, pts);
+    end;
+
+    // free the packet from av_read_frame
+    av_free_packet( @AVPacket );
+  end;
+
+  Result := true;
+end;
+
+procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended);
+var
+  errnum: Integer;
+  NewTime: Extended;
+  TimeDifference: Extended;
+  DropFrameCount: Integer;
+  i: Integer;
+  Success: boolean;
+const
+  FRAME_DROPCOUNT = 3;
+begin
+  if not fOpened then
+    Exit;
+
+  if fPaused then
+    Exit;
+
+  // requested stream position (relative to the last loop's start)
+  NewTime := Time - fLoopTime;
+
+  // check if current texture still contains the active frame
+  if (fFrameTexValid) then
+  begin
+    // time since the last frame was returned
+    TimeDifference := NewTime - fTime;
+
+    {$IFDEF DebugDisplay}
+    DebugWriteln('Time:      '+inttostr(floor(Time*1000)) + sLineBreak +
+                 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak +
+                 'TimeBase:  '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
+                 'TimeDiff:  '+inttostr(floor(TimeDifference*1000)));
+    {$endif}
+
+    // check if last time is more than one frame in the past 
+    if (TimeDifference < fTimeBase) then
+    begin
+      {$ifdef DebugFrames}
+      // frame delay debug display
+      GoldenRec.Spawn(200,15,1,16,0,-1,ColoredStar,$00ff00);
+      {$endif}
+
+      {$IFDEF DebugDisplay}
+      DebugWriteln('not getting new frame' + sLineBreak +
+          'Time:      '+inttostr(floor(Time*1000)) + sLineBreak +
+          'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak +
+          'TimeBase:  '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
+          'TimeDiff:  '+inttostr(floor(TimeDifference*1000)));
+      {$endif}
+
+      // we do not need a new frame now
+      Exit;
+    end;
+  end;
+
+  {$IFDEF VideoBenchmark}
+  Log.BenchmarkStart(15);
+  {$ENDIF}
+  
+  // fetch new frame (updates fTime)
+  Success := DecodeFrame();
+  TimeDifference := NewTime - fTime;
+
+  // check if we have to skip frames
+  if (TimeDifference >= FRAME_DROPCOUNT*fTimeBase) then
+  begin
+    {$IFDEF DebugFrames}
+    //frame drop debug display
+    GoldenRec.Spawn(200,55,1,16,0,-1,ColoredStar,$ff0000);
+    {$ENDIF}
+    {$IFDEF DebugDisplay}
+    DebugWriteln('skipping frames' + sLineBreak +
+        'TimeBase:  '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
+        'TimeDiff:  '+inttostr(floor(TimeDifference*1000)));
+    {$endif}
+
+    // update video-time
+    DropFrameCount := Trunc(TimeDifference / fTimeBase);
+    fTime := fTime + DropFrameCount*fTimeBase;
+
+    // skip half of the frames, this is much smoother than to skip all at once
+    for i := 1 to DropFrameCount (*div 2*) do
+      Success := DecodeFrame();
+  end;
+
+  // check if we got an EOF or error 
+  if (not Success) then
+  begin
+    if fLoop then
+    begin
+      // we have to loop, so rewind
+      SetPosition(0);
+      // record the start-time of the current loop, so we can
+      // determine the position in the stream (fTime-fLoopTime) later.
+      fLoopTime := Time;
+    end;
+    Exit;
+  end;
+
+  // TODO: support for pan&scan
+  //if (fAVFrame.pan_scan <> nil) then
+  //begin
+  //  Writeln(Format('PanScan: %d/%d', [fAVFrame.pan_scan.width, fAVFrame.pan_scan.height]));
+  //end;
+
+  // otherwise we convert the pixeldata from YUV to RGB
+  {$IFDEF UseSWScale}
+  errnum := sws_scale(fSwScaleContext, @(fAVFrame.data), @(fAVFrame.linesize),
+          0, fCodecContext^.Height,
+          @(fAVFrameRGB.data), @(fAVFrameRGB.linesize));
+  {$ELSE}
+  errnum := img_convert(PAVPicture(fAVFrameRGB), PIXEL_FMT_FFMPEG,
+            PAVPicture(fAVFrame), fCodecContext^.pix_fmt,
+            fCodecContext^.width, fCodecContext^.height);
+  {$ENDIF}
+  
+  if (errnum < 0) then
+  begin
+    Log.LogError('Image conversion failed', 'TVideoPlayback_ffmpeg.GetFrame');
+    Exit;
+  end;
+
+  {$IFDEF VideoBenchmark}
+  Log.BenchmarkEnd(15);
+  Log.BenchmarkStart(16);
+  {$ENDIF}
+
+  // TODO: data is not padded, so we will need to tell OpenGL.
+  //   Or should we add padding with avpicture_fill? (check which one is faster)
+  //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+  glBindTexture(GL_TEXTURE_2D, fFrameTex);
+  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
+      fCodecContext^.width, fCodecContext^.height,
+      PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, fAVFrameRGB^.data[0]);
+
+  if (not fFrameTexValid) then
+    fFrameTexValid := true;
+
+  {$ifdef DebugFrames}
+  //frame decode debug display
+  GoldenRec.Spawn(200, 35, 1, 16, 0, -1, ColoredStar, $ffff00);
+  {$endif}
+
+  {$IFDEF VideoBenchmark}
+  Log.BenchmarkEnd(16);
+  Log.LogBenchmark('FFmpeg', 15);
+  Log.LogBenchmark('Texture', 16);
+  {$ENDIF}
+end;
+
+procedure TVideoPlayback_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords);
+var
+  ScreenAspect: double;  // aspect of screen resolution
+  ScaledVideoWidth, ScaledVideoHeight: double;
+begin
+  // Three aspects to take into account:
+  //  1. Screen/display resolution (e.g. 1920x1080 -> 16:9)
+  //  2. Render aspect (fixed to 800x600 -> 4:3)
+  //  3. Movie aspect (video frame aspect stored in fAspect)
+  ScreenAspect := ScreenW / ScreenH;
+
+  case fAspectCorrection of
+    acoStretch: begin
+      ScaledVideoWidth  := RenderW;
+      ScaledVideoHeight := RenderH;
+    end;
+    acoCrop: begin
+      if (ScreenAspect >= fAspect) then
+      begin
+        ScaledVideoWidth  := RenderW;
+        ScaledVideoHeight := RenderH * ScreenAspect/fAspect;
+      end
+      else
+      begin
+        ScaledVideoHeight := RenderH;
+        ScaledVideoWidth  := RenderW * fAspect/ScreenAspect;
+      end;
+    end;
+    acoLetterBox: begin
+      ScaledVideoWidth  := RenderW;
+      ScaledVideoHeight := RenderH * ScreenAspect/fAspect;
+    end
+    else
+      raise Exception.Create('Unhandled aspect correction!');
+  end;
+
+  // center video
+  ScreenRect.Left  := (RenderW - ScaledVideoWidth) / 2;
+  ScreenRect.Right := ScreenRect.Left + ScaledVideoWidth;
+  ScreenRect.Upper := (RenderH - ScaledVideoHeight) / 2;
+  ScreenRect.Lower := ScreenRect.Upper + ScaledVideoHeight;
+
+  // texture contains right/lower (power-of-2) padding.
+  // Determine the texture coords of the video frame.
+  TexRect.Left  := 0;
+  TexRect.Right := fCodecContext^.width  / fTexWidth;
+  TexRect.Upper := 0;
+  TexRect.Lower := fCodecContext^.height / fTexHeight;
+end;
+
+procedure TVideoPlayback_FFmpeg.DrawGL(Screen: integer);
+var
+  ScreenRect: TRectCoords;
+  TexRect: TRectCoords;
+begin
+  // have a nice black background to draw on
+  // (even if there were errors opening the vid)
+  // TODO: Philipp: IMO TVideoPlayback should not clear the screen at
+  //       all, because clearing is already done by the background class
+  //       at this moment.
+  if (Screen = 1) then
+  begin
+    // It is important that we just clear once before we start
+    // drawing the first screen otherwise the first screen
+    // would be cleared by the drawgl called when the second
+    // screen is drawn
+    glClearColor(0, 0, 0, 0);
+    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+  end;
+
+  // exit if there's nothing to draw
+  if (not fOpened) then
+    Exit;
+
+  {$IFDEF VideoBenchmark}
+  Log.BenchmarkStart(15);
+  {$ENDIF}
+
+  // get texture and screen positions
+  GetVideoRect(ScreenRect, TexRect);
+
+  // we could use blending for brightness control, but do we need this?
+  glDisable(GL_BLEND);
+
+  glEnable(GL_TEXTURE_2D);
+  glBindTexture(GL_TEXTURE_2D, fFrameTex);
+  glColor3f(1, 1, 1);
+  glBegin(GL_QUADS);
+    // upper-left coord
+    glTexCoord2f(TexRect.Left, TexRect.Upper);
+    glVertex2f(ScreenRect.Left, ScreenRect.Upper);
+    // lower-left coord
+    glTexCoord2f(TexRect.Left, TexRect.Lower);
+    glVertex2f(ScreenRect.Left, ScreenRect.Lower);
+    // lower-right coord
+    glTexCoord2f(TexRect.Right, TexRect.Lower);
+    glVertex2f(ScreenRect.Right, ScreenRect.Lower);
+    // upper-right coord
+    glTexCoord2f(TexRect.Right, TexRect.Upper);
+    glVertex2f(ScreenRect.Right, ScreenRect.Upper);
+  glEnd;
+  glDisable(GL_TEXTURE_2D);
+
+  {$IFDEF VideoBenchmark}
+  Log.BenchmarkEnd(15);
+  Log.LogBenchmark('DrawGL', 15);
+  {$ENDIF}
+
+  {$IF Defined(Info) or Defined(DebugFrames)}
+  ShowDebugInfo();
+  {$IFEND}
+end;
+
+procedure TVideoPlayback_FFmpeg.ShowDebugInfo();
+begin
+  {$IFDEF Info}
+  if (fTime+fTimeBase < 0) then
+  begin
+    glColor4f(0.7, 1, 0.3, 1);
+    SetFontStyle (1);
+    SetFontItalic(False);
+    SetFontSize(27);
+    SetFontPos (300, 0);
+    glPrint('Delay due to negative VideoGap');
+    glColor4f(1, 1, 1, 1);
+  end;
+  {$ENDIF}
+
+  {$IFDEF DebugFrames}
+    glColor4f(0, 0, 0, 0.2);
+    glbegin(GL_QUADS);
+      glVertex2f(0, 0);
+      glVertex2f(0, 70);
+      glVertex2f(250, 70);
+      glVertex2f(250, 0);
+    glEnd;
+
+    glColor4f(1, 1, 1, 1);
+    SetFontStyle (1);
+    SetFontItalic(False);
+    SetFontSize(27);
+    SetFontPos (5, 0);
+    glPrint('delaying frame');
+    SetFontPos (5, 20);
+    glPrint('fetching frame');
+    SetFontPos (5, 40);
+    glPrint('dropping frame');
+  {$ENDIF}
+end;
+
+procedure TVideoPlayback_FFmpeg.Play;
+begin
+end;
+
+procedure TVideoPlayback_FFmpeg.Pause;
+begin
+  fPaused := not fPaused;
+end;
+
+procedure TVideoPlayback_FFmpeg.Stop;
+begin
+end;
+
+{**
+ * Sets the stream's position.
+ * The stream is set to the first keyframe with timestamp <= Time.
+ * Note that fTime is set to Time no matter if the actual position seeked to is
+ * at Time or the time of a preceding keyframe. fTime will be updated to the
+ * actual frame time when GetFrame() is called the next time.
+ * @param Time new position in seconds
+ *}
+procedure TVideoPlayback_FFmpeg.SetPosition(Time: real);
+var
+  SeekFlags: integer;
+begin
+  if not fOpened then
+    Exit;
+
+  if (Time < 0) then
+    Time := 0;
+
+  // TODO: handle fLoop-times
+  //Time := Time mod VideoDuration;
+
+  // Do not use the AVSEEK_FLAG_ANY here. It will seek to any frame, even
+  // non keyframes (P-/B-frames). It will produce corrupted video frames as
+  // FFmpeg does not use the information of the preceding I-frame.
+  // The picture might be gray or green until the next keyframe occurs.
+  // Instead seek the first keyframe smaller than the requested time
+  // (AVSEEK_FLAG_BACKWARD). As this can be some seconds earlier than the
+  // requested time, let the sync in GetFrame() do its job.
+  SeekFlags := AVSEEK_FLAG_BACKWARD;
+
+  fTime := Time;
+  fEOF := false;
+  fFrameTexValid := false; 
+
+  if (av_seek_frame(fFormatContext, fStreamIndex, Floor(Time/fTimeBase), SeekFlags) < 0) then
+  begin
+    Log.LogError('av_seek_frame() failed', 'TVideoPlayback_ffmpeg.SetPosition');
+    Exit;
+  end;
+
+  avcodec_flush_buffers(fCodecContext);
+end;
+
+function  TVideoPlayback_FFmpeg.GetPosition: real;
+begin
+  Result := fTime;
+end;
+
+initialization
+  MediaManager.Add(TVideoPlayback_FFmpeg.Create);
+
+end.
diff --git a/cmake/src/media/UVisualizer.pas b/cmake/src/media/UVisualizer.pas
new file mode 100644
index 00000000..37e0268a
--- /dev/null
+++ b/cmake/src/media/UVisualizer.pas
@@ -0,0 +1,551 @@
+{* 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 UVisualizer;
+
+(* TODO:
+ *   - fix video/visualizer switching
+ *   - use GL_EXT_framebuffer_object for rendering to a separate framebuffer,
+ *     this will prevent plugins from messing up our render-context
+ *     (-> no stack corruption anymore, no need for Save/RestoreOpenGLState()).
+ *   - create a generic (C-compatible) interface for visualization plugins
+ *   - create a visualization plugin manager
+ *   - write a plugin for projectM in C/C++ (so we need no wrapper anymore)
+ *)
+
+{* Note:
+ * It would be easier to create a seperate Render-Context (RC) for projectM
+ * and switch to it when necessary. This can be achieved by pbuffers
+ * (slow and platform specific) or the OpenGL FramebufferObject (FBO) extension
+ * (fast and plattform-independent but not supported by older graphic-cards/drivers).
+ *
+ * See http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt
+ *
+ * To support as many cards as possible we will stick to the current dirty
+ * solution for now even if it is a pain to save/restore projectM's state due
+ * to bugs etc.
+ *
+ * This also restricts us to projectM. As other plug-ins might have different
+ * needs and bugs concerning the OpenGL state, USDX's state would probably be
+ * corrupted after the plug-in finshed drawing.
+ *}
+
+interface
+
+{$IFDEF FPC}
+  {$MODE DELPHI}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+  SDL,
+  UGraphicClasses,
+  textgl,
+  math,
+  gl,
+  SysUtils,
+  UIni,
+  projectM,
+  UMusic;
+
+implementation
+
+uses
+  UGraphic,
+  UMain,
+  UConfig,
+  ULog;
+
+{$IF PROJECTM_VERSION < 1000000} // < 1.0
+// Initialization data used on projectM 0.9x creation.
+// Since projectM 1.0 this data is passed via the config-file.
+const
+  meshX = 32;
+  meshY = 24;
+  fps   = 30;
+  textureSize = 512;
+{$IFEND}
+
+type
+  TGLMatrix = array[0..3, 0..3] of GLdouble;
+  TGLMatrixStack = array of TGLMatrix;
+
+type
+  TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization )
+    private
+      pm: TProjectM;
+      ProjectMPath : string;
+      Initialized: boolean;
+
+      VisualizerStarted: boolean;
+      VisualizerPaused: boolean;
+
+      VisualTex: GLuint;
+      PCMData: TPCMData;
+      RndPCMcount: integer;
+
+      ModelviewMatrixStack: TGLMatrixStack;
+      ProjectionMatrixStack: TGLMatrixStack;
+      TextureMatrixStack:  TGLMatrixStack;
+
+      procedure VisualizerStart;
+      procedure VisualizerStop;
+
+      procedure VisualizerTogglePause;
+
+      function  GetRandomPCMData(var Data: TPCMData): Cardinal;
+
+      function GetMatrixStackDepth(MatrixMode: GLenum): GLint;
+      procedure SaveMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack);
+      procedure RestoreMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack);
+      procedure SaveOpenGLState();
+      procedure RestoreOpenGLState();
+
+    public
+      function GetName: String;
+
+      function Init(): boolean;
+      function Finalize(): boolean;
+
+      function Open(const aFileName : string): boolean; // true if succeed
+      procedure Close;
+
+      procedure Play;
+      procedure Pause;
+      procedure Stop;
+
+      procedure SetPosition(Time: real);
+      function GetPosition: real;
+
+      procedure GetFrame(Time: Extended);
+      procedure DrawGL(Screen: integer);
+  end;
+
+
+function  TVideoPlayback_ProjectM.GetName: String;
+begin
+  Result := 'ProjectM';
+end;
+
+function TVideoPlayback_ProjectM.Init(): boolean;
+begin
+  Result := true;
+
+  if (Initialized) then
+    Exit;
+  Initialized := true;
+
+  RndPCMcount := 0;
+
+  ProjectMPath := ProjectM_DataDir + PathDelim;
+
+  VisualizerStarted := False;
+  VisualizerPaused  := False;
+
+  {$IFDEF UseTexture}
+  glGenTextures(1, PglUint(@VisualTex));
+  glBindTexture(GL_TEXTURE_2D, VisualTex);
+
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  {$ENDIF}
+end;
+
+function TVideoPlayback_ProjectM.Finalize(): boolean;
+begin
+  VisualizerStop();
+  {$IFDEF UseTexture}
+  glDeleteTextures(1, PglUint(@VisualTex));
+  {$ENDIF}
+  Result := true;
+end;
+
+function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed
+begin
+  Result := false;
+end;
+
+procedure TVideoPlayback_ProjectM.Close;
+begin
+  VisualizerStop();
+end;
+
+procedure TVideoPlayback_ProjectM.Play;
+begin
+  VisualizerStart();
+end;
+
+procedure TVideoPlayback_ProjectM.Pause;
+begin
+  VisualizerTogglePause();
+end;
+
+procedure TVideoPlayback_ProjectM.Stop;
+begin
+  VisualizerStop();
+end;
+
+procedure TVideoPlayback_ProjectM.SetPosition(Time: real);
+begin
+  if assigned(pm) then
+    pm.RandomPreset();
+end;
+
+function  TVideoPlayback_ProjectM.GetPosition: real;
+begin
+  Result := 0;
+end;
+
+{**
+ * Returns the stack depth of the given OpenGL matrix mode stack.
+ *}
+function TVideoPlayback_ProjectM.GetMatrixStackDepth(MatrixMode: GLenum): GLint;
+begin
+  // get number of matrices on stack
+  case (MatrixMode) of
+    GL_PROJECTION:
+      glGetIntegerv(GL_PROJECTION_STACK_DEPTH, @Result);
+    GL_MODELVIEW:
+      glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, @Result);
+    GL_TEXTURE:
+      glGetIntegerv(GL_TEXTURE_STACK_DEPTH, @Result);
+  end;
+end;
+
+{**
+ * Saves the current matrix stack using MatrixMode
+ * (one of GL_PROJECTION/GL_TEXTURE/GL_MODELVIEW)
+ *
+ * Use this function instead of just saving the current matrix with glPushMatrix().
+ * OpenGL specifies the depth of the GL_PROJECTION and GL_TEXTURE stacks to be
+ * at least 2 but projectM already uses 2 stack-entries so overflows might be
+ * possible on older hardware.
+ * In contrast to this the GL_MODELVIEW stack-size is at least 32, but this
+ * function should be used for the modelview stack too. We cannot rely on a
+ * proper stack management of the underlying visualizer (projectM).
+ * For example in the projectM versions 1.0 - 1.01 the modelview- and
+ * projection-matrices were popped without being pushed first.
+ *
+ * By saving the whole stack we are on the safe side, so a nasty bug in the
+ * visualizer does not corrupt USDX.
+ *}
+procedure TVideoPlayback_ProjectM.SaveMatrixStack(MatrixMode: GLenum;
+                var MatrixStack: TGLMatrixStack);
+var
+  I: integer;
+  StackDepth: GLint;
+begin
+  glMatrixMode(MatrixMode);
+
+  StackDepth := GetMatrixStackDepth(MatrixMode);
+  SetLength(MatrixStack, StackDepth);
+
+  // save current matrix stack
+  for I := StackDepth-1 downto 0 do
+  begin
+    // save current matrix
+    case (MatrixMode) of
+      GL_PROJECTION:
+        glGetDoublev(GL_PROJECTION_MATRIX, @MatrixStack[I]);
+      GL_MODELVIEW:
+        glGetDoublev(GL_MODELVIEW_MATRIX, @MatrixStack[I]);
+      GL_TEXTURE:
+        glGetDoublev(GL_TEXTURE_MATRIX, @MatrixStack[I]);
+    end;
+
+    // remove matrix from stack
+    if (I > 0) then
+      glPopMatrix();
+  end;
+
+  // reset default (first) matrix
+  glLoadIdentity();
+end;
+
+{**
+ * Restores the OpenGL matrix stack stored with SaveMatrixStack.
+ *}
+procedure TVideoPlayback_ProjectM.RestoreMatrixStack(MatrixMode: GLenum;
+                var MatrixStack: TGLMatrixStack);
+var
+  I: integer;
+  StackDepth: GLint;
+begin
+  glMatrixMode(MatrixMode);
+
+  StackDepth := GetMatrixStackDepth(MatrixMode);
+  // remove all (except the first) matrices from current stack
+  for I := 1 to StackDepth-1 do
+    glPopMatrix();
+
+  // rebuild stack
+  for I := 0 to High(MatrixStack) do
+  begin
+    glLoadMatrixd(@MatrixStack[I]);
+    if (I < High(MatrixStack)) then
+      glPushMatrix();
+  end;
+
+  // clean stored stack
+  SetLength(MatrixStack, 0);
+end;
+
+{**
+ * Saves the current OpenGL state.
+ * This is necessary to prevent projectM from corrupting USDX's current
+ * OpenGL state.
+ *
+ * The following steps are performed:
+ *   - All attributes are pushed to the attribute-stack
+ *   - Projection-/Texture-matrices are saved
+ *   - Modelview-matrix is pushed to the Modelview-stack
+ *   - the OpenGL error-state (glGetError) is cleared
+ *}
+procedure TVideoPlayback_ProjectM.SaveOpenGLState();
+begin
+  // save all OpenGL state-machine attributes
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+  glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
+
+  SaveMatrixStack(GL_PROJECTION, ProjectionMatrixStack);
+  SaveMatrixStack(GL_MODELVIEW, ModelviewMatrixStack);
+  SaveMatrixStack(GL_TEXTURE, TextureMatrixStack);
+
+  glMatrixMode(GL_MODELVIEW);
+
+  // reset OpenGL error-state
+  glGetError();
+end;
+
+{**
+ * Restores the OpenGL state saved by SaveOpenGLState()
+ * and resets the error-state.
+ *}
+procedure TVideoPlayback_ProjectM.RestoreOpenGLState();
+begin
+  // reset OpenGL error-state
+  glGetError();
+
+  // restore matrix stacks
+  RestoreMatrixStack(GL_PROJECTION, ProjectionMatrixStack);
+  RestoreMatrixStack(GL_MODELVIEW, ModelviewMatrixStack);
+  RestoreMatrixStack(GL_TEXTURE, TextureMatrixStack);
+
+  // restore all OpenGL state-machine attributes
+  // (also restores the matrix mode)
+  glPopClientAttrib();
+  glPopAttrib();
+end;
+
+procedure TVideoPlayback_ProjectM.VisualizerStart;
+begin
+  if VisualizerStarted then
+    Exit;
+
+  // the OpenGL state must be saved before TProjectM.Create is called
+  SaveOpenGLState();
+  try
+
+    try
+      {$IF PROJECTM_VERSION >= 1000000} // >= 1.0
+      pm := TProjectM.Create(ProjectMPath + 'config.inp');
+      {$ELSE}
+      pm := TProjectM.Create(
+        meshX, meshY, fps, textureSize, ScreenW, ScreenH,
+        ProjectMPath + 'presets', ProjectMPath + 'fonts');
+      {$IFEND}
+    except on E: Exception do
+      begin
+        // Create() might fail if the config-file is not found
+        Log.LogError('TProjectM.Create: ' + E.Message, 'TVideoPlayback_ProjectM.VisualizerStart');
+        Exit;
+      end;
+    end;
+
+    // initialize OpenGL
+    pm.ResetGL(ScreenW, ScreenH);
+    // skip projectM default-preset
+    pm.RandomPreset();
+    // projectM >= 1.0 uses the OpenGL FramebufferObject (FBO) extension.
+    // Unfortunately it does NOT reset the framebuffer-context after
+    // TProjectM.Create. Either glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) for
+    // a manual reset or TProjectM.RenderFrame() must be called.
+    // We use the latter so we do not need to load the FBO extension in USDX.
+    pm.RenderFrame();
+
+    VisualizerPaused := false;
+    VisualizerStarted := true;
+  finally
+    RestoreOpenGLState();
+  end;
+end;
+
+procedure TVideoPlayback_ProjectM.VisualizerStop;
+begin
+  if VisualizerStarted then
+  begin
+    VisualizerPaused := false;
+    VisualizerStarted := false;
+    FreeAndNil(pm);
+  end;
+end;
+
+procedure TVideoPlayback_ProjectM.VisualizerTogglePause;
+begin
+  VisualizerPaused := not VisualizerPaused;
+end;
+
+procedure TVideoPlayback_ProjectM.GetFrame(Time: Extended);
+var
+  nSamples: cardinal;
+begin
+  if not VisualizerStarted then
+    Exit;
+
+  if VisualizerPaused then
+    Exit;
+
+  // get audio data
+  nSamples := AudioPlayback.GetPCMData(PcmData);
+
+  // generate some data if non is available
+  if (nSamples = 0) then
+    nSamples := GetRandomPCMData(PcmData);
+
+  // send audio-data to projectM
+  if (nSamples > 0) then
+    pm.AddPCM16Data(PSmallInt(@PcmData), nSamples);
+
+  // store OpenGL state (might be messed up otherwise)
+  SaveOpenGLState();
+  try
+    // setup projectM's OpenGL state
+    pm.ResetGL(ScreenW, ScreenH);
+
+    // let projectM render a frame
+    pm.RenderFrame();
+
+    {$IFDEF UseTexture}
+    glBindTexture(GL_TEXTURE_2D, VisualTex);
+    glFlush();
+    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, VisualWidth, VisualHeight, 0);
+    {$ENDIF}
+  finally
+    // restore USDX OpenGL state
+    RestoreOpenGLState();
+  end;
+
+  // discard projectM's depth buffer information (avoid overlay)
+  glClear(GL_DEPTH_BUFFER_BIT);
+end;
+
+{**
+ * Draws the current frame to screen.
+ * TODO: this is not used yet. Data is directly drawn on GetFrame().
+ *}
+procedure TVideoPlayback_ProjectM.DrawGL(Screen: integer);
+begin
+  {$IFDEF UseTexture}
+  // have a nice black background to draw on
+  if (Screen = 1) then
+  begin
+    glClearColor(0, 0, 0, 0);
+    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+  end;
+
+  // exit if there's nothing to draw
+  if not VisualizerStarted then
+    Exit;
+
+  // setup display
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  // Use count of screens instead of 1 for the right corner
+  // otherwise we would draw the visualization streched over both screens
+  // another point is that we draw over the at this time drawn first
+  // screen, if Screen = 2
+  gluOrtho2D(0, Screens, 0, 1);
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  glLoadIdentity();
+
+  glEnable(GL_BLEND);
+  glEnable(GL_TEXTURE_2D);
+  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+  glBindTexture(GL_TEXTURE_2D, VisualTex);
+  glColor4f(1, 1, 1, 1);
+
+  // draw projectM frame
+  // Screen is 1 to 2. So current screen is from (Screen - 1) to (Screen)
+  glBegin(GL_QUADS);
+    glTexCoord2f(0, 0); glVertex2f((Screen - 1), 0);
+    glTexCoord2f(1, 0); glVertex2f(Screen, 0);
+    glTexCoord2f(1, 1); glVertex2f(Screen, 1);
+    glTexCoord2f(0, 1); glVertex2f((Screen - 1), 1);
+  glEnd();
+
+  glDisable(GL_TEXTURE_2D);
+  glDisable(GL_BLEND);
+
+  // restore state
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+  {$ENDIF}
+end;
+
+{**
+ * Produces random "sound"-data in case no audio-data is available.
+ * Otherwise the visualization will look rather boring.
+ *}
+function  TVideoPlayback_ProjectM.GetRandomPCMData(var Data: TPCMData): Cardinal;
+var
+  i: integer;
+begin
+  // Produce some fake PCM data
+  if (RndPCMcount mod 500 = 0) then
+  begin
+    FillChar(Data, SizeOf(TPCMData), 0);
+  end
+  else
+  begin
+    for i := 0 to 511 do
+    begin
+      Data[i][0] := Random(High(Word)+1);
+      Data[i][1] := Random(High(Word)+1);
+    end;
+  end;
+  Inc(RndPCMcount);
+  Result := 512;
+end;
+
+
+initialization
+  MediaManager.Add(TVideoPlayback_ProjectM.Create);
+
+end.
-- 
cgit v1.2.3