aboutsummaryrefslogtreecommitdiffstats
path: root/cmake/src/media
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2010-05-23 09:07:15 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2010-05-23 09:07:15 +0000
commit678cc132f942ff4d84a803550eedf96acc543bca (patch)
treeeb195abafaf69d55fa0f4b77323e517fcd2263cd /cmake/src/media
parent7e677fd5ebe60c3dd9df8954e1ed28c4afdf8660 (diff)
downloadusdx-678cc132f942ff4d84a803550eedf96acc543bca.tar.gz
usdx-678cc132f942ff4d84a803550eedf96acc543bca.tar.xz
usdx-678cc132f942ff4d84a803550eedf96acc543bca.zip
update to trunk rev. 2391
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2401 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'cmake/src/media')
-rw-r--r--cmake/src/media/UAudioCore_Bass.pas29
-rw-r--r--cmake/src/media/UAudioCore_Portaudio.pas88
-rw-r--r--cmake/src/media/UAudioDecoder_Bass.pas21
-rw-r--r--cmake/src/media/UAudioDecoder_FFmpeg.pas96
-rw-r--r--cmake/src/media/UAudioInput_Bass.pas19
-rw-r--r--cmake/src/media/UAudioInput_Portaudio.pas188
-rw-r--r--cmake/src/media/UAudioPlaybackBase.pas24
-rw-r--r--cmake/src/media/UAudioPlayback_Bass.pas6
-rw-r--r--cmake/src/media/UAudioPlayback_Portaudio.pas22
-rw-r--r--cmake/src/media/UAudioPlayback_SoftMixer.pas94
-rw-r--r--cmake/src/media/UMediaCore_FFmpeg.pas146
-rw-r--r--cmake/src/media/UMedia_dummy.pas210
-rw-r--r--cmake/src/media/UVideo.pas377
-rw-r--r--cmake/src/media/UVisualizer.pas231
14 files changed, 1090 insertions, 461 deletions
diff --git a/cmake/src/media/UAudioCore_Bass.pas b/cmake/src/media/UAudioCore_Bass.pas
index 12623dc1..3a84dcd7 100644
--- a/cmake/src/media/UAudioCore_Bass.pas
+++ b/cmake/src/media/UAudioCore_Bass.pas
@@ -44,10 +44,13 @@ type
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;
+ private
+ function DecodeVersion(VersionHex: integer): string;
end;
implementation
@@ -56,6 +59,11 @@ uses
UMain,
ULog;
+const
+ // BASS 2.4.2 is not ABI compatible with older versions
+ // as (BASS_RECORDINFO.driver was removed)
+ BASS_MIN_REQUIRED_VERSION = $02040201;
+
var
Instance: TAudioCore_Bass;
@@ -71,6 +79,27 @@ begin
Result := Instance;
end;
+function TAudioCore_Bass.DecodeVersion(VersionHex: integer): string;
+var
+ Version: array [0..3] of integer;
+begin
+ Version[0] := (VersionHex shr 24) and $FF;
+ Version[1] := (VersionHex shr 16) and $FF;
+ Version[2] := (VersionHex shr 8) and $FF;
+ Version[3] := (VersionHex shr 0) and $FF;
+ Result := Format('%x.%x.%x.%x', [Version[0], Version[1], Version[2], Version[3]]);
+end;
+
+function TAudioCore_Bass.CheckVersion(): boolean;
+begin
+ Result := BASS_GetVersion() >= BASS_MIN_REQUIRED_VERSION;
+ if (not Result) then
+ begin
+ Log.LogWarn('Could not init BASS audio library. ''bass.dll'' version is ' + DecodeVersion(BASS_GetVersion()) + ' but ' + DecodeVersion(BASS_MIN_REQUIRED_VERSION) + ' or higher is required.',
+ 'TAudioCore_Bass.CheckVersion');
+ end;
+end;
+
function TAudioCore_Bass.ErrorGetString(): string;
begin
Result := ErrorGetString(BASS_ErrorGetCode());
diff --git a/cmake/src/media/UAudioCore_Portaudio.pas b/cmake/src/media/UAudioCore_Portaudio.pas
index 25ceae3c..c97b5d10 100644
--- a/cmake/src/media/UAudioCore_Portaudio.pas
+++ b/cmake/src/media/UAudioCore_Portaudio.pas
@@ -40,11 +40,15 @@ uses
type
TAudioCore_Portaudio = class
+ private
+ InitCount: integer; ///< keeps track of the number of Initialize/Terminate calls
public
constructor Create();
class function GetInstance(): TAudioCore_Portaudio;
+ function Initialize(): boolean;
+ function Terminate(): boolean;
function GetPreferredApiIndex(): TPaHostApiIndex;
- function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+ function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean;
end;
implementation
@@ -92,6 +96,7 @@ var
constructor TAudioCore_Portaudio.Create();
begin
inherited;
+ InitCount := 0;
end;
class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio;
@@ -101,9 +106,60 @@ begin
Result := Instance;
end;
+function TAudioCore_Portaudio.Initialize(): boolean;
+var
+ Err: TPaError;
+begin
+ // initialize only once
+ if (InitCount > 0) then
+ begin
+ Inc(InitCount);
+ Result := true;
+ Exit;
+ end;
+
+ // init Portaudio
+ Err := Pa_Initialize();
+ if (Err <> paNoError) then
+ begin
+ Log.LogError(Pa_GetErrorText(Err), 'TAudioCore_Portaudio.Initialize');
+ Result := false;
+ Exit;
+ end;
+
+ // only increment on success
+ Inc(InitCount);
+ Result := true;
+end;
+
+function TAudioCore_Portaudio.Terminate(): boolean;
+var
+ Err: TPaError;
+begin
+ // decrement usage count
+ Dec(InitCount);
+ if (InitCount > 0) then
+ begin
+ // do not terminate yet
+ Result := true;
+ Exit;
+ end;
+
+ // terminate if usage count is 0
+ Err := Pa_Terminate();
+ if (Err <> paNoError) then
+ begin
+ Log.LogError(Pa_GetErrorText(Err), 'TAudioCore_Portaudio.Terminate');
+ Result := false;
+ Exit;
+ end;
+
+ Result := true;
+end;
+
function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
var
- i: integer;
+ i: integer;
apiIndex: TPaHostApiIndex;
apiInfo: PPaHostApiInfo;
begin
@@ -112,11 +168,11 @@ begin
// select preferred sound-API
for i:= 0 to High(ApiPreferenceOrder) do
begin
- if(ApiPreferenceOrder[i] <> paDefaultApi) then
+ if (ApiPreferenceOrder[i] <> paDefaultApi) then
begin
// check if API is available
apiIndex := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
- if(apiIndex >= 0) then
+ 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
@@ -132,7 +188,7 @@ begin
end;
// None of the preferred APIs is available -> use default
- if(result < 0) then
+ if (result < 0) then
begin
result := Pa_GetDefaultHostApi();
end;
@@ -141,9 +197,9 @@ end;
{*
* Portaudio test callback used by TestDevice().
*}
-function TestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+function TestCallback(input: pointer; output: pointer; frameCount: longword;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl;
+ inputDevice: pointer): integer; cdecl;
begin
// this callback is called only once
result := paAbort;
@@ -189,15 +245,15 @@ end;
* 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;
+function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean;
+const
+ altSampleRates: array[0..1] of double = (44100, 48000); // alternative sample-rates
var
- stream: PPaStream;
- err: TPaError;
+ stream: PPaStream;
+ err: TPaError;
cbWorks: boolean;
cbPolls: integer;
- i: integer;
-const
- altSampleRates: array[0..1] of Double = (44100, 48000); // alternative sample-rates
+ i: integer;
begin
Result := false;
@@ -206,7 +262,7 @@ begin
// check if device supports our input-format
err := Pa_IsFormatSupported(inParams, outParams, sampleRate);
- if(err <> paNoError) then
+ if (err <> paNoError) then
begin
// we cannot fix the error -> exit
if (err <> paInvalidSampleRate) then
@@ -244,14 +300,14 @@ begin
err := Pa_OpenStream(stream, inParams, outParams, sampleRate,
paFramesPerBufferUnspecified,
paNoFlag, @TestCallback, nil);
- if(err <> paNoError) then
+ if (err <> paNoError) then
begin
exit;
end;
// start the callback
err := Pa_StartStream(stream);
- if(err <> paNoError) then
+ if (err <> paNoError) then
begin
Pa_CloseStream(stream);
exit;
diff --git a/cmake/src/media/UAudioDecoder_Bass.pas b/cmake/src/media/UAudioDecoder_Bass.pas
index 6bbdaeaa..d6d2425a 100644
--- a/cmake/src/media/UAudioDecoder_Bass.pas
+++ b/cmake/src/media/UAudioDecoder_Bass.pas
@@ -38,11 +38,12 @@ implementation
uses
Classes,
SysUtils,
+ bass,
UMain,
UMusic,
UAudioCore_Bass,
ULog,
- bass;
+ UPath;
type
TBassDecodeStream = class(TAudioDecodeStream)
@@ -75,7 +76,7 @@ type
function InitializeDecoder(): boolean;
function FinalizeDecoder(): boolean;
- function Open(const Filename: string): TAudioDecodeStream;
+ function Open(const Filename: IPath): TAudioDecodeStream;
end;
var
@@ -213,7 +214,10 @@ end;
function TAudioDecoder_Bass.InitializeDecoder(): boolean;
begin
+ Result := false;
BassCore := TAudioCore_Bass.GetInstance();
+ if not BassCore.CheckVersion then
+ Exit;
Result := true;
end;
@@ -222,7 +226,7 @@ begin
Result := true;
end;
-function TAudioDecoder_Bass.Open(const Filename: string): TAudioDecodeStream;
+function TAudioDecoder_Bass.Open(const Filename: IPath): TAudioDecodeStream;
var
Stream: HSTREAM;
ChannelInfo: BASS_CHANNELINFO;
@@ -237,7 +241,14 @@ begin
// TODO: use BASS_STREAM_PRESCAN for accurate seeking in VBR-files?
// disadvantage: seeking will slow down.
- Stream := BASS_StreamCreateFile(False, PAnsiChar(Filename), 0, 0, BASS_STREAM_DECODE);
+
+ {$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');
@@ -247,7 +258,7 @@ begin
// check if BASS opened some erroneously recognized file-formats
if BASS_ChannelGetInfo(Stream, channelInfo) then
begin
- fileExt := ExtractFileExt(Filename);
+ 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
diff --git a/cmake/src/media/UAudioDecoder_FFmpeg.pas b/cmake/src/media/UAudioDecoder_FFmpeg.pas
index 97d8a8df..7ca98885 100644
--- a/cmake/src/media/UAudioDecoder_FFmpeg.pas
+++ b/cmake/src/media/UAudioDecoder_FFmpeg.pas
@@ -56,23 +56,24 @@ interface
implementation
uses
+ SDL, // SDL redefines some base types -> include before SysUtils to ignore them
Classes,
Math,
- UMusic,
- UIni,
- UMain,
+ SysUtils,
avcodec,
avformat,
avutil,
avio,
mathematics, // used for av_rescale_q
rational,
- SDL,
- SysUtils,
+ UMusic,
+ UIni,
+ UMain,
UMediaCore_FFmpeg,
ULog,
UCommon,
- UConfig;
+ UConfig,
+ UPath;
const
MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
@@ -138,7 +139,7 @@ type
AudioBufferSize: integer;
AudioBuffer: PByteArray;
- Filename: string;
+ Filename: IPath;
procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean);
procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF}
@@ -161,7 +162,7 @@ type
constructor Create();
destructor Destroy(); override;
- function Open(const Filename: string): boolean;
+ function Open(const Filename: IPath): boolean;
procedure Close(); override;
function GetLength(): real; override;
@@ -183,7 +184,7 @@ type
function InitializeDecoder(): boolean;
function FinalizeDecoder(): boolean;
- function Open(const Filename: string): TAudioDecodeStream;
+ function Open(const Filename: IPath): TAudioDecodeStream;
end;
var
@@ -270,7 +271,7 @@ begin
inherited;
end;
-function TFFmpegDecodeStream.Open(const Filename: string): boolean;
+function TFFmpegDecodeStream.Open(const Filename: IPath): boolean;
var
SampleFormat: TAudioSampleFormat;
AVResult: integer;
@@ -280,18 +281,18 @@ begin
Close();
Reset();
- if (not FileExists(Filename)) then
+ if (not Filename.IsFile) then
begin
- Log.LogError('Audio-file does not exist: "' + Filename + '"', 'UAudio_FFmpeg');
+ Log.LogError('Audio-file does not exist: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
Exit;
end;
Self.Filename := Filename;
- // open audio file
- if (av_open_input_file(FormatCtx, PAnsiChar(Filename), nil, 0, nil) <> 0) then
+ // 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 + '"', 'UAudio_FFmpeg');
+ Log.LogError('av_open_input_file failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
Exit;
end;
@@ -301,7 +302,7 @@ begin
// retrieve stream information
if (av_find_stream_info(FormatCtx) < 0) then
begin
- Log.LogError('av_find_stream_info failed: "' + Filename + '"', 'UAudio_FFmpeg');
+ Log.LogError('av_find_stream_info failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
Close();
Exit;
end;
@@ -310,13 +311,13 @@ begin
FormatCtx^.pb.eof_reached := 0;
{$IFDEF DebugFFmpegDecode}
- dump_format(FormatCtx, 0, PAnsiChar(Filename), 0);
+ 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 + '"', 'UAudio_FFmpeg');
+ Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
Close();
Exit;
end;
@@ -324,6 +325,7 @@ begin
//Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg');
AudioStream := FormatCtx.streams[AudioStreamIndex];
+ AudioStreamPos := 0;
CodecCtx := AudioStream^.codec;
// TODO: should we use this or not? Should we allow 5.1 channel audio?
@@ -574,30 +576,38 @@ begin
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();
+ try
+ EOFState := false;
+ ErrorState := false;
+
+ // do not seek if we are already at the correct position.
+ // This is important especially for seeking to position 0 if we already are
+ // at the beginning. Although seeking with AVSEEK_FLAG_BACKWARD for pos 0 works,
+ // it is still a bit choppy (although much better than w/o AVSEEK_FLAG_BACKWARD).
+ if (Time = AudioStreamPos) then
+ Exit;
+
+ // 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;
+
+ // send a reuse signal in case the parser was stopped (e.g. because of an EOF)
+ SDL_CondSignal(ParserIdleCond);
+ finally
+ SDL_mutexV(StateLock);
+ ResumeDecoder();
+ ResumeParser();
+ end;
// in blocking mode, wait until seeking is done
if (Blocking) then
@@ -1117,7 +1127,7 @@ begin
Result := true;
end;
-function TAudioDecoder_FFmpeg.Open(const Filename: string): TAudioDecodeStream;
+function TAudioDecoder_FFmpeg.Open(const Filename: IPath): TAudioDecodeStream;
var
Stream: TFFmpegDecodeStream;
begin
diff --git a/cmake/src/media/UAudioInput_Bass.pas b/cmake/src/media/UAudioInput_Bass.pas
index ad6c3818..b8f914c5 100644
--- a/cmake/src/media/UAudioInput_Bass.pas
+++ b/cmake/src/media/UAudioInput_Bass.pas
@@ -46,6 +46,7 @@ uses
UIni,
ULog,
UAudioCore_Bass,
+ UTextEncoding,
UCommon, // (Note: for MakeLong on non-windows platforms)
{$IFDEF MSWINDOWS}
Windows, // (Note: for MakeLong)
@@ -352,7 +353,7 @@ end;
function TAudioInput_Bass.EnumDevices(): boolean;
var
- Descr: PChar;
+ Descr: UTF8String;
SourceName: PChar;
Flags: integer;
BassDeviceID: integer;
@@ -389,9 +390,12 @@ begin
BassDevice := TBassInputDevice.Create();
AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice;
- Descr := DeviceInfo.name;
-
BassDevice.BassDeviceID := BassDeviceID;
+
+ // BASS device names seem to be encoded with local encoding
+ // TODO: works for windows, check Linux + Mac OS X
+ Descr := DecodeStringUTF8(DeviceInfo.name, encLocale);
+
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)
@@ -459,7 +463,9 @@ begin
break;
SetLength(BassDevice.Source, Length(BassDevice.Source)+1);
- BassDevice.Source[SourceIndex].Name := SourceName;
+ // BASS source names seem to be encoded with local encoding
+ // TODO: works for windows, check Linux + Mac OS X
+ BassDevice.Source[SourceIndex].Name := DecodeStringUTF8(SourceName, encLocale);
// get input-source info
Flags := BASS_RecordGetInput(SourceIndex, PSingle(nil)^);
@@ -489,6 +495,11 @@ end;
function TAudioInput_Bass.InitializeRecord(): boolean;
begin
BassCore := TAudioCore_Bass.GetInstance();
+ if not BassCore.CheckVersion then
+ begin
+ Result := false;
+ Exit;
+ end;
Result := EnumDevices();
end;
diff --git a/cmake/src/media/UAudioInput_Portaudio.pas b/cmake/src/media/UAudioInput_Portaudio.pas
index 31d2882b..c7364eb4 100644
--- a/cmake/src/media/UAudioInput_Portaudio.pas
+++ b/cmake/src/media/UAudioInput_Portaudio.pas
@@ -45,11 +45,14 @@ uses
portmixer,
{$ENDIF}
portaudio,
+ ctypes,
UAudioCore_Portaudio,
- URecord,
+ UUnicodeUtils,
+ UTextEncoding,
UIni,
ULog,
- UMain;
+ UMain,
+ URecord;
type
TAudioInput_Portaudio = class(TAudioInputBase)
@@ -57,7 +60,7 @@ type
AudioCore: TAudioCore_Portaudio;
function EnumDevices(): boolean;
public
- function GetName: String; override;
+ function GetName: string; override;
function InitializeRecord: boolean; override;
function FinalizeRecord: boolean; override;
end;
@@ -70,31 +73,76 @@ type
{$ENDIF}
PaDeviceIndex: TPaDeviceIndex;
public
- function Open(): boolean;
+ function Open(): boolean;
function Close(): boolean;
function Start(): boolean; override;
- function Stop(): boolean; override;
+ function Stop(): boolean; override;
+
+ function DetermineInputLatency(Info: PPaDeviceInfo): TPaTime;
function GetVolume(): single; override;
procedure SetVolume(Volume: single); override;
end;
-function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+function MicrophoneCallback(input: pointer; output: pointer; frameCount: culong;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl; forward;
+ inputDevice: pointer): cint; cdecl; forward;
-function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: culong;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl; forward;
+ inputDevice: pointer): cint; cdecl; forward;
+
+{**
+ * Converts a string returned by Portaudio into UTF8.
+ * If the string already is in UTF8 no conversion is performed, otherwise
+ * the local encoding is used.
+ *}
+function ConvertPaStringToUTF8(const Str: RawByteString): UTF8String;
+begin
+ if (IsUTF8String(Str)) then
+ Result := Str
+ else
+ Result := DecodeStringUTF8(Str, encLocale);
+end;
{ TPortaudioInputDevice }
+function TPortaudioInputDevice.DetermineInputLatency(Info: PPaDeviceInfo): TPaTime;
+begin
+ if (Ini.InputDeviceConfig[CfgIndex].Latency <> -1) then
+ begin
+ // autodetection off -> set user latency
+ Result := Ini.InputDeviceConfig[CfgIndex].Latency / 1000
+ end
+ else
+ begin
+ // 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?)
+ // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+ // not be set correctly in OSS)
+
+ // FIXME: according to the portaudio headers defaultHighInputLatency (approx. 40ms) is
+ // for robust non-interactive applications and defaultLowInputLatency (approx. 15ms)
+ // for interactive performance.
+ // We need defaultLowInputLatency here but this setting is far too buggy. If the callback
+ // does not return quickly the stream will be stuck after returning from the callback
+ // and the callback will not be called anymore and mic-capturing stops.
+ // Audacity (in AudioIO.cpp) uses defaultHighInputLatency if software playthrough is on
+ // and even higher latencies (100ms) without playthrough so this should be ok for now.
+ //Result := Info^.defaultLowInputLatency;
+ Result := Info^.defaultHighInputLatency;
+ end;
+end;
+
function TPortaudioInputDevice.Open(): boolean;
var
Error: TPaError;
inputParams: TPaStreamParameters;
deviceInfo: PPaDeviceInfo;
+ {$IFDEF UsePortmixer}
+ SourceIndex: integer;
+ {$ENDIF}
begin
Result := false;
@@ -107,19 +155,19 @@ begin
device := PaDeviceIndex;
channelCount := AudioFormat.Channels;
sampleFormat := paInt16;
- suggestedLatency := deviceInfo^.defaultLowInputLatency;
+ suggestedLatency := DetermineInputLatency(deviceInfo);
hostApiSpecificStreamInfo := nil;
end;
- //Log.LogStatus(deviceInfo^.name, 'Portaudio');
- //Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
+ Log.LogStatus('Open ' + deviceInfo^.name, 'Portaudio');
+ Log.LogStatus('Latency of ' + deviceInfo^.name + ': ' + floatToStr(inputParams.suggestedLatency), 'Portaudio');
// open input stream
Error := Pa_OpenStream(RecordStream, @inputParams, nil,
AudioFormat.SampleRate,
paFramesPerBufferUnspecified, paNoFlag,
- @MicrophoneCallback, Pointer(Self));
- if(Error <> paNoError) then
+ @MicrophoneCallback, pointer(Self));
+ if (Error <> paNoError) then
begin
Log.LogError('Error opening stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
Exit;
@@ -155,7 +203,7 @@ end;
function TPortaudioInputDevice.Start(): boolean;
var
- Error: TPaError;
+ Error: TPaError;
begin
Result := false;
@@ -169,7 +217,7 @@ begin
// start capture
Error := Pa_StartStream(RecordStream);
- if(Error <> paNoError) then
+ if (Error <> paNoError) then
begin
Log.LogError('Error starting stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Start');
Close();
@@ -268,34 +316,36 @@ 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;
+ i: integer;
+ deviceName: UTF8String;
+ paApiIndex: TPaHostApiIndex;
+ paApiInfo: PPaHostApiInfo;
+ paDeviceIndex:TPaDeviceIndex;
+ paDeviceInfo: PPaDeviceInfo;
+ channelCnt: integer;
+ deviceIndex: integer;
+ 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;
+ mixer: PPxMixer;
+ sourceCnt: integer;
+ sourceIndex: integer;
+ sourceName: UTF8String;
{$ENDIF}
+const
+ MIN_TEST_LATENCY = 100 / 1000; // min. test latency of 100 ms to avoid removal of working devices
begin
Result := false;
// choose the best available Audio-API
paApiIndex := AudioCore.GetPreferredApiIndex();
- if(paApiIndex = -1) then
+ if (paApiIndex = -1) then
begin
Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.EnumDevices');
Exit;
@@ -303,17 +353,17 @@ begin
paApiInfo := Pa_GetHostApiInfo(paApiIndex);
- SC := 0;
+ deviceIndex := 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);
+ paDeviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ paDeviceInfo := Pa_GetDeviceInfo(paDeviceIndex);
- channelCnt := deviceInfo^.maxInputChannels;
+ channelCnt := paDeviceInfo^.maxInputChannels;
// current device is no input device -> skip
if (channelCnt <= 0) then
@@ -326,25 +376,25 @@ begin
channelCnt := 2;
paDevice := TPortaudioInputDevice.Create();
- AudioInputProcessor.DeviceList[SC] := paDevice;
+ AudioInputProcessor.DeviceList[deviceIndex] := paDevice;
// retrieve device-name
- deviceName := deviceInfo^.name;
- paDevice.Name := deviceName;
- paDevice.PaDeviceIndex := deviceIndex;
+ deviceName := ConvertPaStringToUTF8(paDeviceInfo^.name);
+ paDevice.Name := UnifyDeviceName(deviceName, deviceIndex);
+ paDevice.PaDeviceIndex := paDeviceIndex;
- sampleRate := deviceInfo^.defaultSampleRate;
+ sampleRate := paDeviceInfo^.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;
+ // use a stable (high) latency so we do not remove working devices
+ if (paDeviceInfo^.defaultHighInputLatency > MIN_TEST_LATENCY) then
+ latency := paDeviceInfo^.defaultHighInputLatency
+ else
+ latency := MIN_TEST_LATENCY;
// 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;
+ device := paDeviceIndex;
channelCount := channelCnt;
sampleFormat := paInt16;
suggestedLatency := latency;
@@ -364,7 +414,7 @@ begin
// open device for further info
err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
- if(err <> paNoError) then
+ if (err <> paNoError) then
begin
// unable to open device -> skip
errMsg := Pa_GetErrorText(err);
@@ -421,7 +471,7 @@ begin
for sourceIndex := 1 to sourceCnt do
begin
sourceName := Px_GetInputSourceName(mixer, sourceIndex-1);
- paDevice.Source[sourceIndex].Name := sourceName;
+ paDevice.Source[sourceIndex].Name := ConvertPaStringToUTF8(sourceName);
end;
Px_CloseMixer(mixer);
@@ -430,48 +480,41 @@ begin
// close test-stream
Pa_CloseStream(stream);
- Inc(SC);
+ Inc(deviceIndex);
end;
// adjust size to actual input-device count
- SetLength(AudioInputProcessor.DeviceList, SC);
+ SetLength(AudioInputProcessor.DeviceList, deviceIndex);
- Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio');
+ Log.LogStatus('#Input-Devices: ' + inttostr(deviceIndex), 'Portaudio');
Result := true;
end;
function TAudioInput_Portaudio.InitializeRecord(): boolean;
-var
- 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');
- Result := false;
- Exit;
- end;
-
+ if (not AudioCore.Initialize()) then
+ Exit;
Result := EnumDevices();
end;
function TAudioInput_Portaudio.FinalizeRecord: boolean;
begin
CaptureStop;
- Pa_Terminate();
+ AudioCore.Terminate();
Result := inherited FinalizeRecord();
end;
{*
* Portaudio input capture callback.
*}
-function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+function MicrophoneCallback(input: pointer; output: pointer; frameCount: culong;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl;
+ inputDevice: pointer): cint; cdecl;
begin
AudioInputProcessor.HandleMicrophoneData(input, frameCount*4, inputDevice);
result := paContinue;
@@ -480,15 +523,14 @@ end;
{*
* Portaudio test capture callback.
*}
-function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword;
+function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: culong;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl;
+ inputDevice: pointer): cint; cdecl;
begin
// this callback is called only once
result := paAbort;
end;
-
initialization
MediaManager.add(TAudioInput_Portaudio.Create);
diff --git a/cmake/src/media/UAudioPlaybackBase.pas b/cmake/src/media/UAudioPlaybackBase.pas
index 7d143fdc..5f317257 100644
--- a/cmake/src/media/UAudioPlaybackBase.pas
+++ b/cmake/src/media/UAudioPlaybackBase.pas
@@ -34,7 +34,9 @@ interface
{$I switches.inc}
uses
- UMusic;
+ UMusic,
+ UTime,
+ UPath;
type
TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback)
@@ -46,12 +48,12 @@ type
function GetLatency(): double; virtual; abstract;
// open sound or music stream (used by Open() and OpenSound())
- function OpenStream(const Filename: string): TAudioPlaybackStream;
- function OpenDecodeStream(const Filename: string): TAudioDecodeStream;
+ function OpenStream(const Filename: IPath): TAudioPlaybackStream;
+ function OpenDecodeStream(const Filename: IPath): TAudioDecodeStream;
public
function GetName: string; virtual; abstract;
- function Open(const Filename: string): boolean; // true if succeed
+ function Open(const Filename: IPath): boolean; // true if succeed
procedure Close;
procedure Play;
@@ -79,7 +81,7 @@ type
function Length: real;
// Sounds
- function OpenSound(const Filename: string): TAudioPlaybackStream;
+ function OpenSound(const Filename: IPath): TAudioPlaybackStream;
procedure PlaySound(Stream: TAudioPlaybackStream);
procedure StopSound(Stream: TAudioPlaybackStream);
@@ -108,7 +110,7 @@ begin
Result := true;
end;
-function TAudioPlaybackBase.Open(const Filename: string): boolean;
+function TAudioPlaybackBase.Open(const Filename: IPath): boolean;
begin
// free old MusicStream
MusicStream.Free;
@@ -130,7 +132,7 @@ begin
FreeAndNil(MusicStream);
end;
-function TAudioPlaybackBase.OpenDecodeStream(const Filename: String): TAudioDecodeStream;
+function TAudioPlaybackBase.OpenDecodeStream(const Filename: IPath): TAudioDecodeStream;
var
i: integer;
begin
@@ -140,7 +142,7 @@ begin
if (assigned(Result)) then
begin
Log.LogInfo('Using decoder ' + IAudioDecoder(AudioDecoders[i]).GetName() +
- ' for "' + Filename + '"', 'TAudioPlaybackBase.OpenDecodeStream');
+ ' for "' + Filename.ToNative + '"', 'TAudioPlaybackBase.OpenDecodeStream');
Exit;
end;
end;
@@ -157,7 +159,7 @@ begin
SourceStream.Free;
end;
-function TAudioPlaybackBase.OpenStream(const Filename: string): TAudioPlaybackStream;
+function TAudioPlaybackBase.OpenStream(const Filename: IPath): TAudioPlaybackStream;
var
PlaybackStream: TAudioPlaybackStream;
DecodeStream: TAudioDecodeStream;
@@ -169,7 +171,7 @@ begin
DecodeStream := OpenDecodeStream(Filename);
if (not assigned(DecodeStream)) then
begin
- Log.LogStatus('Could not open "' + Filename + '"', 'TAudioPlayback_Bass.OpenStream');
+ Log.LogStatus('Could not open "' + Filename.ToNative + '"', 'TAudioPlayback_Bass.OpenStream');
Exit;
end;
@@ -283,7 +285,7 @@ begin
Result := 0;
end;
-function TAudioPlaybackBase.OpenSound(const Filename: string): TAudioPlaybackStream;
+function TAudioPlaybackBase.OpenSound(const Filename: IPath): TAudioPlaybackStream;
begin
Result := OpenStream(Filename);
end;
diff --git a/cmake/src/media/UAudioPlayback_Bass.pas b/cmake/src/media/UAudioPlayback_Bass.pas
index 923c1d7b..1d7a44dc 100644
--- a/cmake/src/media/UAudioPlayback_Bass.pas
+++ b/cmake/src/media/UAudioPlayback_Bass.pas
@@ -684,9 +684,11 @@ end;
function TAudioPlayback_Bass.InitializePlayback(): boolean;
begin
- result := false;
+ Result := false;
BassCore := TAudioCore_Bass.GetInstance();
+ if not BassCore.CheckVersion then
+ Exit;
EnumDevices();
@@ -706,7 +708,7 @@ begin
//BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
//BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
- result := true;
+ Result := true;
end;
function TAudioPlayback_Bass.FinalizePlayback(): boolean;
diff --git a/cmake/src/media/UAudioPlayback_Portaudio.pas b/cmake/src/media/UAudioPlayback_Portaudio.pas
index ddbd03d6..6fbae6e3 100644
--- a/cmake/src/media/UAudioPlayback_Portaudio.pas
+++ b/cmake/src/media/UAudioPlayback_Portaudio.pas
@@ -307,22 +307,16 @@ 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');
+ if (not AudioCore.Initialize()) then
Exit;
- end;
paApiIndex := AudioCore.GetPreferredApiIndex();
- if(paApiIndex = -1) then
+ if (paApiIndex = -1) then
begin
Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
Exit;
@@ -364,13 +358,19 @@ end;
procedure TAudioPlayback_Portaudio.StopAudioPlaybackEngine();
begin
if (paStream <> nil) then
- Pa_StopStream(paStream);
+ begin
+ Pa_CloseStream(paStream);
+ // wait until stream is closed, otherwise Terminate() might cause a segfault
+ while (Pa_IsStreamActive(paStream) = 1) do
+ ;
+ paStream := nil;
+ end;
end;
function TAudioPlayback_Portaudio.FinalizeAudioPlaybackEngine(): boolean;
begin
- Pa_Terminate();
- Result := true;
+ StopAudioPlaybackEngine();
+ Result := AudioCore.Terminate();
end;
function TAudioPlayback_Portaudio.GetLatency(): double;
diff --git a/cmake/src/media/UAudioPlayback_SoftMixer.pas b/cmake/src/media/UAudioPlayback_SoftMixer.pas
index c87e461d..11df4df5 100644
--- a/cmake/src/media/UAudioPlayback_SoftMixer.pas
+++ b/cmake/src/media/UAudioPlayback_SoftMixer.pas
@@ -47,6 +47,8 @@ type
TGenericPlaybackStream = class(TAudioPlaybackStream)
private
Engine: TAudioPlayback_SoftMixer;
+ LastReadSize: integer; // size of data returned by the last ReadData() call
+ LastReadTime: Cardinal; // time of the last ReadData() call
SampleBuffer: PByteArray;
SampleBufferSize: integer;
@@ -58,7 +60,7 @@ type
SourceBufferCount: integer; // number of available bytes in SourceBuffer
Converter: TAudioConverter;
- Status: TStreamStatus;
+ Status: TStreamStatus;
InternalLock: PSDL_Mutex;
SoundEffects: TList;
fVolume: single;
@@ -86,6 +88,8 @@ type
procedure SetLoop(Enabled: boolean); override;
function GetPosition: real; override;
procedure SetPosition(Time: real); override;
+
+ function GetRemainingBufferSize(): integer;
public
constructor Create(Engine: TAudioPlayback_SoftMixer);
destructor Destroy(); override;
@@ -102,7 +106,7 @@ type
function ReadData(Buffer: PByteArray; BufferSize: integer): integer;
- function GetPCMData(var Data: TPCMData): Cardinal; override;
+ function GetPCMData(var Data: TPCMData): cardinal; override;
procedure GetFFTData(var Data: TFFTData); override;
procedure AddSoundEffect(Effect: TSoundEffect); override;
@@ -148,7 +152,7 @@ type
function CreatePlaybackStream(): TAudioPlaybackStream; override;
public
- function GetName: String; override; abstract;
+ function GetName: string; override; abstract;
function InitializePlayback(): boolean; override;
function FinalizePlayback: boolean; override;
@@ -159,7 +163,7 @@ type
function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
function GetAudioFormatInfo(): TAudioFormatInfo;
- procedure MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single); virtual;
+ procedure MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: cardinal; Volume: single); virtual;
end;
type
@@ -369,6 +373,8 @@ begin
fVolume := 0;
SoundEffects.Clear;
FadeInTime := 0;
+
+ LastReadSize := 0;
end;
function TGenericPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean;
@@ -377,11 +383,11 @@ begin
Close();
- if (not assigned(SourceStream)) then
+ if not assigned(SourceStream) then
Exit;
Self.SourceStream := SourceStream;
- if (not InitFormatConversion()) then
+ if not InitFormatConversion() then
begin
// reset decode-stream so it will not be freed on destruction
Self.SourceStream := nil;
@@ -495,7 +501,11 @@ begin
Exit;
Status := ssStopped;
+ // stop fading
+ FadeInTime := 0;
+ LastReadSize := 0;
+
Mixer := Engine.GetMixer();
if (Mixer <> nil) then
Mixer.RemoveStream(Self);
@@ -543,6 +553,7 @@ begin
SampleBufferCount := 0;
SampleBufferPos := 0;
SourceBufferCount := 0;
+ LastReadSize := 0;
end;
procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PByteArray; BufferSize: integer);
@@ -576,6 +587,8 @@ var
begin
Result := -1;
+ LastReadSize := 0;
+
// sanity check for the source-stream
if (not assigned(SourceStream)) then
Exit;
@@ -747,10 +760,12 @@ begin
end;
// BytesNeeded now contains the number of remaining bytes we were not able to fetch
- Result := BufferSize - BytesNeeded;
+ LastReadTime := SDL_GetTicks;
+ LastReadSize := BufferSize - BytesNeeded;
+ Result := LastReadSize;
end;
-function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal;
+function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): cardinal;
var
ByteCount: integer;
begin
@@ -790,7 +805,7 @@ begin
// only works with SInt16 and Float values at the moment
AudioFormat := GetAudioFormatInfo();
- DataIn := AllocMem(FFTSize * SizeOf(Single));
+ DataIn := AllocMem(FFTSize * SizeOf(single));
if (DataIn = nil) then
Exit;
@@ -842,6 +857,28 @@ begin
UnlockSampleBuffer();
end;
+{**
+ * Returns the approximate number of bytes left in the audio engines buffer queue.
+ *}
+function TGenericPlaybackStream.GetRemainingBufferSize(): integer;
+var
+ TimeDiff: double;
+begin
+ if (LastReadSize <= 0) then
+ begin
+ Result := 0;
+ end
+ else
+ begin
+ TimeDiff := (SDL_GetTicks() - LastReadTime) / 1000;
+ // we gave the data-sink LastReadSize bytes at the last call to ReadData().
+ // Calculate how much of this should be left in the data-sink
+ Result := LastReadSize - Trunc(TimeDiff * Engine.FormatInfo.BytesPerSec);
+ if (Result < 0) then
+ Result := 0;
+ end;
+end;
+
function TGenericPlaybackStream.GetPosition: real;
var
BufferedTime: double;
@@ -850,11 +887,24 @@ begin
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
+ // the duration of source stream data that is buffered in this stream.
+ // (this is the data retrieved from the source but has not been resampled)
+ BufferedTime := SourceBufferCount / SourceStream.GetAudioFormatInfo().BytesPerSec;
+
+ // the duration of data that is buffered in this stream.
+ // (this is the already resampled data that has not yet been passed to the audio engine)
+ BufferedTime := BufferedTime + (SampleBufferCount - SampleBufferPos) / Engine.FormatInfo.BytesPerSec;
+
+ // Now consider the samples left in the engine's (e.g. SDL) buffer.
+ // Otherwise the result calculated so far will not change until the callback
+ // is called the next time.
+ // For example, if the buffer has a size of 2048 frames we would not be
+ // able to return a new new position for approx. 40ms (at 44.1kHz) which
+ // would be very bad for synching.
+ BufferedTime := BufferedTime + GetRemainingBufferSize() / Engine.FormatInfo.BytesPerSec;
+
+ // use the timestamp of the source as reference and subtract the time of
+ // the data that is still buffered and not yet output.
Result := SourceStream.Position - BufferedTime;
UnlockSampleBuffer();
@@ -885,7 +935,7 @@ end;
function TGenericPlaybackStream.GetVolume(): single;
var
- FadeAmount: Single;
+ FadeAmount: single;
begin
LockSampleBuffer();
// adjust volume if fading is enabled
@@ -1033,12 +1083,12 @@ begin
//Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer');
- if(not InitializeAudioPlaybackEngine()) then
+ if (not InitializeAudioPlaybackEngine()) then
Exit;
MixerStream := TAudioMixerStream.Create(Self);
- if(not StartAudioPlaybackEngine()) then
+ if (not StartAudioPlaybackEngine()) then
Exit;
Result := true;
@@ -1100,11 +1150,11 @@ begin
MixerStream.Volume := Volume;
end;
-procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single);
+procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: cardinal; Volume: single);
var
- SampleIndex: Cardinal;
- SampleInt: Integer;
- SampleFlt: Single;
+ SampleIndex: cardinal;
+ SampleInt: integer;
+ SampleFlt: single;
begin
SampleIndex := 0;
case FormatInfo.Format of
@@ -1141,7 +1191,7 @@ begin
// assign result
PSingle(@DstBuffer[SampleIndex])^ := SampleFlt;
// increase index by one sample
- Inc(SampleIndex, SizeOf(Single));
+ Inc(SampleIndex, SizeOf(single));
end;
end;
else
diff --git a/cmake/src/media/UMediaCore_FFmpeg.pas b/cmake/src/media/UMediaCore_FFmpeg.pas
index 9ad19a5b..2d572ff2 100644
--- a/cmake/src/media/UMediaCore_FFmpeg.pas
+++ b/cmake/src/media/UMediaCore_FFmpeg.pas
@@ -34,12 +34,16 @@ interface
{$I switches.inc}
uses
- UMusic,
+ Classes,
+ ctypes,
+ sdl,
avcodec,
avformat,
avutil,
+ avio,
+ UMusic,
ULog,
- sdl;
+ UPath;
type
PPacketQueue = ^TPacketQueue;
@@ -95,7 +99,24 @@ type
implementation
uses
- SysUtils;
+ SysUtils,
+ UConfig;
+
+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;
@@ -103,6 +124,7 @@ var
constructor TMediaCore_FFmpeg.Create();
begin
inherited;
+ av_register_protocol(@UTF8FileProtocol);
AVCodecLock := SDL_CreateMutex();
end;
@@ -163,6 +185,7 @@ begin
begin
Stream := FormatCtx.streams[i];
+{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0
if (Stream.codec.codec_type = CODEC_TYPE_VIDEO) and
(FirstVideoStream < 0) then
begin
@@ -175,6 +198,20 @@ begin
FirstAudioStream := i;
end;
end;
+{$ELSE}
+ if (Stream.codec.codec_type = AVMEDIA_TYPE_VIDEO) and
+ (FirstVideoStream < 0) then
+ begin
+ FirstVideoStream := i;
+ end;
+
+ if (Stream.codec.codec_type = AVMEDIA_TYPE_AUDIO) and
+ (FirstAudioStream < 0) then
+ begin
+ FirstAudioStream := i;
+ end;
+ end;
+{$IFEND}
// return true if either an audio- or video-stream was found
Result := (FirstAudioStream > -1) or
@@ -194,7 +231,11 @@ begin
begin
Stream := FormatCtx^.streams[i];
+{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0
if (Stream.codec^.codec_type = CODEC_TYPE_AUDIO) then
+{$ELSE}
+ if (Stream.codec^.codec_type = AVMEDIA_TYPE_AUDIO) then
+{$IFEND}
begin
StreamIndex := i;
Break;
@@ -220,6 +261,105 @@ begin
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 or fmShareDenyWrite;
+
+ 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();
diff --git a/cmake/src/media/UMedia_dummy.pas b/cmake/src/media/UMedia_dummy.pas
index 7558dd0b..8ebfd3a9 100644
--- a/cmake/src/media/UMedia_dummy.pas
+++ b/cmake/src/media/UMedia_dummy.pas
@@ -36,22 +36,24 @@ interface
implementation
uses
- SysUtils,
- math,
- UMusic;
+ SysUtils,
+ math,
+ UTime,
+ UMusic,
+ UPath;
type
- TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
+ TAudio_Dummy = class( TInterfacedObject, IAudioPlayback, IAudioInput )
private
DummyOutputDeviceList: TAudioOutputDeviceList;
public
constructor Create();
- function GetName: string;
+ function GetName: string;
function Init(): boolean;
function Finalize(): boolean;
- function Open(const aFileName : string): boolean; // true if succeed
+ function Open(const aFileName: IPath): boolean; // true if succeed
procedure Close;
procedure Play;
@@ -63,9 +65,6 @@ type
procedure SetSyncSource(SyncSource: TSyncSource);
- procedure GetFrame(Time: Extended);
- procedure DrawGL(Screen: integer);
-
// IAudioInput
function InitializeRecord: boolean;
function FinalizeRecord: boolean;
@@ -82,13 +81,15 @@ type
procedure FadeIn(Time: real; TargetVolume: single);
procedure SetAppVolume(Volume: single);
procedure SetVolume(Volume: single);
- procedure SetLoop(Enabled: boolean);
procedure Rewind;
+ procedure SetLoop(Enabled: boolean);
+ function GetLoop(): boolean;
+
function Finished: boolean;
function Length: real;
- function OpenSound(const Filename: string): TAudioPlaybackStream;
+ function OpenSound(const Filename: IPath): TAudioPlaybackStream;
procedure CloseSound(var PlaybackStream: TAudioPlaybackStream);
procedure PlaySound(stream: TAudioPlaybackStream);
procedure StopSound(stream: TAudioPlaybackStream);
@@ -97,98 +98,122 @@ type
procedure CloseVoiceStream(var VoiceStream: TAudioVoiceStream);
end;
-function TMedia_dummy.GetName: string;
-begin
- Result := 'dummy';
-end;
+ TVideo_Dummy = class( TInterfacedObject, IVideo )
+ public
+ procedure Close;
-procedure TMedia_dummy.GetFrame(Time: Extended);
-begin
-end;
+ procedure Play;
+ procedure Pause;
+ procedure Stop;
+
+ procedure SetLoop(Enable: boolean);
+ function GetLoop(): boolean;
+
+ procedure SetPosition(Time: real);
+ function GetPosition: real;
+
+ procedure GetFrame(Time: Extended);
+ procedure DrawGL(Screen: integer);
+
+ property Loop: boolean read GetLoop write SetLoop;
+ property Position: real read GetPosition write SetPosition;
+ end;
-procedure TMedia_dummy.DrawGL(Screen: integer);
+ TVideoPlayback_Dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization )
+ public
+ constructor Create();
+ function GetName: string;
+
+ function Init(): boolean;
+ function Finalize(): boolean;
+
+ function Open(const FileName: IPath): IVideo;
+ end;
+
+function TAudio_Dummy.GetName: string;
begin
+ Result := 'AudioDummy';
end;
-constructor TMedia_dummy.Create();
+constructor TAudio_Dummy.Create();
begin
inherited;
end;
-function TMedia_dummy.Init(): boolean;
+function TAudio_Dummy.Init(): boolean;
begin
Result := true;
end;
-function TMedia_dummy.Finalize(): boolean;
+function TAudio_Dummy.Finalize(): boolean;
begin
Result := true;
end;
-function TMedia_dummy.Open(const aFileName : string): boolean; // true if succeed
+function TAudio_Dummy.Open(const aFileName : IPath): boolean; // true if succeed
begin
Result := false;
end;
-procedure TMedia_dummy.Close;
+procedure TAudio_Dummy.Close;
begin
end;
-procedure TMedia_dummy.Play;
+procedure TAudio_Dummy.Play;
begin
end;
-procedure TMedia_dummy.Pause;
+procedure TAudio_Dummy.Pause;
begin
end;
-procedure TMedia_dummy.Stop;
+procedure TAudio_Dummy.Stop;
begin
end;
-procedure TMedia_dummy.SetPosition(Time: real);
+procedure TAudio_Dummy.SetPosition(Time: real);
begin
end;
-function TMedia_dummy.GetPosition: real;
+function TAudio_Dummy.GetPosition: real;
begin
Result := 0;
end;
-procedure TMedia_dummy.SetSyncSource(SyncSource: TSyncSource);
+procedure TAudio_Dummy.SetSyncSource(SyncSource: TSyncSource);
begin
end;
// IAudioInput
-function TMedia_dummy.InitializeRecord: boolean;
+function TAudio_Dummy.InitializeRecord: boolean;
begin
Result := true;
end;
-function TMedia_dummy.FinalizeRecord: boolean;
+function TAudio_Dummy.FinalizeRecord: boolean;
begin
Result := true;
end;
-procedure TMedia_dummy.CaptureStart;
+procedure TAudio_Dummy.CaptureStart;
begin
end;
-procedure TMedia_dummy.CaptureStop;
+procedure TAudio_Dummy.CaptureStop;
begin
end;
-procedure TMedia_dummy.GetFFTData(var data: TFFTData);
+procedure TAudio_Dummy.GetFFTData(var data: TFFTData);
begin
end;
-function TMedia_dummy.GetPCMData(var data: TPCMData): Cardinal;
+function TAudio_Dummy.GetPCMData(var data: TPCMData): Cardinal;
begin
Result := 0;
end;
// IAudioPlayback
-function TMedia_dummy.InitializePlayback: boolean;
+function TAudio_Dummy.InitializePlayback: boolean;
begin
SetLength(DummyOutputDeviceList, 1);
DummyOutputDeviceList[0] := TAudioOutputDevice.Create();
@@ -196,73 +221,152 @@ begin
Result := true;
end;
-function TMedia_dummy.FinalizePlayback: boolean;
+function TAudio_Dummy.FinalizePlayback: boolean;
begin
Result := true;
end;
-function TMedia_dummy.GetOutputDeviceList(): TAudioOutputDeviceList;
+function TAudio_Dummy.GetOutputDeviceList(): TAudioOutputDeviceList;
begin
Result := DummyOutputDeviceList;
end;
-procedure TMedia_dummy.SetAppVolume(Volume: single);
+procedure TAudio_Dummy.SetAppVolume(Volume: single);
+begin
+end;
+
+procedure TAudio_Dummy.SetVolume(Volume: single);
begin
end;
-procedure TMedia_dummy.SetVolume(Volume: single);
+procedure TAudio_Dummy.SetLoop(Enabled: boolean);
begin
end;
-procedure TMedia_dummy.SetLoop(Enabled: boolean);
+function TAudio_Dummy.GetLoop(): boolean;
begin
+ Result := false;
end;
-procedure TMedia_dummy.FadeIn(Time: real; TargetVolume: single);
+procedure TAudio_Dummy.FadeIn(Time: real; TargetVolume: single);
begin
end;
-procedure TMedia_dummy.Rewind;
+procedure TAudio_Dummy.Rewind;
begin
end;
-function TMedia_dummy.Finished: boolean;
+function TAudio_Dummy.Finished: boolean;
begin
Result := false;
end;
-function TMedia_dummy.Length: real;
+function TAudio_Dummy.Length: real;
begin
Result := 60;
end;
-function TMedia_dummy.OpenSound(const Filename: string): TAudioPlaybackStream;
+function TAudio_Dummy.OpenSound(const Filename: IPath): TAudioPlaybackStream;
begin
Result := nil;
end;
-procedure TMedia_dummy.CloseSound(var PlaybackStream: TAudioPlaybackStream);
+procedure TAudio_Dummy.CloseSound(var PlaybackStream: TAudioPlaybackStream);
begin
end;
-procedure TMedia_dummy.PlaySound(stream: TAudioPlaybackStream);
+procedure TAudio_Dummy.PlaySound(stream: TAudioPlaybackStream);
begin
end;
-procedure TMedia_dummy.StopSound(stream: TAudioPlaybackStream);
+procedure TAudio_Dummy.StopSound(stream: TAudioPlaybackStream);
begin
end;
-function TMedia_dummy.CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
+function TAudio_Dummy.CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
begin
Result := nil;
end;
-procedure TMedia_dummy.CloseVoiceStream(var VoiceStream: TAudioVoiceStream);
+procedure TAudio_Dummy.CloseVoiceStream(var VoiceStream: TAudioVoiceStream);
+begin
+end;
+
+
+{ TVideoPlayback_Dummy }
+
+procedure TVideo_Dummy.Close;
begin
end;
+procedure TVideo_Dummy.Play;
+begin
+end;
+
+procedure TVideo_Dummy.Pause;
+begin
+end;
+
+procedure TVideo_Dummy.Stop;
+begin
+end;
+
+procedure TVideo_Dummy.SetLoop(Enable: boolean);
+begin
+end;
+
+function TVideo_Dummy.GetLoop(): boolean;
+begin
+ Result := false;
+end;
+
+procedure TVideo_Dummy.SetPosition(Time: real);
+begin
+end;
+
+function TVideo_Dummy.GetPosition: real;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_Dummy.GetFrame(Time: Extended);
+begin
+end;
+
+procedure TVideo_Dummy.DrawGL(Screen: integer);
+begin
+end;
+
+
+{ TVideoPlayback_Dummy }
+
+constructor TVideoPlayback_Dummy.Create();
+begin
+end;
+
+function TVideoPlayback_Dummy.GetName: string;
+begin
+ Result := 'VideoDummy';
+end;
+
+function TVideoPlayback_Dummy.Init(): boolean;
+begin
+ Result := true;
+end;
+
+function TVideoPlayback_Dummy.Finalize(): boolean;
+begin
+ Result := true;
+end;
+
+function TVideoPlayback_Dummy.Open(const FileName: IPath): IVideo;
+begin
+ Result := TVideo_Dummy.Create;
+end;
+
+
initialization
- MediaManager.Add(TMedia_dummy.Create);
+ MediaManager.Add(TAudio_Dummy.Create);
+ MediaManager.Add(TVideoPlayback_Dummy.Create);
end.
diff --git a/cmake/src/media/UVideo.pas b/cmake/src/media/UVideo.pas
index f55690b2..c7d59fc8 100644
--- a/cmake/src/media/UVideo.pas
+++ b/cmake/src/media/UVideo.pas
@@ -22,7 +22,7 @@
* $URL$
* $Id$
*}
-
+
unit UVideo;
{*
@@ -69,8 +69,9 @@ type
implementation
uses
+ SysUtils,
+ Math,
SDL,
- textgl,
avcodec,
avformat,
avutil,
@@ -79,25 +80,36 @@ uses
{$IFDEF UseSWScale}
swscale,
{$ENDIF}
- UMediaCore_FFmpeg,
- math,
gl,
+ glu,
glext,
- SysUtils,
+ textgl,
+ UMediaCore_FFmpeg,
UCommon,
UConfig,
ULog,
UMusic,
UGraphicClasses,
- UGraphic;
+ UGraphic,
+ UPath;
+
+{$DEFINE PIXEL_FMT_BGR}
const
{$IFDEF PIXEL_FMT_BGR}
PIXEL_FMT_OPENGL = GL_BGR;
PIXEL_FMT_FFMPEG = PIX_FMT_BGR24;
+ PIXEL_FMT_SIZE = 3;
+
+ // looks strange on linux:
+ //PIXEL_FMT_OPENGL = GL_RGBA;
+ //PIXEL_FMT_FFMPEG = PIX_FMT_BGR32;
+ //PIXEL_FMT_SIZE = 4;
{$ELSE}
+ // looks strange on linux:
PIXEL_FMT_OPENGL = GL_RGB;
PIXEL_FMT_FFMPEG = PIX_FMT_RGB24;
+ PIXEL_FMT_SIZE = 3;
{$ENDIF}
type
@@ -106,11 +118,15 @@ type
Upper, Lower: double;
end;
- TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback )
+ IVideo_FFmpeg = interface (IVideo)
+ ['{E640E130-C8C0-4399-AF02-67A3569313AB}']
+ function Open(const FileName: IPath): boolean;
+ end;
+
+ TVideo_FFmpeg = class( TInterfacedObject, IVideo_FFmpeg )
private
fOpened: boolean; //**< stream successfully opened
fPaused: boolean; //**< stream paused
- fInitialized: boolean;
fEOF: boolean; //**< end-of-file state
fLoop: boolean; //**< looping enabled
@@ -135,43 +151,59 @@ type
fAspect: real; //**< width/height ratio
fAspectCorrection: TAspectCorrection;
-
+
fTimeBase: extended; //**< FFmpeg time base per time unit
- fTime: extended; //**< video time position (absolute)
+ fFrameTime: extended; //**< video time position (absolute)
fLoopTime: extended; //**< start time of the current loop
+ fPboEnabled: boolean;
+ fPboId: GLuint;
procedure Reset();
function DecodeFrame(): boolean;
procedure SynchronizeTime(Frame: PAVFrame; var pts: double);
procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords);
-
+
procedure ShowDebugInfo();
public
- function GetName: String;
+ constructor Create;
+ destructor Destroy; override;
+
+ function Open(const FileName: IPath): boolean;
+ procedure Close;
+
+ procedure Play;
+ procedure Pause;
+ procedure Stop;
+
+ procedure SetLoop(Enable: boolean);
+ function GetLoop(): boolean;
- function Init(): boolean;
- function Finalize: boolean;
+ procedure SetPosition(Time: real);
+ function GetPosition: real;
- function Open(const aFileName : string): boolean; // true if succeed
- procedure Close;
+ procedure GetFrame(Time: Extended);
+ procedure DrawGL(Screen: integer);
+ end;
+
+ TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback )
+ private
+ fInitialized: boolean;
- procedure Play;
- procedure Pause;
- procedure Stop;
+ public
+ function GetName: String;
- procedure SetPosition(Time: real);
- function GetPosition: real;
+ function Init(): boolean;
+ function Finalize: boolean;
- procedure GetFrame(Time: Extended);
- procedure DrawGL(Screen: integer);
+ function Open(const FileName : IPath): IVideo;
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;
@@ -218,53 +250,56 @@ begin
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();
+function TVideoPlayback_FFmpeg.Open(const FileName : IPath): IVideo;
+var
+ Video: IVideo_FFmpeg;
begin
- // close previously opened video
- Close();
+ Video := TVideo_FFmpeg.Create;
+ if Video.Open(FileName) then
+ Result := Video
+ else
+ Result := nil;
+end;
- fOpened := False;
- fPaused := False;
- fTimeBase := 0;
- fTime := 0;
- fStream := nil;
- fStreamIndex := -1;
- fFrameTexValid := false;
- fEOF := false;
+{* TVideo_FFmpeg *}
- // TODO: do we really want this by default?
- fLoop := true;
- fLoopTime := 0;
-
- fAspectCorrection := acoCrop;
+constructor TVideo_FFmpeg.Create;
+begin
+ glGenTextures(1, PGLuint(@fFrameTex));
+ Reset();
end;
-function TVideoPlayback_FFmpeg.Open(const aFileName : string): boolean; // true if succeed
+destructor TVideo_FFmpeg.Destroy;
+begin
+ Close();
+ glDeleteTextures(1, PGLuint(@fFrameTex));
+end;
+
+function TVideo_FFmpeg.Open(const FileName : IPath): boolean;
var
errnum: Integer;
+ glErr: GLenum;
AudioStreamIndex: integer;
begin
Result := false;
-
Reset();
- errnum := av_open_input_file(fFormatContext, PChar(aFileName), nil, 0, nil);
+ fPboEnabled := PboSupported;
+
+ // use custom 'ufile' protocol for UTF-8 support
+ errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil);
if (errnum <> 0) then
begin
- Log.LogError('Failed to open file "'+aFileName+'" ('+FFmpegCore.GetErrorString(errnum)+')');
+ Log.LogError('Failed to open file "'+ FileName.ToNative +'" ('+FFmpegCore.GetErrorString(errnum)+')');
Exit;
end;
@@ -409,20 +444,63 @@ begin
fTexWidth := Round(Power(2, Ceil(Log2(fCodecContext^.width))));
fTexHeight := Round(Power(2, Ceil(Log2(fCodecContext^.height))));
+ if (fPboEnabled) then
+ begin
+ glGetError();
+
+ glGenBuffersARB(1, @fPboId);
+ glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, fPboId);
+ glBufferDataARB(
+ GL_PIXEL_UNPACK_BUFFER_ARB,
+ fCodecContext^.width * fCodecContext^.height * PIXEL_FMT_SIZE,
+ nil,
+ GL_STREAM_DRAW_ARB);
+ glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
+
+ glErr := glGetError();
+ if (glErr <> GL_NO_ERROR) then
+ begin
+ fPboEnabled := false;
+ Log.LogError('PBO initialization failed: ' + gluErrorString(glErr), 'TVideo_FFmpeg.Open');
+ end;
+ end;
+
// 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;
+ fOpened := true;
Result := true;
end;
-procedure TVideoPlayback_FFmpeg.Close;
+procedure TVideo_FFmpeg.Reset();
+begin
+ // close previously opened video
+ Close();
+
+ fOpened := False;
+ fPaused := False;
+ fTimeBase := 0;
+ fFrameTime := 0;
+ fStream := nil;
+ fStreamIndex := -1;
+ fFrameTexValid := false;
+
+ fEOF := false;
+
+ fLoop := false;
+ fLoopTime := 0;
+
+ fPboId := 0;
+
+ fAspectCorrection := acoCrop;
+end;
+
+procedure TVideo_FFmpeg.Close;
begin
if (fFrameBuffer <> nil) then
av_free(fFrameBuffer);
@@ -434,7 +512,7 @@ begin
fAVFrame := nil;
fAVFrameRGB := nil;
fFrameBuffer := nil;
-
+
if (fCodecContext <> nil) then
begin
// avcodec_close() is not thread-safe
@@ -452,37 +530,40 @@ begin
fCodecContext := nil;
fFormatContext := nil;
+ if (fPboId <> 0) then
+ glDeleteBuffersARB(1, @fPboId);
+
fOpened := False;
end;
-procedure TVideoPlayback_FFmpeg.SynchronizeTime(Frame: PAVFrame; var pts: double);
+procedure TVideo_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;
+ fFrameTime := pts;
end else
begin
// if we aren't given a pts, set it to the clock
- pts := fTime;
+ pts := fFrameTime;
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;
+ fFrameTime := fFrameTime + 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
+ * The decoded frame is stored in fAVFrame. fFrameTime is updated to the new frame's
* time.
- * @param pts will be updated to the presentation time of the decoded frame.
+ * @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;
+function TVideo_FFmpeg.DecodeFrame(): boolean;
var
FrameFinished: Integer;
VideoPktPts: int64;
@@ -520,7 +601,10 @@ begin
// check for errors
if (url_ferror(pbIOCtx) <> 0) then
+ begin
+ Log.LogError('Video decoding file error', 'TVideoPlayback_FFmpeg.DecodeFrame');
Exit;
+ end;
// url_feof() does not detect an EOF for some mov-files (e.g. deluxe.mov)
// so we have to do it this way.
@@ -531,18 +615,9 @@ begin
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
+ // error occured, log and exit
+ Log.LogError('Video decoding error', 'TVideoPlayback_FFmpeg.DecodeFrame');
Exit;
-
end;
// if we got a packet from the video stream, then decode it
@@ -573,6 +648,10 @@ begin
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
@@ -587,16 +666,18 @@ begin
Result := true;
end;
-procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended);
+procedure TVideo_FFmpeg.GetFrame(Time: Extended);
var
errnum: Integer;
- NewTime: Extended;
- TimeDifference: Extended;
+ glErr: GLenum;
+ CurrentTime: Extended;
+ TimeDiff: Extended;
DropFrameCount: Integer;
i: Integer;
Success: boolean;
+ BufferPtr: PGLvoid;
const
- FRAME_DROPCOUNT = 3;
+ SKIP_FRAME_DIFF = 0.010; // start skipping if we are >= 10ms too late
begin
if not fOpened then
Exit;
@@ -604,24 +685,37 @@ begin
if fPaused then
Exit;
+ {*
+ * TODO:
+ * Check if it is correct to assume that fTimeBase is the time of one frame?
+ * The tutorial and FFPlay do not make this assumption.
+ *}
+
+ {*
+ * Synchronization - begin
+ *}
+
// requested stream position (relative to the last loop's start)
- NewTime := Time - fLoopTime;
+ if (fLoop) then
+ CurrentTime := Time - fLoopTime
+ else
+ CurrentTime := Time;
// check if current texture still contains the active frame
if (fFrameTexValid) then
begin
// time since the last frame was returned
- TimeDifference := NewTime - fTime;
+ TimeDiff := CurrentTime - fFrameTime;
{$IFDEF DebugDisplay}
DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak +
- 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak +
+ 'VideoTime: '+inttostr(floor(fFrameTime*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
+ // check if time has reached the next frame
+ if (TimeDiff < fTimeBase) then
begin
{$ifdef DebugFrames}
// frame delay debug display
@@ -631,7 +725,7 @@ begin
{$IFDEF DebugDisplay}
DebugWriteln('not getting new frame' + sLineBreak +
'Time: '+inttostr(floor(Time*1000)) + sLineBreak +
- 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak +
+ 'VideoTime: '+inttostr(floor(fFrameTime*1000)) + sLineBreak +
'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
'TimeDiff: '+inttostr(floor(TimeDifference*1000)));
{$endif}
@@ -644,13 +738,16 @@ begin
{$IFDEF VideoBenchmark}
Log.BenchmarkStart(15);
{$ENDIF}
-
- // fetch new frame (updates fTime)
+
+ // fetch new frame (updates fFrameTime)
Success := DecodeFrame();
- TimeDifference := NewTime - fTime;
+ TimeDiff := CurrentTime - fFrameTime;
// check if we have to skip frames
- if (TimeDifference >= FRAME_DROPCOUNT*fTimeBase) then
+ // Either if we are one frame behind or if the skip threshold has been reached.
+ // Do not skip if the difference is less than fTimeBase as there is no next frame.
+ // Note: We assume that fTimeBase is the length of one frame.
+ if (TimeDiff >= Max(fTimeBase, SKIP_FRAME_DIFF)) then
begin
{$IFDEF DebugFrames}
//frame drop debug display
@@ -663,15 +760,15 @@ begin
{$endif}
// update video-time
- DropFrameCount := Trunc(TimeDifference / fTimeBase);
- fTime := fTime + DropFrameCount*fTimeBase;
+ DropFrameCount := Trunc(TimeDiff / fTimeBase);
+ fFrameTime := fFrameTime + 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
+ // skip frames
+ for i := 1 to DropFrameCount do
Success := DecodeFrame();
end;
- // check if we got an EOF or error
+ // check if we got an EOF or error
if (not Success) then
begin
if fLoop then
@@ -679,12 +776,16 @@ 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.
+ // determine the position in the stream (fFrameTime-fLoopTime) later.
fLoopTime := Time;
end;
Exit;
end;
+ {*
+ * Synchronization - end
+ *}
+
// TODO: support for pan&scan
//if (fAVFrame.pan_scan <> nil) then
//begin
@@ -693,11 +794,11 @@ begin
// otherwise we convert the pixeldata from YUV to RGB
{$IFDEF UseSWScale}
- errnum := sws_scale(fSwScaleContext, @(fAVFrame.data), @(fAVFrame.linesize),
+ errnum := sws_scale(fSwScaleContext, @fAVFrame.data, @fAVFrame.linesize,
0, fCodecContext^.Height,
- @(fAVFrameRGB.data), @(fAVFrameRGB.linesize));
+ @fAVFrameRGB.data, @fAVFrameRGB.linesize);
{$ELSE}
- // img_convert from lib/ffmpeg/avcodec.pas is actually deprecated.
+ // 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
@@ -707,7 +808,7 @@ begin
PAVPicture(fAVFrame), fCodecContext^.pix_fmt,
fCodecContext^.width, fCodecContext^.height);
{$ENDIF}
-
+
if (errnum < 0) then
begin
Log.LogError('Image conversion failed', 'TVideoPlayback_ffmpeg.GetFrame');
@@ -723,10 +824,51 @@ begin
// 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]);
+ // glTexEnvi with GL_REPLACE might give a small speed improvement
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+
+ if (not fPboEnabled) then
+ begin
+ 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]);
+ end
+ else // fPboEnabled
+ begin
+ glGetError();
+
+ glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, fPboId);
+ glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB,
+ fCodecContext^.height * fCodecContext^.width * PIXEL_FMT_SIZE,
+ nil,
+ GL_STREAM_DRAW_ARB);
+
+ bufferPtr := glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
+ if(bufferPtr <> nil) then
+ begin
+ Move(fAVFrameRGB^.data[0]^, bufferPtr^,
+ fCodecContext^.height * fCodecContext^.width * PIXEL_FMT_SIZE);
+
+ // release pointer to mapping buffer
+ glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB);
+ end;
+
+ glBindTexture(GL_TEXTURE_2D, fFrameTex);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
+ fCodecContext^.width, fCodecContext^.height,
+ PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, nil);
+
+ glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glErr := glGetError();
+ if (glErr <> GL_NO_ERROR) then
+ Log.LogError('PBO texture stream error: ' + gluErrorString(glErr), 'TVideo_FFmpeg.GetFrame');
+ end;
+
+ // reset to default
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if (not fFrameTexValid) then
fFrameTexValid := true;
@@ -743,7 +885,7 @@ begin
{$ENDIF}
end;
-procedure TVideoPlayback_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords);
+procedure TVideo_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords);
var
ScreenAspect: double; // aspect of screen resolution
ScaledVideoWidth, ScaledVideoHeight: double;
@@ -793,7 +935,7 @@ begin
TexRect.Lower := fCodecContext^.height / fTexHeight;
end;
-procedure TVideoPlayback_FFmpeg.DrawGL(Screen: integer);
+procedure TVideo_FFmpeg.DrawGL(Screen: integer);
var
ScreenRect: TRectCoords;
TexRect: TRectCoords;
@@ -856,10 +998,10 @@ begin
{$IFEND}
end;
-procedure TVideoPlayback_FFmpeg.ShowDebugInfo();
+procedure TVideo_FFmpeg.ShowDebugInfo();
begin
{$IFDEF Info}
- if (fTime+fTimeBase < 0) then
+ if (fFrameTime+fTimeBase < 0) then
begin
glColor4f(0.7, 1, 0.3, 1);
SetFontStyle (1);
@@ -893,28 +1035,39 @@ begin
{$ENDIF}
end;
-procedure TVideoPlayback_FFmpeg.Play;
+procedure TVideo_FFmpeg.Play;
begin
end;
-procedure TVideoPlayback_FFmpeg.Pause;
+procedure TVideo_FFmpeg.Pause;
begin
fPaused := not fPaused;
end;
-procedure TVideoPlayback_FFmpeg.Stop;
+procedure TVideo_FFmpeg.Stop;
+begin
+end;
+
+procedure TVideo_FFmpeg.SetLoop(Enable: boolean);
begin
+ fLoop := Enable;
+ fLoopTime := 0;
+end;
+
+function TVideo_FFmpeg.GetLoop(): boolean;
+begin
+ Result := fLoop;
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
+ * Note that fFrameTime is set to Time no matter if the actual position seeked to is
+ * at Time or the time of a preceding keyframe. fFrameTime 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);
+procedure TVideo_FFmpeg.SetPosition(Time: real);
var
SeekFlags: integer;
begin
@@ -936,9 +1089,9 @@ begin
// requested time, let the sync in GetFrame() do its job.
SeekFlags := AVSEEK_FLAG_BACKWARD;
- fTime := Time;
+ fFrameTime := Time;
fEOF := false;
- fFrameTexValid := false;
+ fFrameTexValid := false;
if (av_seek_frame(fFormatContext, fStreamIndex, Floor(Time/fTimeBase), SeekFlags) < 0) then
begin
@@ -949,9 +1102,9 @@ begin
avcodec_flush_buffers(fCodecContext);
end;
-function TVideoPlayback_FFmpeg.GetPosition: real;
+function TVideo_FFmpeg.GetPosition: real;
begin
- Result := fTime;
+ Result := fFrameTime;
end;
initialization
diff --git a/cmake/src/media/UVisualizer.pas b/cmake/src/media/UVisualizer.pas
index 37e0268a..4f553521 100644
--- a/cmake/src/media/UVisualizer.pas
+++ b/cmake/src/media/UVisualizer.pas
@@ -60,12 +60,17 @@ interface
{$I switches.inc}
+{.$DEFINE UseTexture}
+
uses
SDL,
UGraphicClasses,
textgl,
math,
gl,
+ {$IFDEF UseTexture}
+ glu,
+ {$ENDIF}
SysUtils,
UIni,
projectM,
@@ -77,6 +82,7 @@ uses
UGraphic,
UMain,
UConfig,
+ UPath,
ULog;
{$IF PROJECTM_VERSION < 1000000} // < 1.0
@@ -90,31 +96,29 @@ const
{$IFEND}
type
+ TProjectMState = ( pmPlay, pmStop, pmPause );
+
+type
TGLMatrix = array[0..3, 0..3] of GLdouble;
TGLMatrixStack = array of TGLMatrix;
type
- TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization )
+ TVideo_ProjectM = class( TInterfacedObject, IVideo )
private
- pm: TProjectM;
- ProjectMPath : string;
- Initialized: boolean;
-
- VisualizerStarted: boolean;
- VisualizerPaused: boolean;
+ fPm: TProjectM;
+ fProjectMPath : string;
- VisualTex: GLuint;
- PCMData: TPCMData;
- RndPCMcount: integer;
+ fState: TProjectMState;
- ModelviewMatrixStack: TGLMatrixStack;
- ProjectionMatrixStack: TGLMatrixStack;
- TextureMatrixStack: TGLMatrixStack;
+ fVisualTex: GLuint;
+ fPCMData: TPCMData;
+ fRndPCMcount: integer;
- procedure VisualizerStart;
- procedure VisualizerStop;
+ fModelviewMatrixStack: TGLMatrixStack;
+ fProjectionMatrixStack: TGLMatrixStack;
+ fTextureMatrixStack: TGLMatrixStack;
- procedure VisualizerTogglePause;
+ procedure InitProjectM;
function GetRandomPCMData(var Data: TPCMData): Cardinal;
@@ -125,12 +129,9 @@ type
procedure RestoreOpenGLState();
public
- function GetName: String;
+ constructor Create;
+ destructor Destroy; override;
- function Init(): boolean;
- function Finalize(): boolean;
-
- function Open(const aFileName : string): boolean; // true if succeed
procedure Close;
procedure Play;
@@ -140,10 +141,28 @@ type
procedure SetPosition(Time: real);
function GetPosition: real;
+ procedure SetLoop(Enable: boolean);
+ function GetLoop(): boolean;
+
procedure GetFrame(Time: Extended);
procedure DrawGL(Screen: integer);
end;
+ TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoVisualization )
+ private
+ fInitialized: boolean;
+
+ public
+ function GetName: String;
+
+ function Init(): boolean;
+ function Finalize(): boolean;
+
+ function Open(const aFileName: IPath): IVideo;
+ end;
+
+
+{ TVideoPlayback_ProjectM }
function TVideoPlayback_ProjectM.GetName: String;
begin
@@ -153,76 +172,100 @@ end;
function TVideoPlayback_ProjectM.Init(): boolean;
begin
Result := true;
-
- if (Initialized) then
+ if (fInitialized) then
Exit;
- Initialized := true;
+ fInitialized := true;
+end;
- RndPCMcount := 0;
+function TVideoPlayback_ProjectM.Finalize(): boolean;
+begin
+ Result := true;
+end;
+
+function TVideoPlayback_ProjectM.Open(const aFileName: IPath): IVideo;
+begin
+ Result := TVideo_ProjectM.Create;
+end;
- ProjectMPath := ProjectM_DataDir + PathDelim;
- VisualizerStarted := False;
- VisualizerPaused := False;
+{ TVideo_ProjectM }
+
+constructor TVideo_ProjectM.Create;
+begin
+ fRndPCMcount := 0;
+
+ fProjectMPath := ProjectM_DataDir + PathDelim;
+
+ fState := pmStop;
{$IFDEF UseTexture}
- glGenTextures(1, PglUint(@VisualTex));
- glBindTexture(GL_TEXTURE_2D, VisualTex);
+ glGenTextures(1, PglUint(@fVisualTex));
+ glBindTexture(GL_TEXTURE_2D, fVisualTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
{$ENDIF}
+
+ InitProjectM();
end;
-function TVideoPlayback_ProjectM.Finalize(): boolean;
+destructor TVideo_ProjectM.Destroy;
begin
- VisualizerStop();
+ Close();
{$IFDEF UseTexture}
- glDeleteTextures(1, PglUint(@VisualTex));
+ glDeleteTextures(1, PglUint(@fVisualTex));
{$ENDIF}
- Result := true;
end;
-function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed
+procedure TVideo_ProjectM.Close;
begin
- Result := false;
+ FreeAndNil(fPm);
end;
-procedure TVideoPlayback_ProjectM.Close;
+procedure TVideo_ProjectM.Play;
begin
- VisualizerStop();
+ if (fState = pmStop) and (assigned(fPm)) then
+ fPm.RandomPreset();
+ fState := pmPlay;
end;
-procedure TVideoPlayback_ProjectM.Play;
+procedure TVideo_ProjectM.Pause;
begin
- VisualizerStart();
+ if (fState = pmPlay) then
+ fState := pmPause
+ else if (fState = pmPause) then
+ fState := pmPlay;
end;
-procedure TVideoPlayback_ProjectM.Pause;
+procedure TVideo_ProjectM.Stop;
begin
- VisualizerTogglePause();
+ fState := pmStop;
end;
-procedure TVideoPlayback_ProjectM.Stop;
+procedure TVideo_ProjectM.SetPosition(Time: real);
begin
- VisualizerStop();
+ if assigned(fPm) then
+ fPm.RandomPreset();
end;
-procedure TVideoPlayback_ProjectM.SetPosition(Time: real);
+function TVideo_ProjectM.GetPosition: real;
begin
- if assigned(pm) then
- pm.RandomPreset();
+ Result := 0;
end;
-function TVideoPlayback_ProjectM.GetPosition: real;
+procedure TVideo_ProjectM.SetLoop(Enable: boolean);
begin
- Result := 0;
+end;
+
+function TVideo_ProjectM.GetLoop(): boolean;
+begin
+ Result := true;
end;
{**
* Returns the stack depth of the given OpenGL matrix mode stack.
*}
-function TVideoPlayback_ProjectM.GetMatrixStackDepth(MatrixMode: GLenum): GLint;
+function TVideo_ProjectM.GetMatrixStackDepth(MatrixMode: GLenum): GLint;
begin
// get number of matrices on stack
case (MatrixMode) of
@@ -252,7 +295,7 @@ end;
* 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;
+procedure TVideo_ProjectM.SaveMatrixStack(MatrixMode: GLenum;
var MatrixStack: TGLMatrixStack);
var
I: integer;
@@ -288,7 +331,7 @@ end;
{**
* Restores the OpenGL matrix stack stored with SaveMatrixStack.
*}
-procedure TVideoPlayback_ProjectM.RestoreMatrixStack(MatrixMode: GLenum;
+procedure TVideo_ProjectM.RestoreMatrixStack(MatrixMode: GLenum;
var MatrixStack: TGLMatrixStack);
var
I: integer;
@@ -324,15 +367,15 @@ end;
* - Modelview-matrix is pushed to the Modelview-stack
* - the OpenGL error-state (glGetError) is cleared
*}
-procedure TVideoPlayback_ProjectM.SaveOpenGLState();
+procedure TVideo_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);
+ SaveMatrixStack(GL_PROJECTION, fProjectionMatrixStack);
+ SaveMatrixStack(GL_MODELVIEW, fModelviewMatrixStack);
+ SaveMatrixStack(GL_TEXTURE, fTextureMatrixStack);
glMatrixMode(GL_MODELVIEW);
@@ -344,15 +387,15 @@ end;
* Restores the OpenGL state saved by SaveOpenGLState()
* and resets the error-state.
*}
-procedure TVideoPlayback_ProjectM.RestoreOpenGLState();
+procedure TVideo_ProjectM.RestoreOpenGLState();
begin
// reset OpenGL error-state
glGetError();
// restore matrix stacks
- RestoreMatrixStack(GL_PROJECTION, ProjectionMatrixStack);
- RestoreMatrixStack(GL_MODELVIEW, ModelviewMatrixStack);
- RestoreMatrixStack(GL_TEXTURE, TextureMatrixStack);
+ RestoreMatrixStack(GL_PROJECTION, fProjectionMatrixStack);
+ RestoreMatrixStack(GL_MODELVIEW, fModelviewMatrixStack);
+ RestoreMatrixStack(GL_TEXTURE, fTextureMatrixStack);
// restore all OpenGL state-machine attributes
// (also restores the matrix mode)
@@ -360,22 +403,19 @@ begin
glPopAttrib();
end;
-procedure TVideoPlayback_ProjectM.VisualizerStart;
+procedure TVideo_ProjectM.InitProjectM;
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');
+ fPm := TProjectM.Create(fProjectMPath + 'config.inp');
{$ELSE}
- pm := TProjectM.Create(
+ fPm := TProjectM.Create(
meshX, meshY, fps, textureSize, ScreenW, ScreenH,
- ProjectMPath + 'presets', ProjectMPath + 'fonts');
+ fProjectMPath + 'presets', fProjectMPath + 'fonts');
{$IFEND}
except on E: Exception do
begin
@@ -386,72 +426,51 @@ begin
end;
// initialize OpenGL
- pm.ResetGL(ScreenW, ScreenH);
+ fPm.ResetGL(ScreenW, ScreenH);
// skip projectM default-preset
- pm.RandomPreset();
+ fPm.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;
+ fPm.RenderFrame();
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);
+procedure TVideo_ProjectM.GetFrame(Time: Extended);
var
nSamples: cardinal;
begin
- if not VisualizerStarted then
- Exit;
-
- if VisualizerPaused then
+ if (fState <> pmPlay) then
Exit;
// get audio data
- nSamples := AudioPlayback.GetPCMData(PcmData);
+ nSamples := AudioPlayback.GetPCMData(fPCMData);
// generate some data if non is available
if (nSamples = 0) then
- nSamples := GetRandomPCMData(PcmData);
+ nSamples := GetRandomPCMData(fPCMData);
// send audio-data to projectM
if (nSamples > 0) then
- pm.AddPCM16Data(PSmallInt(@PcmData), nSamples);
+ fPm.AddPCM16Data(PSmallInt(@fPCMData), nSamples);
// store OpenGL state (might be messed up otherwise)
SaveOpenGLState();
try
// setup projectM's OpenGL state
- pm.ResetGL(ScreenW, ScreenH);
+ fPm.ResetGL(ScreenW, ScreenH);
// let projectM render a frame
- pm.RenderFrame();
+ fPm.RenderFrame();
{$IFDEF UseTexture}
- glBindTexture(GL_TEXTURE_2D, VisualTex);
+ glBindTexture(GL_TEXTURE_2D, fVisualTex);
glFlush();
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, VisualWidth, VisualHeight, 0);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, fVisualWidth, fVisualHeight, 0);
{$ENDIF}
finally
// restore USDX OpenGL state
@@ -466,7 +485,7 @@ 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);
+procedure TVideo_ProjectM.DrawGL(Screen: integer);
begin
{$IFDEF UseTexture}
// have a nice black background to draw on
@@ -477,7 +496,7 @@ begin
end;
// exit if there's nothing to draw
- if not VisualizerStarted then
+ if (fState <> pmPlay) then
Exit;
// setup display
@@ -496,7 +515,7 @@ begin
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glBindTexture(GL_TEXTURE_2D, VisualTex);
+ glBindTexture(GL_TEXTURE_2D, fVisualTex);
glColor4f(1, 1, 1, 1);
// draw projectM frame
@@ -523,12 +542,12 @@ 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;
+function TVideo_ProjectM.GetRandomPCMData(var Data: TPCMData): Cardinal;
var
i: integer;
begin
// Produce some fake PCM data
- if (RndPCMcount mod 500 = 0) then
+ if (fRndPCMcount mod 500 = 0) then
begin
FillChar(Data, SizeOf(TPCMData), 0);
end
@@ -540,7 +559,7 @@ begin
Data[i][1] := Random(High(Word)+1);
end;
end;
- Inc(RndPCMcount);
+ Inc(fRndPCMcount);
Result := 512;
end;