aboutsummaryrefslogtreecommitdiffstats
path: root/src/media
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/media/UAudioConverter.pas483
-rw-r--r--src/media/UAudioCore_Bass.pas160
-rw-r--r--src/media/UAudioCore_Portaudio.pas281
-rw-r--r--src/media/UAudioDecoder_Bass.pas278
-rw-r--r--src/media/UAudioDecoder_FFmpeg.pas1141
-rw-r--r--src/media/UAudioInput_Bass.pas510
-rw-r--r--src/media/UAudioInput_Portaudio.pas495
-rw-r--r--src/media/UAudioPlaybackBase.pas318
-rw-r--r--src/media/UAudioPlayback_Bass.pas758
-rw-r--r--src/media/UAudioPlayback_Portaudio.pas385
-rw-r--r--src/media/UAudioPlayback_SDL.pas182
-rw-r--r--src/media/UAudioPlayback_SoftMixer.pas1154
-rw-r--r--src/media/UMediaCore_FFmpeg.pas550
-rw-r--r--src/media/UMediaCore_SDL.pas63
-rw-r--r--src/media/UMedia_dummy.pas269
-rw-r--r--src/media/UVideo.pas966
-rw-r--r--src/media/UVisualizer.pas552
17 files changed, 0 insertions, 8545 deletions
diff --git a/src/media/UAudioConverter.pas b/src/media/UAudioConverter.pas
deleted file mode 100644
index 657b80dd..00000000
--- a/src/media/UAudioConverter.pas
+++ /dev/null
@@ -1,483 +0,0 @@
-{* 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/src/media/UAudioCore_Bass.pas b/src/media/UAudioCore_Bass.pas
deleted file mode 100644
index 197f9760..00000000
--- a/src/media/UAudioCore_Bass.pas
+++ /dev/null
@@ -1,160 +0,0 @@
-{* 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 CheckVersion(): boolean;
- 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;
-
-const
- // TODO: 2.4.2 is not ABI compatible with older versions
- // as (BASS_RECORDINFO.driver was removed)
- //BASS_MIN_REQUIRED_VERSION = $02040201;
- BASS_MIN_REQUIRED_VERSION = $02000000;
-
-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.CheckVersion(): boolean;
-begin
- Result := BASS_GetVersion() >= BASS_MIN_REQUIRED_VERSION;
-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/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas
deleted file mode 100644
index 25ceae3c..00000000
--- a/src/media/UAudioCore_Portaudio.pas
+++ /dev/null
@@ -1,281 +0,0 @@
-{* 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/src/media/UAudioDecoder_Bass.pas b/src/media/UAudioDecoder_Bass.pas
deleted file mode 100644
index d6d2425a..00000000
--- a/src/media/UAudioDecoder_Bass.pas
+++ /dev/null
@@ -1,278 +0,0 @@
-{* 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,
- bass,
- UMain,
- UMusic,
- UAudioCore_Bass,
- ULog,
- UPath;
-
-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: IPath): 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
- Result := false;
- BassCore := TAudioCore_Bass.GetInstance();
- if not BassCore.CheckVersion then
- Exit;
- Result := true;
-end;
-
-function TAudioDecoder_Bass.FinalizeDecoder(): boolean;
-begin
- Result := true;
-end;
-
-function TAudioDecoder_Bass.Open(const Filename: IPath): 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.
-
- {$IFDEF MSWINDOWS}
- // Windows: Use UTF-16 version
- Stream := BASS_StreamCreateFile(False, PWideChar(Filename.ToWide), 0, 0, BASS_STREAM_DECODE or BASS_UNICODE);
- {$ELSE}
- // Mac OS X: Use UTF8/ANSI version
- Stream := BASS_StreamCreateFile(False, PAnsiChar(Filename.ToNative), 0, 0, BASS_STREAM_DECODE);
- {$ENDIF}
- 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 := Filename.GetExtension.ToUTF8;
- // 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/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas
deleted file mode 100644
index d079afdc..00000000
--- a/src/media/UAudioDecoder_FFmpeg.pas
+++ /dev/null
@@ -1,1141 +0,0 @@
-{* 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
- SDL, // SDL redefines some base types -> include before SysUtils to ignore them
- Classes,
- Math,
- SysUtils,
- avcodec,
- avformat,
- avutil,
- avio,
- mathematics, // used for av_rescale_q
- rational,
- UMusic,
- UIni,
- UMain,
- UMediaCore_FFmpeg,
- ULog,
- UCommon,
- UConfig,
- UPath;
-
-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: IPath;
-
- 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: IPath): 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: IPath): 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: IPath): boolean;
-var
- SampleFormat: TAudioSampleFormat;
- AVResult: integer;
-begin
- Result := false;
-
- Close();
- Reset();
-
- if (not Filename.IsFile) then
- begin
- Log.LogError('Audio-file does not exist: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
- Exit;
- end;
-
- Self.Filename := Filename;
-
- // use custom 'ufile' protocol for UTF-8 support
- if (av_open_input_file(FormatCtx, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil) <> 0) then
- begin
- Log.LogError('av_open_input_file failed: "' + Filename.ToNative + '"', '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.ToNative + '"', '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.ToNative), 0);
- {$ENDIF}
-
- AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx);
- if (AudioStreamIndex < 0) then
- begin
- Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', '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;
- if CodecCtx^.channels > 255 then
- Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open');
- FormatInfo := TAudioFormatInfo.Create(
- byte(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
- // there is a type size mismatch warnign because start_time and duration are cint64.
- // So, in principle there could be an overflow when doing the sum.
- 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: IPath): 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/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas
deleted file mode 100644
index 9d4417f1..00000000
--- a/src/media/UAudioInput_Bass.pas
+++ /dev/null
@@ -1,510 +0,0 @@
-{* 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();
- if not BassCore.CheckVersion then
- begin
- Result := false;
- Exit;
- end;
- Result := EnumDevices();
-end;
-
-function TAudioInput_Bass.FinalizeRecord(): boolean;
-begin
- CaptureStop;
- Result := inherited FinalizeRecord;
-end;
-
-
-initialization
- MediaManager.Add(TAudioInput_Bass.Create);
-
-end.
diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas
deleted file mode 100644
index 31d2882b..00000000
--- a/src/media/UAudioInput_Portaudio.pas
+++ /dev/null
@@ -1,495 +0,0 @@
-{* 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/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas
deleted file mode 100644
index 228a438f..00000000
--- a/src/media/UAudioPlaybackBase.pas
+++ /dev/null
@@ -1,318 +0,0 @@
-{* 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,
- UPath;
-
-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: IPath): TAudioPlaybackStream;
- function OpenDecodeStream(const Filename: IPath): TAudioDecodeStream;
- public
- function GetName: string; virtual; abstract;
-
- function Open(const Filename: IPath): boolean; // true if succeed
- procedure Close;
-
- procedure Play;
- procedure Pause;
- procedure Stop;
- procedure FadeIn(Time: real; TargetVolume: single);
-
- procedure SetSyncSource(SyncSource: ISyncSource);
-
- 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: IPath): 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: IPath): 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: IPath): 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.ToNative + '"', '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: IPath): 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.ToNative + '"', '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: ISyncSource);
-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: IPath): 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/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas
deleted file mode 100644
index 1d7a44dc..00000000
--- a/src/media/UAudioPlayback_Bass.pas
+++ /dev/null
@@ -1,758 +0,0 @@
-{* 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();
- if not BassCore.CheckVersion then
- Exit;
-
- 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/src/media/UAudioPlayback_Portaudio.pas b/src/media/UAudioPlayback_Portaudio.pas
deleted file mode 100644
index ddbd03d6..00000000
--- a/src/media/UAudioPlayback_Portaudio.pas
+++ /dev/null
@@ -1,385 +0,0 @@
-{* 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/src/media/UAudioPlayback_SDL.pas b/src/media/UAudioPlayback_SDL.pas
deleted file mode 100644
index 8403ef03..00000000
--- a/src/media/UAudioPlayback_SDL.pas
+++ /dev/null
@@ -1,182 +0,0 @@
-{* 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/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas
deleted file mode 100644
index c87e461d..00000000
--- a/src/media/UAudioPlayback_SoftMixer.pas
+++ /dev/null
@@ -1,1154 +0,0 @@
-{* 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/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas
deleted file mode 100644
index b4951fe1..00000000
--- a/src/media/UMediaCore_FFmpeg.pas
+++ /dev/null
@@ -1,550 +0,0 @@
-{* 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
- Classes,
- ctypes,
- sdl,
- avcodec,
- avformat,
- avutil,
- avio,
- UMusic,
- ULog,
- UPath;
-
-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;
-
-function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl; forward;
-function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward;
-function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward;
-function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl; forward;
-function FFmpegStreamClose(h: PURLContext): cint; cdecl; forward;
-
-const
- UTF8FileProtocol: TURLProtocol = (
- name: 'ufile';
- url_open: FFmpegStreamOpen;
- url_read: FFmpegStreamRead;
- url_write: FFmpegStreamWrite;
- url_seek: FFmpegStreamSeek;
- url_close: FFmpegStreamClose;
- );
-
-var
- Instance: TMediaCore_FFmpeg;
-
-constructor TMediaCore_FFmpeg.Create();
-begin
- inherited;
- av_register_protocol(@UTF8FileProtocol);
- 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;
-
-
-{**
- * UTF-8 Filename wrapper based on:
- * http://www.mail-archive.com/libav-user@mplayerhq.hu/msg02460.html
- *}
-
-function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl;
-var
- Stream: TStream;
- Mode: word;
- ProtPrefix: string;
- FilePath: IPath;
-begin
- // check for protocol prefix ('ufile:') and strip it
- ProtPrefix := Format('%s:', [UTF8FileProtocol.name]);
- if (StrLComp(filename, PChar(ProtPrefix), Length(ProtPrefix)) = 0) then
- begin
- Inc(filename, Length(ProtPrefix));
- end;
-
- FilePath := Path(filename);
-
- if ((flags and URL_RDWR) <> 0) then
- Mode := fmCreate
- else if ((flags and URL_WRONLY) <> 0) then
- Mode := fmCreate // TODO: fmCreate is Read+Write -> reopen with fmOpenWrite
- else
- Mode := fmOpenRead;
-
- Result := 0;
-
- try
- Stream := TBinaryFileStream.Create(FilePath, Mode);
- h.priv_data := Stream;
- except
- Result := AVERROR_NOENT;
- end;
-end;
-
-function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl;
-var
- Stream: TStream;
-begin
- Stream := TStream(h.priv_data);
- if (Stream = nil) then
- raise EInvalidContainer.Create('FFmpegStreamRead on nil');
- try
- Result := Stream.Read(buf[0], size);
- except
- Result := -1;
- end;
-end;
-
-function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl;
-var
- Stream: TStream;
-begin
- Stream := TStream(h.priv_data);
- if (Stream = nil) then
- raise EInvalidContainer.Create('FFmpegStreamWrite on nil');
- try
- Result := Stream.Write(buf[0], size);
- except
- Result := -1;
- end;
-end;
-
-function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl;
-var
- Stream : TStream;
- Origin : TSeekOrigin;
-begin
- Stream := TStream(h.priv_data);
- if (Stream = nil) then
- raise EInvalidContainer.Create('FFmpegStreamSeek on nil');
- case whence of
- 0 {SEEK_SET}: Origin := soBeginning;
- 1 {SEEK_CUR}: Origin := soCurrent;
- 2 {SEEK_END}: Origin := soEnd;
- AVSEEK_SIZE: begin
- Result := Stream.Size;
- Exit;
- end
- else
- Origin := soBeginning;
- end;
- Result := Stream.Seek(pos, Origin);
-end;
-
-function FFmpegStreamClose(h: PURLContext): cint; cdecl;
-var
- Stream : TStream;
-begin
- Stream := TStream(h.priv_data);
- Stream.Free;
- Result := 0;
-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/src/media/UMediaCore_SDL.pas b/src/media/UMediaCore_SDL.pas
deleted file mode 100644
index 74c75e16..00000000
--- a/src/media/UMediaCore_SDL.pas
+++ /dev/null
@@ -1,63 +0,0 @@
-{* 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/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas
deleted file mode 100644
index c38a8e60..00000000
--- a/src/media/UMedia_dummy.pas
+++ /dev/null
@@ -1,269 +0,0 @@
-{* 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,
- UPath;
-
-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: IPath): boolean; // true if succeed
- procedure Close;
-
- procedure Play;
- procedure Pause;
- procedure Stop;
-
- procedure SetPosition(Time: real);
- function GetPosition: real;
-
- procedure SetSyncSource(SyncSource: ISyncSource);
-
- 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: IPath): 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 : IPath): 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: ISyncSource);
-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: IPath): 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/src/media/UVideo.pas b/src/media/UVideo.pas
deleted file mode 100644
index 6db9cd20..00000000
--- a/src/media/UVideo.pas
+++ /dev/null
@@ -1,966 +0,0 @@
-{* 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
- SysUtils,
- Math,
- SDL,
- avcodec,
- avformat,
- avutil,
- avio,
- rational,
- {$IFDEF UseSWScale}
- swscale,
- {$ENDIF}
- gl,
- glext,
- textgl,
- UMediaCore_FFmpeg,
- UCommon,
- UConfig,
- ULog,
- UMusic,
- UGraphicClasses,
- UGraphic,
- UPath;
-
-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 FileName : IPath): 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 FileName : IPath): boolean; // true if succeed
-var
- errnum: Integer;
- AudioStreamIndex: integer;
-begin
- Result := false;
-
- Reset();
-
- // use custom 'ufile' protocol for UTF-8 support
- errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToNative), nil, 0, nil);
- if (errnum <> 0) then
- begin
- Log.LogError('Failed to open file "'+ FileName.ToNative +'" ('+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;
-
- if fStream^.start_time <> AV_NOPTS_VALUE then
- pts := pts - fStream^.start_time;
-
- 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}
- // img_convert from lib/ffmpeg/avcodec.pas is actually deprecated.
- // If ./configure does not find SWScale then this gives the error
- // that the identifier img_convert is not known or similar.
- // I think this should be removed, but am not sure whether there should
- // be some other replacement or a warning, Therefore, I leave it for now.
- // April 2009, mischi
- 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/src/media/UVisualizer.pas b/src/media/UVisualizer.pas
deleted file mode 100644
index b25d68a9..00000000
--- a/src/media/UVisualizer.pas
+++ /dev/null
@@ -1,552 +0,0 @@
-{* 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,
- UPath,
- 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: IPath): 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: IPath): 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.