From 4c6d2f4cd49a4f6016026ef81db31c3656bb5e8c Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 13 Sep 2008 08:27:50 +0000 Subject: Media modules moved from base to media git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1374 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioConverter.pas | 458 +++++++++++++ src/media/UAudioCore_Bass.pas | 123 ++++ src/media/UAudioCore_Portaudio.pas | 257 ++++++++ src/media/UAudioDecoder_Bass.pas | 242 +++++++ src/media/UAudioDecoder_FFmpeg.pas | 1114 +++++++++++++++++++++++++++++++ src/media/UAudioInput_Bass.pas | 481 ++++++++++++++ src/media/UAudioInput_Portaudio.pas | 474 +++++++++++++ src/media/UAudioPlaybackBase.pas | 292 ++++++++ src/media/UAudioPlayback_Bass.pas | 731 +++++++++++++++++++++ src/media/UAudioPlayback_Portaudio.pas | 361 ++++++++++ src/media/UAudioPlayback_SDL.pas | 160 +++++ src/media/UAudioPlayback_SoftMixer.pas | 1132 ++++++++++++++++++++++++++++++++ src/media/UMediaCore_FFmpeg.pas | 405 ++++++++++++ src/media/UMediaCore_SDL.pas | 38 ++ src/media/UMedia_dummy.pas | 243 +++++++ src/media/UVideo.pas | 828 +++++++++++++++++++++++ src/media/UVisualizer.pas | 442 +++++++++++++ 17 files changed, 7781 insertions(+) create mode 100644 src/media/UAudioConverter.pas create mode 100644 src/media/UAudioCore_Bass.pas create mode 100644 src/media/UAudioCore_Portaudio.pas create mode 100644 src/media/UAudioDecoder_Bass.pas create mode 100644 src/media/UAudioDecoder_FFmpeg.pas create mode 100644 src/media/UAudioInput_Bass.pas create mode 100644 src/media/UAudioInput_Portaudio.pas create mode 100644 src/media/UAudioPlaybackBase.pas create mode 100644 src/media/UAudioPlayback_Bass.pas create mode 100644 src/media/UAudioPlayback_Portaudio.pas create mode 100644 src/media/UAudioPlayback_SDL.pas create mode 100644 src/media/UAudioPlayback_SoftMixer.pas create mode 100644 src/media/UMediaCore_FFmpeg.pas create mode 100644 src/media/UMediaCore_SDL.pas create mode 100644 src/media/UMedia_dummy.pas create mode 100644 src/media/UVideo.pas create mode 100644 src/media/UVisualizer.pas (limited to 'src/media') diff --git a/src/media/UAudioConverter.pas b/src/media/UAudioConverter.pas new file mode 100644 index 00000000..5647f27b --- /dev/null +++ b/src/media/UAudioConverter.pas @@ -0,0 +1,458 @@ +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: PChar; OutputBuffer: PChar; 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: PChar; OutputBuffer: PChar; 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: PChar; OutputBuffer: PChar; 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: PChar; OutputBuffer: PChar; 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: PChar; OutputBuffer: PChar; 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: PChar; OutputBuffer: PChar; var InputSize: integer): integer; +var + FloatInputBuffer: PSingle; + FloatOutputBuffer: PSingle; + TempBuffer: PChar; + 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 new file mode 100644 index 00000000..beb2db16 --- /dev/null +++ b/src/media/UAudioCore_Bass.pas @@ -0,0 +1,123 @@ +unit UAudioCore_Bass; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + Classes, + SysUtils, + UMusic, + bass; // (Note: DWORD is defined here) + +type + TAudioCore_Bass = class + public + constructor Create(); + class function GetInstance(): TAudioCore_Bass; + function ErrorGetString(): string; overload; + function ErrorGetString(errCode: integer): string; overload; + function ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean; + function ConvertBASSFlagsToAudioFormat(Flags: DWORD; out Format: TAudioSampleFormat): boolean; + end; + +implementation + +uses + UMain, + ULog; + +var + Instance: TAudioCore_Bass; + +constructor TAudioCore_Bass.Create(); +begin + inherited; +end; + +class function TAudioCore_Bass.GetInstance(): TAudioCore_Bass; +begin + if (not Assigned(Instance)) then + Instance := TAudioCore_Bass.Create(); + Result := Instance; +end; + +function TAudioCore_Bass.ErrorGetString(): string; +begin + Result := ErrorGetString(BASS_ErrorGetCode()); +end; + +function TAudioCore_Bass.ErrorGetString(errCode: integer): string; +begin + case errCode of + BASS_OK: result := 'No error'; + BASS_ERROR_MEM: result := 'Insufficient memory'; + BASS_ERROR_FILEOPEN: result := 'File could not be opened'; + BASS_ERROR_DRIVER: result := 'Device driver not available'; + BASS_ERROR_BUFLOST: result := 'Buffer lost'; + BASS_ERROR_HANDLE: result := 'Invalid Handle'; + BASS_ERROR_FORMAT: result := 'Sample-Format not supported'; + BASS_ERROR_POSITION: result := 'Illegal position'; + BASS_ERROR_INIT: result := 'BASS_Init has not been successfully called'; + BASS_ERROR_START: result := 'Paused/stopped'; + BASS_ERROR_ALREADY: result := 'Already created/used'; + BASS_ERROR_NOCHAN: result := 'No free channels'; + BASS_ERROR_ILLTYPE: result := 'Type is invalid'; + BASS_ERROR_ILLPARAM: result := 'Illegal parameter'; + BASS_ERROR_NO3D: result := 'No 3D support'; + BASS_ERROR_NOEAX: result := 'No EAX support'; + BASS_ERROR_DEVICE: result := 'Invalid device number'; + BASS_ERROR_NOPLAY: result := 'Channel not playing'; + BASS_ERROR_FREQ: result := 'Freq out of range'; + BASS_ERROR_NOTFILE: result := 'Not a file stream'; + BASS_ERROR_NOHW: result := 'No hardware support'; + BASS_ERROR_EMPTY: result := 'Is empty'; + BASS_ERROR_NONET: result := 'Network unavailable'; + BASS_ERROR_CREATE: result := 'Creation error'; + BASS_ERROR_NOFX: result := 'DX8 effects unavailable'; + BASS_ERROR_NOTAVAIL: result := 'Not available'; + BASS_ERROR_DECODE: result := 'Is a decoding channel'; + BASS_ERROR_DX: result := 'Insufficient version of DirectX'; + BASS_ERROR_TIMEOUT: result := 'Timeout'; + BASS_ERROR_FILEFORM: result := 'File-Format not recognised/supported'; + BASS_ERROR_SPEAKER: result := 'Requested speaker(s) not support'; + BASS_ERROR_VERSION: result := 'Version error'; + BASS_ERROR_CODEC: result := 'Codec not available/supported'; + BASS_ERROR_ENDED: result := 'The channel/file has ended'; + BASS_ERROR_UNKNOWN: result := 'Unknown error'; + else result := 'Unknown error'; + end; +end; + +function TAudioCore_Bass.ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean; +begin + case Format of + asfS16: Flags := 0; + asfFloat: Flags := BASS_SAMPLE_FLOAT; + asfU8: Flags := BASS_SAMPLE_8BITS; + else begin + Result := false; + Exit; + end; + end; + + Result := true; +end; + +function TAudioCore_Bass.ConvertBASSFlagsToAudioFormat(Flags: DWORD; out Format: TAudioSampleFormat): boolean; +begin + if ((Flags and BASS_SAMPLE_FLOAT) <> 0) then + Format := asfFloat + else if ((Flags and BASS_SAMPLE_8BITS) <> 0) then + Format := asfU8 + else + Format := asfS16; + + Result := true; +end; + +end. diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas new file mode 100644 index 00000000..cd279d99 --- /dev/null +++ b/src/media/UAudioCore_Portaudio.pas @@ -0,0 +1,257 @@ +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 new file mode 100644 index 00000000..dba1fde4 --- /dev/null +++ b/src/media/UAudioDecoder_Bass.pas @@ -0,0 +1,242 @@ +unit UAudioDecoder_Bass; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +implementation + +uses + Classes, + SysUtils, + UMain, + UMusic, + UAudioCore_Bass, + ULog, + bass; + +type + TBassDecodeStream = class(TAudioDecodeStream) + private + Handle: HSTREAM; + FormatInfo : TAudioFormatInfo; + Error: boolean; + public + constructor Create(Handle: HSTREAM); + destructor Destroy(); override; + + procedure Close(); override; + + function GetLength(): real; override; + function GetAudioFormatInfo(): TAudioFormatInfo; override; + function GetPosition: real; override; + procedure SetPosition(Time: real); override; + function GetLoop(): boolean; override; + procedure SetLoop(Enabled: boolean); override; + function IsEOF(): boolean; override; + function IsError(): boolean; override; + + function ReadData(Buffer: PChar; BufSize: integer): integer; override; + end; + +type + TAudioDecoder_Bass = class( TInterfacedObject, IAudioDecoder ) + public + function GetName: string; + + function InitializeDecoder(): boolean; + function FinalizeDecoder(): boolean; + function Open(const Filename: string): TAudioDecodeStream; + end; + +var + BassCore: TAudioCore_Bass; + + +{ TBassDecodeStream } + +constructor TBassDecodeStream.Create(Handle: HSTREAM); +var + ChannelInfo: BASS_CHANNELINFO; + Format: TAudioSampleFormat; +begin + inherited Create(); + Self.Handle := Handle; + + // setup format info + if (not BASS_ChannelGetInfo(Handle, ChannelInfo)) then + begin + raise Exception.Create('Failed to open decode-stream'); + end; + BassCore.ConvertBASSFlagsToAudioFormat(ChannelInfo.flags, Format); + FormatInfo := TAudioFormatInfo.Create(ChannelInfo.chans, ChannelInfo.freq, format); + + Error := false; +end; + +destructor TBassDecodeStream.Destroy(); +begin + Close(); + inherited; +end; + +procedure TBassDecodeStream.Close(); +begin + if (Handle <> 0) then + begin + BASS_StreamFree(Handle); + Handle := 0; + end; + PerformOnClose(); + FreeAndNil(FormatInfo); + Error := false; +end; + +function TBassDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo; +begin + Result := FormatInfo; +end; + +function TBassDecodeStream.GetLength(): real; +var + bytes: QWORD; +begin + bytes := BASS_ChannelGetLength(Handle, BASS_POS_BYTE); + Result := BASS_ChannelBytes2Seconds(Handle, bytes); +end; + +function TBassDecodeStream.GetPosition: real; +var + bytes: QWORD; +begin + bytes := BASS_ChannelGetPosition(Handle, BASS_POS_BYTE); + Result := BASS_ChannelBytes2Seconds(Handle, bytes); +end; + +procedure TBassDecodeStream.SetPosition(Time: real); +var + bytes: QWORD; +begin + bytes := BASS_ChannelSeconds2Bytes(Handle, Time); + BASS_ChannelSetPosition(Handle, bytes, BASS_POS_BYTE); +end; + +function TBassDecodeStream.GetLoop(): boolean; +var + flags: DWORD; +begin + // retrieve channel flags + flags := BASS_ChannelFlags(Handle, 0, 0); + if (flags = DWORD(-1)) then + begin + Log.LogError('BASS_ChannelFlags: ' + BassCore.ErrorGetString(), 'TBassDecodeStream.GetLoop'); + Result := false; + Exit; + end; + Result := (flags and BASS_SAMPLE_LOOP) <> 0; +end; + +procedure TBassDecodeStream.SetLoop(Enabled: boolean); +var + flags: DWORD; +begin + // set/unset loop-flag + if (Enabled) then + flags := BASS_SAMPLE_LOOP + else + flags := 0; + + // set new flag-bits + if (BASS_ChannelFlags(Handle, flags, BASS_SAMPLE_LOOP) = DWORD(-1)) then + begin + Log.LogError('BASS_ChannelFlags: ' + BassCore.ErrorGetString(), 'TBassDecodeStream.SetLoop'); + Exit; + end; +end; + +function TBassDecodeStream.IsEOF(): boolean; +begin + Result := (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_STOPPED); +end; + +function TBassDecodeStream.IsError(): boolean; +begin + Result := Error; +end; + +function TBassDecodeStream.ReadData(Buffer: PChar; BufSize: integer): integer; +begin + Result := BASS_ChannelGetData(Handle, Buffer, BufSize); + // check error state (do not handle EOF as error) + if ((Result = -1) and (BASS_ErrorGetCode() <> BASS_ERROR_ENDED)) then + Error := true + else + Error := false; +end; + + +{ TAudioDecoder_Bass } + +function TAudioDecoder_Bass.GetName: String; +begin + result := 'BASS_Decoder'; +end; + +function TAudioDecoder_Bass.InitializeDecoder(): boolean; +begin + BassCore := TAudioCore_Bass.GetInstance(); + Result := true; +end; + +function TAudioDecoder_Bass.FinalizeDecoder(): boolean; +begin + Result := true; +end; + +function TAudioDecoder_Bass.Open(const Filename: string): TAudioDecodeStream; +var + Stream: HSTREAM; + ChannelInfo: BASS_CHANNELINFO; + FileExt: string; +begin + Result := nil; + + // check if BASS was initialized + // in case the decoder is not used with BASS playback, init the NO_SOUND device + if ((integer(BASS_GetDevice) = -1) and (BASS_ErrorGetCode() = BASS_ERROR_INIT)) then + BASS_Init(0, 44100, 0, 0, nil); + + // TODO: use BASS_STREAM_PRESCAN for accurate seeking in VBR-files? + // disadvantage: seeking will slow down. + Stream := BASS_StreamCreateFile(False, PChar(Filename), 0, 0, BASS_STREAM_DECODE); + if (Stream = 0) then + begin + //Log.LogError(BassCore.ErrorGetString(), 'TAudioDecoder_Bass.Open'); + Exit; + end; + + // check if BASS opened some erroneously recognized file-formats + if BASS_ChannelGetInfo(Stream, channelInfo) then + begin + fileExt := ExtractFileExt(Filename); + // BASS opens FLV-files (maybe others too) although it cannot handle them. + // Setting BASS_CONFIG_VERIFY to the max. value (100000) does not help. + if ((fileExt = '.flv') and (channelInfo.ctype = BASS_CTYPE_STREAM_MP1)) then + begin + BASS_StreamFree(Stream); + Exit; + end; + end; + + Result := TBassDecodeStream.Create(Stream); +end; + + +initialization + MediaManager.Add(TAudioDecoder_Bass.Create); + +end. diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas new file mode 100644 index 00000000..d9b4c93c --- /dev/null +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -0,0 +1,1114 @@ +unit UAudioDecoder_FFmpeg; + +(******************************************************************************* + * + * This unit is primarily based upon - + * http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html + * + * and tutorial03.c + * + * http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html + * + *******************************************************************************) + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +// show FFmpeg specific debug output +{.$DEFINE DebugFFmpegDecode} + +// FFmpeg is very verbose and shows a bunch of errors. +// Those errors (they can be considered as warnings by us) can be ignored +// as they do not give any useful information. +// There is no solution to fix this except for turning them off. +{.$DEFINE EnableFFmpegErrorOutput} + +implementation + +uses + Classes, + SysUtils, + Math, + UMusic, + UIni, + UMain, + avcodec, + avformat, + avutil, + avio, + mathematics, // used for av_rescale_q + rational, + UMediaCore_FFmpeg, + SDL, + ULog, + UCommon, + UConfig; + +const + MAX_AUDIOQ_SIZE = (5 * 16 * 1024); + +const + // TODO: The factor 3/2 might not be necessary as we do not need extra + // space for synchronizing as in the tutorial. + AUDIO_BUFFER_SIZE = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) div 2; + +type + TFFmpegDecodeStream = class(TAudioDecodeStream) + private + StateLock: PSDL_Mutex; + + EOFState: boolean; // end-of-stream flag (locked by StateLock) + ErrorState: boolean; // error flag (locked by StateLock) + + QuitRequest: boolean; // (locked by StateLock) + ParserIdleCond: PSDL_Cond; + + // parser pause/resume data + ParserLocked: boolean; + ParserPauseRequestCount: integer; + ParserUnlockedCond: PSDL_Cond; + ParserResumeCond: PSDL_Cond; + + SeekRequest: boolean; // (locked by StateLock) + SeekFlags: integer; // (locked by StateLock) + SeekPos: double; // stream position to seek for (in secs) (locked by StateLock) + SeekFlush: boolean; // true if the buffers should be flushed after seeking (locked by StateLock) + SeekFinishedCond: PSDL_Cond; + + Loop: boolean; // (locked by StateLock) + + ParseThread: PSDL_Thread; + PacketQueue: TPacketQueue; + + FormatInfo: TAudioFormatInfo; + + // FFmpeg specific data + FormatCtx: PAVFormatContext; + CodecCtx: PAVCodecContext; + Codec: PAVCodec; + + AudioStreamIndex: integer; + AudioStream: PAVStream; + AudioStreamPos: double; // stream position in seconds (locked by DecoderLock) + + // decoder pause/resume data + DecoderLocked: boolean; + DecoderPauseRequestCount: integer; + DecoderUnlockedCond: PSDL_Cond; + DecoderResumeCond: PSDL_Cond; + + // state-vars for DecodeFrame (locked by DecoderLock) + AudioPaket: TAVPacket; + AudioPaketData: PChar; + AudioPaketSize: integer; + AudioPaketSilence: integer; // number of bytes of silence to return + + // state-vars for AudioCallback (locked by DecoderLock) + AudioBufferPos: integer; + AudioBufferSize: integer; + AudioBuffer: PChar; + + Filename: string; + + procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); + procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF} + procedure SetError(State: boolean); {$IFDEF HasInline}inline;{$ENDIF} + function IsSeeking(): boolean; + function IsQuit(): boolean; + + procedure Reset(); + + procedure Parse(); + function ParseLoop(): boolean; + procedure PauseParser(); + procedure ResumeParser(); + + function DecodeFrame(Buffer: PChar; BufferSize: integer): integer; + procedure FlushCodecBuffers(); + procedure PauseDecoder(); + procedure ResumeDecoder(); + public + constructor Create(); + destructor Destroy(); override; + + function Open(const Filename: string): boolean; + procedure Close(); override; + + function GetLength(): real; override; + function GetAudioFormatInfo(): TAudioFormatInfo; override; + function GetPosition: real; override; + procedure SetPosition(Time: real); override; + function GetLoop(): boolean; override; + procedure SetLoop(Enabled: boolean); override; + function IsEOF(): boolean; override; + function IsError(): boolean; override; + + function ReadData(Buffer: PChar; BufferSize: integer): integer; override; + end; + +type + TAudioDecoder_FFmpeg = class( TInterfacedObject, IAudioDecoder ) + public + function GetName: string; + + function InitializeDecoder(): boolean; + function FinalizeDecoder(): boolean; + function Open(const Filename: string): TAudioDecodeStream; + end; + +var + FFmpegCore: TMediaCore_FFmpeg; + +function ParseThreadMain(Data: Pointer): integer; cdecl; forward; + + +{ TFFmpegDecodeStream } + +constructor TFFmpegDecodeStream.Create(); +begin + inherited Create(); + + StateLock := SDL_CreateMutex(); + ParserUnlockedCond := SDL_CreateCond(); + ParserResumeCond := SDL_CreateCond(); + ParserIdleCond := SDL_CreateCond(); + SeekFinishedCond := SDL_CreateCond(); + DecoderUnlockedCond := SDL_CreateCond(); + DecoderResumeCond := SDL_CreateCond(); + + // according to the documentation of avcodec_decode_audio(2), sample-data + // should be aligned on a 16 byte boundary. Otherwise internal calls + // (e.g. to SSE or Altivec operations) might fail or lack performance on some + // CPUs. Although GetMem() in Delphi and FPC seems to use a 16 byte or higher + // alignment for buffers of this size (alignment depends on the size of the + // requested buffer), we will set the alignment explicitly as the minimum + // alignment used by Delphi and FPC is on an 8 byte boundary. + // + // Note: AudioBuffer was previously defined as a field of type TAudioBuffer + // (array[0..AUDIO_BUFFER_SIZE-1] of byte) and hence statically allocated. + // Fields of records are aligned different to memory allocated with GetMem(), + // aligning depending on the type but will be at least 2 bytes. + // AudioBuffer was not aligned to a 16 byte boundary. The {$ALIGN x} directive + // was not applicable as Delphi in contrast to FPC provides at most 8 byte + // alignment ({$ALIGN 16} is not supported) by this directive. + AudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16); + + Reset(); +end; + +procedure TFFmpegDecodeStream.Reset(); +begin + ParseThread := nil; + + EOFState := false; + ErrorState := false; + Loop := false; + QuitRequest := false; + + AudioPaketData := nil; + AudioPaketSize := 0; + AudioPaketSilence := 0; + + AudioBufferPos := 0; + AudioBufferSize := 0; + + ParserLocked := false; + ParserPauseRequestCount := 0; + DecoderLocked := false; + DecoderPauseRequestCount := 0; + + FillChar(AudioPaket, SizeOf(TAVPacket), 0); +end; + +{* + * Frees the decode-stream data. + *} +destructor TFFmpegDecodeStream.Destroy(); +begin + Close(); + + SDL_DestroyMutex(StateLock); + SDL_DestroyCond(ParserUnlockedCond); + SDL_DestroyCond(ParserResumeCond); + SDL_DestroyCond(ParserIdleCond); + SDL_DestroyCond(SeekFinishedCond); + SDL_DestroyCond(DecoderUnlockedCond); + SDL_DestroyCond(DecoderResumeCond); + + FreeAlignedMem(AudioBuffer); + + inherited; +end; + +function TFFmpegDecodeStream.Open(const Filename: string): boolean; +var + SampleFormat: TAudioSampleFormat; + AVResult: integer; +begin + Result := false; + + Close(); + Reset(); + + if (not FileExists(Filename)) then + begin + Log.LogError('Audio-file does not exist: "' + Filename + '"', 'UAudio_FFmpeg'); + Exit; + end; + + Self.Filename := Filename; + + // open audio file + if (av_open_input_file(FormatCtx, PChar(Filename), nil, 0, nil) <> 0) then + begin + Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg'); + Exit; + end; + + // generate PTS values if they do not exist + FormatCtx^.flags := FormatCtx^.flags or AVFMT_FLAG_GENPTS; + + // retrieve stream information + if (av_find_stream_info(FormatCtx) < 0) then + begin + Log.LogError('av_find_stream_info failed: "' + Filename + '"', 'UAudio_FFmpeg'); + Close(); + Exit; + end; + + // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end + FormatCtx^.pb.eof_reached := 0; + + {$IFDEF DebugFFmpegDecode} + dump_format(FormatCtx, 0, pchar(Filename), 0); + {$ENDIF} + + AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); + if (AudioStreamIndex < 0) then + begin + Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename + '"', 'UAudio_FFmpeg'); + Close(); + Exit; + end; + + //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg'); + + AudioStream := FormatCtx.streams[AudioStreamIndex]; + CodecCtx := AudioStream^.codec; + + // TODO: should we use this or not? Should we allow 5.1 channel audio? + (* + {$IF LIBAVCODEC_VERSION >= 51042000} + if (CodecCtx^.channels > 0) then + CodecCtx^.request_channels := Min(2, CodecCtx^.channels) + else + CodecCtx^.request_channels := 2; + {$IFEND} + *) + + Codec := avcodec_find_decoder(CodecCtx^.codec_id); + if (Codec = nil) then + begin + Log.LogError('Unsupported codec!', 'UAudio_FFmpeg'); + CodecCtx := nil; + Close(); + Exit; + end; + + // set debug options + CodecCtx^.debug_mv := 0; + CodecCtx^.debug := 0; + + // detect bug-workarounds automatically + CodecCtx^.workaround_bugs := FF_BUG_AUTODETECT; + // error resilience strategy (careful/compliant/agressive/very_aggressive) + //CodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT; + // allow non spec compliant speedup tricks. + //CodecCtx^.flags2 := CodecCtx^.flags2 or CODEC_FLAG2_FAST; + + // Note: avcodec_open() and avcodec_close() are not thread-safe and will + // fail if called concurrently by different threads. + FFmpegCore.LockAVCodec(); + try + AVResult := avcodec_open(CodecCtx, Codec); + finally + FFmpegCore.UnlockAVCodec(); + end; + if (AVResult < 0) then + begin + Log.LogError('avcodec_open failed!', 'UAudio_FFmpeg'); + Close(); + Exit; + end; + + // now initialize the audio-format + + if (not FFmpegCore.ConvertFFmpegToAudioFormat(CodecCtx^.sample_fmt, SampleFormat)) then + begin + // try standard format + SampleFormat := asfS16; + end; + + FormatInfo := TAudioFormatInfo.Create( + CodecCtx^.channels, + CodecCtx^.sample_rate, + SampleFormat + ); + + + PacketQueue := TPacketQueue.Create(); + + // finally start the decode thread + ParseThread := SDL_CreateThread(@ParseThreadMain, Self); + + Result := true; +end; + +procedure TFFmpegDecodeStream.Close(); +var + ThreadResult: integer; +begin + // wake threads waiting for packet-queue data + // Note: normally, there are no waiting threads. If there were waiting + // ones, they would block the audio-callback thread. + if (assigned(PacketQueue)) then + PacketQueue.Abort(); + + // send quit request (to parse-thread etc) + SDL_mutexP(StateLock); + QuitRequest := true; + SDL_CondBroadcast(ParserIdleCond); + SDL_mutexV(StateLock); + + // abort parse-thread + if (ParseThread <> nil) then + begin + // and wait until it terminates + SDL_WaitThread(ParseThread, ThreadResult); + ParseThread := nil; + end; + + // Close the codec + if (CodecCtx <> nil) then + begin + // avcodec_close() is not thread-safe + FFmpegCore.LockAVCodec(); + try + avcodec_close(CodecCtx); + finally + FFmpegCore.UnlockAVCodec(); + end; + CodecCtx := nil; + end; + + // Close the video file + if (FormatCtx <> nil) then + begin + av_close_input_file(FormatCtx); + FormatCtx := nil; + end; + + PerformOnClose(); + + FreeAndNil(PacketQueue); + FreeAndNil(FormatInfo); +end; + +function TFFmpegDecodeStream.GetLength(): real; +begin + // do not forget to consider the start_time value here + Result := (FormatCtx^.start_time + FormatCtx^.duration) / AV_TIME_BASE; +end; + +function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo; +begin + Result := FormatInfo; +end; + +function TFFmpegDecodeStream.IsEOF(): boolean; +begin + SDL_mutexP(StateLock); + Result := EOFState; + SDL_mutexV(StateLock); +end; + +procedure TFFmpegDecodeStream.SetEOF(State: boolean); +begin + SDL_mutexP(StateLock); + EOFState := State; + SDL_mutexV(StateLock); +end; + +function TFFmpegDecodeStream.IsError(): boolean; +begin + SDL_mutexP(StateLock); + Result := ErrorState; + SDL_mutexV(StateLock); +end; + +procedure TFFmpegDecodeStream.SetError(State: boolean); +begin + SDL_mutexP(StateLock); + ErrorState := State; + SDL_mutexV(StateLock); +end; + +function TFFmpegDecodeStream.IsSeeking(): boolean; +begin + SDL_mutexP(StateLock); + Result := SeekRequest; + SDL_mutexV(StateLock); +end; + +function TFFmpegDecodeStream.IsQuit(): boolean; +begin + SDL_mutexP(StateLock); + Result := QuitRequest; + SDL_mutexV(StateLock); +end; + +function TFFmpegDecodeStream.GetPosition(): real; +var + BufferSizeSec: double; +begin + PauseDecoder(); + + // ReadData() does not return all of the buffer retrieved by DecodeFrame(). + // Determine the size of the unused part of the decode-buffer. + BufferSizeSec := (AudioBufferSize - AudioBufferPos) / + FormatInfo.BytesPerSec; + + // subtract the size of unused buffer-data from the audio clock. + Result := AudioStreamPos - BufferSizeSec; + + ResumeDecoder(); +end; + +procedure TFFmpegDecodeStream.SetPosition(Time: real); +begin + SetPositionIntern(Time, true, true); +end; + +function TFFmpegDecodeStream.GetLoop(): boolean; +begin + SDL_mutexP(StateLock); + Result := Loop; + SDL_mutexV(StateLock); +end; + +procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean); +begin + SDL_mutexP(StateLock); + Loop := Enabled; + SDL_mutexV(StateLock); +end; + + +(******************************************** + * Parser section + ********************************************) + +procedure TFFmpegDecodeStream.PauseParser(); +begin + if (SDL_ThreadID() = ParseThread.threadid) then + Exit; + + SDL_mutexP(StateLock); + Inc(ParserPauseRequestCount); + while (ParserLocked) do + SDL_CondWait(ParserUnlockedCond, StateLock); + SDL_mutexV(StateLock); +end; + +procedure TFFmpegDecodeStream.ResumeParser(); +begin + if (SDL_ThreadID() = ParseThread.threadid) then + Exit; + + SDL_mutexP(StateLock); + Dec(ParserPauseRequestCount); + SDL_CondSignal(ParserResumeCond); + SDL_mutexV(StateLock); +end; + +procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); +begin + // - Pause the parser first to prevent it from putting obsolete packages + // into the queue after the queue was flushed and before seeking is done. + // Otherwise we will hear fragments of old data, if the stream was seeked + // in stopped mode and resumed afterwards (applies to non-blocking mode only). + // - Pause the decoder to avoid race-condition that might occur otherwise. + // - Last lock the state lock because we are manipulating some shared state-vars. + PauseParser(); + PauseDecoder(); + SDL_mutexP(StateLock); + + // configure seek parameters + SeekPos := Time; + SeekFlush := Flush; + SeekFlags := AVSEEK_FLAG_ANY; + SeekRequest := true; + + // Note: the BACKWARD-flag seeks to the first position <= the position + // searched for. Otherwise e.g. position 0 might not be seeked correct. + // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame + // following. In streams with few key-frames (like many flv-files) the next + // key-frame after 0 might be 5secs ahead. + if (Time < AudioStreamPos) then + SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD; + + EOFState := false; + ErrorState := false; + + // send a reuse signal in case the parser was stopped (e.g. because of an EOF) + SDL_CondSignal(ParserIdleCond); + + SDL_mutexV(StateLock); + ResumeDecoder(); + ResumeParser(); + + // in blocking mode, wait until seeking is done + if (Blocking) then + begin + SDL_mutexP(StateLock); + while (SeekRequest) do + SDL_CondWait(SeekFinishedCond, StateLock); + SDL_mutexV(StateLock); + end; +end; + +function ParseThreadMain(Data: Pointer): integer; cdecl; +var + Stream: TFFmpegDecodeStream; +begin + Stream := TFFmpegDecodeStream(Data); + if (Stream <> nil) then + Stream.Parse(); + Result := 0; +end; + +procedure TFFmpegDecodeStream.Parse(); +begin + // reuse thread as long as the stream is not terminated + while (ParseLoop()) do + begin + // wait for reuse or destruction of stream + SDL_mutexP(StateLock); + while (not (SeekRequest or QuitRequest)) do + SDL_CondWait(ParserIdleCond, StateLock); + SDL_mutexV(StateLock); + end; +end; + +(** + * Parser main loop. + * Will not return until parsing of the stream is finished. + * Reasons for the parser to return are: + * - the end-of-file is reached + * - an error occured + * - the stream was quited (received a quit-request) + * Returns true if the stream can be resumed or false if the stream has to + * be terminated. + *) +function TFFmpegDecodeStream.ParseLoop(): boolean; +var + Packet: TAVPacket; + StatusPacket: PAVPacket; + 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: PChar; 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 (PChar(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 := PChar(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: PChar; BufferSize: integer): integer; +var + CopyByteCount: integer; // number of bytes to copy + RemainByteCount: integer; // number of bytes left (remain) to read + BufferPos: integer; + + // prioritize pause requests + procedure LockDecoder(); + begin + SDL_mutexP(StateLock); + while (DecoderPauseRequestCount > 0) do + SDL_CondWait(DecoderResumeCond, StateLock); + DecoderLocked := true; + SDL_mutexV(StateLock); + end; + + procedure UnlockDecoder(); + begin + SDL_mutexP(StateLock); + DecoderLocked := false; + SDL_CondBroadcast(DecoderUnlockedCond); + SDL_mutexV(StateLock); + end; + +begin + Result := -1; + + // set number of bytes to copy to the output buffer + BufferPos := 0; + + LockDecoder(); + try + // leave if end-of-file is reached + if (EOF) then + Exit; + + // copy data to output buffer + while (BufferPos < BufferSize) do + begin + // check if we need more data + if (AudioBufferPos >= AudioBufferSize) then + begin + AudioBufferPos := 0; + + // we have already sent all our data; get more + AudioBufferSize := DecodeFrame(AudioBuffer, AUDIO_BUFFER_SIZE); + + // check for errors or EOF + if(AudioBufferSize < 0) then + begin + Result := BufferPos; + Exit; + end; + end; + + // calc number of new bytes in the decode-buffer + CopyByteCount := AudioBufferSize - AudioBufferPos; + // resize copy-count if more bytes available than needed (remaining bytes are used the next time) + RemainByteCount := BufferSize - BufferPos; + if (CopyByteCount > RemainByteCount) then + CopyByteCount := RemainByteCount; + + Move(AudioBuffer[AudioBufferPos], Buffer[BufferPos], CopyByteCount); + + Inc(BufferPos, CopyByteCount); + Inc(AudioBufferPos, CopyByteCount); + end; + finally + UnlockDecoder(); + end; + + Result := BufferSize; +end; + + +{ TAudioDecoder_FFmpeg } + +function TAudioDecoder_FFmpeg.GetName: String; +begin + Result := 'FFmpeg_Decoder'; +end; + +function TAudioDecoder_FFmpeg.InitializeDecoder: boolean; +begin + //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFmpeg'); + FFmpegCore := TMediaCore_FFmpeg.GetInstance(); + av_register_all(); + + // Do not show uninformative error messages by default. + // FFmpeg prints all error-infos on the console by default what + // is very confusing as the playback of the files is correct. + // We consider these errors to be internal to FFMpeg. They can be fixed + // by the FFmpeg guys only and do not provide any useful information in + // respect to USDX. + {$IFNDEF EnableFFmpegErrorOutput} + {$IF LIBAVUTIL_VERSION_MAJOR >= 50} + av_log_set_level(AV_LOG_FATAL); + {$ELSE} + // FATAL and ERROR share one log-level, so we have to use QUIET + av_log_set_level(AV_LOG_QUIET); + {$IFEND} + {$ENDIF} + + Result := true; +end; + +function TAudioDecoder_FFmpeg.FinalizeDecoder(): boolean; +begin + Result := true; +end; + +function TAudioDecoder_FFmpeg.Open(const Filename: string): TAudioDecodeStream; +var + Stream: TFFmpegDecodeStream; +begin + Result := nil; + + Stream := TFFmpegDecodeStream.Create(); + if (not Stream.Open(Filename)) then + begin + Stream.Free; + Exit; + end; + + Result := Stream; +end; + + +initialization + MediaManager.Add(TAudioDecoder_FFmpeg.Create); + +end. diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas new file mode 100644 index 00000000..65a4704d --- /dev/null +++ b/src/media/UAudioInput_Bass.pas @@ -0,0 +1,481 @@ +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: Cardinal; inputDevice: Pointer): boolean; {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF} +begin + AudioInputProcessor.HandleMicrophoneData(buffer, len, inputDevice); + Result := true; +end; + + +{ TBassInputDevice } + +function TBassInputDevice.GetInputSource(): integer; +var + SourceCnt: integer; + i: integer; + flags: DWORD; +begin + // get input-source config (subtract virtual device to get BASS indices) + SourceCnt := Length(Source)-1; + + // find source + Result := -1; + for i := 0 to SourceCnt-1 do + begin + // get input settings + flags := BASS_RecordGetInput(i, PSingle(nil)^); + if (flags = DWORD(-1)) then + begin + Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.GetInputSource'); + Exit; + end; + + // check if current source is selected + if ((flags and BASS_INPUT_OFF) = 0) then + begin + // selected source found + Result := i; + Exit; + end; + end; +end; + +function TBassInputDevice.SetInputSource(SourceIndex: integer): boolean; +var + SourceCnt: integer; + i: integer; + flags: DWORD; +begin + Result := false; + + // check for invalid source index + if (SourceIndex < 0) then + Exit; + + // get input-source config (subtract virtual device to get BASS indices) + SourceCnt := Length(Source)-1; + + // turn on selected source (turns off the others for single-in devices) + if (not BASS_RecordSetInput(SourceIndex, BASS_INPUT_ON, -1)) then + begin + Log.LogError('BASS_RecordSetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Start'); + Exit; + end; + + // turn off all other sources (not needed for single-in devices) + if (not SingleIn) then + begin + for i := 0 to SourceCnt-1 do + begin + if (i = SourceIndex) then + continue; + // get input settings + flags := BASS_RecordGetInput(i, PSingle(nil)^); + if (flags = DWORD(-1)) then + begin + Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString(), 'TBassInputDevice.GetInputSource'); + Exit; + end; + // deselect source if selected + if ((flags and BASS_INPUT_OFF) = 0) then + BASS_RecordSetInput(i, BASS_INPUT_OFF, -1); + end; + end; + + Result := true; +end; + +function TBassInputDevice.Open(): boolean; +var + FormatFlags: DWORD; + SourceIndex: integer; +const + latency = 20; // 20ms callback period (= latency) +begin + Result := false; + + if (not BASS_RecordInit(BassDeviceID)) then + begin + Log.LogError('BASS_RecordInit['+Name+']: ' + + BassCore.ErrorGetString(), 'TBassInputDevice.Open'); + Exit; + end; + + if (not BassCore.ConvertAudioFormatToBASSFlags(AudioFormat.Format, FormatFlags)) then + begin + Log.LogError('Unhandled sample-format', 'TBassInputDevice.Open'); + Exit; + end; + + // start capturing in paused state + RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels, + MakeLong(FormatFlags or BASS_RECORD_PAUSE, latency), + @MicrophoneCallback, Self); + if (RecordStream = 0) then + begin + Log.LogError('BASS_RecordStart: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Open'); + BASS_RecordFree; + Exit; + end; + + // save current source selection and select new source + SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1; + if (SourceIndex = -1) then + begin + // nothing to do if default source is used + SourceRestore := -1; + end + else + begin + // store current source-index and select new source + SourceRestore := GetInputSource(); + SetInputSource(SourceIndex); + end; + + Result := true; +end; + +{* Start input-capturing on this device. *} +function TBassInputDevice.Start(): boolean; +begin + Result := false; + + // recording already started -> stop first + if (RecordStream <> 0) then + Stop(); + + // TODO: Do not open the device here (takes too much time). + if not Open() then + Exit; + + if (not BASS_ChannelPlay(RecordStream, true)) then + begin + Log.LogError('BASS_ChannelPlay: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Start'); + Exit; + end; + + Result := true; +end; + +{* Stop input-capturing on this device. *} +function TBassInputDevice.Stop(): boolean; +begin + Result := false; + + if (RecordStream = 0) then + Exit; + if (not BASS_RecordSetDevice(BassDeviceID)) then + Exit; + + if (not BASS_ChannelStop(RecordStream)) then + begin + Log.LogError('BASS_ChannelStop: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Stop'); + end; + + // TODO: Do not close the device here (takes too much time). + Result := Close(); +end; + +function TBassInputDevice.Close(): boolean; +begin + // restore source selection + if (SourceRestore >= 0) then + begin + SetInputSource(SourceRestore); + end; + + // free data + if (not BASS_RecordFree()) then + begin + Log.LogError('BASS_RecordFree: ' + BassCore.ErrorGetString(), 'TBassInputDevice.Close'); + Result := false; + end + else + begin + Result := true; + end; + + RecordStream := 0; +end; + +function TBassInputDevice.GetVolume(): single; +var + SourceIndex: integer; + lVolume: Single; +begin + Result := 0; + + SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1; + if (SourceIndex = -1) then + begin + // if default source used find selected source + SourceIndex := GetInputSource(); + if (SourceIndex = -1) then + Exit; + end; + + if (BASS_RecordGetInput(SourceIndex, lVolume) = DWORD(-1)) then + begin + Log.LogError('BASS_RecordGetInput: ' + BassCore.ErrorGetString() , 'TBassInputDevice.GetVolume'); + Exit; + end; + Result := lVolume; +end; + +procedure TBassInputDevice.SetVolume(Volume: single); +var + SourceIndex: integer; +begin + SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1; + if (SourceIndex = -1) then + begin + // if default source used find selected source + SourceIndex := GetInputSource(); + if (SourceIndex = -1) then + Exit; + end; + + // clip volume to valid range + if (Volume > 1.0) then + Volume := 1.0 + else if (Volume < 0) then + Volume := 0; + + if (not BASS_RecordSetInput(SourceIndex, 0, Volume)) then + begin + Log.LogError('BASS_RecordSetInput: ' + BassCore.ErrorGetString() , 'TBassInputDevice.SetVolume'); + end; +end; + + +{ TAudioInput_Bass } + +function TAudioInput_Bass.GetName: String; +begin + result := 'BASS_Input'; +end; + +function TAudioInput_Bass.EnumDevices(): boolean; +var + Descr: PChar; + SourceName: PChar; + Flags: integer; + BassDeviceID: integer; + BassDevice: TBassInputDevice; + DeviceIndex: integer; + DeviceInfo: BASS_DEVICEINFO; + SourceIndex: integer; + RecordInfo: BASS_RECORDINFO; + SelectedSourceIndex: integer; +begin + result := false; + + DeviceIndex := 0; + BassDeviceID := 0; + SetLength(AudioInputProcessor.DeviceList, 0); + + // checks for recording devices and puts them into an array + while true do + begin + if (not BASS_RecordGetDeviceInfo(BassDeviceID, DeviceInfo)) then + break; + + // try to initialize the device + if not BASS_RecordInit(BassDeviceID) then + begin + Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']', + 'TAudioInput_Bass.InitializeRecord'); + end + else + begin + SetLength(AudioInputProcessor.DeviceList, DeviceIndex+1); + + // TODO: free object on termination + BassDevice := TBassInputDevice.Create(); + AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice; + + Descr := DeviceInfo.name; + + BassDevice.BassDeviceID := BassDeviceID; + BassDevice.Name := UnifyDeviceName(Descr, DeviceIndex); + + // zero info-struct as some fields might not be set (e.g. freq is just set on Vista and MacOSX) + FillChar(RecordInfo, SizeOf(RecordInfo), 0); + // retrieve recording device info + BASS_RecordGetInfo(RecordInfo); + + // check if BASS has capture-freq. info + if (RecordInfo.freq > 0) then + begin + // use current input sample rate (available only on Windows Vista and OSX). + // Recording at this rate will give the best quality and performance, as no resampling is required. + // FIXME: does BASS use LSB/MSB or system integer values for 16bit? + BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16) + end + else + begin + // BASS does not provide an explizit input channel count (except BASS_RECORDINFO.formats) + // but it doesn't fail if we use stereo input on a mono device + // -> use stereo by default + BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16) + end; + + // get info if multiple input-sources can be selected at once + BassDevice.SingleIn := RecordInfo.singlein; + + // init list for capture buffers per channel + SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels); + + BassDevice.MicSource := -1; + BassDevice.SourceRestore := -1; + + // add a virtual default source (will not change mixer-settings) + SetLength(BassDevice.Source, 1); + BassDevice.Source[0].Name := DEFAULT_SOURCE_NAME; + + // add real input sources + SourceIndex := 1; + + // process each input + while true do + begin + SourceName := BASS_RecordGetInputName(SourceIndex-1); + + {$IFDEF DARWIN} + // Under MacOSX the SingStar Mics have an empty InputName. + // So, we have to add a hard coded Workaround for this problem + // FIXME: - Do we need this anymore? Doesn't the (new) default source already solve this problem? + // - Normally a nil return value of BASS_RecordGetInputName() means end-of-list, so maybe + // BASS is not able to detect any mic-sources (the default source will work then). + // - Does BASS_RecordGetInfo() return true or false? If it returns true in this case + // we could use this value to check if the device exists. + // Please check that, eddie. + // If it returns false, then the source is not detected and it does not make sense to add a second + // fake device here. + // What about BASS_RecordGetInput()? Does it return a value <> -1? + // - Does it even work at all with this fake source-index, now that input switching works? + // This info was not used before (sources were never switched), so it did not matter what source-index was used. + // But now BASS_RecordSetInput() will probably fail. + if ((SourceName = nil) and (SourceIndex = 1) and (Pos('USBMIC Serial#', Descr) > 0)) then + SourceName := 'Microphone' + {$ENDIF} + + if (SourceName = nil) then + break; + + SetLength(BassDevice.Source, Length(BassDevice.Source)+1); + BassDevice.Source[SourceIndex].Name := SourceName; + + // get input-source info + Flags := BASS_RecordGetInput(SourceIndex, PSingle(nil)^); + if (Flags <> -1) then + begin + // is the current source a mic-source? + if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then + BassDevice.MicSource := SourceIndex; + end; + + Inc(SourceIndex); + end; + + // FIXME: this call hangs in FPC (windows) every 2nd time USDX is called. + // Maybe because the sound-device was not released properly? + BASS_RecordFree; + + Inc(DeviceIndex); + end; + + Inc(BassDeviceID); + end; + + result := true; +end; + +function TAudioInput_Bass.InitializeRecord(): boolean; +begin + BassCore := TAudioCore_Bass.GetInstance(); + Result := EnumDevices(); +end; + +function TAudioInput_Bass.FinalizeRecord(): boolean; +begin + CaptureStop; + Result := inherited FinalizeRecord; +end; + + +initialization + MediaManager.Add(TAudioInput_Bass.Create); + +end. diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas new file mode 100644 index 00000000..9a1c3e99 --- /dev/null +++ b/src/media/UAudioInput_Portaudio.pas @@ -0,0 +1,474 @@ +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; + SourceIndex: integer; +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} + 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', '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 new file mode 100644 index 00000000..2337d43f --- /dev/null +++ b/src/media/UAudioPlaybackBase.pas @@ -0,0 +1,292 @@ +unit UAudioPlaybackBase; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + UMusic; + +type + TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback) + protected + OutputDeviceList: TAudioOutputDeviceList; + MusicStream: TAudioPlaybackStream; + function CreatePlaybackStream(): TAudioPlaybackStream; virtual; abstract; + procedure ClearOutputDeviceList(); + function GetLatency(): double; virtual; abstract; + + // open sound or music stream (used by Open() and OpenSound()) + function OpenStream(const Filename: string): TAudioPlaybackStream; + function OpenDecodeStream(const Filename: string): TAudioDecodeStream; + public + function GetName: string; virtual; abstract; + + function Open(const Filename: string): boolean; // true if succeed + procedure Close; + + procedure Play; + procedure Pause; + procedure Stop; + procedure FadeIn(Time: real; TargetVolume: single); + + procedure SetSyncSource(SyncSource: TSyncSource); + + procedure SetPosition(Time: real); + function GetPosition: real; + + function InitializePlayback: boolean; virtual; abstract; + function FinalizePlayback: boolean; virtual; + + //function SetOutputDevice(Device: TAudioOutputDevice): boolean; + function GetOutputDeviceList(): TAudioOutputDeviceList; + + procedure SetAppVolume(Volume: single); virtual; abstract; + procedure SetVolume(Volume: single); + procedure SetLoop(Enabled: boolean); + + procedure Rewind; + function Finished: boolean; + function Length: real; + + // Sounds + function OpenSound(const Filename: string): TAudioPlaybackStream; + procedure PlaySound(Stream: TAudioPlaybackStream); + procedure StopSound(Stream: TAudioPlaybackStream); + + // Equalizer + procedure GetFFTData(var Data: TFFTData); + + // Interface for Visualizer + function GetPCMData(var Data: TPCMData): Cardinal; + + function CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; virtual; abstract; + end; + + +implementation + +uses + ULog, + SysUtils; + +{ TAudioPlaybackBase } + +function TAudioPlaybackBase.FinalizePlayback: boolean; +begin + FreeAndNil(MusicStream); + ClearOutputDeviceList(); + Result := true; +end; + +function TAudioPlaybackBase.Open(const Filename: string): boolean; +begin + // free old MusicStream + MusicStream.Free; + + MusicStream := OpenStream(Filename); + if not assigned(MusicStream) then + begin + Result := false; + Exit; + end; + + //MusicStream.AddSoundEffect(TVoiceRemoval.Create()); + + Result := true; +end; + +procedure TAudioPlaybackBase.Close; +begin + FreeAndNil(MusicStream); +end; + +function TAudioPlaybackBase.OpenDecodeStream(const Filename: String): TAudioDecodeStream; +var + i: integer; +begin + for i := 0 to AudioDecoders.Count-1 do + begin + Result := IAudioDecoder(AudioDecoders[i]).Open(Filename); + if (assigned(Result)) then + begin + Log.LogInfo('Using decoder ' + IAudioDecoder(AudioDecoders[i]).GetName() + + ' for "' + Filename + '"', 'TAudioPlaybackBase.OpenDecodeStream'); + Exit; + end; + end; + Result := nil; +end; + +procedure OnClosePlaybackStream(Stream: TAudioProcessingStream); +var + PlaybackStream: TAudioPlaybackStream; + SourceStream: TAudioSourceStream; +begin + PlaybackStream := TAudioPlaybackStream(Stream); + SourceStream := PlaybackStream.GetSourceStream(); + SourceStream.Free; +end; + +function TAudioPlaybackBase.OpenStream(const Filename: string): TAudioPlaybackStream; +var + PlaybackStream: TAudioPlaybackStream; + DecodeStream: TAudioDecodeStream; +begin + Result := nil; + + //Log.LogStatus('Loading Sound: "' + Filename + '"', 'TAudioPlayback_Bass.OpenStream'); + + DecodeStream := OpenDecodeStream(Filename); + if (not assigned(DecodeStream)) then + begin + Log.LogStatus('Could not open "' + Filename + '"', 'TAudioPlayback_Bass.OpenStream'); + Exit; + end; + + // create a matching playback-stream for the decoder + PlaybackStream := CreatePlaybackStream(); + if (not PlaybackStream.Open(DecodeStream)) then + begin + FreeAndNil(PlaybackStream); + FreeAndNil(DecodeStream); + Exit; + end; + + PlaybackStream.AddOnCloseHandler(OnClosePlaybackStream); + + Result := PlaybackStream; +end; + +procedure TAudioPlaybackBase.Play; +begin + if assigned(MusicStream) then + MusicStream.Play(); +end; + +procedure TAudioPlaybackBase.Pause; +begin + if assigned(MusicStream) then + MusicStream.Pause(); +end; + +procedure TAudioPlaybackBase.Stop; +begin + if assigned(MusicStream) then + MusicStream.Stop(); +end; + +function TAudioPlaybackBase.Length: real; +begin + if assigned(MusicStream) then + Result := MusicStream.Length + else + Result := 0; +end; + +function TAudioPlaybackBase.GetPosition: real; +begin + if assigned(MusicStream) then + Result := MusicStream.Position + else + Result := 0; +end; + +procedure TAudioPlaybackBase.SetPosition(Time: real); +begin + if assigned(MusicStream) then + MusicStream.Position := Time; +end; + +procedure TAudioPlaybackBase.SetSyncSource(SyncSource: TSyncSource); +begin + if assigned(MusicStream) then + MusicStream.SetSyncSource(SyncSource); +end; + +procedure TAudioPlaybackBase.Rewind; +begin + SetPosition(0); +end; + +function TAudioPlaybackBase.Finished: boolean; +begin + if assigned(MusicStream) then + Result := (MusicStream.Status = ssStopped) + else + Result := true; +end; + +procedure TAudioPlaybackBase.SetVolume(Volume: single); +begin + if assigned(MusicStream) then + MusicStream.Volume := Volume; +end; + +procedure TAudioPlaybackBase.FadeIn(Time: real; TargetVolume: single); +begin + if assigned(MusicStream) then + MusicStream.FadeIn(Time, TargetVolume); +end; + +procedure TAudioPlaybackBase.SetLoop(Enabled: boolean); +begin + if assigned(MusicStream) then + MusicStream.Loop := Enabled; +end; + +// Equalizer +procedure TAudioPlaybackBase.GetFFTData(var data: TFFTData); +begin + if assigned(MusicStream) then + MusicStream.GetFFTData(data); +end; + +{* + * Copies interleaved PCM SInt16 stereo samples into data. + * Returns the number of frames + *} +function TAudioPlaybackBase.GetPCMData(var data: TPCMData): Cardinal; +begin + if assigned(MusicStream) then + Result := MusicStream.GetPCMData(data) + else + Result := 0; +end; + +function TAudioPlaybackBase.OpenSound(const Filename: string): TAudioPlaybackStream; +begin + Result := OpenStream(Filename); +end; + +procedure TAudioPlaybackBase.PlaySound(stream: TAudioPlaybackStream); +begin + if assigned(stream) then + stream.Play(); +end; + +procedure TAudioPlaybackBase.StopSound(stream: TAudioPlaybackStream); +begin + if assigned(stream) then + stream.Stop(); +end; + +procedure TAudioPlaybackBase.ClearOutputDeviceList(); +var + DeviceIndex: integer; +begin + for DeviceIndex := 0 to High(OutputDeviceList) do + OutputDeviceList[DeviceIndex].Free(); + SetLength(OutputDeviceList, 0); +end; + +function TAudioPlaybackBase.GetOutputDeviceList(): TAudioOutputDeviceList; +begin + Result := OutputDeviceList; +end; + +end. diff --git a/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas new file mode 100644 index 00000000..41a91173 --- /dev/null +++ b/src/media/UAudioPlayback_Bass.pas @@ -0,0 +1,731 @@ +unit UAudioPlayback_Bass; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +implementation + +uses + Classes, + SysUtils, + Math, + UIni, + UMain, + UMusic, + UAudioPlaybackBase, + UAudioCore_Bass, + ULog, + sdl, + bass; + +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: PChar; 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: PChar; BufferSize: integer); override; + function ReadData(Buffer: PChar; 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: PChar; BufferSize: integer): integer; +var + AdjustedSize: integer; + RequestedSourceSize, SourceSize: integer; + SkipCount: integer; + SourceFormatInfo: TAudioFormatInfo; + FrameSize: integer; + PadFrame: PChar; + //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: PChar; 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: PChar; BufferSize: integer): integer; +begin + Result := -1; +end; + +function TBassVoiceStream.IsEOF(): boolean; +begin + Result := false; +end; + +function TBassVoiceStream.IsError(): boolean; +begin + Result := false; +end; + + +{ TAudioPlayback_Bass } + +function TAudioPlayback_Bass.GetName: String; +begin + Result := 'BASS_Playback'; +end; + +function TAudioPlayback_Bass.EnumDevices(): boolean; +var + BassDeviceID: DWORD; + DeviceIndex: integer; + Device: TBassOutputDevice; + DeviceInfo: BASS_DEVICEINFO; +begin + Result := true; + + ClearOutputDeviceList(); + + // skip "no sound"-device (ID = 0) + BassDeviceID := 1; + + while (true) do + begin + // check for device + if (not BASS_GetDeviceInfo(BassDeviceID, DeviceInfo)) then + Break; + + // set device info + Device := TBassOutputDevice.Create(); + Device.Name := DeviceInfo.name; + Device.BassDeviceID := BassDeviceID; + + // add device to list + SetLength(OutputDeviceList, BassDeviceID); + OutputDeviceList[BassDeviceID-1] := Device; + + Inc(BassDeviceID); + end; +end; + +function TAudioPlayback_Bass.InitializePlayback(): boolean; +begin + result := false; + + BassCore := TAudioCore_Bass.GetInstance(); + + EnumDevices(); + + //Log.BenchmarkStart(4); + //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize'); + + // TODO: use BASS_DEVICE_LATENCY to determine the latency + if not BASS_Init(-1, 44100, 0, 0, nil) then + begin + Log.LogError('Could not initialize BASS', 'TAudioPlayback_Bass.InitializePlayback'); + Exit; + end; + + //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4); + + // config playing buffer + //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10); + //BASS_SetConfig(BASS_CONFIG_BUFFER, 100); + + result := true; +end; + +function TAudioPlayback_Bass.FinalizePlayback(): boolean; +begin + Close; + BASS_Free; + inherited FinalizePlayback(); + Result := true; +end; + +function TAudioPlayback_Bass.CreatePlaybackStream(): TAudioPlaybackStream; +begin + Result := TBassPlaybackStream.Create(); +end; + +procedure TAudioPlayback_Bass.SetAppVolume(Volume: single); +begin + // set volume for this application (ranges from 0..10000 since BASS 2.4) + BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Round(Volume*10000)); +end; + +function TAudioPlayback_Bass.CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; +var + VoiceStream: TAudioVoiceStream; +begin + Result := nil; + + VoiceStream := TBassVoiceStream.Create(); + if (not VoiceStream.Open(ChannelMap, FormatInfo)) then + begin + VoiceStream.Free; + Exit; + end; + + Result := VoiceStream; +end; + +function TAudioPlayback_Bass.GetLatency(): double; +begin + Result := 0; +end; + + +initialization + MediaManager.Add(TAudioPlayback_Bass.Create); + +end. diff --git a/src/media/UAudioPlayback_Portaudio.pas b/src/media/UAudioPlayback_Portaudio.pas new file mode 100644 index 00000000..c3717ba6 --- /dev/null +++ b/src/media/UAudioPlayback_Portaudio.pas @@ -0,0 +1,361 @@ +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 new file mode 100644 index 00000000..deef91e8 --- /dev/null +++ b/src/media/UAudioPlayback_SDL.pas @@ -0,0 +1,160 @@ +unit UAudioPlayback_SDL; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + + +uses + Classes, + SysUtils, + UMusic; + +implementation + +uses + sdl, + UAudioPlayback_SoftMixer, + 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: PChar; size: Cardinal; volume: Single); override; + end; + + +{ TAudioPlayback_SDL } + +procedure SDLAudioCallback(userdata: Pointer; stream: PChar; 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: PChar; 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 new file mode 100644 index 00000000..6ddae980 --- /dev/null +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -0,0 +1,1132 @@ +unit UAudioPlayback_SoftMixer; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + + +uses + Classes, + SysUtils, + sdl, + URingBuffer, + UMusic, + UAudioPlaybackBase; + +type + TAudioPlayback_SoftMixer = class; + + TGenericPlaybackStream = class(TAudioPlaybackStream) + private + Engine: TAudioPlayback_SoftMixer; + + SampleBuffer: PChar; + SampleBufferSize: integer; + SampleBufferCount: integer; // number of available bytes in SampleBuffer + SampleBufferPos: cardinal; + + SourceBuffer: PChar; + 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: PChar; 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: PChar; 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: PChar; + 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: PChar; 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: PChar; 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: PChar; 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: PChar; BufferSize: integer); override; + function ReadData(Buffer: PChar; 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: PChar; 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: PChar; 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: PChar; BufferSize: integer): integer; +var + ConversionInputCount: integer; + ConversionOutputSize: integer; // max. number of converted data (= buffer size) + ConversionOutputCount: integer; // actual number of converted data + SourceSize: integer; + RequestedSourceSize: integer; + NeededSampleBufferSize: integer; + BytesNeeded, BytesAvail: 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: PChar; + i: integer; +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: PChar; 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: PChar; 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: PChar; 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: PChar; 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 new file mode 100644 index 00000000..cdd320ac --- /dev/null +++ b/src/media/UMediaCore_FFmpeg.pas @@ -0,0 +1,405 @@ +unit UMediaCore_FFmpeg; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + UMusic, + avcodec, + avformat, + avutil, + ULog, + sdl; + +type + PPacketQueue = ^TPacketQueue; + TPacketQueue = class + private + FirstListEntry: PAVPacketList; + LastListEntry: PAVPacketList; + PacketCount: integer; + Mutex: PSDL_Mutex; + Condition: PSDL_Cond; + Size: integer; + AbortRequest: boolean; + public + constructor Create(); + destructor Destroy(); override; + + function Put(Packet : PAVPacket): integer; + function PutStatus(StatusFlag: integer; StatusInfo: Pointer): integer; + procedure FreeStatusInfo(var Packet: TAVPacket); + function GetStatusInfo(var Packet: TAVPacket): Pointer; + function Get(var Packet: TAVPacket; Blocking: boolean): integer; + function GetSize(): integer; + procedure Flush(); + procedure Abort(); + function IsAborted(): boolean; + end; + +const + STATUS_PACKET: PChar = 'STATUS_PACKET'; +const + PKT_STATUS_FLAG_EOF = 1; // signal end-of-file + PKT_STATUS_FLAG_FLUSH = 2; // request the decoder to flush its avcodec decode buffers + PKT_STATUS_FLAG_ERROR = 3; // signal an error state + PKT_STATUS_FLAG_EMPTY = 4; // request the decoder to output empty data (silence or black frames) + +type + TMediaCore_FFmpeg = class + private + AVCodecLock: PSDL_Mutex; + public + constructor Create(); + destructor Destroy(); override; + class function GetInstance(): TMediaCore_FFmpeg; + + function GetErrorString(ErrorNum: integer): string; + function FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer ): boolean; + function FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer; + function ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean; + procedure LockAVCodec(); + procedure UnlockAVCodec(); + end; + +implementation + +uses + SysUtils; + +var + Instance: TMediaCore_FFmpeg; + +constructor TMediaCore_FFmpeg.Create(); +begin + inherited; + AVCodecLock := SDL_CreateMutex(); +end; + +destructor TMediaCore_FFmpeg.Destroy(); +begin + SDL_DestroyMutex(AVCodecLock); + inherited; +end; + +class function TMediaCore_FFmpeg.GetInstance(): TMediaCore_FFmpeg; +begin + if (not Assigned(Instance)) then + Instance := TMediaCore_FFmpeg.Create(); + Result := Instance; +end; + +procedure TMediaCore_FFmpeg.LockAVCodec(); +begin + SDL_mutexP(AVCodecLock); +end; + +procedure TMediaCore_FFmpeg.UnlockAVCodec(); +begin + SDL_mutexV(AVCodecLock); +end; + +function TMediaCore_FFmpeg.GetErrorString(ErrorNum: integer): string; +begin + case ErrorNum of + AVERROR_IO: Result := 'AVERROR_IO'; + AVERROR_NUMEXPECTED: Result := 'AVERROR_NUMEXPECTED'; + AVERROR_INVALIDDATA: Result := 'AVERROR_INVALIDDATA'; + AVERROR_NOMEM: Result := 'AVERROR_NOMEM'; + AVERROR_NOFMT: Result := 'AVERROR_NOFMT'; + AVERROR_NOTSUPP: Result := 'AVERROR_NOTSUPP'; + AVERROR_NOENT: Result := 'AVERROR_NOENT'; + AVERROR_PATCHWELCOME: Result := 'AVERROR_PATCHWELCOME'; + else Result := 'AVERROR_#'+inttostr(ErrorNum); + end; +end; + +{ + @param(FormatCtx is a PAVFormatContext returned from av_open_input_file ) + @param(FirstVideoStream is an OUT value of type integer, this is the index of the video stream) + @param(FirstAudioStream is an OUT value of type integer, this is the index of the audio stream) + @returns(@true on success, @false otherwise) +} +function TMediaCore_FFmpeg.FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer): boolean; +var + i: integer; + Stream: PAVStream; +begin + // find the first video stream + FirstAudioStream := -1; + FirstVideoStream := -1; + + for i := 0 to FormatCtx.nb_streams-1 do + begin + Stream := FormatCtx.streams[i]; + + if (Stream.codec.codec_type = CODEC_TYPE_VIDEO) and + (FirstVideoStream < 0) then + begin + FirstVideoStream := i; + end; + + if (Stream.codec.codec_type = CODEC_TYPE_AUDIO) and + (FirstAudioStream < 0) then + begin + FirstAudioStream := i; + end; + end; + + // return true if either an audio- or video-stream was found + Result := (FirstAudioStream > -1) or + (FirstVideoStream > -1) ; +end; + +function TMediaCore_FFmpeg.FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer; +var + i: integer; + StreamIndex: integer; + Stream: PAVStream; +begin + // find the first audio stream + StreamIndex := -1; + + for i := 0 to FormatCtx^.nb_streams-1 do + begin + Stream := FormatCtx^.streams[i]; + + if (Stream.codec^.codec_type = CODEC_TYPE_AUDIO) then + begin + StreamIndex := i; + Break; + end; + end; + + Result := StreamIndex; +end; + +function TMediaCore_FFmpeg.ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean; +begin + case FFmpegFormat of + SAMPLE_FMT_U8: Format := asfU8; + SAMPLE_FMT_S16: Format := asfS16; + SAMPLE_FMT_S24: Format := asfS24; + SAMPLE_FMT_S32: Format := asfS32; + SAMPLE_FMT_FLT: Format := asfFloat; + else begin + Result := false; + Exit; + end; + end; + Result := true; +end; + +{ TPacketQueue } + +constructor TPacketQueue.Create(); +begin + inherited; + + FirstListEntry := nil; + LastListEntry := nil; + PacketCount := 0; + Size := 0; + + Mutex := SDL_CreateMutex(); + Condition := SDL_CreateCond(); +end; + +destructor TPacketQueue.Destroy(); +begin + Flush(); + SDL_DestroyMutex(Mutex); + SDL_DestroyCond(Condition); + inherited; +end; + +procedure TPacketQueue.Abort(); +begin + SDL_LockMutex(Mutex); + + AbortRequest := true; + + SDL_CondBroadcast(Condition); + SDL_UnlockMutex(Mutex); +end; + +function TPacketQueue.IsAborted(): boolean; +begin + SDL_LockMutex(Mutex); + Result := AbortRequest; + SDL_UnlockMutex(Mutex); +end; + +function TPacketQueue.Put(Packet : PAVPacket): integer; +var + CurrentListEntry : PAVPacketList; +begin + Result := -1; + + if (Packet = nil) then + Exit; + + if (PChar(Packet^.data) <> STATUS_PACKET) then + begin + if (av_dup_packet(Packet) < 0) then + Exit; + end; + + CurrentListEntry := av_malloc(SizeOf(TAVPacketList)); + if (CurrentListEntry = nil) then + Exit; + + CurrentListEntry^.pkt := Packet^; + CurrentListEntry^.next := nil; + + SDL_LockMutex(Mutex); + try + if (LastListEntry = nil) then + FirstListEntry := CurrentListEntry + else + LastListEntry^.next := CurrentListEntry; + + LastListEntry := CurrentListEntry; + Inc(PacketCount); + + Size := Size + CurrentListEntry^.pkt.size; + SDL_CondSignal(Condition); + finally + SDL_UnlockMutex(Mutex); + end; + + Result := 0; +end; + +(** + * Adds a status packet (EOF, Flush, etc.) to the end of the queue. + * StatusInfo can be used to pass additional information to the decoder. + * Only assign nil or a valid pointer to data allocated with Getmem() to + * StatusInfo because the pointer will be disposed with Freemem() on a call + * to Flush(). If the packet is removed from the queue it is the decoder's + * responsibility to free the StatusInfo data with FreeStatusInfo(). + *) +function TPacketQueue.PutStatus(StatusFlag: integer; StatusInfo: Pointer): integer; +var + TempPacket: PAVPacket; +begin + // create temp. package + TempPacket := av_malloc(SizeOf(TAVPacket)); + if (TempPacket = nil) then + begin + Result := -1; + Exit; + end; + // init package + av_init_packet(TempPacket^); + TempPacket^.data := Pointer(STATUS_PACKET); + TempPacket^.flags := StatusFlag; + TempPacket^.priv := StatusInfo; + // put a copy of the package into the queue + Result := Put(TempPacket); + // data has been copied -> delete temp. package + av_free(TempPacket); +end; + +procedure TPacketQueue.FreeStatusInfo(var Packet: TAVPacket); +begin + if (Packet.priv <> nil) then + FreeMem(Packet.priv); +end; + +function TPacketQueue.GetStatusInfo(var Packet: TAVPacket): Pointer; +begin + Result := Packet.priv; +end; + +function TPacketQueue.Get(var Packet: TAVPacket; Blocking: boolean): integer; +var + CurrentListEntry: PAVPacketList; +const + WAIT_TIMEOUT = 10; // timeout in ms +begin + Result := -1; + + SDL_LockMutex(Mutex); + try + while (true) do + begin + if (AbortRequest) then + Exit; + + CurrentListEntry := FirstListEntry; + if (CurrentListEntry <> nil) then + begin + FirstListEntry := CurrentListEntry^.next; + if (FirstListEntry = nil) then + LastListEntry := nil; + Dec(PacketCount); + + Size := Size - CurrentListEntry^.pkt.size; + Packet := CurrentListEntry^.pkt; + av_free(CurrentListEntry); + + Result := 1; + Break; + end + else if (not Blocking) then + begin + Result := 0; + Break; + end + else + begin + // block until a new package arrives, + // but do not wait till infinity to avoid deadlocks + if (SDL_CondWaitTimeout(Condition, Mutex, WAIT_TIMEOUT) = SDL_MUTEX_TIMEDOUT) then + begin + Result := 0; + Break; + end; + end; + end; + finally + SDL_UnlockMutex(Mutex); + end; +end; + +function TPacketQueue.GetSize(): integer; +begin + SDL_LockMutex(Mutex); + Result := Size; + SDL_UnlockMutex(Mutex); +end; + +procedure TPacketQueue.Flush(); +var + CurrentListEntry, TempListEntry: PAVPacketList; +begin + SDL_LockMutex(Mutex); + + CurrentListEntry := FirstListEntry; + while(CurrentListEntry <> nil) do + begin + TempListEntry := CurrentListEntry^.next; + // free status data + if (PChar(CurrentListEntry^.pkt.data) = STATUS_PACKET) then + FreeStatusInfo(CurrentListEntry^.pkt); + // free packet data + av_free_packet(@CurrentListEntry^.pkt); + // Note: param must be a pointer to a pointer! + av_freep(@CurrentListEntry); + CurrentListEntry := TempListEntry; + end; + LastListEntry := nil; + FirstListEntry := nil; + PacketCount := 0; + Size := 0; + + SDL_UnlockMutex(Mutex); +end; + +end. diff --git a/src/media/UMediaCore_SDL.pas b/src/media/UMediaCore_SDL.pas new file mode 100644 index 00000000..252f72a0 --- /dev/null +++ b/src/media/UMediaCore_SDL.pas @@ -0,0 +1,38 @@ +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 new file mode 100644 index 00000000..438b89ab --- /dev/null +++ b/src/media/UMedia_dummy.pas @@ -0,0 +1,243 @@ +unit UMedia_dummy; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +implementation + +uses + SysUtils, + math, + UMusic; + +type + TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput ) + private + DummyOutputDeviceList: TAudioOutputDeviceList; + public + constructor Create(); + function GetName: string; + + function Init(): boolean; + function Finalize(): boolean; + + function Open(const aFileName : string): boolean; // true if succeed + procedure Close; + + procedure Play; + procedure Pause; + procedure Stop; + + procedure SetPosition(Time: real); + function GetPosition: real; + + procedure SetSyncSource(SyncSource: TSyncSource); + + procedure GetFrame(Time: Extended); + procedure DrawGL(Screen: integer); + + // IAudioInput + function InitializeRecord: boolean; + function FinalizeRecord: boolean; + procedure CaptureStart; + procedure CaptureStop; + procedure GetFFTData(var data: TFFTData); + function GetPCMData(var data: TPCMData): Cardinal; + + // IAudioPlayback + function InitializePlayback: boolean; + function FinalizePlayback: boolean; + + function GetOutputDeviceList(): TAudioOutputDeviceList; + procedure FadeIn(Time: real; TargetVolume: single); + procedure SetAppVolume(Volume: single); + procedure SetVolume(Volume: single); + procedure SetLoop(Enabled: boolean); + procedure Rewind; + + function Finished: boolean; + function Length: real; + + function OpenSound(const Filename: string): TAudioPlaybackStream; + procedure CloseSound(var PlaybackStream: TAudioPlaybackStream); + procedure PlaySound(stream: TAudioPlaybackStream); + procedure StopSound(stream: TAudioPlaybackStream); + + function CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; + procedure CloseVoiceStream(var VoiceStream: TAudioVoiceStream); + end; + +function TMedia_dummy.GetName: string; +begin + Result := 'dummy'; +end; + +procedure TMedia_dummy.GetFrame(Time: Extended); +begin +end; + +procedure TMedia_dummy.DrawGL(Screen: integer); +begin +end; + +constructor TMedia_dummy.Create(); +begin + inherited; +end; + +function TMedia_dummy.Init(): boolean; +begin + Result := true; +end; + +function TMedia_dummy.Finalize(): boolean; +begin + Result := true; +end; + +function TMedia_dummy.Open(const aFileName : string): boolean; // true if succeed +begin + Result := false; +end; + +procedure TMedia_dummy.Close; +begin +end; + +procedure TMedia_dummy.Play; +begin +end; + +procedure TMedia_dummy.Pause; +begin +end; + +procedure TMedia_dummy.Stop; +begin +end; + +procedure TMedia_dummy.SetPosition(Time: real); +begin +end; + +function TMedia_dummy.GetPosition: real; +begin + Result := 0; +end; + +procedure TMedia_dummy.SetSyncSource(SyncSource: TSyncSource); +begin +end; + +// IAudioInput +function TMedia_dummy.InitializeRecord: boolean; +begin + Result := true; +end; + +function TMedia_dummy.FinalizeRecord: boolean; +begin + Result := true; +end; + +procedure TMedia_dummy.CaptureStart; +begin +end; + +procedure TMedia_dummy.CaptureStop; +begin +end; + +procedure TMedia_dummy.GetFFTData(var data: TFFTData); +begin +end; + +function TMedia_dummy.GetPCMData(var data: TPCMData): Cardinal; +begin + Result := 0; +end; + +// IAudioPlayback +function TMedia_dummy.InitializePlayback: boolean; +begin + SetLength(DummyOutputDeviceList, 1); + DummyOutputDeviceList[0] := TAudioOutputDevice.Create(); + DummyOutputDeviceList[0].Name := '[Dummy Device]'; + Result := true; +end; + +function TMedia_dummy.FinalizePlayback: boolean; +begin + Result := true; +end; + +function TMedia_dummy.GetOutputDeviceList(): TAudioOutputDeviceList; +begin + Result := DummyOutputDeviceList; +end; + +procedure TMedia_dummy.SetAppVolume(Volume: single); +begin +end; + +procedure TMedia_dummy.SetVolume(Volume: single); +begin +end; + +procedure TMedia_dummy.SetLoop(Enabled: boolean); +begin +end; + +procedure TMedia_dummy.FadeIn(Time: real; TargetVolume: single); +begin +end; + +procedure TMedia_dummy.Rewind; +begin +end; + +function TMedia_dummy.Finished: boolean; +begin + Result := false; +end; + +function TMedia_dummy.Length: real; +begin + Result := 60; +end; + +function TMedia_dummy.OpenSound(const Filename: string): TAudioPlaybackStream; +begin + Result := nil; +end; + +procedure TMedia_dummy.CloseSound(var PlaybackStream: TAudioPlaybackStream); +begin +end; + +procedure TMedia_dummy.PlaySound(stream: TAudioPlaybackStream); +begin +end; + +procedure TMedia_dummy.StopSound(stream: TAudioPlaybackStream); +begin +end; + +function TMedia_dummy.CreateVoiceStream(Channel: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; +begin + Result := nil; +end; + +procedure TMedia_dummy.CloseVoiceStream(var VoiceStream: TAudioVoiceStream); +begin +end; + +initialization + MediaManager.Add(TMedia_dummy.Create); + +end. diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas new file mode 100644 index 00000000..0ab1d350 --- /dev/null +++ b/src/media/UVideo.pas @@ -0,0 +1,828 @@ +{############################################################################## + # FFmpeg support for UltraStar deluxe # + # # + # Created by b1indy # + # based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/) # + # with modifications by Jay Binks # + # # + # http://www.mail-archive.com/fpc-pascal@lists.freepascal.org/msg09949.html # + # http://www.nabble.com/file/p11795857/mpegpas01.zip # + # # + ##############################################################################} + +unit UVideo; + +// 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} + +implementation + +uses + SDL, + textgl, + avcodec, + avformat, + avutil, + avio, + rational, + {$IFDEF UseSWScale} + swscale, + {$ENDIF} + UMediaCore_FFmpeg, + math, + gl, + glext, + SysUtils, + UCommon, + UConfig, + ULog, + UMusic, + UGraphicClasses, + UGraphic; + +const +{$IFDEF PIXEL_FMT_BGR} + PIXEL_FMT_OPENGL = GL_BGR; + PIXEL_FMT_FFMPEG = PIX_FMT_BGR24; +{$ELSE} + PIXEL_FMT_OPENGL = GL_RGB; + PIXEL_FMT_FFMPEG = PIX_FMT_RGB24; +{$ENDIF} + +type + TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback ) + private + fVideoOpened, + fVideoPaused: Boolean; + + VideoStream: PAVStream; + VideoStreamIndex : Integer; + VideoFormatContext: PAVFormatContext; + VideoCodecContext: PAVCodecContext; + VideoCodec: PAVCodec; + + AVFrame: PAVFrame; + AVFrameRGB: PAVFrame; + FrameBuffer: PByte; + + {$IFDEF UseSWScale} + SoftwareScaleContext: PSwsContext; + {$ENDIF} + + fVideoTex: GLuint; + TexWidth, TexHeight: Cardinal; + + VideoAspect: Real; + VideoTimeBase, VideoTime: Extended; + fLoopTime: Extended; + + EOF: boolean; + Loop: boolean; + + Initialized: boolean; + + procedure Reset(); + function DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; + procedure SynchronizeVideo(Frame: PAVFrame; var pts: double); + public + function GetName: String; + + function Init(): boolean; + function Finalize: boolean; + + function Open(const aFileName : string): boolean; // true if succeed + procedure Close; + + procedure Play; + procedure Pause; + procedure Stop; + + procedure SetPosition(Time: real); + function GetPosition: real; + + procedure GetFrame(Time: Extended); + procedure DrawGL(Screen: integer); + end; + +var + FFmpegCore: TMediaCore_FFmpeg; + + +// These are called whenever we allocate a frame buffer. +// We use this to store the global_pts in a frame at the time it is allocated. +function PtsGetBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame): integer; cdecl; +var + pts: Pint64; + VideoPktPts: Pint64; +begin + Result := avcodec_default_get_buffer(CodecCtx, Frame); + VideoPktPts := CodecCtx^.opaque; + if (VideoPktPts <> nil) then + begin + // Note: we must copy the pts instead of passing a pointer, because the packet + // (and with it the pts) might change before a frame is returned by av_decode_video. + pts := av_malloc(sizeof(int64)); + pts^ := VideoPktPts^; + Frame^.opaque := pts; + end; +end; + +procedure PtsReleaseBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame); cdecl; +begin + if (Frame <> nil) then + av_freep(@Frame^.opaque); + avcodec_default_release_buffer(CodecCtx, Frame); +end; + + +{*------------------------------------------------------------------------------ + * TVideoPlayback_ffmpeg + *------------------------------------------------------------------------------} + +function TVideoPlayback_FFmpeg.GetName: String; +begin + result := 'FFmpeg_Video'; +end; + +function TVideoPlayback_FFmpeg.Init(): boolean; +begin + Result := true; + + if (Initialized) then + Exit; + Initialized := true; + + FFmpegCore := TMediaCore_FFmpeg.GetInstance(); + + Reset(); + av_register_all(); + glGenTextures(1, PGLuint(@fVideoTex)); +end; + +function TVideoPlayback_FFmpeg.Finalize(): boolean; +begin + Close(); + glDeleteTextures(1, PGLuint(@fVideoTex)); + Result := true; +end; + +procedure TVideoPlayback_FFmpeg.Reset(); +begin + // close previously opened video + Close(); + + fVideoOpened := False; + fVideoPaused := False; + VideoTimeBase := 0; + VideoTime := 0; + VideoStream := nil; + VideoStreamIndex := -1; + + EOF := false; + + // TODO: do we really want this by default? + Loop := true; + fLoopTime := 0; +end; + +function TVideoPlayback_FFmpeg.Open(const aFileName : string): boolean; // true if succeed +var + errnum: Integer; + AudioStreamIndex: integer; +begin + Result := false; + + Reset(); + + errnum := av_open_input_file(VideoFormatContext, PChar(aFileName), nil, 0, nil); + if (errnum <> 0) then + begin + Log.LogError('Failed to open file "'+aFileName+'" ('+FFmpegCore.GetErrorString(errnum)+')'); + Exit; + end; + + // update video info + if (av_find_stream_info(VideoFormatContext) < 0) then + begin + Log.LogError('No stream info found', 'TVideoPlayback_ffmpeg.Open'); + Close(); + Exit; + end; + Log.LogInfo('VideoStreamIndex : ' + inttostr(VideoStreamIndex), 'TVideoPlayback_ffmpeg.Open'); + + // find video stream + FFmpegCore.FindStreamIDs(VideoFormatContext, VideoStreamIndex, AudioStreamIndex); + if (VideoStreamIndex < 0) then + begin + Log.LogError('No video stream found', 'TVideoPlayback_ffmpeg.Open'); + Close(); + Exit; + end; + + VideoStream := VideoFormatContext^.streams[VideoStreamIndex]; + VideoCodecContext := VideoStream^.codec; + + VideoCodec := avcodec_find_decoder(VideoCodecContext^.codec_id); + if (VideoCodec = nil) then + begin + Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open'); + Close(); + Exit; + end; + + // set debug options + VideoCodecContext^.debug_mv := 0; + VideoCodecContext^.debug := 0; + + // detect bug-workarounds automatically + VideoCodecContext^.workaround_bugs := FF_BUG_AUTODETECT; + // error resilience strategy (careful/compliant/agressive/very_aggressive) + //VideoCodecContext^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT; + // allow non spec compliant speedup tricks. + //VideoCodecContext^.flags2 := VideoCodecContext^.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(VideoCodecContext, VideoCodec); + 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 + VideoCodecContext^.get_buffer := PtsGetBuffer; + VideoCodecContext^.release_buffer := PtsReleaseBuffer; + + {$ifdef DebugDisplay} + DebugWriteln('Found a matching Codec: '+ VideoCodecContext^.Codec.Name + sLineBreak + + sLineBreak + + ' Width = '+inttostr(VideoCodecContext^.width) + + ', Height='+inttostr(VideoCodecContext^.height) + sLineBreak + + ' Aspect : '+inttostr(VideoCodecContext^.sample_aspect_ratio.num) + '/' + + inttostr(VideoCodecContext^.sample_aspect_ratio.den) + sLineBreak + + ' Framerate : '+inttostr(VideoCodecContext^.time_base.num) + '/' + + inttostr(VideoCodecContext^.time_base.den)); + {$endif} + + // allocate space for decoded frame and rgb frame + AVFrame := avcodec_alloc_frame(); + AVFrameRGB := avcodec_alloc_frame(); + FrameBuffer := av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG, + VideoCodecContext^.width, VideoCodecContext^.height)); + + if ((AVFrame = nil) or (AVFrameRGB = nil) or (FrameBuffer = 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(AVFrameRGB), FrameBuffer, PIXEL_FMT_FFMPEG, + VideoCodecContext^.width, VideoCodecContext^.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 + VideoAspect := av_q2d(VideoCodecContext^.sample_aspect_ratio); + if (VideoAspect = 0) then + VideoAspect := VideoCodecContext^.width / + VideoCodecContext^.height + else + VideoAspect := VideoAspect * VideoCodecContext^.width / + VideoCodecContext^.height; + + VideoTimeBase := 1/av_q2d(VideoStream^.r_frame_rate); + + // hack to get reasonable timebase (for divx and others) + if (VideoTimeBase < 0.02) then // 0.02 <-> 50 fps + begin + VideoTimeBase := av_q2d(VideoStream^.r_frame_rate); + while (VideoTimeBase > 50) do + VideoTimeBase := VideoTimeBase/10; + VideoTimeBase := 1/VideoTimeBase; + end; + + Log.LogInfo('VideoTimeBase: ' + floattostr(VideoTimeBase), 'TVideoPlayback_ffmpeg.Open'); + Log.LogInfo('Framerate: '+inttostr(floor(1/VideoTimeBase))+'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. + SoftwareScaleContext := sws_getContext( + VideoCodecContext^.width, VideoCodecContext^.height, + integer(VideoCodecContext^.pix_fmt), + VideoCodecContext^.width, VideoCodecContext^.height, + integer(PIXEL_FMT_FFMPEG), + SWS_FAST_BILINEAR, nil, nil, nil); + if (SoftwareScaleContext = nil) then + begin + Log.LogError('Failed to get swscale context', 'TVideoPlayback_ffmpeg.Open'); + Close(); + Exit; + end; + {$ENDIF} + + TexWidth := Round(Power(2, Ceil(Log2(VideoCodecContext^.width)))); + TexHeight := Round(Power(2, Ceil(Log2(VideoCodecContext^.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, fVideoTex); + glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexImage2D(GL_TEXTURE_2D, 0, 3, TexWidth, TexHeight, 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); + + + fVideoOpened := True; + + Result := true; +end; + +procedure TVideoPlayback_FFmpeg.Close; +begin + if (FrameBuffer <> nil) then + av_free(FrameBuffer); + if (AVFrameRGB <> nil) then + av_free(AVFrameRGB); + if (AVFrame <> nil) then + av_free(AVFrame); + + AVFrame := nil; + AVFrameRGB := nil; + FrameBuffer := nil; + + if (VideoCodecContext <> nil) then + begin + // avcodec_close() is not thread-safe + FFmpegCore.LockAVCodec(); + try + avcodec_close(VideoCodecContext); + finally + FFmpegCore.UnlockAVCodec(); + end; + end; + + if (VideoFormatContext <> nil) then + av_close_input_file(VideoFormatContext); + + VideoCodecContext := nil; + VideoFormatContext := nil; + + fVideoOpened := False; +end; + +procedure TVideoPlayback_FFmpeg.SynchronizeVideo(Frame: PAVFrame; var pts: double); +var + FrameDelay: double; +begin + if (pts <> 0) then + begin + // if we have pts, set video clock to it + VideoTime := pts; + end else + begin + // if we aren't given a pts, set it to the clock + pts := VideoTime; + end; + // update the video clock + FrameDelay := av_q2d(VideoCodecContext^.time_base); + // if we are repeating a frame, adjust clock accordingly + FrameDelay := FrameDelay + Frame^.repeat_pict * (FrameDelay * 0.5); + VideoTime := VideoTime + FrameDelay; +end; + +function TVideoPlayback_FFmpeg.DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; +var + FrameFinished: Integer; + VideoPktPts: int64; + pbIOCtx: PByteIOContext; + errnum: integer; +begin + Result := false; + FrameFinished := 0; + + if EOF 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(VideoFormatContext, AVPacket); + if (errnum < 0) then + begin + // failed to read a frame, check reason + + {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} + pbIOCtx := VideoFormatContext^.pb; + {$ELSE} + pbIOCtx := @VideoFormatContext^.pb; + {$IFEND} + + // check for end-of-file (eof is not an error) + if (url_feof(pbIOCtx) <> 0) then + begin + EOF := 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 ((VideoFormatContext^.file_size <> 0) and + (pbIOCtx^.pos >= VideoFormatContext^.file_size)) then + begin + EOF := true; + Exit; + end; + + // no error -> wait for user input + SDL_Delay(100); + continue; + end; + + // if we got a packet from the video stream, then decode it + if (AVPacket.stream_index = VideoStreamIndex) then + begin + // save pts to be stored in pFrame in first call of PtsGetBuffer() + VideoPktPts := AVPacket.pts; + VideoCodecContext^.opaque := @VideoPktPts; + + // decode packet + avcodec_decode_video(VideoCodecContext, AVFrame, + frameFinished, AVPacket.data, AVPacket.size); + + // reset opaque data + VideoCodecContext^.opaque := nil; + + // update pts + if (AVPacket.dts <> AV_NOPTS_VALUE) then + begin + pts := AVPacket.dts; + end + else if ((AVFrame^.opaque <> nil) and + (Pint64(AVFrame^.opaque)^ <> AV_NOPTS_VALUE)) then + begin + pts := Pint64(AVFrame^.opaque)^; + end + else + begin + pts := 0; + end; + pts := pts * av_q2d(VideoStream^.time_base); + + // synchronize on each complete frame + if (frameFinished <> 0) then + SynchronizeVideo(AVFrame, pts); + end; + + // free the packet from av_read_frame + av_free_packet( @AVPacket ); + end; + + Result := true; +end; + +procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended); +var + AVPacket: TAVPacket; + errnum: Integer; + myTime: Extended; + TimeDifference: Extended; + DropFrameCount: Integer; + pts: double; + i: Integer; +const + FRAME_DROPCOUNT = 3; +begin + if not fVideoOpened then + Exit; + + if fVideoPaused then + Exit; + + // current time, relative to last loop (if any) + myTime := Time - fLoopTime; + // time since the last frame was returned + TimeDifference := myTime - VideoTime; + + {$IFDEF DebugDisplay} + DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak + + 'VideoTime: '+inttostr(floor(VideoTime*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(VideoTimeBase*1000)) + sLineBreak + + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); + {$endif} + + // check if a new frame is needed + if (VideoTime <> 0) and (TimeDifference < VideoTimeBase) 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(VideoTime*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(VideoTimeBase*1000)) + sLineBreak + + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); + {$endif} + + // we do not need a new frame now + Exit; + end; + + // update video-time to the next frame + VideoTime := VideoTime + VideoTimeBase; + TimeDifference := myTime - VideoTime; + + // check if we have to skip frames + if (TimeDifference >= FRAME_DROPCOUNT*VideoTimeBase) 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(VideoTimeBase*1000)) + sLineBreak + + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); + {$endif} + + // update video-time + DropFrameCount := Trunc(TimeDifference / VideoTimeBase); + VideoTime := VideoTime + DropFrameCount*VideoTimeBase; + + // skip half of the frames, this is much smoother than to skip all at once + for i := 1 to DropFrameCount (*div 2*) do + DecodeFrame(AVPacket, pts); + end; + + {$IFDEF VideoBenchmark} + Log.BenchmarkStart(15); + {$ENDIF} + + if (not DecodeFrame(AVPacket, pts)) then + begin + if Loop then + begin + // Record the time we looped. This is used to keep the loops in time. otherwise they speed + SetPosition(0); + fLoopTime := Time; + end; + Exit; + end; + + // TODO: support for pan&scan + //if (AVFrame.pan_scan <> nil) then + //begin + // Writeln(Format('PanScan: %d/%d', [AVFrame.pan_scan.width, AVFrame.pan_scan.height])); + //end; + + // otherwise we convert the pixeldata from YUV to RGB + {$IFDEF UseSWScale} + errnum := sws_scale(SoftwareScaleContext, @(AVFrame.data), @(AVFrame.linesize), + 0, VideoCodecContext^.Height, + @(AVFrameRGB.data), @(AVFrameRGB.linesize)); + {$ELSE} + errnum := img_convert(PAVPicture(AVFrameRGB), PIXEL_FMT_FFMPEG, + PAVPicture(AVFrame), VideoCodecContext^.pix_fmt, + VideoCodecContext^.width, VideoCodecContext^.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, fVideoTex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + VideoCodecContext^.width, VideoCodecContext^.height, + PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, AVFrameRGB^.data[0]); + + {$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.DrawGL(Screen: integer); +var + TexVideoRightPos, TexVideoLowerPos: Single; + ScreenLeftPos, ScreenRightPos: Single; + ScreenUpperPos, ScreenLowerPos: Single; + ScaledVideoWidth, ScaledVideoHeight: Single; + ScreenMidPosX, ScreenMidPosY: Single; + ScreenAspect: Single; +begin + // have a nice black background to draw on (even if there were errors opening the vid) + 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 fVideoOpened) then + Exit; + + {$IFDEF VideoBenchmark} + Log.BenchmarkStart(15); + {$ENDIF} + + // TODO: add a SetAspectCorrectionMode() function so we can switch + // aspect correction. The screens video backgrounds look very ugly with aspect + // correction because of the white bars at the top and bottom. + + ScreenAspect := ScreenW / ScreenH; + ScaledVideoWidth := RenderW; + ScaledVideoHeight := RenderH * ScreenAspect/VideoAspect; + + // Note: Scaling the width does not look good because the video might contain + // black borders at the top already + //ScaledVideoHeight := RenderH; + //ScaledVideoWidth := RenderW * VideoAspect/ScreenAspect; + + // center the video + ScreenMidPosX := RenderW/2; + ScreenMidPosY := RenderH/2; + ScreenLeftPos := ScreenMidPosX - ScaledVideoWidth/2; + ScreenRightPos := ScreenMidPosX + ScaledVideoWidth/2; + ScreenUpperPos := ScreenMidPosY - ScaledVideoHeight/2; + ScreenLowerPos := ScreenMidPosY + ScaledVideoHeight/2; + // the video-texture contains empty borders because its width and height must be + // a power of 2. So we have to determine the texture coords of the video. + TexVideoRightPos := VideoCodecContext^.width / TexWidth; + TexVideoLowerPos := VideoCodecContext^.height / TexHeight; + + // we could use blending for brightness control, but do we need this? + glDisable(GL_BLEND); + + // TODO: disable other stuff like lightning, etc. + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, fVideoTex); + glColor3f(1, 1, 1); + glBegin(GL_QUADS); + // upper-left coord + glTexCoord2f(0, 0); + glVertex2f(ScreenLeftPos, ScreenUpperPos); + // lower-left coord + glTexCoord2f(0, TexVideoLowerPos); + glVertex2f(ScreenLeftPos, ScreenLowerPos); + // lower-right coord + glTexCoord2f(TexVideoRightPos, TexVideoLowerPos); + glVertex2f(ScreenRightPos, ScreenLowerPos); + // upper-right coord + glTexCoord2f(TexVideoRightPos, 0); + glVertex2f(ScreenRightPos, ScreenUpperPos); + glEnd; + glDisable(GL_TEXTURE_2D); + + {$IFDEF VideoBenchmark} + Log.BenchmarkEnd(15); + Log.LogBenchmark('DrawGL', 15); + {$ENDIF} + + {$IFDEF Info} + if (fVideoSkipTime+VideoTime+VideoTimeBase < 0) then + begin + glColor4f(0.7, 1, 0.3, 1); + SetFontStyle (1); + SetFontItalic(False); + SetFontSize(9); + 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(9); + 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 + fVideoPaused := not fVideoPaused; +end; + +procedure TVideoPlayback_FFmpeg.Stop; +begin +end; + +procedure TVideoPlayback_FFmpeg.SetPosition(Time: real); +var + SeekFlags: integer; +begin + if not fVideoOpened then + Exit; + + if (Time < 0) then + Time := 0; + + // TODO: handle loop-times + //Time := Time mod VideoDuration; + + // backward seeking might fail without AVSEEK_FLAG_BACKWARD + SeekFlags := AVSEEK_FLAG_ANY; + if (Time < VideoTime) then + SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD; + + VideoTime := Time; + EOF := false; + + if (av_seek_frame(VideoFormatContext, VideoStreamIndex, Floor(Time/VideoTimeBase), SeekFlags) < 0) then + begin + Log.LogError('av_seek_frame() failed', 'TVideoPlayback_ffmpeg.SetPosition'); + Exit; + end; + + avcodec_flush_buffers(VideoCodecContext); +end; + +function TVideoPlayback_FFmpeg.GetPosition: real; +begin + // TODO: return video-position in seconds + Result := VideoTime; +end; + +initialization + MediaManager.Add(TVideoPlayback_FFmpeg.Create); + +end. diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas new file mode 100644 index 00000000..e2125201 --- /dev/null +++ b/src/media/UVisualizer.pas @@ -0,0 +1,442 @@ +unit UVisualizer; + +(* TODO: + * - fix video/visualizer switching + * - use GL_EXT_framebuffer_object for rendering to a separate framebuffer, + * this will prevent plugins from messing up our render-context + * (-> no stack corruption anymore, no need for Save/RestoreOpenGLState()). + * - create a generic (C-compatible) interface for visualization plugins + * - create a visualization plugin manager + * - write a plugin for projectM in C/C++ (so we need no wrapper anymore) + *) + +{* Note: + * It would be easier to create a seperate Render-Context (RC) for projectM + * and switch to it when necessary. This can be achieved by pbuffers + * (slow and platform specific) or the OpenGL FramebufferObject (FBO) extension + * (fast and plattform-independent but not supported by older graphic-cards/drivers). + * + * See http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt + * + * To support as many cards as possible we will stick to the current dirty + * solution for now even if it is a pain to save/restore projectM's state due + * to bugs etc. + * + * This also restricts us to projectM. As other plug-ins might have different + * needs and bugs concerning the OpenGL state, USDX's state would probably be + * corrupted after the plug-in finshed drawing. + *} + +interface + +{$IFDEF FPC} + {$MODE DELPHI} +{$ENDIF} + +{$I switches.inc} + +uses + SDL, + UGraphicClasses, + textgl, + math, + gl, + SysUtils, + UIni, + projectM, + UMusic; + +implementation + +uses + UGraphic, + UMain, + UConfig, + ULog; + +{$IF PROJECTM_VERSION < 1000000} // < 1.0 +// Initialization data used on projectM 0.9x creation. +// Since projectM 1.0 this data is passed via the config-file. +const + meshX = 32; + meshY = 24; + fps = 30; + textureSize = 512; +{$IFEND} + +type + TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization ) + private + pm: TProjectM; + ProjectMPath : string; + Initialized: boolean; + + VisualizerStarted: boolean; + VisualizerPaused: boolean; + + VisualTex: GLuint; + PCMData: TPCMData; + RndPCMcount: integer; + + projMatrix: array[0..3, 0..3] of GLdouble; + texMatrix: array[0..3, 0..3] of GLdouble; + + procedure VisualizerStart; + procedure VisualizerStop; + + procedure VisualizerTogglePause; + + function GetRandomPCMData(var data: TPCMData): Cardinal; + + procedure SaveOpenGLState(); + procedure RestoreOpenGLState(); + + public + function GetName: String; + + function Init(): boolean; + function Finalize(): boolean; + + function Open(const aFileName : string): boolean; // true if succeed + procedure Close; + + procedure Play; + procedure Pause; + procedure Stop; + + procedure SetPosition(Time: real); + function GetPosition: real; + + procedure GetFrame(Time: Extended); + procedure DrawGL(Screen: integer); + end; + + +function TVideoPlayback_ProjectM.GetName: String; +begin + Result := 'ProjectM'; +end; + +function TVideoPlayback_ProjectM.Init(): boolean; +begin + Result := true; + + if (Initialized) then + Exit; + Initialized := true; + + RndPCMcount := 0; + + ProjectMPath := ProjectM_DataDir + PathDelim; + + VisualizerStarted := False; + VisualizerPaused := False; + + {$IFDEF UseTexture} + glGenTextures(1, PglUint(@VisualTex)); + glBindTexture(GL_TEXTURE_2D, VisualTex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + {$ENDIF} +end; + +function TVideoPlayback_ProjectM.Finalize(): boolean; +begin + VisualizerStop(); + {$IFDEF UseTexture} + glDeleteTextures(1, PglUint(@VisualTex)); + {$ENDIF} + Result := true; +end; + +function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed +begin + Result := false; +end; + +procedure TVideoPlayback_ProjectM.Close; +begin + VisualizerStop(); +end; + +procedure TVideoPlayback_ProjectM.Play; +begin + VisualizerStart(); +end; + +procedure TVideoPlayback_ProjectM.Pause; +begin + VisualizerTogglePause(); +end; + +procedure TVideoPlayback_ProjectM.Stop; +begin + VisualizerStop(); +end; + +procedure TVideoPlayback_ProjectM.SetPosition(Time: real); +begin + if assigned(pm) then + pm.RandomPreset(); +end; + +function TVideoPlayback_ProjectM.GetPosition: real; +begin + Result := 0; +end; + +{** + * 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); + + // Note: we do not use glPushMatrix() for the GL_PROJECTION and GL_TEXTURE stacks. + // OpenGL specifies the depth of those 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, so we can + // use glPushMatrix() for this stack. + + // save projection-matrix + glMatrixMode(GL_PROJECTION); + glGetDoublev(GL_PROJECTION_MATRIX, @projMatrix); + {$IF PROJECTM_VERSION = 1000000} // 1.0, 1.01 + // bugfix: projection-matrix is popped without being pushed first + glPushMatrix(); + {$IFEND} + + // save texture-matrix + glMatrixMode(GL_TEXTURE); + glGetDoublev(GL_TEXTURE_MATRIX, @texMatrix); + + // save modelview-matrix + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + {$IF PROJECTM_VERSION = 1000000} // 1.0, 1.01 + // bugfix: modelview-matrix is popped without being pushed first + glPushMatrix(); + {$IFEND} + + // 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 projection-matrix + glMatrixMode(GL_PROJECTION); + glLoadMatrixd(@projMatrix); + + // restore texture-matrix + glMatrixMode(GL_TEXTURE); + glLoadMatrixd(@texMatrix); + + // restore modelview-matrix + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + // restore all OpenGL state-machine attributes + glPopAttrib(); +end; + +procedure TVideoPlayback_ProjectM.VisualizerStart; +begin + if VisualizerStarted then + Exit; + + // the OpenGL state must be saved before + 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(); + + VisualizerStarted := True; + finally + RestoreOpenGLState(); + end; +end; + +procedure TVideoPlayback_ProjectM.VisualizerStop; +begin + if VisualizerStarted then + begin + VisualizerStarted := False; + FreeAndNil(pm); + end; +end; + +procedure TVideoPlayback_ProjectM.VisualizerTogglePause; +begin + VisualizerPaused := not VisualizerPaused; +end; + +procedure TVideoPlayback_ProjectM.GetFrame(Time: Extended); +var + nSamples: cardinal; + stackDepth: Integer; +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(); + gluOrtho2D(0, 1, 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 + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2f(0, 0); + glTexCoord2f(1, 0); glVertex2f(1, 0); + glTexCoord2f(1, 1); glVertex2f(1, 1); + glTexCoord2f(0, 1); glVertex2f(0, 1); + glEnd(); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + + // restore state + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + {$ENDIF} +end; + +{** + * Produces random "sound"-data in case no audio-data is available. + * Otherwise the visualization will look rather boring. + *} +function TVideoPlayback_ProjectM.GetRandomPCMData(var data: TPCMData): Cardinal; +var + i: integer; +begin + // Produce some fake PCM data + if (RndPCMcount mod 500 = 0) then + begin + FillChar(data, SizeOf(TPCMData), 0); + end + else + begin + for i := 0 to 511 do + begin + data[i][0] := Random(High(Word)+1); + data[i][1] := Random(High(Word)+1); + end; + end; + Inc(RndPCMcount); + Result := 512; +end; + + +initialization + MediaManager.Add(TVideoPlayback_ProjectM.Create); + +end. -- cgit v1.2.3 From a40eb015f8dc43ee087bb8374e67c6a75eb9fbc6 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 13 Sep 2008 13:02:27 +0000 Subject: - stop both Video and Visualizer (because of the video-toggle with 'v'-key both could have been active) (See SF-tracker Patch 2078902) - VideoLoaded does not belong to the song state anymore git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1381 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVisualizer.pas | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'src/media') diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index e2125201..879d6c42 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -78,15 +78,15 @@ type PCMData: TPCMData; RndPCMcount: integer; - projMatrix: array[0..3, 0..3] of GLdouble; - texMatrix: array[0..3, 0..3] of GLdouble; + ProjMatrix: array[0..3, 0..3] of GLdouble; + TexMatrix: array[0..3, 0..3] of GLdouble; procedure VisualizerStart; procedure VisualizerStop; procedure VisualizerTogglePause; - function GetRandomPCMData(var data: TPCMData): Cardinal; + function GetRandomPCMData(var Data: TPCMData): Cardinal; procedure SaveOpenGLState(); procedure RestoreOpenGLState(); @@ -210,7 +210,7 @@ begin // save projection-matrix glMatrixMode(GL_PROJECTION); - glGetDoublev(GL_PROJECTION_MATRIX, @projMatrix); + glGetDoublev(GL_PROJECTION_MATRIX, @ProjMatrix); {$IF PROJECTM_VERSION = 1000000} // 1.0, 1.01 // bugfix: projection-matrix is popped without being pushed first glPushMatrix(); @@ -218,7 +218,7 @@ begin // save texture-matrix glMatrixMode(GL_TEXTURE); - glGetDoublev(GL_TEXTURE_MATRIX, @texMatrix); + glGetDoublev(GL_TEXTURE_MATRIX, @TexMatrix); // save modelview-matrix glMatrixMode(GL_MODELVIEW); @@ -243,11 +243,11 @@ begin // restore projection-matrix glMatrixMode(GL_PROJECTION); - glLoadMatrixd(@projMatrix); + glLoadMatrixd(@ProjMatrix); // restore texture-matrix glMatrixMode(GL_TEXTURE); - glLoadMatrixd(@texMatrix); + glLoadMatrixd(@TexMatrix); // restore modelview-matrix glMatrixMode(GL_MODELVIEW); @@ -293,7 +293,8 @@ begin // We use the latter so we do not need to load the FBO extension in USDX. pm.RenderFrame(); - VisualizerStarted := True; + VisualizerPaused := false; + VisualizerStarted := true; finally RestoreOpenGLState(); end; @@ -303,7 +304,8 @@ procedure TVideoPlayback_ProjectM.VisualizerStop; begin if VisualizerStarted then begin - VisualizerStarted := False; + VisualizerPaused := false; + VisualizerStarted := false; FreeAndNil(pm); end; end; @@ -414,21 +416,21 @@ 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 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); + 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); + Data[i][0] := Random(High(Word)+1); + Data[i][1] := Random(High(Word)+1); end; end; Inc(RndPCMcount); -- cgit v1.2.3 From f16756422a5dbb24ce1b751bb9e2bb1de4f19713 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 23 Sep 2008 21:17:50 +0000 Subject: added file headers git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1404 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioConverter.pas | 25 +++++++++++++++++++ src/media/UAudioCore_Bass.pas | 25 +++++++++++++++++++ src/media/UAudioCore_Portaudio.pas | 25 +++++++++++++++++++ src/media/UAudioDecoder_Bass.pas | 25 +++++++++++++++++++ src/media/UAudioDecoder_FFmpeg.pas | 25 +++++++++++++++++++ src/media/UAudioInput_Bass.pas | 25 +++++++++++++++++++ src/media/UAudioInput_Portaudio.pas | 25 +++++++++++++++++++ src/media/UAudioPlaybackBase.pas | 25 +++++++++++++++++++ src/media/UAudioPlayback_Bass.pas | 25 +++++++++++++++++++ src/media/UAudioPlayback_Portaudio.pas | 25 +++++++++++++++++++ src/media/UAudioPlayback_SDL.pas | 25 +++++++++++++++++++ src/media/UAudioPlayback_SoftMixer.pas | 25 +++++++++++++++++++ src/media/UMediaCore_FFmpeg.pas | 25 +++++++++++++++++++ src/media/UMediaCore_SDL.pas | 25 +++++++++++++++++++ src/media/UMedia_dummy.pas | 25 +++++++++++++++++++ src/media/UVideo.pas | 44 ++++++++++++++++++++++++---------- src/media/UVisualizer.pas | 25 +++++++++++++++++++ 17 files changed, 432 insertions(+), 12 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioConverter.pas b/src/media/UAudioConverter.pas index 5647f27b..24131b16 100644 --- a/src/media/UAudioConverter.pas +++ b/src/media/UAudioConverter.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioCore_Bass.pas b/src/media/UAudioCore_Bass.pas index beb2db16..12623dc1 100644 --- a/src/media/UAudioCore_Bass.pas +++ b/src/media/UAudioCore_Bass.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index cd279d99..30773b07 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioDecoder_Bass.pas b/src/media/UAudioDecoder_Bass.pas index dba1fde4..3c31175d 100644 --- a/src/media/UAudioDecoder_Bass.pas +++ b/src/media/UAudioDecoder_Bass.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index d9b4c93c..399c2f52 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -1,3 +1,28 @@ +{* 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; (******************************************************************************* diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index 65a4704d..39b49138 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 9a1c3e99..14291658 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index 2337d43f..7d143fdc 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas index 41a91173..d68ac1d4 100644 --- a/src/media/UAudioPlayback_Bass.pas +++ b/src/media/UAudioPlayback_Bass.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioPlayback_Portaudio.pas b/src/media/UAudioPlayback_Portaudio.pas index c3717ba6..0a6f37bb 100644 --- a/src/media/UAudioPlayback_Portaudio.pas +++ b/src/media/UAudioPlayback_Portaudio.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioPlayback_SDL.pas b/src/media/UAudioPlayback_SDL.pas index deef91e8..238edac1 100644 --- a/src/media/UAudioPlayback_SDL.pas +++ b/src/media/UAudioPlayback_SDL.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index 6ddae980..efe663b3 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index cdd320ac..e57760d6 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/src/media/UMediaCore_FFmpeg.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UMediaCore_SDL.pas b/src/media/UMediaCore_SDL.pas index 252f72a0..74c75e16 100644 --- a/src/media/UMediaCore_SDL.pas +++ b/src/media/UMediaCore_SDL.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 438b89ab..7558dd0b 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -1,3 +1,28 @@ +{* 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 diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 0ab1d350..32484d5e 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -1,17 +1,37 @@ -{############################################################################## - # FFmpeg support for UltraStar deluxe # - # # - # Created by b1indy # - # based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/) # - # with modifications by Jay Binks # - # # - # http://www.mail-archive.com/fpc-pascal@lists.freepascal.org/msg09949.html # - # http://www.nabble.com/file/p11795857/mpegpas01.zip # - # # - ##############################################################################} - +{* 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/) + * + * http://www.mail-archive.com/fpc-pascal@lists.freepascal.org/msg09949.html + * http://www.nabble.com/file/p11795857/mpegpas01.zip + *} + // uncomment if you want to see the debug stuff {.$define DebugDisplay} {.$define DebugFrames} diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index 879d6c42..c33a4a09 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -1,3 +1,28 @@ +{* 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: -- cgit v1.2.3 From d397c0364763dbf0449adb2c8568377bba62d7fa Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Sat, 27 Sep 2008 23:28:57 +0000 Subject: remove some white space. no code change git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1416 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 1 - src/media/UAudioInput_Bass.pas | 1 - src/media/UAudioInput_Portaudio.pas | 1 - src/media/UAudioPlayback_Portaudio.pas | 1 - src/media/UAudioPlayback_SDL.pas | 1 - src/media/UAudioPlayback_SoftMixer.pas | 1 - 6 files changed, 6 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 30773b07..25ceae3c 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -33,7 +33,6 @@ interface {$I ../switches.inc} - uses Classes, SysUtils, diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index 39b49138..cf292c45 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -33,7 +33,6 @@ interface {$I switches.inc} - uses Classes, SysUtils, diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 14291658..53080a03 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -33,7 +33,6 @@ interface {$I ../switches.inc} - uses Classes, SysUtils, diff --git a/src/media/UAudioPlayback_Portaudio.pas b/src/media/UAudioPlayback_Portaudio.pas index 0a6f37bb..ddbd03d6 100644 --- a/src/media/UAudioPlayback_Portaudio.pas +++ b/src/media/UAudioPlayback_Portaudio.pas @@ -33,7 +33,6 @@ interface {$I switches.inc} - uses Classes, SysUtils, diff --git a/src/media/UAudioPlayback_SDL.pas b/src/media/UAudioPlayback_SDL.pas index 238edac1..b0887676 100644 --- a/src/media/UAudioPlayback_SDL.pas +++ b/src/media/UAudioPlayback_SDL.pas @@ -33,7 +33,6 @@ interface {$I switches.inc} - uses Classes, SysUtils, diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index efe663b3..f3797dd6 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -33,7 +33,6 @@ interface {$I switches.inc} - uses Classes, SysUtils, -- cgit v1.2.3 From 08a0ddf3d8f9eb819e03d9cd6c8d79bd8634fec6 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 1 Oct 2008 12:28:15 +0000 Subject: - FFmpeg header update - update to newest revision - if linked libs are too new, USDX will not compile anymore and display an error message (to avoid mysterious crashes if an unsupported version of FFmpeg is used) - comment change in UVisualizer.pas git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1428 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMediaCore_FFmpeg.pas | 2 +- src/media/UVisualizer.pas | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index e57760d6..9ad19a5b 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/src/media/UMediaCore_FFmpeg.pas @@ -209,9 +209,9 @@ begin case FFmpegFormat of SAMPLE_FMT_U8: Format := asfU8; SAMPLE_FMT_S16: Format := asfS16; - SAMPLE_FMT_S24: Format := asfS24; SAMPLE_FMT_S32: Format := asfS32; SAMPLE_FMT_FLT: Format := asfFloat; + SAMPLE_FMT_DBL: Format := asfDouble; else begin Result := false; Exit; diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index c33a4a09..a81e3e52 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -287,7 +287,7 @@ begin if VisualizerStarted then Exit; - // the OpenGL state must be saved before + // the OpenGL state must be saved before TProjectM.Create is called SaveOpenGLState(); try -- cgit v1.2.3 From 17983edf6d6dd8b7603ab0fc0d2a30f5789e33bc Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 1 Oct 2008 15:43:46 +0000 Subject: safer context switch - clear client attribs with glPopClientAttrib - store complete USDX's OpenGL matrix stack (not just the top one) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1429 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVisualizer.pas | 152 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 115 insertions(+), 37 deletions(-) (limited to 'src/media') diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index a81e3e52..2d3511d4 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -89,6 +89,10 @@ const textureSize = 512; {$IFEND} +type + TGLMatrix = array[0..3, 0..3] of GLdouble; + TGLMatrixStack = array of TGLMatrix; + type TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization ) private @@ -103,8 +107,9 @@ type PCMData: TPCMData; RndPCMcount: integer; - ProjMatrix: array[0..3, 0..3] of GLdouble; - TexMatrix: array[0..3, 0..3] of GLdouble; + ModelviewMatrixStack: TGLMatrixStack; + ProjectionMatrixStack: TGLMatrixStack; + TextureMatrixStack: TGLMatrixStack; procedure VisualizerStart; procedure VisualizerStop; @@ -113,6 +118,9 @@ type 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(); @@ -211,6 +219,100 @@ 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 @@ -226,32 +328,14 @@ procedure TVideoPlayback_ProjectM.SaveOpenGLState(); begin // save all OpenGL state-machine attributes glPushAttrib(GL_ALL_ATTRIB_BITS); + glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); - // Note: we do not use glPushMatrix() for the GL_PROJECTION and GL_TEXTURE stacks. - // OpenGL specifies the depth of those 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, so we can - // use glPushMatrix() for this stack. + SaveMatrixStack(GL_PROJECTION, ProjectionMatrixStack); + writeln(ProjectionMatrixStack[0][0][0]); + SaveMatrixStack(GL_MODELVIEW, ModelviewMatrixStack); + SaveMatrixStack(GL_TEXTURE, TextureMatrixStack); - // save projection-matrix - glMatrixMode(GL_PROJECTION); - glGetDoublev(GL_PROJECTION_MATRIX, @ProjMatrix); - {$IF PROJECTM_VERSION = 1000000} // 1.0, 1.01 - // bugfix: projection-matrix is popped without being pushed first - glPushMatrix(); - {$IFEND} - - // save texture-matrix - glMatrixMode(GL_TEXTURE); - glGetDoublev(GL_TEXTURE_MATRIX, @TexMatrix); - - // save modelview-matrix glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - {$IF PROJECTM_VERSION = 1000000} // 1.0, 1.01 - // bugfix: modelview-matrix is popped without being pushed first - glPushMatrix(); - {$IFEND} // reset OpenGL error-state glGetError(); @@ -266,19 +350,14 @@ begin // reset OpenGL error-state glGetError(); - // restore projection-matrix - glMatrixMode(GL_PROJECTION); - glLoadMatrixd(@ProjMatrix); - - // restore texture-matrix - glMatrixMode(GL_TEXTURE); - glLoadMatrixd(@TexMatrix); - - // restore modelview-matrix - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); + // 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; @@ -343,7 +422,6 @@ end; procedure TVideoPlayback_ProjectM.GetFrame(Time: Extended); var nSamples: cardinal; - stackDepth: Integer; begin if not VisualizerStarted then Exit; -- cgit v1.2.3 From 461b900dfcab9a702c6b15cfe0e7d19150feb6f0 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 1 Oct 2008 17:23:37 +0000 Subject: debug output removed git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1430 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVisualizer.pas | 1 - 1 file changed, 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index 2d3511d4..9af6d8c2 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -331,7 +331,6 @@ begin glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); SaveMatrixStack(GL_PROJECTION, ProjectionMatrixStack); - writeln(ProjectionMatrixStack[0][0][0]); SaveMatrixStack(GL_MODELVIEW, ModelviewMatrixStack); SaveMatrixStack(GL_TEXTURE, TextureMatrixStack); -- cgit v1.2.3 From 2f768387f3849699320229a5b756db78af1207a2 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 19 Oct 2008 16:24:59 +0000 Subject: The size given to TextGL.SetSize() now expresses the size in pixel (formerly it was 1/3 of the pixel-size). For theme and plugin compatibility the following functions multiply the size with 3: - UScreenSingModi.Print - TTheme.ThemeLoadText - TTheme.ThemeLoadSelectSlide TODO: Convert the themes/plugins and remove the "*3" git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1459 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 32484d5e..84949766 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -764,7 +764,7 @@ begin glColor4f(0.7, 1, 0.3, 1); SetFontStyle (1); SetFontItalic(False); - SetFontSize(9); + SetFontSize(27); SetFontPos (300, 0); glPrint('Delay due to negative VideoGap'); glColor4f(1, 1, 1, 1); @@ -783,7 +783,7 @@ begin glColor4f(1, 1, 1, 1); SetFontStyle (1); SetFontItalic(False); - SetFontSize(9); + SetFontSize(27); SetFontPos (5, 0); glPrint('delaying frame'); SetFontPos (5, 20); -- cgit v1.2.3 From eb04519b61a3af0a46f59bb71a9ce7634023a70e Mon Sep 17 00:00:00 2001 From: tobigun Date: Fri, 31 Oct 2008 14:56:19 +0000 Subject: - cleanup (private and protected class members prepended with f...) - TAspectCorrection introduced (default: crop) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1487 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 480 ++++++++++++++++++++++++++++----------------------- 1 file changed, 266 insertions(+), 214 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 84949766..9f3fc6da 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -27,9 +27,6 @@ unit UVideo; {* * based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/) - * - * http://www.mail-archive.com/fpc-pascal@lists.freepascal.org/msg09949.html - * http://www.nabble.com/file/p11795857/mpegpas01.zip *} // uncomment if you want to see the debug stuff @@ -51,6 +48,24 @@ interface {$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 @@ -86,40 +101,52 @@ const {$ENDIF} type + TRectCoords = record + Left, Right: double; + Upper, Lower: double; + end; + TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback ) private - fVideoOpened, - fVideoPaused: Boolean; + fOpened: boolean; //**< stream successfully opened + fPaused: boolean; //**< stream paused + fInitialized: boolean; + fEOF: boolean; //**< end-of-file state - VideoStream: PAVStream; - VideoStreamIndex : Integer; - VideoFormatContext: PAVFormatContext; - VideoCodecContext: PAVCodecContext; - VideoCodec: PAVCodec; + fLoop: boolean; //**< looping enabled - AVFrame: PAVFrame; - AVFrameRGB: PAVFrame; - FrameBuffer: PByte; + fStream: PAVStream; + fStreamIndex : integer; + fFormatContext: PAVFormatContext; + fCodecContext: PAVCodecContext; + fCodec: PAVCodec; - {$IFDEF UseSWScale} - SoftwareScaleContext: PSwsContext; - {$ENDIF} - - fVideoTex: GLuint; - TexWidth, TexHeight: Cardinal; + fAVFrame: PAVFrame; + fAVFrameRGB: PAVFrame; - VideoAspect: Real; - VideoTimeBase, VideoTime: Extended; - fLoopTime: Extended; + fFrameBuffer: PByte; //**< stores a FFmpeg video frame + fFrameTex: GLuint; //**< OpenGL texture for FrameBuffer + fTexWidth, fTexHeight: cardinal; - EOF: boolean; - Loop: boolean; + {$IFDEF UseSWScale} + fSwScaleContext: PSwsContext; + {$ENDIF} - Initialized: boolean; + fAspect: real; //**< width/height ratio + fAspectCorrection: TAspectCorrection; + fTimeBase: extended; //**< FFmpeg time base per time unit + fTime: extended; //**< video time position (absolute) + fLoopTime: extended; //**< video time position (relative to current loop) + procedure Reset(); function DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; procedure SynchronizeVideo(Frame: PAVFrame; var pts: double); + + procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); + + procedure ShowDebugInfo(); + public function GetName: String; @@ -184,21 +211,21 @@ function TVideoPlayback_FFmpeg.Init(): boolean; begin Result := true; - if (Initialized) then + if (fInitialized) then Exit; - Initialized := true; + fInitialized := true; FFmpegCore := TMediaCore_FFmpeg.GetInstance(); Reset(); av_register_all(); - glGenTextures(1, PGLuint(@fVideoTex)); + glGenTextures(1, PGLuint(@fFrameTex)); end; function TVideoPlayback_FFmpeg.Finalize(): boolean; begin Close(); - glDeleteTextures(1, PGLuint(@fVideoTex)); + glDeleteTextures(1, PGLuint(@fFrameTex)); Result := true; end; @@ -207,18 +234,20 @@ begin // close previously opened video Close(); - fVideoOpened := False; - fVideoPaused := False; - VideoTimeBase := 0; - VideoTime := 0; - VideoStream := nil; - VideoStreamIndex := -1; + fOpened := False; + fPaused := False; + fTimeBase := 0; + fTime := 0; + fStream := nil; + fStreamIndex := -1; - EOF := false; + fEOF := false; // TODO: do we really want this by default? - Loop := true; + fLoop := true; fLoopTime := 0; + + fAspectCorrection := acoCrop; end; function TVideoPlayback_FFmpeg.Open(const aFileName : string): boolean; // true if succeed @@ -230,7 +259,7 @@ begin Reset(); - errnum := av_open_input_file(VideoFormatContext, PChar(aFileName), nil, 0, nil); + errnum := av_open_input_file(fFormatContext, PChar(aFileName), nil, 0, nil); if (errnum <> 0) then begin Log.LogError('Failed to open file "'+aFileName+'" ('+FFmpegCore.GetErrorString(errnum)+')'); @@ -238,28 +267,28 @@ begin end; // update video info - if (av_find_stream_info(VideoFormatContext) < 0) then + 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(VideoStreamIndex), 'TVideoPlayback_ffmpeg.Open'); + Log.LogInfo('VideoStreamIndex : ' + inttostr(fStreamIndex), 'TVideoPlayback_ffmpeg.Open'); // find video stream - FFmpegCore.FindStreamIDs(VideoFormatContext, VideoStreamIndex, AudioStreamIndex); - if (VideoStreamIndex < 0) then + FFmpegCore.FindStreamIDs(fFormatContext, fStreamIndex, AudioStreamIndex); + if (fStreamIndex < 0) then begin Log.LogError('No video stream found', 'TVideoPlayback_ffmpeg.Open'); Close(); Exit; end; - VideoStream := VideoFormatContext^.streams[VideoStreamIndex]; - VideoCodecContext := VideoStream^.codec; + fStream := fFormatContext^.streams[fStreamIndex]; + fCodecContext := fStream^.codec; - VideoCodec := avcodec_find_decoder(VideoCodecContext^.codec_id); - if (VideoCodec = nil) then + fCodec := avcodec_find_decoder(fCodecContext^.codec_id); + if (fCodec = nil) then begin Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open'); Close(); @@ -267,21 +296,21 @@ begin end; // set debug options - VideoCodecContext^.debug_mv := 0; - VideoCodecContext^.debug := 0; + fCodecContext^.debug_mv := 0; + fCodecContext^.debug := 0; // detect bug-workarounds automatically - VideoCodecContext^.workaround_bugs := FF_BUG_AUTODETECT; + fCodecContext^.workaround_bugs := FF_BUG_AUTODETECT; // error resilience strategy (careful/compliant/agressive/very_aggressive) - //VideoCodecContext^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT; + //fCodecContext^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT; // allow non spec compliant speedup tricks. - //VideoCodecContext^.flags2 := VideoCodecContext^.flags2 or CODEC_FLAG2_FAST; + //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(VideoCodecContext, VideoCodec); + errnum := avcodec_open(fCodecContext, fCodec); finally FFmpegCore.UnlockAVCodec(); end; @@ -293,27 +322,27 @@ begin end; // register custom callbacks for pts-determination - VideoCodecContext^.get_buffer := PtsGetBuffer; - VideoCodecContext^.release_buffer := PtsReleaseBuffer; + fCodecContext^.get_buffer := PtsGetBuffer; + fCodecContext^.release_buffer := PtsReleaseBuffer; {$ifdef DebugDisplay} - DebugWriteln('Found a matching Codec: '+ VideoCodecContext^.Codec.Name + sLineBreak + + DebugWriteln('Found a matching Codec: '+ fCodecContext^.Codec.Name + sLineBreak + sLineBreak + - ' Width = '+inttostr(VideoCodecContext^.width) + - ', Height='+inttostr(VideoCodecContext^.height) + sLineBreak + - ' Aspect : '+inttostr(VideoCodecContext^.sample_aspect_ratio.num) + '/' + - inttostr(VideoCodecContext^.sample_aspect_ratio.den) + sLineBreak + - ' Framerate : '+inttostr(VideoCodecContext^.time_base.num) + '/' + - inttostr(VideoCodecContext^.time_base.den)); + ' 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 - AVFrame := avcodec_alloc_frame(); - AVFrameRGB := avcodec_alloc_frame(); - FrameBuffer := av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG, - VideoCodecContext^.width, VideoCodecContext^.height)); + fAVFrame := avcodec_alloc_frame(); + fAVFrameRGB := avcodec_alloc_frame(); + fFrameBuffer := av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG, + fCodecContext^.width, fCodecContext^.height)); - if ((AVFrame = nil) or (AVFrameRGB = nil) or (FrameBuffer = nil)) then + if ((fAVFrame = nil) or (fAVFrameRGB = nil) or (fFrameBuffer = nil)) then begin Log.LogError('Failed to allocate buffers', 'TVideoPlayback_ffmpeg.Open'); Close(); @@ -322,8 +351,8 @@ begin // 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(AVFrameRGB), FrameBuffer, PIXEL_FMT_FFMPEG, - VideoCodecContext^.width, VideoCodecContext^.height); + 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'); @@ -332,27 +361,27 @@ begin end; // calculate some information for video display - VideoAspect := av_q2d(VideoCodecContext^.sample_aspect_ratio); - if (VideoAspect = 0) then - VideoAspect := VideoCodecContext^.width / - VideoCodecContext^.height + fAspect := av_q2d(fCodecContext^.sample_aspect_ratio); + if (fAspect = 0) then + fAspect := fCodecContext^.width / + fCodecContext^.height else - VideoAspect := VideoAspect * VideoCodecContext^.width / - VideoCodecContext^.height; + fAspect := fAspect * fCodecContext^.width / + fCodecContext^.height; - VideoTimeBase := 1/av_q2d(VideoStream^.r_frame_rate); + fTimeBase := 1/av_q2d(fStream^.r_frame_rate); // hack to get reasonable timebase (for divx and others) - if (VideoTimeBase < 0.02) then // 0.02 <-> 50 fps + if (fTimeBase < 0.02) then // 0.02 <-> 50 fps begin - VideoTimeBase := av_q2d(VideoStream^.r_frame_rate); - while (VideoTimeBase > 50) do - VideoTimeBase := VideoTimeBase/10; - VideoTimeBase := 1/VideoTimeBase; + fTimeBase := av_q2d(fStream^.r_frame_rate); + while (fTimeBase > 50) do + fTimeBase := fTimeBase/10; + fTimeBase := 1/fTimeBase; end; - Log.LogInfo('VideoTimeBase: ' + floattostr(VideoTimeBase), 'TVideoPlayback_ffmpeg.Open'); - Log.LogInfo('Framerate: '+inttostr(floor(1/VideoTimeBase))+'fps', 'TVideoPlayback_ffmpeg.Open'); + 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(). @@ -361,13 +390,13 @@ begin // 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. - SoftwareScaleContext := sws_getContext( - VideoCodecContext^.width, VideoCodecContext^.height, - integer(VideoCodecContext^.pix_fmt), - VideoCodecContext^.width, VideoCodecContext^.height, + fSwScaleContext := sws_getContext( + fCodecContext^.width, fCodecContext^.height, + integer(fCodecContext^.pix_fmt), + fCodecContext^.width, fCodecContext^.height, integer(PIXEL_FMT_FFMPEG), SWS_FAST_BILINEAR, nil, nil, nil); - if (SoftwareScaleContext = nil) then + if (fSwScaleContext = nil) then begin Log.LogError('Failed to get swscale context', 'TVideoPlayback_ffmpeg.Open'); Close(); @@ -375,55 +404,53 @@ begin end; {$ENDIF} - TexWidth := Round(Power(2, Ceil(Log2(VideoCodecContext^.width)))); - TexHeight := Round(Power(2, Ceil(Log2(VideoCodecContext^.height)))); + 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, fVideoTex); + glBindTexture(GL_TEXTURE_2D, fFrameTex); glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexImage2D(GL_TEXTURE_2D, 0, 3, TexWidth, TexHeight, 0, + 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); - - fVideoOpened := True; - + fOpened := True; Result := true; end; procedure TVideoPlayback_FFmpeg.Close; begin - if (FrameBuffer <> nil) then - av_free(FrameBuffer); - if (AVFrameRGB <> nil) then - av_free(AVFrameRGB); - if (AVFrame <> nil) then - av_free(AVFrame); - - AVFrame := nil; - AVFrameRGB := nil; - FrameBuffer := nil; + 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 (VideoCodecContext <> nil) then + if (fCodecContext <> nil) then begin // avcodec_close() is not thread-safe FFmpegCore.LockAVCodec(); try - avcodec_close(VideoCodecContext); + avcodec_close(fCodecContext); finally FFmpegCore.UnlockAVCodec(); end; end; - if (VideoFormatContext <> nil) then - av_close_input_file(VideoFormatContext); + if (fFormatContext <> nil) then + av_close_input_file(fFormatContext); - VideoCodecContext := nil; - VideoFormatContext := nil; + fCodecContext := nil; + fFormatContext := nil; - fVideoOpened := False; + fOpened := False; end; procedure TVideoPlayback_FFmpeg.SynchronizeVideo(Frame: PAVFrame; var pts: double); @@ -433,17 +460,17 @@ begin if (pts <> 0) then begin // if we have pts, set video clock to it - VideoTime := pts; + fTime := pts; end else begin // if we aren't given a pts, set it to the clock - pts := VideoTime; + pts := fTime; end; // update the video clock - FrameDelay := av_q2d(VideoCodecContext^.time_base); + FrameDelay := av_q2d(fCodecContext^.time_base); // if we are repeating a frame, adjust clock accordingly FrameDelay := FrameDelay + Frame^.repeat_pict * (FrameDelay * 0.5); - VideoTime := VideoTime + FrameDelay; + fTime := fTime + FrameDelay; end; function TVideoPlayback_FFmpeg.DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; @@ -456,13 +483,13 @@ begin Result := false; FrameFinished := 0; - if EOF then + 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(VideoFormatContext, AVPacket); + errnum := av_read_frame(fFormatContext, AVPacket); if (errnum < 0) then begin // failed to read a frame, check reason @@ -470,13 +497,13 @@ begin {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} pbIOCtx := VideoFormatContext^.pb; {$ELSE} - pbIOCtx := @VideoFormatContext^.pb; + pbIOCtx := @fFormatContext^.pb; {$IFEND} - // check for end-of-file (eof is not an error) + // check for end-of-file (EOF is not an error) if (url_feof(pbIOCtx) <> 0) then begin - EOF := true; + fEOF := true; Exit; end; @@ -486,10 +513,10 @@ begin // url_feof() does not detect an EOF for some mov-files (e.g. deluxe.mov) // so we have to do it this way. - if ((VideoFormatContext^.file_size <> 0) and - (pbIOCtx^.pos >= VideoFormatContext^.file_size)) then + if ((fFormatContext^.file_size <> 0) and + (pbIOCtx^.pos >= fFormatContext^.file_size)) then begin - EOF := true; + fEOF := true; Exit; end; @@ -499,38 +526,38 @@ begin end; // if we got a packet from the video stream, then decode it - if (AVPacket.stream_index = VideoStreamIndex) then + if (AVPacket.stream_index = fStreamIndex) then begin // save pts to be stored in pFrame in first call of PtsGetBuffer() VideoPktPts := AVPacket.pts; - VideoCodecContext^.opaque := @VideoPktPts; + fCodecContext^.opaque := @VideoPktPts; // decode packet - avcodec_decode_video(VideoCodecContext, AVFrame, + avcodec_decode_video(fCodecContext, fAVFrame, frameFinished, AVPacket.data, AVPacket.size); // reset opaque data - VideoCodecContext^.opaque := nil; + fCodecContext^.opaque := nil; // update pts if (AVPacket.dts <> AV_NOPTS_VALUE) then begin pts := AVPacket.dts; end - else if ((AVFrame^.opaque <> nil) and - (Pint64(AVFrame^.opaque)^ <> AV_NOPTS_VALUE)) then + else if ((fAVFrame^.opaque <> nil) and + (Pint64(fAVFrame^.opaque)^ <> AV_NOPTS_VALUE)) then begin - pts := Pint64(AVFrame^.opaque)^; + pts := Pint64(fAVFrame^.opaque)^; end else begin pts := 0; end; - pts := pts * av_q2d(VideoStream^.time_base); + pts := pts * av_q2d(fStream^.time_base); // synchronize on each complete frame if (frameFinished <> 0) then - SynchronizeVideo(AVFrame, pts); + SynchronizeVideo(fAVFrame, pts); end; // free the packet from av_read_frame @@ -552,26 +579,26 @@ var const FRAME_DROPCOUNT = 3; begin - if not fVideoOpened then + if not fOpened then Exit; - if fVideoPaused then + if fPaused then Exit; - // current time, relative to last loop (if any) + // current time, relative to last fLoop (if any) myTime := Time - fLoopTime; // time since the last frame was returned - TimeDifference := myTime - VideoTime; + TimeDifference := myTime - fTime; {$IFDEF DebugDisplay} DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak + - 'VideoTime: '+inttostr(floor(VideoTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(VideoTimeBase*1000)) + sLineBreak + + 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} // check if a new frame is needed - if (VideoTime <> 0) and (TimeDifference < VideoTimeBase) then + if (fTime <> 0) and (TimeDifference < fTimeBase) then begin {$ifdef DebugFrames} // frame delay debug display @@ -581,8 +608,8 @@ begin {$IFDEF DebugDisplay} DebugWriteln('not getting new frame' + sLineBreak + 'Time: '+inttostr(floor(Time*1000)) + sLineBreak + - 'VideoTime: '+inttostr(floor(VideoTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(VideoTimeBase*1000)) + sLineBreak + + 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} @@ -591,11 +618,11 @@ begin end; // update video-time to the next frame - VideoTime := VideoTime + VideoTimeBase; - TimeDifference := myTime - VideoTime; + fTime := fTime + fTimeBase; + TimeDifference := myTime - fTime; // check if we have to skip frames - if (TimeDifference >= FRAME_DROPCOUNT*VideoTimeBase) then + if (TimeDifference >= FRAME_DROPCOUNT*fTimeBase) then begin {$IFDEF DebugFrames} //frame drop debug display @@ -603,13 +630,13 @@ begin {$ENDIF} {$IFDEF DebugDisplay} DebugWriteln('skipping frames' + sLineBreak + - 'TimeBase: '+inttostr(floor(VideoTimeBase*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} // update video-time - DropFrameCount := Trunc(TimeDifference / VideoTimeBase); - VideoTime := VideoTime + DropFrameCount*VideoTimeBase; + 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 @@ -622,7 +649,7 @@ begin if (not DecodeFrame(AVPacket, pts)) then begin - if Loop then + if fLoop then begin // Record the time we looped. This is used to keep the loops in time. otherwise they speed SetPosition(0); @@ -632,16 +659,16 @@ begin end; // TODO: support for pan&scan - //if (AVFrame.pan_scan <> nil) then + //if (fAVFrame.pan_scan <> nil) then //begin - // Writeln(Format('PanScan: %d/%d', [AVFrame.pan_scan.width, AVFrame.pan_scan.height])); + // 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(SoftwareScaleContext, @(AVFrame.data), @(AVFrame.linesize), - 0, VideoCodecContext^.Height, - @(AVFrameRGB.data), @(AVFrameRGB.linesize)); + errnum := sws_scale(fSwScaleContext, @(fAVFrame.data), @(fAVFrame.linesize), + 0, fCodecContext^.Height, + @(fAVFrameRGB.data), @(fAVFrameRGB.linesize)); {$ELSE} errnum := img_convert(PAVPicture(AVFrameRGB), PIXEL_FMT_FFMPEG, PAVPicture(AVFrame), VideoCodecContext^.pix_fmt, @@ -663,10 +690,10 @@ begin // Or should we add padding with avpicture_fill? (check which one is faster) //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glBindTexture(GL_TEXTURE_2D, fVideoTex); + glBindTexture(GL_TEXTURE_2D, fFrameTex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - VideoCodecContext^.width, VideoCodecContext^.height, - PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, AVFrameRGB^.data[0]); + fCodecContext^.width, fCodecContext^.height, + PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, fAVFrameRGB^.data[0]); {$ifdef DebugFrames} //frame decode debug display @@ -680,76 +707,94 @@ begin {$ENDIF} end; -procedure TVideoPlayback_FFmpeg.DrawGL(Screen: integer); +procedure TVideoPlayback_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords); var - TexVideoRightPos, TexVideoLowerPos: Single; - ScreenLeftPos, ScreenRightPos: Single; - ScreenUpperPos, ScreenLowerPos: Single; - ScaledVideoWidth, ScaledVideoHeight: Single; - ScreenMidPosX, ScreenMidPosY: Single; - ScreenAspect: Single; + ScreenAspect: double; // aspect of screen resolution + ScaledVideoWidth, ScaledVideoHeight: double; begin - // have a nice black background to draw on (even if there were errors opening the vid) - if (Screen = 1) then - begin - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + // 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; 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) + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + // exit if there's nothing to draw - if (not fVideoOpened) then + if (not fOpened) then Exit; {$IFDEF VideoBenchmark} Log.BenchmarkStart(15); {$ENDIF} - // TODO: add a SetAspectCorrectionMode() function so we can switch - // aspect correction. The screens video backgrounds look very ugly with aspect - // correction because of the white bars at the top and bottom. - - ScreenAspect := ScreenW / ScreenH; - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH * ScreenAspect/VideoAspect; - - // Note: Scaling the width does not look good because the video might contain - // black borders at the top already - //ScaledVideoHeight := RenderH; - //ScaledVideoWidth := RenderW * VideoAspect/ScreenAspect; - - // center the video - ScreenMidPosX := RenderW/2; - ScreenMidPosY := RenderH/2; - ScreenLeftPos := ScreenMidPosX - ScaledVideoWidth/2; - ScreenRightPos := ScreenMidPosX + ScaledVideoWidth/2; - ScreenUpperPos := ScreenMidPosY - ScaledVideoHeight/2; - ScreenLowerPos := ScreenMidPosY + ScaledVideoHeight/2; - // the video-texture contains empty borders because its width and height must be - // a power of 2. So we have to determine the texture coords of the video. - TexVideoRightPos := VideoCodecContext^.width / TexWidth; - TexVideoLowerPos := VideoCodecContext^.height / TexHeight; + // get texture and screen positions + GetVideoRect(ScreenRect, TexRect); // we could use blending for brightness control, but do we need this? glDisable(GL_BLEND); - // TODO: disable other stuff like lightning, etc. - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, fVideoTex); + glBindTexture(GL_TEXTURE_2D, fFrameTex); glColor3f(1, 1, 1); glBegin(GL_QUADS); // upper-left coord - glTexCoord2f(0, 0); - glVertex2f(ScreenLeftPos, ScreenUpperPos); + glTexCoord2f(TexRect.Left, TexRect.Upper); + glVertex2f(ScreenRect.Left, ScreenRect.Upper); // lower-left coord - glTexCoord2f(0, TexVideoLowerPos); - glVertex2f(ScreenLeftPos, ScreenLowerPos); + glTexCoord2f(TexRect.Left, TexRect.Lower); + glVertex2f(ScreenRect.Left, ScreenRect.Lower); // lower-right coord - glTexCoord2f(TexVideoRightPos, TexVideoLowerPos); - glVertex2f(ScreenRightPos, ScreenLowerPos); + glTexCoord2f(TexRect.Right, TexRect.Lower); + glVertex2f(ScreenRect.Right, ScreenRect.Lower); // upper-right coord - glTexCoord2f(TexVideoRightPos, 0); - glVertex2f(ScreenRightPos, ScreenUpperPos); + glTexCoord2f(TexRect.Right, TexRect.Upper); + glVertex2f(ScreenRect.Right, ScreenRect.Upper); glEnd; glDisable(GL_TEXTURE_2D); @@ -758,8 +803,15 @@ begin Log.LogBenchmark('DrawGL', 15); {$ENDIF} + {$IF Defined(Info) or Defined(DebugFrames)} + ShowDebugInfo(); + {$IFEND} +end; + +procedure TVideoPlayback_FFmpeg.ShowDebugInfo(); +begin {$IFDEF Info} - if (fVideoSkipTime+VideoTime+VideoTimeBase < 0) then + if (fTime+fTimeBase < 0) then begin glColor4f(0.7, 1, 0.3, 1); SetFontStyle (1); @@ -799,7 +851,7 @@ end; procedure TVideoPlayback_FFmpeg.Pause; begin - fVideoPaused := not fVideoPaused; + fPaused := not fPaused; end; procedure TVideoPlayback_FFmpeg.Stop; @@ -810,36 +862,36 @@ procedure TVideoPlayback_FFmpeg.SetPosition(Time: real); var SeekFlags: integer; begin - if not fVideoOpened then + if not fOpened then Exit; if (Time < 0) then Time := 0; - // TODO: handle loop-times + // TODO: handle fLoop-times //Time := Time mod VideoDuration; // backward seeking might fail without AVSEEK_FLAG_BACKWARD SeekFlags := AVSEEK_FLAG_ANY; - if (Time < VideoTime) then + if (Time < fTime) then SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD; - VideoTime := Time; - EOF := false; + fTime := Time; + fEOF := false; - if (av_seek_frame(VideoFormatContext, VideoStreamIndex, Floor(Time/VideoTimeBase), SeekFlags) < 0) then + 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(VideoCodecContext); + avcodec_flush_buffers(fCodecContext); end; function TVideoPlayback_FFmpeg.GetPosition: real; begin // TODO: return video-position in seconds - Result := VideoTime; + Result := fTime; end; initialization -- cgit v1.2.3 From da91f86e54d9f87dd28a7558b5a6e930362c3803 Mon Sep 17 00:00:00 2001 From: tobigun Date: Fri, 31 Oct 2008 15:45:18 +0000 Subject: - swscale get_context parameter from cint -> TAVPixelFormat - some refactoring missed last time git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1489 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 9f3fc6da..06577d9e 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -392,9 +392,9 @@ begin // could be observed in comparison to the RGB versions. fSwScaleContext := sws_getContext( fCodecContext^.width, fCodecContext^.height, - integer(fCodecContext^.pix_fmt), + fCodecContext^.pix_fmt, fCodecContext^.width, fCodecContext^.height, - integer(PIXEL_FMT_FFMPEG), + PIXEL_FMT_FFMPEG, SWS_FAST_BILINEAR, nil, nil, nil); if (fSwScaleContext = nil) then begin @@ -495,7 +495,7 @@ begin // failed to read a frame, check reason {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} - pbIOCtx := VideoFormatContext^.pb; + pbIOCtx := fFormatContext^.pb; {$ELSE} pbIOCtx := @fFormatContext^.pb; {$IFEND} @@ -670,9 +670,9 @@ begin 0, fCodecContext^.Height, @(fAVFrameRGB.data), @(fAVFrameRGB.linesize)); {$ELSE} - errnum := img_convert(PAVPicture(AVFrameRGB), PIXEL_FMT_FFMPEG, - PAVPicture(AVFrame), VideoCodecContext^.pix_fmt, - VideoCodecContext^.width, VideoCodecContext^.height); + errnum := img_convert(PAVPicture(fAVFrameRGB), PIXEL_FMT_FFMPEG, + PAVPicture(fAVFrame), fCodecContext^.pix_fmt, + fCodecContext^.width, fCodecContext^.height); {$ENDIF} if (errnum < 0) then -- cgit v1.2.3 From 2b125425a66f54922e8aaf34c2a616c43f024f11 Mon Sep 17 00:00:00 2001 From: tobigun Date: Mon, 3 Nov 2008 01:15:43 +0000 Subject: improved seeking: - the position will not be set to non-keyframes (AVSEEK_FLAG_ANY) anymore as it might result in gray or green pictures (FFmpeg does not use the information of the last keyframe after seeking) - after seeking the texture was not updated correctly. - some comments added git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1497 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 135 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 50 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 06577d9e..197fc572 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -126,6 +126,7 @@ type 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} @@ -137,11 +138,11 @@ type fTimeBase: extended; //**< FFmpeg time base per time unit fTime: extended; //**< video time position (absolute) - fLoopTime: extended; //**< video time position (relative to current loop) + fLoopTime: extended; //**< start time of the current loop procedure Reset(); - function DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; - procedure SynchronizeVideo(Frame: PAVFrame; var pts: double); + function DecodeFrame(): boolean; + procedure SynchronizeTime(Frame: PAVFrame; var pts: double); procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); @@ -240,6 +241,7 @@ begin fTime := 0; fStream := nil; fStreamIndex := -1; + fFrameTexValid := false; fEOF := false; @@ -453,7 +455,7 @@ begin fOpened := False; end; -procedure TVideoPlayback_FFmpeg.SynchronizeVideo(Frame: PAVFrame; var pts: double); +procedure TVideoPlayback_FFmpeg.SynchronizeTime(Frame: PAVFrame; var pts: double); var FrameDelay: double; begin @@ -473,12 +475,21 @@ begin fTime := fTime + FrameDelay; end; -function TVideoPlayback_FFmpeg.DecodeFrame(var AVPacket: TAVPacket; out pts: double): boolean; +{** + * 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; @@ -555,9 +566,9 @@ begin end; pts := pts * av_q2d(fStream^.time_base); - // synchronize on each complete frame + // synchronize time on each complete frame if (frameFinished <> 0) then - SynchronizeVideo(fAVFrame, pts); + SynchronizeTime(fAVFrame, pts); end; // free the packet from av_read_frame @@ -569,13 +580,12 @@ end; procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended); var - AVPacket: TAVPacket; errnum: Integer; - myTime: Extended; + NewTime: Extended; TimeDifference: Extended; DropFrameCount: Integer; - pts: double; i: Integer; + Success: boolean; const FRAME_DROPCOUNT = 3; begin @@ -585,41 +595,50 @@ begin if fPaused then Exit; - // current time, relative to last fLoop (if any) - myTime := Time - fLoopTime; - // time since the last frame was returned - TimeDifference := myTime - 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} + // requested stream position (relative to the last loop's start) + NewTime := Time - fLoopTime; - // check if a new frame is needed - if (fTime <> 0) and (TimeDifference < fTimeBase) then + // check if current texture still contains the active frame + if (fFrameTexValid) then begin - {$ifdef DebugFrames} - // frame delay debug display - GoldenRec.Spawn(200,15,1,16,0,-1,ColoredStar,$00ff00); - {$endif} + // time since the last frame was returned + TimeDifference := NewTime - fTime; {$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))); + 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} - // we do not need a new frame now - Exit; + // 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; - // update video-time to the next frame - fTime := fTime + fTimeBase; - TimeDifference := myTime - fTime; + {$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 @@ -640,19 +659,18 @@ begin // skip half of the frames, this is much smoother than to skip all at once for i := 1 to DropFrameCount (*div 2*) do - DecodeFrame(AVPacket, pts); + Success := DecodeFrame(); end; - {$IFDEF VideoBenchmark} - Log.BenchmarkStart(15); - {$ENDIF} - - if (not DecodeFrame(AVPacket, pts)) then + // check if we got an EOF or error + if (not Success) then begin if fLoop then begin - // Record the time we looped. This is used to keep the loops in time. otherwise they speed + // 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; @@ -695,6 +713,9 @@ begin 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); @@ -738,7 +759,9 @@ begin acoLetterBox: begin ScaledVideoWidth := RenderW; ScaledVideoHeight := RenderH * ScreenAspect/fAspect; - end; + end + else + raise Exception.Create('Unhandled aspect correction!'); end; // center video @@ -858,6 +881,14 @@ 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; @@ -871,13 +902,18 @@ begin // TODO: handle fLoop-times //Time := Time mod VideoDuration; - // backward seeking might fail without AVSEEK_FLAG_BACKWARD - SeekFlags := AVSEEK_FLAG_ANY; - if (Time < fTime) then - SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD; + // 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 @@ -890,7 +926,6 @@ end; function TVideoPlayback_FFmpeg.GetPosition: real; begin - // TODO: return video-position in seconds Result := fTime; end; -- cgit v1.2.3 From e0337089ed8777756ecfbdfcbc417a5392e90838 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 4 Feb 2009 17:42:12 +0000 Subject: PChar replaced by PByteArray (for byte buffers) or PAnsiString (for zero-terminated strings) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1583 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioConverter.pas | 14 ++++++------- src/media/UAudioDecoder_Bass.pas | 6 +++--- src/media/UAudioDecoder_FFmpeg.pas | 26 +++++++++++------------ src/media/UAudioPlayback_Bass.pas | 18 ++++++++-------- src/media/UAudioPlayback_SDL.pas | 14 ++++++------- src/media/UAudioPlayback_SoftMixer.pas | 38 +++++++++++++++++----------------- 6 files changed, 57 insertions(+), 59 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioConverter.pas b/src/media/UAudioConverter.pas index 24131b16..657b80dd 100644 --- a/src/media/UAudioConverter.pas +++ b/src/media/UAudioConverter.pas @@ -70,7 +70,7 @@ type function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override; destructor Destroy(); override; - function Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; override; + function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override; function GetOutputBufferSize(InputSize: integer): integer; override; function GetRatio(): double; override; end; @@ -87,7 +87,7 @@ type function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override; destructor Destroy(); override; - function Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; override; + function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override; function GetOutputBufferSize(InputSize: integer): integer; override; function GetRatio(): double; override; end; @@ -103,7 +103,7 @@ type function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; override; destructor Destroy(); override; - function Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; override; + function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override; function GetOutputBufferSize(InputSize: integer): integer; override; function GetRatio(): double; override; end; @@ -173,7 +173,7 @@ begin Result := cvt.len_ratio; end; -function TAudioConverter_SDL.Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; +function TAudioConverter_SDL.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; begin Result := -1; @@ -242,7 +242,7 @@ begin inherited; end; -function TAudioConverter_FFmpeg.Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; +function TAudioConverter_FFmpeg.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; var InputSampleCount: integer; OutputSampleCount: integer; @@ -360,11 +360,11 @@ begin inherited; end; -function TAudioConverter_SRC.Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; +function TAudioConverter_SRC.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; var FloatInputBuffer: PSingle; FloatOutputBuffer: PSingle; - TempBuffer: PChar; + TempBuffer: PByteArray; TempSize: integer; NumSamples: integer; OutputSize: integer; diff --git a/src/media/UAudioDecoder_Bass.pas b/src/media/UAudioDecoder_Bass.pas index 3c31175d..6bbdaeaa 100644 --- a/src/media/UAudioDecoder_Bass.pas +++ b/src/media/UAudioDecoder_Bass.pas @@ -65,7 +65,7 @@ type function IsEOF(): boolean; override; function IsError(): boolean; override; - function ReadData(Buffer: PChar; BufSize: integer): integer; override; + function ReadData(Buffer: PByteArray; BufSize: integer): integer; override; end; type @@ -193,7 +193,7 @@ begin Result := Error; end; -function TBassDecodeStream.ReadData(Buffer: PChar; BufSize: integer): integer; +function TBassDecodeStream.ReadData(Buffer: PByteArray; BufSize: integer): integer; begin Result := BASS_ChannelGetData(Handle, Buffer, BufSize); // check error state (do not handle EOF as error) @@ -237,7 +237,7 @@ begin // TODO: use BASS_STREAM_PRESCAN for accurate seeking in VBR-files? // disadvantage: seeking will slow down. - Stream := BASS_StreamCreateFile(False, PChar(Filename), 0, 0, BASS_STREAM_DECODE); + Stream := BASS_StreamCreateFile(False, PAnsiChar(Filename), 0, 0, BASS_STREAM_DECODE); if (Stream = 0) then begin //Log.LogError(BassCore.ErrorGetString(), 'TAudioDecoder_Bass.Open'); diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 399c2f52..2d221f40 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -57,7 +57,6 @@ implementation uses Classes, - SysUtils, Math, UMusic, UIni, @@ -68,8 +67,9 @@ uses avio, mathematics, // used for av_rescale_q rational, - UMediaCore_FFmpeg, SDL, + SysUtils, + UMediaCore_FFmpeg, ULog, UCommon, UConfig; @@ -129,14 +129,14 @@ type // state-vars for DecodeFrame (locked by DecoderLock) AudioPaket: TAVPacket; - AudioPaketData: PChar; + 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: PChar; + AudioBuffer: PByteArray; Filename: string; @@ -153,7 +153,7 @@ type procedure PauseParser(); procedure ResumeParser(); - function DecodeFrame(Buffer: PChar; BufferSize: integer): integer; + function DecodeFrame(Buffer: PByteArray; BufferSize: integer): integer; procedure FlushCodecBuffers(); procedure PauseDecoder(); procedure ResumeDecoder(); @@ -173,11 +173,11 @@ type function IsEOF(): boolean; override; function IsError(): boolean; override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; override; + function ReadData(Buffer: PByteArray; BufferSize: integer): integer; override; end; type - TAudioDecoder_FFmpeg = class( TInterfacedObject, IAudioDecoder ) + TAudioDecoder_FFmpeg = class(TInterfacedObject, IAudioDecoder) public function GetName: string; @@ -289,7 +289,7 @@ begin Self.Filename := Filename; // open audio file - if (av_open_input_file(FormatCtx, PChar(Filename), nil, 0, nil) <> 0) then + if (av_open_input_file(FormatCtx, PAnsiChar(Filename), nil, 0, nil) <> 0) then begin Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg'); Exit; @@ -310,7 +310,7 @@ begin FormatCtx^.pb.eof_reached := 0; {$IFDEF DebugFFmpegDecode} - dump_format(FormatCtx, 0, pchar(Filename), 0); + dump_format(FormatCtx, 0, PAnsiChar(Filename), 0); {$ENDIF} AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); @@ -862,7 +862,7 @@ begin end; end; -function TFFmpegDecodeStream.DecodeFrame(Buffer: PChar; BufferSize: integer): integer; +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 @@ -945,7 +945,7 @@ begin Exit; // handle Status-packet - if (PChar(AudioPaket.data) = STATUS_PACKET) then + if (PAnsiChar(AudioPaket.data) = STATUS_PACKET) then begin AudioPaket.data := nil; AudioPaketData := nil; @@ -986,7 +986,7 @@ begin Continue; end; - AudioPaketData := PChar(AudioPaket.data); + AudioPaketData := AudioPaket.data; AudioPaketSize := AudioPaket.size; // if available, update the stream position to the presentation time of this package @@ -1005,7 +1005,7 @@ begin end; end; -function TFFmpegDecodeStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +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 diff --git a/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas index d68ac1d4..923c1d7b 100644 --- a/src/media/UAudioPlayback_Bass.pas +++ b/src/media/UAudioPlayback_Bass.pas @@ -37,7 +37,6 @@ implementation uses Classes, - SysUtils, Math, UIni, UMain, @@ -46,7 +45,8 @@ uses UAudioCore_Bass, ULog, sdl, - bass; + bass, + SysUtils; type PHDSP = ^HDSP; @@ -90,7 +90,7 @@ type function GetAudioFormatInfo(): TAudioFormatInfo; override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; + function ReadData(Buffer: PByteArray; BufferSize: integer): integer; property EOF: boolean READ IsEOF; end; @@ -106,8 +106,8 @@ type function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override; procedure Close(); override; - procedure WriteData(Buffer: PChar; BufferSize: integer); override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; 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; @@ -163,14 +163,14 @@ begin Result := BytesRead; end; -function TBassPlaybackStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TBassPlaybackStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; var AdjustedSize: integer; RequestedSourceSize, SourceSize: integer; SkipCount: integer; SourceFormatInfo: TAudioFormatInfo; FrameSize: integer; - PadFrame: PChar; + PadFrame: PByteArray; //Info: BASS_INFO; //Latency: double; begin @@ -610,7 +610,7 @@ begin inherited Close(); end; -procedure TBassVoiceStream.WriteData(Buffer: PChar; BufferSize: integer); +procedure TBassVoiceStream.WriteData(Buffer: PByteArray; BufferSize: integer); var QueueSize: DWORD; begin if ((Handle <> 0) and (BufferSize > 0)) then @@ -626,7 +626,7 @@ begin end; // Note: we do not need the read-function for the BASS implementation -function TBassVoiceStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TBassVoiceStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; begin Result := -1; end; diff --git a/src/media/UAudioPlayback_SDL.pas b/src/media/UAudioPlayback_SDL.pas index b0887676..8403ef03 100644 --- a/src/media/UAudioPlayback_SDL.pas +++ b/src/media/UAudioPlayback_SDL.pas @@ -33,16 +33,14 @@ interface {$I switches.inc} -uses - Classes, - SysUtils, - UMusic; - implementation uses + Classes, sdl, + SysUtils, UAudioPlayback_SoftMixer, + UMusic, ULog, UIni, UMain; @@ -60,13 +58,13 @@ type function GetLatency(): double; override; public function GetName: String; override; - procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Single); override; + procedure MixBuffers(dst, src: PByteArray; size: Cardinal; volume: Single); override; end; { TAudioPlayback_SDL } -procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl; +procedure SDLAudioCallback(userdata: Pointer; stream: PByteArray; len: integer); cdecl; var Engine: TAudioPlayback_SDL; begin @@ -172,7 +170,7 @@ begin Result := Latency; end; -procedure TAudioPlayback_SDL.MixBuffers(dst, src: PChar; size: Cardinal; volume: Single); +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; diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index f3797dd6..c8da6bcd 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -35,8 +35,8 @@ interface uses Classes, - SysUtils, sdl, + SysUtils, URingBuffer, UMusic, UAudioPlaybackBase; @@ -48,12 +48,12 @@ type private Engine: TAudioPlayback_SoftMixer; - SampleBuffer: PChar; + SampleBuffer: PByteArray; SampleBufferSize: integer; SampleBufferCount: integer; // number of available bytes in SampleBuffer SampleBufferPos: cardinal; - SourceBuffer: PChar; + SourceBuffer: PByteArray; SourceBufferSize: integer; SourceBufferCount: integer; // number of available bytes in SourceBuffer @@ -70,7 +70,7 @@ type procedure Reset(); - procedure ApplySoundEffects(Buffer: PChar; BufferSize: integer); + procedure ApplySoundEffects(Buffer: PByteArray; BufferSize: integer); function InitFormatConversion(): boolean; procedure FlushBuffers(); @@ -100,7 +100,7 @@ type function GetAudioFormatInfo(): TAudioFormatInfo; override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; + function ReadData(Buffer: PByteArray; BufferSize: integer): integer; function GetPCMData(var Data: TPCMData): Cardinal; override; procedure GetFFTData(var Data: TFFTData); override; @@ -114,7 +114,7 @@ type Engine: TAudioPlayback_SoftMixer; ActiveStreams: TList; - MixerBuffer: PChar; + MixerBuffer: PByteArray; InternalLock: PSDL_Mutex; AppVolume: single; @@ -129,7 +129,7 @@ type destructor Destroy(); override; procedure AddStream(Stream: TAudioPlaybackStream); procedure RemoveStream(Stream: TAudioPlaybackStream); - function ReadData(Buffer: PChar; BufferSize: integer): integer; + function ReadData(Buffer: PByteArray; BufferSize: integer): integer; property Volume: single read GetVolume write SetVolume; end; @@ -144,7 +144,7 @@ type function StartAudioPlaybackEngine(): boolean; virtual; abstract; procedure StopAudioPlaybackEngine(); virtual; abstract; function FinalizeAudioPlaybackEngine(): boolean; virtual; abstract; - procedure AudioCallback(Buffer: PChar; Size: integer); {$IFDEF HasInline}inline;{$ENDIF} + procedure AudioCallback(Buffer: PByteArray; Size: integer); {$IFDEF HasInline}inline;{$ENDIF} function CreatePlaybackStream(): TAudioPlaybackStream; override; public @@ -159,7 +159,7 @@ type function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF} function GetAudioFormatInfo(): TAudioFormatInfo; - procedure MixBuffers(DstBuffer, SrcBuffer: PChar; Size: Cardinal; Volume: Single); virtual; + procedure MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single); virtual; end; type @@ -174,8 +174,8 @@ type function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override; procedure Close(); override; - procedure WriteData(Buffer: PChar; BufferSize: integer); override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; 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; @@ -276,7 +276,7 @@ begin Unlock(); end; -function TAudioMixerStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TAudioMixerStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; var i: integer; Size: integer; @@ -545,7 +545,7 @@ begin SourceBufferCount := 0; end; -procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PChar; BufferSize: integer); +procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PByteArray; BufferSize: integer); var i: integer; begin @@ -558,7 +558,7 @@ begin end; end; -function TGenericPlaybackStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TGenericPlaybackStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; var ConversionInputCount: integer; ConversionOutputSize: integer; // max. number of converted data (= buffer size) @@ -573,7 +573,7 @@ var SkipSourceCount: integer; // number of source-data bytes to skip FillCount: integer; // number of bytes to fill with padding data CopyCount: integer; - PadFrame: PChar; + PadFrame: PByteArray; i: integer; begin Result := -1; @@ -986,7 +986,7 @@ begin inherited Close(); end; -procedure TGenericVoiceStream.WriteData(Buffer: PChar; BufferSize: integer); +procedure TGenericVoiceStream.WriteData(Buffer: PByteArray; BufferSize: integer); begin // lock access to buffer SDL_mutexP(BufferLock); @@ -999,7 +999,7 @@ begin end; end; -function TGenericVoiceStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TGenericVoiceStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; begin Result := -1; @@ -1059,7 +1059,7 @@ begin Result := true; end; -procedure TAudioPlayback_SoftMixer.AudioCallback(Buffer: PChar; Size: integer); +procedure TAudioPlayback_SoftMixer.AudioCallback(Buffer: PByteArray; Size: integer); begin MixerStream.ReadData(Buffer, Size); end; @@ -1102,7 +1102,7 @@ begin MixerStream.Volume := Volume; end; -procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PChar; Size: Cardinal; Volume: Single); +procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PByteArray; Size: Cardinal; Volume: Single); var SampleIndex: Cardinal; SampleInt: Integer; -- cgit v1.2.3 From 28eae548734d597d4722d9fb80c7082474533098 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Sun, 15 Feb 2009 17:32:53 +0000 Subject: patch #2388324. Exit with corrupt videos. Courtesy to Hawkear git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1593 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 197fc572..2d3b7620 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -532,8 +532,17 @@ begin end; // no error -> wait for user input - SDL_Delay(100); +{ + 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 -- cgit v1.2.3 From f57b5a260d4d98a910f62316efe653a1a7e704b7 Mon Sep 17 00:00:00 2001 From: whiteshark0 Date: Sun, 1 Mar 2009 12:53:55 +0000 Subject: fixed first screen was cleared when second screen was drawn. #76 should be fixed now commented some weird stuff in TScreenSing.Draw git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1613 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 2d3b7620..35f8ab4d 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -794,8 +794,18 @@ var begin // have a nice black background to draw on // (even if there were errors opening the vid) - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + // 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 -- cgit v1.2.3 From 486feffb21e42c713fa97a157cf860554be66976 Mon Sep 17 00:00:00 2001 From: whiteshark0 Date: Sat, 7 Mar 2009 21:42:17 +0000 Subject: overdrawing of first screen when using visualizations fixed. visualizations are still streched over both screens when UseTexture in UVisualizer is undefined git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1628 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVisualizer.pas | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src/media') diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index 9af6d8c2..37e0268a 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -484,7 +484,11 @@ begin glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); - gluOrtho2D(0, 1, 0, 1); + // 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(); @@ -496,11 +500,12 @@ begin 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(0, 0); - glTexCoord2f(1, 0); glVertex2f(1, 0); - glTexCoord2f(1, 1); glVertex2f(1, 1); - glTexCoord2f(0, 1); glVertex2f(0, 1); + 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); -- cgit v1.2.3 From 690aaa588853d232a7aba68f2518b9aa76f9d53e Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 9 Apr 2009 22:45:05 +0000 Subject: Warnings and Notes cleanup git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1661 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlayback_SoftMixer.pas | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index c8da6bcd..c87e461d 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -51,7 +51,7 @@ type SampleBuffer: PByteArray; SampleBufferSize: integer; SampleBufferCount: integer; // number of available bytes in SampleBuffer - SampleBufferPos: cardinal; + SampleBufferPos: integer; SourceBuffer: PByteArray; SourceBufferSize: integer; @@ -564,9 +564,8 @@ var ConversionOutputSize: integer; // max. number of converted data (= buffer size) ConversionOutputCount: integer; // actual number of converted data SourceSize: integer; - RequestedSourceSize: integer; NeededSampleBufferSize: integer; - BytesNeeded, BytesAvail: integer; + BytesNeeded: integer; SourceFormatInfo, OutputFormatInfo: TAudioFormatInfo; SourceFrameSize, OutputFrameSize: integer; SkipOutputCount: integer; // number of output-data bytes to skip @@ -574,7 +573,6 @@ var FillCount: integer; // number of bytes to fill with padding data CopyCount: integer; PadFrame: PByteArray; - i: integer; begin Result := -1; -- cgit v1.2.3 From 9ec474eb703bfb72a88b26f63d40039c860919e6 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 9 Apr 2009 22:48:04 +0000 Subject: Warnings and Notes cleanup git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1662 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 1 - src/media/UAudioInput_Portaudio.pas | 3 --- 2 files changed, 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 2d221f40..f62cb92c 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -643,7 +643,6 @@ end; function TFFmpegDecodeStream.ParseLoop(): boolean; var Packet: TAVPacket; - StatusPacket: PAVPacket; SeekTarget: int64; ByteIOCtx: PByteIOContext; ErrorCode: integer; diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 53080a03..31d2882b 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -95,7 +95,6 @@ var Error: TPaError; inputParams: TPaStreamParameters; deviceInfo: PPaDeviceInfo; - SourceIndex: integer; begin Result := false; @@ -291,8 +290,6 @@ var sourceIndex: integer; sourceName: string; {$ENDIF} - cbPolls: integer; - cbWorks: boolean; begin Result := false; -- cgit v1.2.3 From 1f6d632264411399a142393da0cbc2fb478159b2 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 9 Apr 2009 23:52:22 +0000 Subject: Warnings and Notes cleanup git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1663 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Bass.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index cf292c45..ad6c3818 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -95,7 +95,7 @@ var * user - players associated with left/right channels *} function MicrophoneCallback(stream: HSTREAM; buffer: Pointer; - len: Cardinal; inputDevice: Pointer): boolean; {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF} + len: integer; inputDevice: Pointer): boolean; {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF} begin AudioInputProcessor.HandleMicrophoneData(buffer, len, inputDevice); Result := true; -- cgit v1.2.3 From da0500ede1f7e4a518193b3caabfd72be2555c61 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Wed, 13 May 2009 20:04:50 +0000 Subject: Hint about removal of deprecated function img_convert added, but no code change git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1724 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 35f8ab4d..f55690b2 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -697,6 +697,12 @@ begin 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); -- cgit v1.2.3 From 62aea784507707eac56c5305fb191365987ae596 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Fri, 15 May 2009 16:09:25 +0000 Subject: resolve some type size mismatch warnings + an additional check. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1727 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index f62cb92c..97d8a8df 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -378,14 +378,14 @@ begin // try standard format SampleFormat := asfS16; end; - + if CodecCtx^.channels > 255 then + Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open'); FormatInfo := TAudioFormatInfo.Create( - CodecCtx^.channels, + byte(CodecCtx^.channels), CodecCtx^.sample_rate, SampleFormat ); - PacketQueue := TPacketQueue.Create(); // finally start the decode thread @@ -446,7 +446,9 @@ end; function TFFmpegDecodeStream.GetLength(): real; begin - // do not forget to consider the start_time value here + // 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; -- cgit v1.2.3 From 917901e8e33438c425aef50a0a7417f32d77b760 Mon Sep 17 00:00:00 2001 From: s_alexander Date: Mon, 9 Nov 2009 00:27:55 +0000 Subject: merged unicode branch (r1931) into trunk git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1939 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Bass.pas | 12 ++++ src/media/UAudioDecoder_Bass.pas | 21 +++++-- src/media/UAudioDecoder_FFmpeg.pas | 39 ++++++------ src/media/UAudioInput_Bass.pas | 5 ++ src/media/UAudioPlaybackBase.pas | 23 +++---- src/media/UAudioPlayback_Bass.pas | 6 +- src/media/UMediaCore_FFmpeg.pas | 124 ++++++++++++++++++++++++++++++++++++- src/media/UMedia_dummy.pas | 15 ++--- src/media/UVideo.pas | 22 ++++--- src/media/UVisualizer.pas | 5 +- 10 files changed, 214 insertions(+), 58 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioCore_Bass.pas b/src/media/UAudioCore_Bass.pas index 12623dc1..197f9760 100644 --- a/src/media/UAudioCore_Bass.pas +++ b/src/media/UAudioCore_Bass.pas @@ -44,6 +44,7 @@ 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; @@ -56,6 +57,12 @@ 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; @@ -71,6 +78,11 @@ begin 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()); diff --git a/src/media/UAudioDecoder_Bass.pas b/src/media/UAudioDecoder_Bass.pas index 6bbdaeaa..d6d2425a 100644 --- a/src/media/UAudioDecoder_Bass.pas +++ b/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/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 97d8a8df..d079afdc 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/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; @@ -1117,7 +1118,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/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index ad6c3818..9d4417f1 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -489,6 +489,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/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index 7d143fdc..de2d5563 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -34,7 +34,8 @@ interface {$I switches.inc} uses - UMusic; + UMusic, + UPath; type TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback) @@ -46,12 +47,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 +80,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 +109,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 +131,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 +141,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 +158,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 +170,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 +284,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/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas index 923c1d7b..1d7a44dc 100644 --- a/src/media/UAudioPlayback_Bass.pas +++ b/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/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index 9ad19a5b..b4951fe1 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/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; @@ -97,12 +101,29 @@ 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; @@ -220,6 +241,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; + + 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/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 7558dd0b..25e94724 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -36,9 +36,10 @@ interface implementation uses - SysUtils, - math, - UMusic; + SysUtils, + math, + UMusic, + UPath; type TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput ) @@ -51,7 +52,7 @@ type 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; @@ -88,7 +89,7 @@ type 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); @@ -125,7 +126,7 @@ begin Result := true; end; -function TMedia_dummy.Open(const aFileName : string): boolean; // true if succeed +function TMedia_dummy.Open(const aFileName : IPath): boolean; // true if succeed begin Result := false; end; @@ -236,7 +237,7 @@ begin Result := 60; end; -function TMedia_dummy.OpenSound(const Filename: string): TAudioPlaybackStream; +function TMedia_dummy.OpenSound(const Filename: IPath): TAudioPlaybackStream; begin Result := nil; end; diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index f55690b2..8d441e6c 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -69,8 +69,9 @@ type implementation uses + SysUtils, + Math, SDL, - textgl, avcodec, avformat, avutil, @@ -79,17 +80,17 @@ uses {$IFDEF UseSWScale} swscale, {$ENDIF} - UMediaCore_FFmpeg, - math, gl, glext, - SysUtils, + textgl, + UMediaCore_FFmpeg, UCommon, UConfig, ULog, UMusic, UGraphicClasses, - UGraphic; + UGraphic, + UPath; const {$IFDEF PIXEL_FMT_BGR} @@ -154,7 +155,7 @@ type function Init(): boolean; function Finalize: boolean; - function Open(const aFileName : string): boolean; // true if succeed + function Open(const FileName : IPath): boolean; // true if succeed procedure Close; procedure Play; @@ -252,7 +253,7 @@ begin fAspectCorrection := acoCrop; end; -function TVideoPlayback_FFmpeg.Open(const aFileName : string): boolean; // true if succeed +function TVideoPlayback_FFmpeg.Open(const FileName : IPath): boolean; // true if succeed var errnum: Integer; AudioStreamIndex: integer; @@ -261,10 +262,11 @@ begin Reset(); - errnum := av_open_input_file(fFormatContext, PChar(aFileName), nil, 0, nil); + // 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; @@ -434,7 +436,7 @@ begin fAVFrame := nil; fAVFrameRGB := nil; fFrameBuffer := nil; - + if (fCodecContext <> nil) then begin // avcodec_close() is not thread-safe diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index 37e0268a..b25d68a9 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -77,6 +77,7 @@ uses UGraphic, UMain, UConfig, + UPath, ULog; {$IF PROJECTM_VERSION < 1000000} // < 1.0 @@ -130,7 +131,7 @@ type 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; @@ -183,7 +184,7 @@ begin Result := true; end; -function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed +function TVideoPlayback_ProjectM.Open(const aFileName: IPath): boolean; // true if succeed begin Result := false; end; -- cgit v1.2.3 From d9c0d98f3240a21d0191721c86e0944c66af2f17 Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Mon, 7 Dec 2009 16:50:21 +0000 Subject: fix: open video files with the system's native encoding and path delimiter. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2000 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 8d441e6c..3f25cdf7 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -263,7 +263,7 @@ begin Reset(); // use custom 'ufile' protocol for UTF-8 support - errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil); + 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)+')'); -- cgit v1.2.3 From 24c8e13594e6bfd8fdf835b915c834de8894334d Mon Sep 17 00:00:00 2001 From: s_alexander Date: Tue, 8 Dec 2009 02:38:39 +0000 Subject: added starting pts handling some video files have a starting point for the pts calculation that starting point has to be subtracted from the pts of each frame to avoid hanging of the video git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2003 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 3f25cdf7..6db9cd20 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -22,7 +22,7 @@ * $URL$ * $Id$ *} - + unit UVideo; {* @@ -136,7 +136,7 @@ type 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 @@ -146,7 +146,7 @@ type procedure SynchronizeTime(Frame: PAVFrame; var pts: double); procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); - + procedure ShowDebugInfo(); public @@ -172,7 +172,7 @@ type 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; @@ -249,7 +249,7 @@ begin // TODO: do we really want this by default? fLoop := true; fLoopTime := 0; - + fAspectCorrection := acoCrop; end; @@ -481,7 +481,7 @@ 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. + * @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; @@ -575,6 +575,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 @@ -622,7 +626,7 @@ begin 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} - // check if last time is more than one frame in the past + // check if last time is more than one frame in the past if (TimeDifference < fTimeBase) then begin {$ifdef DebugFrames} @@ -646,7 +650,7 @@ begin {$IFDEF VideoBenchmark} Log.BenchmarkStart(15); {$ENDIF} - + // fetch new frame (updates fTime) Success := DecodeFrame(); TimeDifference := NewTime - fTime; @@ -673,7 +677,7 @@ begin 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 @@ -699,7 +703,7 @@ begin 0, fCodecContext^.Height, @(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 @@ -709,7 +713,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'); @@ -940,7 +944,7 @@ begin fTime := Time; fEOF := false; - fFrameTexValid := false; + fFrameTexValid := false; if (av_seek_frame(fFormatContext, fStreamIndex, Floor(Time/fTimeBase), SeekFlags) < 0) then begin -- cgit v1.2.3 From 115be023b64d9de135a09ab2a63553856eef15d9 Mon Sep 17 00:00:00 2001 From: s_alexander Date: Sun, 20 Dec 2009 20:35:40 +0000 Subject: changed TSyncSource to interface ISyncSource git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2051 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlaybackBase.pas | 4 ++-- src/media/UMedia_dummy.pas | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index de2d5563..228a438f 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -60,7 +60,7 @@ type procedure Stop; procedure FadeIn(Time: real; TargetVolume: single); - procedure SetSyncSource(SyncSource: TSyncSource); + procedure SetSyncSource(SyncSource: ISyncSource); procedure SetPosition(Time: real); function GetPosition: real; @@ -228,7 +228,7 @@ begin MusicStream.Position := Time; end; -procedure TAudioPlaybackBase.SetSyncSource(SyncSource: TSyncSource); +procedure TAudioPlaybackBase.SetSyncSource(SyncSource: ISyncSource); begin if assigned(MusicStream) then MusicStream.SetSyncSource(SyncSource); diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 25e94724..c38a8e60 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -62,7 +62,7 @@ type procedure SetPosition(Time: real); function GetPosition: real; - procedure SetSyncSource(SyncSource: TSyncSource); + procedure SetSyncSource(SyncSource: ISyncSource); procedure GetFrame(Time: Extended); procedure DrawGL(Screen: integer); @@ -156,7 +156,7 @@ begin Result := 0; end; -procedure TMedia_dummy.SetSyncSource(SyncSource: TSyncSource); +procedure TMedia_dummy.SetSyncSource(SyncSource: ISyncSource); begin end; -- cgit v1.2.3 From 1b38db896c698b754ff145931e432f4f3ee05345 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Tue, 29 Dec 2009 14:04:52 +0000 Subject: revert submission 2051. leeds to crash on 2nd song. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2056 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlaybackBase.pas | 4 ++-- src/media/UMedia_dummy.pas | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index 228a438f..de2d5563 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -60,7 +60,7 @@ type procedure Stop; procedure FadeIn(Time: real; TargetVolume: single); - procedure SetSyncSource(SyncSource: ISyncSource); + procedure SetSyncSource(SyncSource: TSyncSource); procedure SetPosition(Time: real); function GetPosition: real; @@ -228,7 +228,7 @@ begin MusicStream.Position := Time; end; -procedure TAudioPlaybackBase.SetSyncSource(SyncSource: ISyncSource); +procedure TAudioPlaybackBase.SetSyncSource(SyncSource: TSyncSource); begin if assigned(MusicStream) then MusicStream.SetSyncSource(SyncSource); diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index c38a8e60..25e94724 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -62,7 +62,7 @@ type procedure SetPosition(Time: real); function GetPosition: real; - procedure SetSyncSource(SyncSource: ISyncSource); + procedure SetSyncSource(SyncSource: TSyncSource); procedure GetFrame(Time: Extended); procedure DrawGL(Screen: integer); @@ -156,7 +156,7 @@ begin Result := 0; end; -procedure TMedia_dummy.SetSyncSource(SyncSource: ISyncSource); +procedure TMedia_dummy.SetSyncSource(SyncSource: TSyncSource); begin end; -- cgit v1.2.3 From c1d1fa060bb87972647662e7cdfa75a1b43d9a58 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Sun, 7 Feb 2010 00:29:16 +0000 Subject: small edits git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2102 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlayback_SoftMixer.pas | 38 ++++++++++++++++------------------ 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index c87e461d..203536d6 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -58,7 +58,7 @@ type SourceBufferCount: integer; // number of available bytes in SourceBuffer Converter: TAudioConverter; - Status: TStreamStatus; + Status: TStreamStatus; InternalLock: PSDL_Mutex; SoundEffects: TList; fVolume: single; @@ -102,7 +102,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 +148,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 +159,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 @@ -377,11 +377,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; @@ -443,8 +443,7 @@ var Mixer: TAudioMixerStream; begin // only paused streams are not flushed - if (Status = ssPaused) then - NeedsRewind := false; + NeedsRewind := not (Status = ssPaused); // rewind if necessary. Cases that require no rewind are: // - stream was created and never played @@ -750,7 +749,7 @@ begin Result := BufferSize - BytesNeeded; end; -function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal; +function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): cardinal; var ByteCount: integer; begin @@ -790,7 +789,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; @@ -872,8 +871,7 @@ begin LockSampleBuffer(); SourceStream.Position := Time; - if (Status = ssStopped) then - NeedsRewind := false; + NeedsRewind := not (Status = ssStopped); // do not use outdated data FlushBuffers(); @@ -885,7 +883,7 @@ end; function TGenericPlaybackStream.GetVolume(): single; var - FadeAmount: Single; + FadeAmount: single; begin LockSampleBuffer(); // adjust volume if fading is enabled @@ -1033,12 +1031,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 +1098,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 +1139,7 @@ begin // assign result PSingle(@DstBuffer[SampleIndex])^ := SampleFlt; // increase index by one sample - Inc(SampleIndex, SizeOf(Single)); + Inc(SampleIndex, SizeOf(single)); end; end; else -- cgit v1.2.3 From 5ae777e92dd891a0aa8010caf584e708cd508b88 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Wed, 24 Feb 2010 19:38:05 +0000 Subject: unifiy the name of portaudio inputs git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2153 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Portaudio.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 31d2882b..e278e69a 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -330,7 +330,7 @@ begin // retrieve device-name deviceName := deviceInfo^.name; - paDevice.Name := deviceName; + paDevice.Name := UnifyDeviceName(deviceName, deviceIndex); paDevice.PaDeviceIndex := deviceIndex; sampleRate := deviceInfo^.defaultSampleRate; -- cgit v1.2.3 From f948cdceadcd8e6161fbd2c6effecdd079361510 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 25 Feb 2010 11:10:26 +0000 Subject: cosmetics. no code change. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2155 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Portaudio.pas | 41 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index e278e69a..4a021cc7 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -46,10 +46,10 @@ uses {$ENDIF} portaudio, UAudioCore_Portaudio, - URecord, UIni, ULog, - UMain; + UMain, + URecord; type TAudioInput_Portaudio = class(TAudioInputBase) @@ -57,7 +57,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,22 +70,22 @@ 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 GetVolume(): single; override; procedure SetVolume(Volume: single); override; end; -function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword; +function MicrophoneCallback(input: pointer; output: pointer; frameCount: longword; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: Pointer): Integer; cdecl; forward; + inputDevice: pointer): integer; cdecl; forward; -function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword; +function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: longword; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: Pointer): Integer; cdecl; forward; + inputDevice: pointer): integer; cdecl; forward; { TPortaudioInputDevice } @@ -118,8 +118,8 @@ begin 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 +155,7 @@ end; function TPortaudioInputDevice.Start(): boolean; var - Error: TPaError; + Error: TPaError; begin Result := false; @@ -169,7 +169,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(); @@ -295,7 +295,7 @@ begin // 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; @@ -364,7 +364,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); @@ -449,7 +449,7 @@ begin // initialize portaudio err := Pa_Initialize(); - if(err <> paNoError) then + if (err <> paNoError) then begin Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord'); Result := false; @@ -469,9 +469,9 @@ end; {* * Portaudio input capture callback. *} -function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword; +function MicrophoneCallback(input: pointer; output: pointer; frameCount: longword; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: Pointer): Integer; cdecl; + inputDevice: pointer): integer; cdecl; begin AudioInputProcessor.HandleMicrophoneData(input, frameCount*4, inputDevice); result := paContinue; @@ -480,15 +480,14 @@ end; {* * Portaudio test capture callback. *} -function MicrophoneTestCallback(input: Pointer; output: Pointer; frameCount: Longword; +function MicrophoneTestCallback(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; end; - initialization MediaManager.add(TAudioInput_Portaudio.Create); -- cgit v1.2.3 From d9a216be7770eb5003f03a3f0856920643425f54 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 25 Feb 2010 11:28:04 +0000 Subject: cosmetics. no code change. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2156 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 25ceae3c..1c7e3ef5 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -44,7 +44,7 @@ type constructor Create(); class function GetInstance(): TAudioCore_Portaudio; function GetPreferredApiIndex(): TPaHostApiIndex; - function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean; + function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean; end; implementation @@ -103,7 +103,7 @@ end; function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex; var - i: integer; + i: integer; apiIndex: TPaHostApiIndex; apiInfo: PPaHostApiInfo; begin @@ -112,11 +112,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 +132,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 +141,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 +189,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 +206,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 +244,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; -- cgit v1.2.3 From 03d4c6996af2582d672454b0f5a1a6e4b9cbe5ad Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Fri, 12 Mar 2010 21:11:54 +0000 Subject: rename cosmetics. no code change. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2192 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Portaudio.pas | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 4a021cc7..c5ec8115 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -268,27 +268,27 @@ 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; + paApiIndex: TPaHostApiIndex; + paApiInfo: PPaHostApiInfo; + deviceName: string; + deviceIndex: TPaDeviceIndex; + deviceInfo: PPaDeviceInfo; + channelCnt: integer; + soundcardCnt: 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: string; {$ENDIF} begin Result := false; @@ -303,7 +303,7 @@ begin paApiInfo := Pa_GetHostApiInfo(paApiIndex); - SC := 0; + soundcardCnt := 0; // init array-size to max. input-devices count SetLength(AudioInputProcessor.DeviceList, paApiInfo^.deviceCount); @@ -326,7 +326,7 @@ begin channelCnt := 2; paDevice := TPortaudioInputDevice.Create(); - AudioInputProcessor.DeviceList[SC] := paDevice; + AudioInputProcessor.DeviceList[soundCardCnt] := paDevice; // retrieve device-name deviceName := deviceInfo^.name; @@ -430,13 +430,13 @@ begin // close test-stream Pa_CloseStream(stream); - Inc(SC); + Inc(soundCardCnt); end; // adjust size to actual input-device count - SetLength(AudioInputProcessor.DeviceList, SC); + SetLength(AudioInputProcessor.DeviceList, soundCardCnt); - Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio'); + Log.LogStatus('#Input-Devices: ' + inttostr(soundCardCnt), 'Portaudio'); Result := true; end; -- cgit v1.2.3 From 4ab95b917cedf79375da2918b3553e97e0ff404b Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 6 Apr 2010 19:11:40 +0000 Subject: - Revert quick-fix from revision 2000 - The problem has been fixed by the previous commit (overload Path()) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2220 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 6db9cd20..27f29f7e 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -263,7 +263,7 @@ begin Reset(); // use custom 'ufile' protocol for UTF-8 support - errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToNative), nil, 0, nil); + errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil); if (errnum <> 0) then begin Log.LogError('Failed to open file "'+ FileName.ToNative +'" ('+FFmpegCore.GetErrorString(errnum)+')'); -- cgit v1.2.3 From f1566a45ac7de81f31a1b419d88e1742089ae56d Mon Sep 17 00:00:00 2001 From: whiteshark0 Date: Tue, 13 Apr 2010 16:23:14 +0000 Subject: =?UTF-8?q?-=20save=20input=20device=20names=20as=20UTF8Strings=20?= =?UTF-8?q?-=20device=20names=20of=20basslib=20are=20assumed=20to=20be=20i?= =?UTF-8?q?n=20local=20encoding=20-=20device=20names=20of=20portaudio=20ar?= =?UTF-8?q?e=20assumed=20to=20be=20in=20utf-8=20fixes:=20display=20of=20de?= =?UTF-8?q?vice=20names=20w/=20special=20characters=20w/=20basslib=20('?= =?UTF-8?q?=C3=A4'=20for=20me)=20to-do:=20proof=20encoding=20of=20device?= =?UTF-8?q?=20names=20for=20basslib=20and=20portaudio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2234 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Bass.pas | 10 +++++++--- src/media/UAudioInput_Portaudio.pas | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index 9d4417f1..e51ba254 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/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 name seems to be encoded w/ local encoding + // to-do : check if this is correct + 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) diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index c5ec8115..95b0f104 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -271,7 +271,7 @@ var i: integer; paApiIndex: TPaHostApiIndex; paApiInfo: PPaHostApiInfo; - deviceName: string; + deviceName: UTF8String; deviceIndex: TPaDeviceIndex; deviceInfo: PPaDeviceInfo; channelCnt: integer; -- cgit v1.2.3 From 7bf53987b4699ef418476dd9de8ee37bba53cba9 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 18 Apr 2010 18:04:52 +0000 Subject: open ffmpeg video and audio files in shared mode. Crashed previously when a file was opened twice in read-only mode. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2248 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMediaCore_FFmpeg.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index b4951fe1..9eab7621 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/src/media/UMediaCore_FFmpeg.pas @@ -268,7 +268,7 @@ begin else if ((flags and URL_WRONLY) <> 0) then Mode := fmCreate // TODO: fmCreate is Read+Write -> reopen with fmOpenWrite else - Mode := fmOpenRead; + Mode := fmOpenRead or fmShareDenyWrite; Result := 0; -- cgit v1.2.3 From ec49bde7e1919e5a460c05903b6ccafc00be0455 Mon Sep 17 00:00:00 2001 From: tobigun Date: Mon, 19 Apr 2010 21:40:23 +0000 Subject: bugfix for crash with portaudio (all platforms): - UAudioInput_Portaudio: UnifyDeviceNames was called with the wrong index further changes: - BASS input source-names seem to be encoded with local encoding and are converted to UTF8 - Portaudio source and device names encoding is auto-detected and converted to UTF8 - some clean-up git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2252 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Bass.pas | 8 ++++-- src/media/UAudioInput_Portaudio.pas | 57 ++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 22 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index e51ba254..b8f914c5 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -392,8 +392,8 @@ begin BassDevice.BassDeviceID := BassDeviceID; - // bass device name seems to be encoded w/ local encoding - // to-do : check if this is correct + // 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); @@ -463,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)^); diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 95b0f104..26919d42 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -46,6 +46,8 @@ uses {$ENDIF} portaudio, UAudioCore_Portaudio, + UUnicodeUtils, + UTextEncoding, UIni, ULog, UMain, @@ -88,6 +90,20 @@ function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: lon inputDevice: pointer): integer; 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.Open(): boolean; @@ -95,6 +111,9 @@ var Error: TPaError; inputParams: TPaStreamParameters; deviceInfo: PPaDeviceInfo; + {$IFDEF UsePortmixer} + SourceIndex: integer; + {$ENDIF} begin Result := false; @@ -269,13 +288,13 @@ end; function TAudioInput_Portaudio.EnumDevices(): boolean; var i: integer; + deviceName: UTF8String; paApiIndex: TPaHostApiIndex; paApiInfo: PPaHostApiInfo; - deviceName: UTF8String; - deviceIndex: TPaDeviceIndex; - deviceInfo: PPaDeviceInfo; + paDeviceIndex:TPaDeviceIndex; + paDeviceInfo: PPaDeviceInfo; channelCnt: integer; - soundcardCnt: integer; + deviceIndex: integer; err: TPaError; errMsg: string; paDevice: TPortaudioInputDevice; @@ -288,7 +307,7 @@ var mixer: PPxMixer; sourceCnt: integer; sourceIndex: integer; - sourceName: string; + sourceName: UTF8String; {$ENDIF} begin Result := false; @@ -303,17 +322,17 @@ begin paApiInfo := Pa_GetHostApiInfo(paApiIndex); - soundcardCnt := 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 +345,25 @@ begin channelCnt := 2; paDevice := TPortaudioInputDevice.Create(); - AudioInputProcessor.DeviceList[soundCardCnt] := paDevice; + AudioInputProcessor.DeviceList[deviceIndex] := paDevice; // retrieve device-name - deviceName := deviceInfo^.name; + deviceName := ConvertPaStringToUTF8(paDeviceInfo^.name); paDevice.Name := UnifyDeviceName(deviceName, deviceIndex); - paDevice.PaDeviceIndex := 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; + latency := paDeviceInfo^.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; + device := paDeviceIndex; channelCount := channelCnt; sampleFormat := paInt16; suggestedLatency := latency; @@ -421,7 +440,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,13 +449,13 @@ begin // close test-stream Pa_CloseStream(stream); - Inc(soundCardCnt); + Inc(deviceIndex); end; // adjust size to actual input-device count - SetLength(AudioInputProcessor.DeviceList, soundCardCnt); + SetLength(AudioInputProcessor.DeviceList, deviceIndex); - Log.LogStatus('#Input-Devices: ' + inttostr(soundCardCnt), 'Portaudio'); + Log.LogStatus('#Input-Devices: ' + inttostr(deviceIndex), 'Portaudio'); Result := true; end; -- cgit v1.2.3 From 942773883703b519f5b2528aac504e3e6656be65 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 20 Apr 2010 19:21:23 +0000 Subject: - force a BASS version >= 2.4.2 as the BASS ABI is slightly different (although we don't use the changed stuff) - IMPORTANT: if a version < 2.4.2 is present, usdx will log an error message but will start without sound. If you don't get sound anymore, update BASS git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2256 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Bass.pas | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioCore_Bass.pas b/src/media/UAudioCore_Bass.pas index 197f9760..3a84dcd7 100644 --- a/src/media/UAudioCore_Bass.pas +++ b/src/media/UAudioCore_Bass.pas @@ -49,6 +49,8 @@ type 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 @@ -58,10 +60,9 @@ uses ULog; const - // TODO: 2.4.2 is not ABI compatible with older versions + // BASS 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; + BASS_MIN_REQUIRED_VERSION = $02040201; var Instance: TAudioCore_Bass; @@ -78,9 +79,25 @@ 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; -- cgit v1.2.3 From 868ce765441473e7d1fec9b3ad22a707f121a637 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 18:27:36 +0000 Subject: - add video loop option - allow multiple instances of a video git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2260 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMedia_dummy.pas | 200 +++++++++++++++++++++++++++++---------- src/media/UVideo.pas | 162 +++++++++++++++++++------------ src/media/UVisualizer.pas | 230 ++++++++++++++++++++++++--------------------- 3 files changed, 378 insertions(+), 214 deletions(-) (limited to 'src/media') diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 25e94724..35b8bd70 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -42,17 +42,17 @@ uses 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: IPath): boolean; // true if succeed + function Open(const aFileName: IPath): boolean; // true if succeed procedure Close; procedure Play; @@ -64,9 +64,6 @@ type procedure SetSyncSource(SyncSource: TSyncSource); - procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); - // IAudioInput function InitializeRecord: boolean; function FinalizeRecord: boolean; @@ -83,9 +80,11 @@ 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; @@ -98,98 +97,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 : IPath): 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(); @@ -197,73 +220,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: IPath): 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/src/media/UVideo.pas b/src/media/UVideo.pas index 27f29f7e..fd3c7e2a 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -107,11 +107,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 @@ -150,23 +154,37 @@ type procedure ShowDebugInfo(); public - function GetName: String; + constructor Create; + destructor Destroy; override; + + function Open(const FileName: IPath): boolean; + procedure Close; - function Init(): boolean; - function Finalize: boolean; + 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); + end; - function Open(const FileName : IPath): boolean; // true if succeed - procedure Close; + 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 @@ -219,47 +237,46 @@ 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; +constructor TVideo_FFmpeg.Create; +begin + glGenTextures(1, PGLuint(@fFrameTex)); + Reset(); +end; - fAspectCorrection := acoCrop; +destructor TVideo_FFmpeg.Destroy; +begin + Close(); + glDeleteTextures(1, PGLuint(@fFrameTex)); end; -function TVideoPlayback_FFmpeg.Open(const FileName : IPath): boolean; // true if succeed +function TVideo_FFmpeg.Open(const FileName : IPath): boolean; var errnum: Integer; AudioStreamIndex: integer; begin Result := false; - Reset(); // use custom 'ufile' protocol for UTF-8 support @@ -408,6 +425,7 @@ begin end; {$ENDIF} + fTexWidth := Round(Power(2, Ceil(Log2(fCodecContext^.width)))); fTexHeight := Round(Power(2, Ceil(Log2(fCodecContext^.height)))); @@ -420,11 +438,32 @@ begin 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; + fTime := 0; + fStream := nil; + fStreamIndex := -1; + fFrameTexValid := false; + + fEOF := false; + + fLoop := false; + fLoopTime := 0; + + fAspectCorrection := acoCrop; +end; + +procedure TVideo_FFmpeg.Close; begin if (fFrameBuffer <> nil) then av_free(fFrameBuffer); @@ -457,7 +496,7 @@ begin 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 @@ -484,7 +523,7 @@ end; * @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; @@ -522,7 +561,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. @@ -533,18 +575,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 @@ -593,7 +626,7 @@ begin Result := true; end; -procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended); +procedure TVideo_FFmpeg.GetFrame(Time: Extended); var errnum: Integer; NewTime: Extended; @@ -749,7 +782,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; @@ -799,7 +832,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; @@ -862,7 +895,7 @@ begin {$IFEND} end; -procedure TVideoPlayback_FFmpeg.ShowDebugInfo(); +procedure TVideo_FFmpeg.ShowDebugInfo(); begin {$IFDEF Info} if (fTime+fTimeBase < 0) then @@ -899,17 +932,28 @@ 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; {** @@ -920,7 +964,7 @@ end; * 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 @@ -955,7 +999,7 @@ begin avcodec_flush_buffers(fCodecContext); end; -function TVideoPlayback_FFmpeg.GetPosition: real; +function TVideo_FFmpeg.GetPosition: real; begin Result := fTime; end; diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index b25d68a9..4f553521 100644 --- a/src/media/UVisualizer.pas +++ b/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, @@ -90,32 +95,30 @@ const textureSize = 512; {$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; @@ -126,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: IPath): boolean; // true if succeed procedure Close; procedure Play; @@ -141,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 @@ -154,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: IPath): 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 @@ -253,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; @@ -289,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; @@ -325,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); @@ -345,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) @@ -361,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 @@ -387,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 @@ -467,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 @@ -478,7 +496,7 @@ begin end; // exit if there's nothing to draw - if not VisualizerStarted then + if (fState <> pmPlay) then Exit; // setup display @@ -497,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 @@ -524,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 @@ -541,7 +559,7 @@ begin Data[i][1] := Random(High(Word)+1); end; end; - Inc(RndPCMcount); + Inc(fRndPCMcount); Result := 512; end; -- cgit v1.2.3 From 8be69a4b6a53c84e0e19c7c0ec8dde9a96f03bc0 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 18:45:47 +0000 Subject: - better lyric <-> video sync - fixed glTexEnv() bug: GL_TEXTURE_2D is not valid here - pixel buffer test - some cleanup git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2261 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 178 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 139 insertions(+), 39 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index fd3c7e2a..0716bee2 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -81,6 +81,7 @@ uses swscale, {$ENDIF} gl, + glu, glext, textgl, UMediaCore_FFmpeg, @@ -92,13 +93,23 @@ uses 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 @@ -142,9 +153,11 @@ type 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); @@ -274,11 +287,14 @@ end; function TVideo_FFmpeg.Open(const FileName : IPath): boolean; var errnum: Integer; + glErr: GLenum; AudioStreamIndex: integer; begin Result := false; Reset(); + 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 @@ -425,14 +441,33 @@ begin end; {$ENDIF} - 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); @@ -447,10 +482,10 @@ begin // close previously opened video Close(); - fOpened := False; - fPaused := False; - fTimeBase := 0; - fTime := 0; + fOpened := False; + fPaused := False; + fTimeBase := 0; + fFrameTime := 0; fStream := nil; fStreamIndex := -1; fFrameTexValid := false; @@ -460,6 +495,8 @@ begin fLoop := false; fLoopTime := 0; + fPboId := 0; + fAspectCorrection := acoCrop; end; @@ -493,6 +530,9 @@ begin fCodecContext := nil; fFormatContext := nil; + if (fPboId <> 0) then + glDeleteBuffersARB(1, @fPboId); + fOpened := False; end; @@ -503,22 +543,22 @@ 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. * returns true if a frame could be decoded. False if an error or EOF occured. @@ -629,13 +669,15 @@ end; 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; @@ -643,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 @@ -670,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} @@ -684,12 +739,15 @@ begin 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 @@ -702,11 +760,11 @@ 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; @@ -718,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 @@ -732,9 +794,9 @@ 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. // If ./configure does not find SWScale then this gives the error @@ -762,10 +824,48 @@ 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]); + // TODO: check if this is faster + //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; if (not fFrameTexValid) then fFrameTexValid := true; @@ -898,7 +998,7 @@ end; 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); @@ -959,8 +1059,8 @@ 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 *} @@ -986,7 +1086,7 @@ begin // requested time, let the sync in GetFrame() do its job. SeekFlags := AVSEEK_FLAG_BACKWARD; - fTime := Time; + fFrameTime := Time; fEOF := false; fFrameTexValid := false; @@ -1001,7 +1101,7 @@ end; function TVideo_FFmpeg.GetPosition: real; begin - Result := fTime; + Result := fFrameTime; end; initialization -- cgit v1.2.3 From 6f210823899dfd7de608701da76625c64174a999 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 19:52:06 +0000 Subject: - fixed UAudioPlayback_SoftMixer.GetPosition(): - previously it returned the position that would have been reached after ALL data that was returned at ReadData() was processed by the engine - due to this the returned position did not change for about 40ms (with SDL, samples: 2048, 44.1kHz) and the position was too far ahead (40ms) - this has been fixed by guessing the amount that is still buffered by the engine (SDL or portaudio) - sync should work better now with SDL and portaudio (BASS is not affected by this patch) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2263 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlayback_SoftMixer.pas | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index 203536d6..306b83a9 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/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; @@ -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; @@ -369,6 +373,8 @@ begin fVolume := 0; SoundEffects.Clear; FadeInTime := 0; + + LastReadSize := 0; end; function TGenericPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean; @@ -494,7 +500,11 @@ begin Exit; Status := ssStopped; + // stop fading + FadeInTime := 0; + LastReadSize := 0; + Mixer := Engine.GetMixer(); if (Mixer <> nil) then Mixer.RemoveStream(Self); @@ -542,6 +552,7 @@ begin SampleBufferCount := 0; SampleBufferPos := 0; SourceBufferCount := 0; + LastReadSize := 0; end; procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PByteArray; BufferSize: integer); @@ -575,6 +586,8 @@ var begin Result := -1; + LastReadSize := 0; + // sanity check for the source-stream if (not assigned(SourceStream)) then Exit; @@ -746,7 +759,9 @@ 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; @@ -841,6 +856,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; @@ -849,11 +886,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(); -- cgit v1.2.3 From 4d414b0a788982775b0c8081b888156ad6a10497 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 22:47:23 +0000 Subject: fixed bug in SDL/Portaudio playback stream: - positioning was broken in revision 1661 (9.4.2009, a long time) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2269 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlayback_SoftMixer.pas | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index 306b83a9..11df4df5 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -449,7 +449,8 @@ var Mixer: TAudioMixerStream; begin // only paused streams are not flushed - NeedsRewind := not (Status = ssPaused); + if (Status = ssPaused) then + NeedsRewind := false; // rewind if necessary. Cases that require no rewind are: // - stream was created and never played @@ -921,7 +922,8 @@ begin LockSampleBuffer(); SourceStream.Position := Time; - NeedsRewind := not (Status = ssStopped); + if (Status = ssStopped) then + NeedsRewind := false; // do not use outdated data FlushBuffers(); -- cgit v1.2.3 From 7a9910634301c97ef9b785913ce4d72af8bfcfbc Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 23:30:30 +0000 Subject: fixed choppy start of some songs when using ffmpeg git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2270 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index d079afdc..7ca98885 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -325,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? @@ -575,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 -- cgit v1.2.3 From edfc692c991e08af0163aa6812e5972478d7191b Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 22 Apr 2010 01:04:24 +0000 Subject: - now it is possible to sync lyrics to audio - ini option SyncTo added - lyric to audio is default now (instead of sync audio to lyrics) - modified RelativeTimer (hopefully easier to use and more self-explanatory) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2273 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioPlaybackBase.pas | 1 + src/media/UMedia_dummy.pas | 1 + 2 files changed, 2 insertions(+) (limited to 'src/media') diff --git a/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index de2d5563..5f317257 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -35,6 +35,7 @@ interface uses UMusic, + UTime, UPath; type diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 35b8bd70..8ebfd3a9 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -38,6 +38,7 @@ implementation uses SysUtils, math, + UTime, UMusic, UPath; -- cgit v1.2.3 From d142fd8700057ad284e2620076d6716387f3cd63 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 25 Apr 2010 17:14:14 +0000 Subject: wrong usage of glTexEnvi fixed - the environment must be GL_TEXTURE_ENV and not GL_TEXTURE_2D - it must be set before a draw function (glBegin(), ...) and not before glTexImage2D() as the current texture will not store this setting (the setting is global for all textures). - the setting must be set to the default (GL_MODULATE) after usage, otherwise later opengl drawing calls will be unwantedly affected too. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2309 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 0716bee2..c7d59fc8 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -824,8 +824,8 @@ begin // Or should we add padding with avpicture_fill? (check which one is faster) //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - // TODO: check if this is faster - //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + // 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 @@ -867,6 +867,9 @@ begin 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; -- cgit v1.2.3 From 27b3e332076162b37a7a53f059004d23ad69f9d2 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 27 Apr 2010 19:09:40 +0000 Subject: - device input latency is now configurable via config.ini - latency[i] determines the latency for device i in milliseconds or -1 for autodetection (default) - this is necessary as mic capturing with portaudio (on linux) gets stuck if latency is too low. Either because portaudio's latency autodetection does not work or because the mic capture callback takes too long before it returns. In both cases the user should set the latency to a value of 100 (ms). - better input device test, it should not remove working devices anymore. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2313 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Portaudio.pas | 65 +++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 17 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 26919d42..92e549ff 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -45,6 +45,7 @@ uses portmixer, {$ENDIF} portaudio, + ctypes, UAudioCore_Portaudio, UUnicodeUtils, UTextEncoding, @@ -77,18 +78,19 @@ type function Start(): 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. @@ -106,6 +108,33 @@ 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; @@ -126,12 +155,12 @@ 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, @@ -309,6 +338,8 @@ var 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; @@ -354,13 +385,13 @@ begin 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 := paDeviceInfo^.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 := paDeviceIndex; @@ -488,9 +519,9 @@ 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; @@ -499,9 +530,9 @@ 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; -- cgit v1.2.3 From c61600bfdda6608feb6a390d20e2e1200afe93f6 Mon Sep 17 00:00:00 2001 From: tobigun Date: Fri, 30 Apr 2010 13:49:10 +0000 Subject: portaudio playback (not used by default) segfault fixed git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2321 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 56 ++++++++++++++++++++++++++++++++++ src/media/UAudioInput_Portaudio.pas | 15 +++------ src/media/UAudioPlayback_Portaudio.pas | 22 ++++++------- 3 files changed, 71 insertions(+), 22 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 1c7e3ef5..c97b5d10 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -40,9 +40,13 @@ 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; end; @@ -92,6 +96,7 @@ var constructor TAudioCore_Portaudio.Create(); begin inherited; + InitCount := 0; end; class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio; @@ -101,6 +106,57 @@ 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; diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 92e549ff..c7364eb4 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -492,27 +492,20 @@ begin 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; diff --git a/src/media/UAudioPlayback_Portaudio.pas b/src/media/UAudioPlayback_Portaudio.pas index ddbd03d6..6fbae6e3 100644 --- a/src/media/UAudioPlayback_Portaudio.pas +++ b/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; -- cgit v1.2.3 From 2a8098bc38661d97e3e7dcf61898797ea3427a4f Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Wed, 12 May 2010 11:37:31 +0000 Subject: fix No. 2 of ffmpeg update. Move from CodecType to AVMediaType. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2364 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMediaCore_FFmpeg.pas | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index 9eab7621..2d572ff2 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/src/media/UMediaCore_FFmpeg.pas @@ -99,7 +99,8 @@ 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; @@ -184,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 @@ -196,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 @@ -215,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; -- cgit v1.2.3 From d121acb2a38cb3e6229b91b59ebd708810080dda Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Sun, 23 May 2010 14:58:39 +0000 Subject: added window-mode and reflection-mode to DrawGL git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2409 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 209 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 163 insertions(+), 46 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index c7d59fc8..994a0321 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -50,19 +50,20 @@ interface type {** - * vacStretch: Stretch to screen width and height + * acoStretch: 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 + * acoCrop: 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 + * acoLetterBox: 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); @@ -114,8 +115,14 @@ const type TRectCoords = record - Left, Right: double; - Upper, Lower: double; + Left, Right: double; + Upper, Lower: double; + Windowed: boolean; //draw video in a window instead of full screen + //full screen means black background without blending + Reflection: boolean; + ReflectionSpacing: real; + TargetAspect: TAspectCorrection; //for zooming/aspect-switching + ZoomFactor: double; //0..1 ==> 0..100% end; IVideo_FFmpeg = interface (IVideo) @@ -162,7 +169,7 @@ type function DecodeFrame(): boolean; procedure SynchronizeTime(Frame: PAVFrame; var pts: double); - procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); + procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords; Window: TRectCoords); procedure ShowDebugInfo(); @@ -184,7 +191,8 @@ type function GetPosition: real; procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); + procedure DrawGL(Screen: integer); overload; + procedure DrawGL(Screen: integer; Window: TRectCoords; Blend: real); overload; end; TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback ) @@ -885,47 +893,87 @@ begin {$ENDIF} end; -procedure TVideo_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords); +procedure TVideo_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords; Window: 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; + RectS, RectT: TRectCoords; + + procedure GetCoords(var SRect: TRectCoords; Win: TRectCoords; Aspect: TAspectCorrection); + var + ScreenAspect: double; // aspect of screen resolution + ScaledVideoWidth: double; + ScaledVideoHeight: double; + rW, rH: 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) + if (Win.windowed) then + begin + rW := (Win.Right-Win.Left); + rH := (Win.Lower-Win.Upper); + ScreenAspect := rW*((ScreenW/Screens)/RenderW)/(rH*(ScreenH/RenderH)); + end else + begin + rW := RenderW; + rH := RenderH; + ScreenAspect := (ScreenW/Screens) / ScreenH; end; - acoCrop: begin - if (ScreenAspect >= fAspect) then - begin - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH * ScreenAspect/fAspect; + + case Aspect of + acoStretch: begin + ScaledVideoWidth := rW; + ScaledVideoHeight := rH; + end; + + acoCrop: begin + if (ScreenAspect >= fAspect) then + begin + ScaledVideoWidth := rW; + ScaledVideoHeight := rH * ScreenAspect/fAspect; + end + else + begin + ScaledVideoHeight := rH; + ScaledVideoWidth := rW * fAspect/ScreenAspect; + end; + end; + + acoLetterBox: begin + if (ScreenAspect <= fAspect) then + begin + ScaledVideoWidth := rW; + ScaledVideoHeight := rH * ScreenAspect/fAspect; + end + else + begin + ScaledVideoHeight := rH; + ScaledVideoWidth := rW * fAspect/ScreenAspect; + end; end else - begin - ScaledVideoHeight := RenderH; - ScaledVideoWidth := RenderW * fAspect/ScreenAspect; - end; + raise Exception.Create('Unhandled aspect correction!'); end; - acoLetterBox: begin - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH * ScreenAspect/fAspect; - end - else - raise Exception.Create('Unhandled aspect correction!'); + + SRect.Left := (rW - ScaledVideoWidth) / 2 + Win.Left; + SRect.Right := SRect.Left + ScaledVideoWidth; + SRect.Upper := (rH - ScaledVideoHeight) / 2 + Win.Upper; + SRect.Lower := SRect.Upper + ScaledVideoHeight; end; +begin + if (Window.TargetAspect = fAspectCorrection) then + GetCoords(ScreenRect, Window, fAspectCorrection) + else + begin + GetCoords(RectS, Window, fAspectCorrection); + GetCoords(RectT, Window, Window.TargetAspect); - // center video - ScreenRect.Left := (RenderW - ScaledVideoWidth) / 2; - ScreenRect.Right := ScreenRect.Left + ScaledVideoWidth; - ScreenRect.Upper := (RenderH - ScaledVideoHeight) / 2; - ScreenRect.Lower := ScreenRect.Upper + ScaledVideoHeight; + ScreenRect.Left := RectS.Left + (RectT.Left-RectS.Left)*Window.ZoomFactor; + ScreenRect.Right := RectS.Right + (RectT.Right-RectS.Right)*Window.ZoomFactor; + ScreenRect.Upper := RectS.Upper + (RectT.Upper-RectS.Upper)*Window.ZoomFactor; + ScreenRect.Lower := RectS.Lower + (RectT.Lower-RectS.Lower)*Window.ZoomFactor; + end; // texture contains right/lower (power-of-2) padding. // Determine the texture coords of the video frame. @@ -936,16 +984,32 @@ begin end; procedure TVideo_FFmpeg.DrawGL(Screen: integer); +var + Window: TRectCoords; + +begin + Window.Left := 0; + Window.Right := ScreenW; + Window.Upper := 0; + Window.Lower := ScreenH; + Window.Windowed := false; + Window.Reflection := false; + Window.TargetAspect := fAspectCorrection; + DrawGL(Screen, Window, 1); +end; + +procedure TVideo_FFmpeg.DrawGL(Screen: integer; Window: TRectCoords; Blend: real); 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 + if (Screen = 1) and not Window.Windowed then begin // It is important that we just clear once before we start // drawing the first screen otherwise the first screen @@ -964,14 +1028,25 @@ begin {$ENDIF} // get texture and screen positions - GetVideoRect(ScreenRect, TexRect); + GetVideoRect(ScreenRect, TexRect, Window); - // we could use blending for brightness control, but do we need this? - glDisable(GL_BLEND); + if Window.Windowed then + begin + glScissor(round((Window.Left)*(ScreenW/Screens)/RenderW+(ScreenW/Screens)*(Screen-1)), + round((RenderH-Window.Lower)*ScreenH/RenderH), + round((Window.Right-Window.Left)*(ScreenW/Screens)/RenderW), + round((Window.Lower-Window.Upper)*ScreenH/RenderH)); + glEnable(GL_SCISSOR_TEST); + + // we could use blending for brightness control, but do we need this? + // the window-mode needs blending! + glEnable(GL_BLEND); + end else + glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, fFrameTex); - glColor3f(1, 1, 1); + glColor4f(1, 1, 1, Blend); glBegin(GL_QUADS); // upper-left coord glTexCoord2f(TexRect.Left, TexRect.Upper); @@ -986,7 +1061,49 @@ begin glTexCoord2f(TexRect.Right, TexRect.Upper); glVertex2f(ScreenRect.Right, ScreenRect.Upper); glEnd; + glDisable(GL_SCISSOR_TEST); + + //Draw Reflection + if Window.Reflection then + begin + glScissor(round((Window.Left)*(ScreenW/Screens)/RenderW+(ScreenW/Screens)*(Screen-1)), + round((RenderH-Window.Lower-Window.ReflectionSpacing-(Window.Lower-Window.Upper)*0.5)*ScreenH/RenderH), + round((Window.Right-Window.Left)*(ScreenW/Screens)/RenderW), + round((Window.Lower-Window.Upper)*ScreenH/RenderH*0.5)); + glEnable(GL_SCISSOR_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //Draw + glBegin(GL_QUADS);//Top Left + glColor4f(1, 1, 1, Blend-0.3); + glTexCoord2f(TexRect.Left, TexRect.Lower); + glVertex2f(ScreenRect.Left, Window.Lower + Window.ReflectionSpacing); + + //Bottom Left + glColor4f(1, 1, 1, 0); + glTexCoord2f(TexRect.Left, (TexRect.Lower-TexRect.Upper)*0.5); + glVertex2f(ScreenRect.Left, + Window.Lower + (ScreenRect.Lower-ScreenRect.Upper)*0.5 + Window.ReflectionSpacing); + + //Bottom Right + glColor4f(1, 1, 1, 0); + glTexCoord2f(TexRect.Right, (TexRect.Lower-TexRect.Upper)*0.5); + glVertex2f(ScreenRect.Right, + Window.Lower + (ScreenRect.Lower-ScreenRect.Upper)*0.5 + Window.ReflectionSpacing); + + //Top Right + glColor4f(1, 1, 1, Blend-0.3); + glTexCoord2f(TexRect.Right, TexRect.Lower); + glVertex2f(ScreenRect.Right, Window.Lower + Window.ReflectionSpacing); + glEnd; + + glDisable(GL_SCISSOR_TEST); + end; + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); {$IFDEF VideoBenchmark} Log.BenchmarkEnd(15); -- cgit v1.2.3 From 5922a70e798ed9ab8c8abf542794602fb82780c5 Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Mon, 24 May 2010 13:27:43 +0000 Subject: revert of last made changes (rev. 2409) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2410 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 209 ++++++++++++--------------------------------------- 1 file changed, 46 insertions(+), 163 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 994a0321..c7d59fc8 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -50,20 +50,19 @@ interface type {** - * acoStretch: Stretch to screen width and height + * vacStretch: Stretch to screen width and height * - ignores aspect * + no borders * + no image data loss - * acoCrop: Stretch to screen width or height, crop the other dimension + * vacCrop: Stretch to screen width or height, crop the other dimension * + keeps aspect * + no borders * - frame borders are cropped (image data loss) - * acoLetterBox: Stretch to screen width, add bars at or crop top and bottom + * 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); @@ -115,14 +114,8 @@ const type TRectCoords = record - Left, Right: double; - Upper, Lower: double; - Windowed: boolean; //draw video in a window instead of full screen - //full screen means black background without blending - Reflection: boolean; - ReflectionSpacing: real; - TargetAspect: TAspectCorrection; //for zooming/aspect-switching - ZoomFactor: double; //0..1 ==> 0..100% + Left, Right: double; + Upper, Lower: double; end; IVideo_FFmpeg = interface (IVideo) @@ -169,7 +162,7 @@ type function DecodeFrame(): boolean; procedure SynchronizeTime(Frame: PAVFrame; var pts: double); - procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords; Window: TRectCoords); + procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); procedure ShowDebugInfo(); @@ -191,8 +184,7 @@ type function GetPosition: real; procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); overload; - procedure DrawGL(Screen: integer; Window: TRectCoords; Blend: real); overload; + procedure DrawGL(Screen: integer); end; TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback ) @@ -893,87 +885,47 @@ begin {$ENDIF} end; -procedure TVideo_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords; Window: TRectCoords); +procedure TVideo_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords); var - RectS, RectT: TRectCoords; - - procedure GetCoords(var SRect: TRectCoords; Win: TRectCoords; Aspect: TAspectCorrection); - var - ScreenAspect: double; // aspect of screen resolution - ScaledVideoWidth: double; - ScaledVideoHeight: double; - rW, rH: 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) - if (Win.windowed) then - begin - rW := (Win.Right-Win.Left); - rH := (Win.Lower-Win.Upper); - ScreenAspect := rW*((ScreenW/Screens)/RenderW)/(rH*(ScreenH/RenderH)); - end else - begin - rW := RenderW; - rH := RenderH; - ScreenAspect := (ScreenW/Screens) / ScreenH; + 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; - - case Aspect of - acoStretch: begin - ScaledVideoWidth := rW; - ScaledVideoHeight := rH; - end; - - acoCrop: begin - if (ScreenAspect >= fAspect) then - begin - ScaledVideoWidth := rW; - ScaledVideoHeight := rH * ScreenAspect/fAspect; - end - else - begin - ScaledVideoHeight := rH; - ScaledVideoWidth := rW * fAspect/ScreenAspect; - end; - end; - - acoLetterBox: begin - if (ScreenAspect <= fAspect) then - begin - ScaledVideoWidth := rW; - ScaledVideoHeight := rH * ScreenAspect/fAspect; - end - else - begin - ScaledVideoHeight := rH; - ScaledVideoWidth := rW * fAspect/ScreenAspect; - end; + acoCrop: begin + if (ScreenAspect >= fAspect) then + begin + ScaledVideoWidth := RenderW; + ScaledVideoHeight := RenderH * ScreenAspect/fAspect; end else - raise Exception.Create('Unhandled aspect correction!'); + begin + ScaledVideoHeight := RenderH; + ScaledVideoWidth := RenderW * fAspect/ScreenAspect; + end; end; - - SRect.Left := (rW - ScaledVideoWidth) / 2 + Win.Left; - SRect.Right := SRect.Left + ScaledVideoWidth; - SRect.Upper := (rH - ScaledVideoHeight) / 2 + Win.Upper; - SRect.Lower := SRect.Upper + ScaledVideoHeight; + acoLetterBox: begin + ScaledVideoWidth := RenderW; + ScaledVideoHeight := RenderH * ScreenAspect/fAspect; + end + else + raise Exception.Create('Unhandled aspect correction!'); end; -begin - if (Window.TargetAspect = fAspectCorrection) then - GetCoords(ScreenRect, Window, fAspectCorrection) - else - begin - GetCoords(RectS, Window, fAspectCorrection); - GetCoords(RectT, Window, Window.TargetAspect); - ScreenRect.Left := RectS.Left + (RectT.Left-RectS.Left)*Window.ZoomFactor; - ScreenRect.Right := RectS.Right + (RectT.Right-RectS.Right)*Window.ZoomFactor; - ScreenRect.Upper := RectS.Upper + (RectT.Upper-RectS.Upper)*Window.ZoomFactor; - ScreenRect.Lower := RectS.Lower + (RectT.Lower-RectS.Lower)*Window.ZoomFactor; - 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. @@ -984,32 +936,16 @@ begin end; procedure TVideo_FFmpeg.DrawGL(Screen: integer); -var - Window: TRectCoords; - -begin - Window.Left := 0; - Window.Right := ScreenW; - Window.Upper := 0; - Window.Lower := ScreenH; - Window.Windowed := false; - Window.Reflection := false; - Window.TargetAspect := fAspectCorrection; - DrawGL(Screen, Window, 1); -end; - -procedure TVideo_FFmpeg.DrawGL(Screen: integer; Window: TRectCoords; Blend: real); 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) and not Window.Windowed then + if (Screen = 1) then begin // It is important that we just clear once before we start // drawing the first screen otherwise the first screen @@ -1028,25 +964,14 @@ begin {$ENDIF} // get texture and screen positions - GetVideoRect(ScreenRect, TexRect, Window); + GetVideoRect(ScreenRect, TexRect); - if Window.Windowed then - begin - glScissor(round((Window.Left)*(ScreenW/Screens)/RenderW+(ScreenW/Screens)*(Screen-1)), - round((RenderH-Window.Lower)*ScreenH/RenderH), - round((Window.Right-Window.Left)*(ScreenW/Screens)/RenderW), - round((Window.Lower-Window.Upper)*ScreenH/RenderH)); - glEnable(GL_SCISSOR_TEST); - - // we could use blending for brightness control, but do we need this? - // the window-mode needs blending! - glEnable(GL_BLEND); - end else - glDisable(GL_BLEND); + // we could use blending for brightness control, but do we need this? + glDisable(GL_BLEND); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, fFrameTex); - glColor4f(1, 1, 1, Blend); + glColor3f(1, 1, 1); glBegin(GL_QUADS); // upper-left coord glTexCoord2f(TexRect.Left, TexRect.Upper); @@ -1061,49 +986,7 @@ begin glTexCoord2f(TexRect.Right, TexRect.Upper); glVertex2f(ScreenRect.Right, ScreenRect.Upper); glEnd; - glDisable(GL_SCISSOR_TEST); - - //Draw Reflection - if Window.Reflection then - begin - glScissor(round((Window.Left)*(ScreenW/Screens)/RenderW+(ScreenW/Screens)*(Screen-1)), - round((RenderH-Window.Lower-Window.ReflectionSpacing-(Window.Lower-Window.Upper)*0.5)*ScreenH/RenderH), - round((Window.Right-Window.Left)*(ScreenW/Screens)/RenderW), - round((Window.Lower-Window.Upper)*ScreenH/RenderH*0.5)); - glEnable(GL_SCISSOR_TEST); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - //Draw - glBegin(GL_QUADS);//Top Left - glColor4f(1, 1, 1, Blend-0.3); - glTexCoord2f(TexRect.Left, TexRect.Lower); - glVertex2f(ScreenRect.Left, Window.Lower + Window.ReflectionSpacing); - - //Bottom Left - glColor4f(1, 1, 1, 0); - glTexCoord2f(TexRect.Left, (TexRect.Lower-TexRect.Upper)*0.5); - glVertex2f(ScreenRect.Left, - Window.Lower + (ScreenRect.Lower-ScreenRect.Upper)*0.5 + Window.ReflectionSpacing); - - //Bottom Right - glColor4f(1, 1, 1, 0); - glTexCoord2f(TexRect.Right, (TexRect.Lower-TexRect.Upper)*0.5); - glVertex2f(ScreenRect.Right, - Window.Lower + (ScreenRect.Lower-ScreenRect.Upper)*0.5 + Window.ReflectionSpacing); - - //Top Right - glColor4f(1, 1, 1, Blend-0.3); - glTexCoord2f(TexRect.Right, TexRect.Lower); - glVertex2f(ScreenRect.Right, Window.Lower + Window.ReflectionSpacing); - glEnd; - - glDisable(GL_SCISSOR_TEST); - end; - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); {$IFDEF VideoBenchmark} Log.BenchmarkEnd(15); -- cgit v1.2.3 From 6894d29e66396ef47b246347fcae1132e5daf84e Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 3 Jun 2010 15:58:43 +0000 Subject: better EOF handling for FFmpeg audio decoding (EOF handled like it is done in UVideo) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2438 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 7ca98885..f7f9c661 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -812,9 +812,18 @@ begin Exit; end; - // no error -> wait for user input - SDL_Delay(100); - Continue; + // url_feof() does not detect an EOF for some files + // so we have to do it this way. + if ((FormatCtx^.file_size <> 0) and + (ByteIOCtx^.pos >= FormatCtx^.file_size)) then + begin + PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + Exit; + end; + + // unknown error occured, exit + PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + Exit; end; if (Packet.stream_index = AudioStreamIndex) then -- cgit v1.2.3 From 85e05243b664f1b1b82c8c79ad370ae22b6e49c5 Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 3 Jun 2010 15:59:38 +0000 Subject: prefix class fields with 'f' git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2439 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 544 ++++++++++++++++++------------------- 1 file changed, 272 insertions(+), 272 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index f7f9c661..2badb84d 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -86,60 +86,60 @@ const type TFFmpegDecodeStream = class(TAudioDecodeStream) private - StateLock: PSDL_Mutex; + fStateLock: PSDL_Mutex; - EOFState: boolean; // end-of-stream flag (locked by StateLock) - ErrorState: boolean; // error flag (locked by StateLock) + fEOFState: boolean; // end-of-stream flag (locked by StateLock) + fErrorState: boolean; // error flag (locked by StateLock) - QuitRequest: boolean; // (locked by StateLock) - ParserIdleCond: PSDL_Cond; + fQuitRequest: boolean; // (locked by StateLock) + fParserIdleCond: 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) + fParserLocked: boolean; + fParserPauseRequestCount: integer; + fParserUnlockedCond: PSDL_Cond; + fParserResumeCond: PSDL_Cond; + + fSeekRequest: boolean; // (locked by StateLock) + fSeekFlags: integer; // (locked by StateLock) + fSeekPos: double; // stream position to seek for (in secs) (locked by StateLock) + fSeekFlush: boolean; // true if the buffers should be flushed after seeking (locked by StateLock) SeekFinishedCond: PSDL_Cond; - Loop: boolean; // (locked by StateLock) + fLoop: boolean; // (locked by StateLock) - ParseThread: PSDL_Thread; - PacketQueue: TPacketQueue; + fParseThread: PSDL_Thread; + fPacketQueue: TPacketQueue; - FormatInfo: TAudioFormatInfo; + fFormatInfo: TAudioFormatInfo; // FFmpeg specific data - FormatCtx: PAVFormatContext; - CodecCtx: PAVCodecContext; - Codec: PAVCodec; + fFormatCtx: PAVFormatContext; + fCodecCtx: PAVCodecContext; + fCodec: PAVCodec; - AudioStreamIndex: integer; - AudioStream: PAVStream; - AudioStreamPos: double; // stream position in seconds (locked by DecoderLock) + fAudioStreamIndex: integer; + fAudioStream: PAVStream; + fAudioStreamPos: double; // stream position in seconds (locked by DecoderLock) // decoder pause/resume data - DecoderLocked: boolean; - DecoderPauseRequestCount: integer; - DecoderUnlockedCond: PSDL_Cond; - DecoderResumeCond: PSDL_Cond; + fDecoderLocked: boolean; + fDecoderPauseRequestCount: integer; + fDecoderUnlockedCond: PSDL_Cond; + fDecoderResumeCond: PSDL_Cond; // state-vars for DecodeFrame (locked by DecoderLock) - AudioPaket: TAVPacket; - AudioPaketData: PByteArray; - AudioPaketSize: integer; - AudioPaketSilence: integer; // number of bytes of silence to return + fAudioPaket: TAVPacket; + fAudioPaketData: PByteArray; + fAudioPaketSize: integer; + fAudioPaketSilence: integer; // number of bytes of silence to return // state-vars for AudioCallback (locked by DecoderLock) - AudioBufferPos: integer; - AudioBufferSize: integer; - AudioBuffer: PByteArray; + fAudioBufferPos: integer; + fAudioBufferSize: integer; + fAudioBuffer: PByteArray; - Filename: IPath; + fFilename: IPath; procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF} @@ -199,13 +199,13 @@ constructor TFFmpegDecodeStream.Create(); begin inherited Create(); - StateLock := SDL_CreateMutex(); - ParserUnlockedCond := SDL_CreateCond(); - ParserResumeCond := SDL_CreateCond(); - ParserIdleCond := SDL_CreateCond(); + fStateLock := SDL_CreateMutex(); + fParserUnlockedCond := SDL_CreateCond(); + fParserResumeCond := SDL_CreateCond(); + fParserIdleCond := SDL_CreateCond(); SeekFinishedCond := SDL_CreateCond(); - DecoderUnlockedCond := SDL_CreateCond(); - DecoderResumeCond := SDL_CreateCond(); + fDecoderUnlockedCond := SDL_CreateCond(); + fDecoderResumeCond := SDL_CreateCond(); // according to the documentation of avcodec_decode_audio(2), sample-data // should be aligned on a 16 byte boundary. Otherwise internal calls @@ -222,33 +222,33 @@ begin // 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); + fAudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16); Reset(); end; procedure TFFmpegDecodeStream.Reset(); begin - ParseThread := nil; + fParseThread := nil; - EOFState := false; - ErrorState := false; - Loop := false; - QuitRequest := false; + fEOFState := false; + fErrorState := false; + fLoop := false; + fQuitRequest := false; - AudioPaketData := nil; - AudioPaketSize := 0; - AudioPaketSilence := 0; + fAudioPaketData := nil; + fAudioPaketSize := 0; + fAudioPaketSilence := 0; - AudioBufferPos := 0; - AudioBufferSize := 0; + fAudioBufferPos := 0; + fAudioBufferSize := 0; - ParserLocked := false; - ParserPauseRequestCount := 0; - DecoderLocked := false; - DecoderPauseRequestCount := 0; + fParserLocked := false; + fParserPauseRequestCount := 0; + fDecoderLocked := false; + fDecoderPauseRequestCount := 0; - FillChar(AudioPaket, SizeOf(TAVPacket), 0); + FillChar(fAudioPaket, SizeOf(TAVPacket), 0); end; {* @@ -258,15 +258,15 @@ destructor TFFmpegDecodeStream.Destroy(); begin Close(); - SDL_DestroyMutex(StateLock); - SDL_DestroyCond(ParserUnlockedCond); - SDL_DestroyCond(ParserResumeCond); - SDL_DestroyCond(ParserIdleCond); + SDL_DestroyMutex(fStateLock); + SDL_DestroyCond(fParserUnlockedCond); + SDL_DestroyCond(fParserResumeCond); + SDL_DestroyCond(fParserIdleCond); SDL_DestroyCond(SeekFinishedCond); - SDL_DestroyCond(DecoderUnlockedCond); - SDL_DestroyCond(DecoderResumeCond); + SDL_DestroyCond(fDecoderUnlockedCond); + SDL_DestroyCond(fDecoderResumeCond); - FreeAlignedMem(AudioBuffer); + FreeAlignedMem(fAudioBuffer); inherited; end; @@ -287,20 +287,20 @@ begin Exit; end; - Self.Filename := Filename; + Self.fFilename := Filename; // use custom 'ufile' protocol for UTF-8 support - if (av_open_input_file(FormatCtx, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil) <> 0) then + if (av_open_input_file(fFormatCtx, 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; + fFormatCtx^.flags := fFormatCtx^.flags or AVFMT_FLAG_GENPTS; // retrieve stream information - if (av_find_stream_info(FormatCtx) < 0) then + if (av_find_stream_info(fFormatCtx) < 0) then begin Log.LogError('av_find_stream_info failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); @@ -308,14 +308,14 @@ begin end; // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end - FormatCtx^.pb.eof_reached := 0; + fFormatCtx^.pb.eof_reached := 0; {$IFDEF DebugFFmpegDecode} - dump_format(FormatCtx, 0, PAnsiChar(Filename.ToNative), 0); + dump_format(fFormatCtx, 0, PAnsiChar(Filename.ToNative), 0); {$ENDIF} - AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); - if (AudioStreamIndex < 0) then + fAudioStreamIndex := FFmpegCore.FindAudioStreamIndex(fFormatCtx); + if (fAudioStreamIndex < 0) then begin Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); @@ -324,9 +324,9 @@ begin //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg'); - AudioStream := FormatCtx.streams[AudioStreamIndex]; - AudioStreamPos := 0; - CodecCtx := AudioStream^.codec; + fAudioStream := fFormatCtx.streams[fAudioStreamIndex]; + fAudioStreamPos := 0; + fCodecCtx := fAudioStream^.codec; // TODO: should we use this or not? Should we allow 5.1 channel audio? (* @@ -338,21 +338,21 @@ begin {$IFEND} *) - Codec := avcodec_find_decoder(CodecCtx^.codec_id); - if (Codec = nil) then + fCodec := avcodec_find_decoder(fCodecCtx^.codec_id); + if (fCodec = nil) then begin Log.LogError('Unsupported codec!', 'UAudio_FFmpeg'); - CodecCtx := nil; + fCodecCtx := nil; Close(); Exit; end; // set debug options - CodecCtx^.debug_mv := 0; - CodecCtx^.debug := 0; + fCodecCtx^.debug_mv := 0; + fCodecCtx^.debug := 0; // detect bug-workarounds automatically - CodecCtx^.workaround_bugs := FF_BUG_AUTODETECT; + fCodecCtx^.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. @@ -362,7 +362,7 @@ begin // fail if called concurrently by different threads. FFmpegCore.LockAVCodec(); try - AVResult := avcodec_open(CodecCtx, Codec); + AVResult := avcodec_open(fCodecCtx, fCodec); finally FFmpegCore.UnlockAVCodec(); end; @@ -375,23 +375,23 @@ begin // now initialize the audio-format - if (not FFmpegCore.ConvertFFmpegToAudioFormat(CodecCtx^.sample_fmt, SampleFormat)) then + if (not FFmpegCore.ConvertFFmpegToAudioFormat(fCodecCtx^.sample_fmt, SampleFormat)) then begin // try standard format SampleFormat := asfS16; end; - if CodecCtx^.channels > 255 then + if fCodecCtx^.channels > 255 then Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open'); - FormatInfo := TAudioFormatInfo.Create( - byte(CodecCtx^.channels), - CodecCtx^.sample_rate, + fFormatInfo := TAudioFormatInfo.Create( + byte(fCodecCtx^.channels), + fCodecCtx^.sample_rate, SampleFormat ); - PacketQueue := TPacketQueue.Create(); + fPacketQueue := TPacketQueue.Create(); // finally start the decode thread - ParseThread := SDL_CreateThread(@ParseThreadMain, Self); + fParseThread := SDL_CreateThread(@ParseThreadMain, Self); Result := true; end; @@ -403,47 +403,47 @@ 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(); + if (assigned(fPacketQueue)) then + fPacketQueue.Abort(); // send quit request (to parse-thread etc) - SDL_mutexP(StateLock); - QuitRequest := true; - SDL_CondBroadcast(ParserIdleCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fQuitRequest := true; + SDL_CondBroadcast(fParserIdleCond); + SDL_mutexV(fStateLock); // abort parse-thread - if (ParseThread <> nil) then + if (fParseThread <> nil) then begin // and wait until it terminates - SDL_WaitThread(ParseThread, ThreadResult); - ParseThread := nil; + SDL_WaitThread(fParseThread, ThreadResult); + fParseThread := nil; end; // Close the codec - if (CodecCtx <> nil) then + if (fCodecCtx <> nil) then begin // avcodec_close() is not thread-safe FFmpegCore.LockAVCodec(); try - avcodec_close(CodecCtx); + avcodec_close(fCodecCtx); finally FFmpegCore.UnlockAVCodec(); end; - CodecCtx := nil; + fCodecCtx := nil; end; // Close the video file - if (FormatCtx <> nil) then + if (fFormatCtx <> nil) then begin - av_close_input_file(FormatCtx); - FormatCtx := nil; + av_close_input_file(fFormatCtx); + fFormatCtx := nil; end; PerformOnClose(); - FreeAndNil(PacketQueue); - FreeAndNil(FormatInfo); + FreeAndNil(fPacketQueue); + FreeAndNil(fFormatInfo); end; function TFFmpegDecodeStream.GetLength(): real; @@ -451,54 +451,54 @@ 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; + Result := (fFormatCtx^.start_time + fFormatCtx^.duration) / AV_TIME_BASE; end; function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo; begin - Result := FormatInfo; + Result := fFormatInfo; end; function TFFmpegDecodeStream.IsEOF(): boolean; begin - SDL_mutexP(StateLock); - Result := EOFState; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fEOFState; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetEOF(State: boolean); begin - SDL_mutexP(StateLock); - EOFState := State; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fEOFState := State; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsError(): boolean; begin - SDL_mutexP(StateLock); - Result := ErrorState; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fErrorState; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetError(State: boolean); begin - SDL_mutexP(StateLock); - ErrorState := State; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fErrorState := State; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsSeeking(): boolean; begin - SDL_mutexP(StateLock); - Result := SeekRequest; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fSeekRequest; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsQuit(): boolean; begin - SDL_mutexP(StateLock); - Result := QuitRequest; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fQuitRequest; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.GetPosition(): real; @@ -509,11 +509,11 @@ begin // 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; + BufferSizeSec := (fAudioBufferSize - fAudioBufferPos) / + fFormatInfo.BytesPerSec; // subtract the size of unused buffer-data from the audio clock. - Result := AudioStreamPos - BufferSizeSec; + Result := fAudioStreamPos - BufferSizeSec; ResumeDecoder(); end; @@ -525,16 +525,16 @@ end; function TFFmpegDecodeStream.GetLoop(): boolean; begin - SDL_mutexP(StateLock); - Result := Loop; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fLoop; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean); begin - SDL_mutexP(StateLock); - Loop := Enabled; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fLoop := Enabled; + SDL_mutexV(fStateLock); end; @@ -544,25 +544,25 @@ end; procedure TFFmpegDecodeStream.PauseParser(); begin - if (SDL_ThreadID() = ParseThread.threadid) then + if (SDL_ThreadID() = fParseThread.threadid) then Exit; - SDL_mutexP(StateLock); - Inc(ParserPauseRequestCount); - while (ParserLocked) do - SDL_CondWait(ParserUnlockedCond, StateLock); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Inc(fParserPauseRequestCount); + while (fParserLocked) do + SDL_CondWait(fParserUnlockedCond, fStateLock); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.ResumeParser(); begin - if (SDL_ThreadID() = ParseThread.threadid) then + if (SDL_ThreadID() = fParseThread.threadid) then Exit; - SDL_mutexP(StateLock); - Dec(ParserPauseRequestCount); - SDL_CondSignal(ParserResumeCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Dec(fParserPauseRequestCount); + SDL_CondSignal(fParserResumeCond); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); @@ -575,36 +575,36 @@ begin // - Last lock the state lock because we are manipulating some shared state-vars. PauseParser(); PauseDecoder(); - SDL_mutexP(StateLock); + SDL_mutexP(fStateLock); try - EOFState := false; - ErrorState := false; + fEOFState := false; + fErrorState := 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 + if (Time = fAudioStreamPos) then Exit; // configure seek parameters - SeekPos := Time; - SeekFlush := Flush; - SeekFlags := AVSEEK_FLAG_ANY; - SeekRequest := true; + fSeekPos := Time; + fSeekFlush := Flush; + fSeekFlags := AVSEEK_FLAG_ANY; + fSeekRequest := 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; + if (Time <= fAudioStreamPos) then + fSeekFlags := fSeekFlags or AVSEEK_FLAG_BACKWARD; // send a reuse signal in case the parser was stopped (e.g. because of an EOF) - SDL_CondSignal(ParserIdleCond); + SDL_CondSignal(fParserIdleCond); finally - SDL_mutexV(StateLock); + SDL_mutexV(fStateLock); ResumeDecoder(); ResumeParser(); end; @@ -612,10 +612,10 @@ begin // 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); + SDL_mutexP(fStateLock); + while (fSeekRequest) do + SDL_CondWait(SeekFinishedCond, fStateLock); + SDL_mutexV(fStateLock); end; end; @@ -635,10 +635,10 @@ begin 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); + SDL_mutexP(fStateLock); + while (not (fSeekRequest or fQuitRequest)) do + SDL_CondWait(fParserIdleCond, fStateLock); + SDL_mutexV(fStateLock); end; end; @@ -669,19 +669,19 @@ var // 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); + SDL_mutexP(fStateLock); + while (fParserPauseRequestCount > 0) do + SDL_CondWait(fParserResumeCond, fStateLock); + fParserLocked := true; + SDL_mutexV(fStateLock); end; procedure UnlockParser(); begin - SDL_mutexP(StateLock); - ParserLocked := false; - SDL_CondBroadcast(ParserUnlockedCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fParserLocked := false; + SDL_CondBroadcast(fParserUnlockedCond); + SDL_mutexV(fStateLock); end; begin @@ -699,92 +699,92 @@ begin end; // handle seek-request (Note: no need to lock SeekRequest here) - if (SeekRequest) then + if (fSeekRequest) then begin // first try: seek on the audio stream - SeekTarget := Round(SeekPos / av_q2d(AudioStream^.time_base)); + SeekTarget := Round(fSeekPos / av_q2d(fAudioStream^.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 (SeekTarget < fAudioStream^.start_time) then + StartSilence := (fAudioStream^.start_time - SeekTarget) * av_q2d(fAudioStream^.time_base); + ErrorCode := av_seek_frame(fFormatCtx, fAudioStreamIndex, SeekTarget, fSeekFlags); 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); + SeekTarget := Round(fSeekPos * 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); + if (SeekTarget < fFormatCtx^.start_time) then + StartSilence := (fFormatCtx^.start_time - SeekTarget) / AV_TIME_BASE; + ErrorCode := av_seek_frame(fFormatCtx, -1, SeekTarget, fSeekFlags); 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); + SDL_mutexP(fStateLock); try if (ErrorCode < 0) then begin // seeking failed - ErrorState := true; - Log.LogStatus('Seek Error in "'+FormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); + fErrorState := true; + Log.LogStatus('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); end else begin - if (SeekFlush) then + if (fSeekFlush) then begin // flush queue (we will send a Flush-Packet when seeking is finished) - PacketQueue.Flush(); + fPacketQueue.Flush(); // flush the decode buffers - AudioBufferSize := 0; - AudioBufferPos := 0; - AudioPaketSize := 0; - AudioPaketSilence := 0; + fAudioBufferSize := 0; + fAudioBufferPos := 0; + fAudioPaketSize := 0; + fAudioPaketSilence := 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; + fAudioStreamPos := fSeekPos; end else begin // request avcodec buffer flush - PacketQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil); + fPacketQueue.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 + if ((StartSilence > 0) and (not fLoop)) then begin GetMem(StartSilencePtr, SizeOf(StartSilence)); StartSilencePtr^ := StartSilence; - PacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr); end; end; - SeekRequest := false; + fSeekRequest := false; SDL_CondBroadcast(SeekFinishedCond); finally - SDL_mutexV(StateLock); + SDL_mutexV(fStateLock); ResumeDecoder(); end; end; - if (PacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then + if (fPacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then begin SDL_Delay(10); Continue; end; - if (av_read_frame(FormatCtx, Packet) < 0) then + if (av_read_frame(fFormatCtx, Packet) < 0) then begin // failed to read a frame, check reason {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} - ByteIOCtx := FormatCtx^.pb; + ByteIOCtx := fFormatCtx^.pb; {$ELSE} - ByteIOCtx := @FormatCtx^.pb; + ByteIOCtx := @fFormatCtx^.pb; {$IFEND} // check for end-of-file (eof is not an error) @@ -799,7 +799,7 @@ begin else begin // signal end-of-file - PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); Exit; end; end; @@ -808,26 +808,26 @@ begin if (url_ferror(ByteIOCtx) <> 0) then begin // an error occured -> abort and wait for repositioning or termination - PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); Exit; end; // url_feof() does not detect an EOF for some files // so we have to do it this way. - if ((FormatCtx^.file_size <> 0) and - (ByteIOCtx^.pos >= FormatCtx^.file_size)) then + if ((fFormatCtx^.file_size <> 0) and + (ByteIOCtx^.pos >= fFormatCtx^.file_size)) then begin - PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); Exit; end; // unknown error occured, exit - PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); Exit; end; - if (Packet.stream_index = AudioStreamIndex) then - PacketQueue.Put(@Packet) + if (Packet.stream_index = fAudioStreamIndex) then + fPacketQueue.Put(@Packet) else av_free_packet(@Packet); @@ -844,28 +844,28 @@ end; procedure TFFmpegDecodeStream.PauseDecoder(); begin - SDL_mutexP(StateLock); - Inc(DecoderPauseRequestCount); - while (DecoderLocked) do - SDL_CondWait(DecoderUnlockedCond, StateLock); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Inc(fDecoderPauseRequestCount); + while (fDecoderLocked) do + SDL_CondWait(fDecoderUnlockedCond, fStateLock); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.ResumeDecoder(); begin - SDL_mutexP(StateLock); - Dec(DecoderPauseRequestCount); - SDL_CondSignal(DecoderResumeCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Dec(fDecoderPauseRequestCount); + SDL_CondSignal(fDecoderResumeCond); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.FlushCodecBuffers(); begin // if no flush operation is specified, avcodec_flush_buffers will not do anything. - if (@CodecCtx.codec.flush <> nil) then + if (@fCodecCtx.codec.flush <> nil) then begin // flush buffers used by avcodec_decode_audio, etc. - avcodec_flush_buffers(CodecCtx); + avcodec_flush_buffers(fCodecCtx); end else begin @@ -874,8 +874,8 @@ begin // We will just reopen the codec. FFmpegCore.LockAVCodec(); try - avcodec_close(CodecCtx); - avcodec_open(CodecCtx, Codec); + avcodec_close(fCodecCtx); + avcodec_open(fCodecCtx, fCodec); finally FFmpegCore.UnlockAVCodec(); end; @@ -901,27 +901,27 @@ begin 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 + if (fAudioPaketSilence > 0) then begin - DataSize := Min(AudioPaketSilence, BufferSize); + DataSize := Min(fAudioPaketSilence, BufferSize); FillChar(Buffer[0], DataSize, 0); - Dec(AudioPaketSilence, DataSize); - AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec; + Dec(fAudioPaketSilence, DataSize); + fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec; Result := DataSize; Exit; end; // read packet data - while (AudioPaketSize > 0) do + while (fAudioPaketSize > 0) do begin DataSize := BufferSize; {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0 - PaketDecodedSize := avcodec_decode_audio2(CodecCtx, PSmallint(Buffer), - DataSize, AudioPaketData, AudioPaketSize); + PaketDecodedSize := avcodec_decode_audio2(fCodecCtx, PSmallint(Buffer), + DataSize, fAudioPaketData, fAudioPaketSize); {$ELSE} - PaketDecodedSize := avcodec_decode_audio(CodecCtx, PSmallint(Buffer), - DataSize, AudioPaketData, AudioPaketSize); + PaketDecodedSize := avcodec_decode_audio(fCodecCtx, PSmallint(Buffer), + DataSize, fAudioPaketData, fAudioPaketSize); {$IFEND} if(PaketDecodedSize < 0) then @@ -930,19 +930,19 @@ begin {$IFDEF DebugFFmpegDecode} DebugWriteln('Skip audio frame'); {$ENDIF} - AudioPaketSize := 0; + fAudioPaketSize := 0; Break; end; - Inc(AudioPaketData, PaketDecodedSize); - Dec(AudioPaketSize, PaketDecodedSize); + Inc(fAudioPaketData, PaketDecodedSize); + Dec(fAudioPaketSize, 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; + fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec; // we have data, return it and come back for more later Result := DataSize; @@ -950,8 +950,8 @@ begin end; // free old packet data - if (AudioPaket.data <> nil) then - av_free_packet(@AudioPaket); + if (fAudioPaket.data <> nil) then + av_free_packet(@fAudioPaket); // do not block queue on seeking (to avoid deadlocks on the DecoderLock) if (IsSeeking()) then @@ -961,17 +961,17 @@ begin // request a new packet and block if none available. // If this fails, the queue was aborted. - if (PacketQueue.Get(AudioPaket, BlockQueue) <= 0) then + if (fPacketQueue.Get(fAudioPaket, BlockQueue) <= 0) then Exit; // handle Status-packet - if (PAnsiChar(AudioPaket.data) = STATUS_PACKET) then + if (PAnsiChar(fAudioPaket.data) = STATUS_PACKET) then begin - AudioPaket.data := nil; - AudioPaketData := nil; - AudioPaketSize := 0; + fAudioPaket.data := nil; + fAudioPaketData := nil; + fAudioPaketSize := 0; - case (AudioPaket.flags) of + case (fAudioPaket.flags) of PKT_STATUS_FLAG_FLUSH: begin // just used if SetPositionIntern was called without the flush flag. @@ -993,9 +993,9 @@ begin end; PKT_STATUS_FLAG_EMPTY: begin - SilenceDuration := PDouble(PacketQueue.GetStatusInfo(AudioPaket))^; - AudioPaketSilence := Round(SilenceDuration * FormatInfo.SampleRate) * FormatInfo.FrameSize; - PacketQueue.FreeStatusInfo(AudioPaket); + SilenceDuration := PDouble(fPacketQueue.GetStatusInfo(fAudioPaket))^; + fAudioPaketSilence := Round(SilenceDuration * fFormatInfo.SampleRate) * fFormatInfo.FrameSize; + fPacketQueue.FreeStatusInfo(fAudioPaket); end else begin @@ -1006,20 +1006,20 @@ begin Continue; end; - AudioPaketData := AudioPaket.data; - AudioPaketSize := AudioPaket.size; + fAudioPaketData := fAudioPaket.data; + fAudioPaketSize := fAudioPaket.size; // if available, update the stream position to the presentation time of this package - if(AudioPaket.pts <> AV_NOPTS_VALUE) then + if(fAudioPaket.pts <> AV_NOPTS_VALUE) then begin {$IFDEF DebugFFmpegDecode} - TmpPos := AudioStreamPos; + TmpPos := fAudioStreamPos; {$ENDIF} - AudioStreamPos := av_q2d(AudioStream^.time_base) * AudioPaket.pts; + fAudioStreamPos := av_q2d(fAudioStream^.time_base) * fAudioPaket.pts; {$IFDEF DebugFFmpegDecode} - DebugWriteln('Timestamp: ' + floattostrf(AudioStreamPos, ffFixed, 15, 3) + ' ' + + DebugWriteln('Timestamp: ' + floattostrf(fAudioStreamPos, ffFixed, 15, 3) + ' ' + '(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' + - 'Diff: ' + floattostrf(AudioStreamPos-TmpPos, ffFixed, 15, 3)); + 'Diff: ' + floattostrf(fAudioStreamPos-TmpPos, ffFixed, 15, 3)); {$ENDIF} end; end; @@ -1034,19 +1034,19 @@ var // prioritize pause requests procedure LockDecoder(); begin - SDL_mutexP(StateLock); - while (DecoderPauseRequestCount > 0) do - SDL_CondWait(DecoderResumeCond, StateLock); - DecoderLocked := true; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + while (fDecoderPauseRequestCount > 0) do + SDL_CondWait(fDecoderResumeCond, fStateLock); + fDecoderLocked := true; + SDL_mutexV(fStateLock); end; procedure UnlockDecoder(); begin - SDL_mutexP(StateLock); - DecoderLocked := false; - SDL_CondBroadcast(DecoderUnlockedCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fDecoderLocked := false; + SDL_CondBroadcast(fDecoderUnlockedCond); + SDL_mutexV(fStateLock); end; begin @@ -1065,15 +1065,15 @@ begin while (BufferPos < BufferSize) do begin // check if we need more data - if (AudioBufferPos >= AudioBufferSize) then + if (fAudioBufferPos >= fAudioBufferSize) then begin - AudioBufferPos := 0; + fAudioBufferPos := 0; // we have already sent all our data; get more - AudioBufferSize := DecodeFrame(AudioBuffer, AUDIO_BUFFER_SIZE); + fAudioBufferSize := DecodeFrame(fAudioBuffer, AUDIO_BUFFER_SIZE); // check for errors or EOF - if(AudioBufferSize < 0) then + if(fAudioBufferSize < 0) then begin Result := BufferPos; Exit; @@ -1081,16 +1081,16 @@ begin end; // calc number of new bytes in the decode-buffer - CopyByteCount := AudioBufferSize - AudioBufferPos; + CopyByteCount := fAudioBufferSize - fAudioBufferPos; // 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); + Move(fAudioBuffer[fAudioBufferPos], Buffer[BufferPos], CopyByteCount); Inc(BufferPos, CopyByteCount); - Inc(AudioBufferPos, CopyByteCount); + Inc(fAudioBufferPos, CopyByteCount); end; finally UnlockDecoder(); -- cgit v1.2.3 From 9c6324e2165b68654db070e275a69cda0459516d Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 5 Jun 2010 09:09:31 +0000 Subject: compare FFmpeg header and DLL versions and log an error if both do not match git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2442 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMediaCore_FFmpeg.pas | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'src/media') diff --git a/src/media/UMediaCore_FFmpeg.pas b/src/media/UMediaCore_FFmpeg.pas index 2d572ff2..eb136995 100644 --- a/src/media/UMediaCore_FFmpeg.pas +++ b/src/media/UMediaCore_FFmpeg.pas @@ -41,6 +41,7 @@ uses avformat, avutil, avio, + swscale, UMusic, ULog, UPath; @@ -121,9 +122,83 @@ const var Instance: TMediaCore_FFmpeg; +function AV_VERSION_INT(a, b, c: cardinal): cuint; +begin + Result := (a shl 16) or (b shl 8) or c; +end; + +procedure CheckVersions(); +var + libVersion: cuint; + headerVersion: cuint; + + function hexVerToStr(Version: cuint): string; + var + Major, Minor, Release: cardinal; + begin + Major := (Version shr 16) and $FF;; + Minor := (Version shr 8) and $FF; + Release := Version and $FF; + Result := Format('%d.%d.%d', [Major, Minor, Release]); + end; + +begin + libVersion := avcodec_version(); + headerVersion := AV_VERSION_INT( + LIBAVCODEC_VERSION_MAJOR, + LIBAVCODEC_VERSION_MINOR, + LIBAVCODEC_VERSION_RELEASE); + if (libVersion <> headerVersion) then + begin + Log.LogError(Format('%s header (%s) and DLL (%s) versions do not match.', + ['libavcodec', hexVerToStr(headerVersion), hexVerToStr(libVersion)])); + end; + + {$IF LIBAVFORMAT_VERSION >= 52020000} // 52.20.0 + libVersion := avformat_version(); + headerVersion := AV_VERSION_INT( + LIBAVFORMAT_VERSION_MAJOR, + LIBAVFORMAT_VERSION_MINOR, + LIBAVFORMAT_VERSION_RELEASE); + if (libVersion <> headerVersion) then + begin + Log.LogError(Format('%s header (%s) and DLL (%s) versions do not match.', + ['libavformat', hexVerToStr(headerVersion), hexVerToStr(libVersion)])); + end; + {$IFEND} + + {$IF LIBAVUTIL_VERSION >= 49008000} // 49.8.0 + libVersion := avutil_version(); + headerVersion := AV_VERSION_INT( + LIBAVUTIL_VERSION_MAJOR, + LIBAVUTIL_VERSION_MINOR, + LIBAVUTIL_VERSION_RELEASE); + if (libVersion <> headerVersion) then + begin + Log.LogError(Format('%s header (%s) and DLL (%s) versions do not match.', + ['libavutil', hexVerToStr(headerVersion), hexVerToStr(libVersion)])); + end; + {$IFEND} + + {$IF LIBSWSCALE_VERSION >= 000006001} // 0.6.1 + libVersion := swscale_version(); + headerVersion := AV_VERSION_INT( + LIBSWSCALE_VERSION_MAJOR, + LIBSWSCALE_VERSION_MINOR, + LIBSWSCALE_VERSION_RELEASE); + if (libVersion <> headerVersion) then + begin + Log.LogError(Format('%s header (%s) and DLL (%s) versions do not match.', + ['libswscale', hexVerToStr(headerVersion), hexVerToStr(libVersion)])); + end; + {$IFEND} +end; + constructor TMediaCore_FFmpeg.Create(); begin inherited; + + CheckVersions(); av_register_protocol(@UTF8FileProtocol); AVCodecLock := SDL_CreateMutex(); end; -- cgit v1.2.3 From d35ae3cd792d22766f56c8210781148c36c9ae62 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 6 Jun 2010 08:23:22 +0000 Subject: Do not overwrite a devices MicSource if it is already set (Note: MicSource is not used at the moment) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2447 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioInput_Bass.pas | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UAudioInput_Bass.pas b/src/media/UAudioInput_Bass.pas index b8f914c5..0e79b343 100644 --- a/src/media/UAudioInput_Bass.pas +++ b/src/media/UAudioInput_Bass.pas @@ -471,9 +471,12 @@ begin 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 + // chech if current source is a mic (and none was set before) + if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) and + (BassDevice.MicSource = -1) then + begin BassDevice.MicSource := SourceIndex; + end; end; Inc(SourceIndex); -- cgit v1.2.3 From f261cd24db299daee8a0d8e470ff7d173f1a1c75 Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Thu, 10 Jun 2010 18:27:53 +0000 Subject: merge of VideoPreview branch into trunk git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2475 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UMedia_dummy.pas | 126 +++++++++++- src/media/UVideo.pas | 467 ++++++++++++++++++++++++++++++++++++++------- src/media/UVisualizer.pas | 129 ++++++++++++- 3 files changed, 642 insertions(+), 80 deletions(-) (limited to 'src/media') diff --git a/src/media/UMedia_dummy.pas b/src/media/UMedia_dummy.pas index 8ebfd3a9..46cbe6b8 100644 --- a/src/media/UMedia_dummy.pas +++ b/src/media/UMedia_dummy.pas @@ -112,9 +112,43 @@ type procedure SetPosition(Time: real); function GetPosition: real; - procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); + procedure SetScreen(Screen: integer); + function GetScreen(): integer; + + procedure SetScreenPosition(X, Y, Z: double); + procedure GetScreenPosition(var X, Y, Z: double); + + procedure SetWidth(Width: double); + function GetWidth(): double; + + procedure SetHeight(Height: double); + function GetHeight(): double; + + procedure SetFrameRange(Range: TRectCoords); + function GetFrameRange(): TRectCoords; + + function GetFrameAspect(): real; + + procedure SetAspectCorrection(AspectCorrection: TAspectCorrection); + function GetAspectCorrection(): TAspectCorrection; + + procedure SetAlpha(Alpha: double); + function GetAlpha(): double; + + procedure SetReflectionSpacing(Spacing: double); + function GetReflectionSpacing(): double; + procedure GetFrame(Time: Extended); + procedure Draw(); + procedure DrawReflection(); + + property Screen: integer read GetScreen; + property Width: double read GetWidth write SetWidth; + property Height: double read GetHeight write SetWidth; + property Alpha: double read GetAlpha write SetAlpha; + property ReflectionSpacing: double read GetReflectionSpacing write SetReflectionSpacing; + property FrameAspect: real read GetFrameAspect; + property AspectCorrection: TAspectCorrection read GetAspectCorrection; property Loop: boolean read GetLoop write SetLoop; property Position: real read GetPosition write SetPosition; end; @@ -329,11 +363,97 @@ begin Result := 0; end; +procedure TVideo_Dummy.SetScreen(Screen: integer); +begin +end; + +function TVideo_Dummy.GetScreen(): integer; +begin + Result := 0; +end; + +procedure TVideo_Dummy.SetScreenPosition(X, Y, Z: double); +begin +end; + +procedure TVideo_Dummy.GetScreenPosition(var X, Y, Z: double); +begin + X := 0; + Y := 0; + Z := 0; +end; + +procedure TVideo_Dummy.SetWidth(Width: double); +begin +end; + +function TVideo_Dummy.GetWidth(): double; +begin + Result := 0; +end; + +procedure TVideo_Dummy.SetHeight(Height: double); +begin +end; + +function TVideo_Dummy.GetHeight(): double; +begin + Result := 0; +end; + +procedure TVideo_Dummy.SetFrameRange(Range: TRectCoords); +begin +end; + +function TVideo_Dummy.GetFrameRange(): TRectCoords; +begin + Result.Left := 0; + Result.Right := 0; + Result.Upper := 0; + Result.Lower := 0; +end; + +function TVideo_Dummy.GetFrameAspect(): real; +begin + Result := 0; +end; + +procedure TVideo_Dummy.SetAspectCorrection(AspectCorrection: TAspectCorrection); +begin +end; + +function TVideo_Dummy.GetAspectCorrection(): TAspectCorrection; +begin + Result := acoStretch; +end; + +procedure TVideo_Dummy.SetAlpha(Alpha: double); +begin +end; + +function TVideo_Dummy.GetAlpha(): double; +begin + Result := 0; +end; + +procedure TVideo_Dummy.SetReflectionSpacing(Spacing: double); +begin +end; + +function TVideo_Dummy.GetReflectionSpacing(): double; +begin + Result := 0; +end; + procedure TVideo_Dummy.GetFrame(Time: Extended); begin end; -procedure TVideo_Dummy.DrawGL(Screen: integer); +procedure TVideo_Dummy.Draw(); +begin +end; + +procedure TVideo_Dummy.DrawReflection(); begin end; diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index c7d59fc8..e8cfbbf7 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -48,24 +48,6 @@ interface {$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 @@ -112,12 +94,9 @@ const PIXEL_FMT_SIZE = 3; {$ENDIF} -type - TRectCoords = record - Left, Right: double; - Upper, Lower: double; - end; + ReflectionH = 0.5; //reflection height (50%) +type IVideo_FFmpeg = interface (IVideo) ['{E640E130-C8C0-4399-AF02-67A3569313AB}'] function Open(const FileName: IPath): boolean; @@ -149,6 +128,20 @@ type fSwScaleContext: PSwsContext; {$ENDIF} + fScreen: integer; //actual screen to draw on + + fPosX: double; + fPosY: double; + fPosZ: double; + fWidth: double; + fHeight: double; + + fFrameRange: TRectCoords; + + fAlpha: double; + fReflectionSpacing: double; + + fAspect: real; //**< width/height ratio fAspectCorrection: TAspectCorrection; @@ -163,6 +156,8 @@ type procedure SynchronizeTime(Frame: PAVFrame; var pts: double); procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords); + procedure DrawBorders(ScreenRect: TRectCoords); + procedure DrawBordersReflected(ScreenRect: TRectCoords; AlphaUpper, AlphaLower: double); procedure ShowDebugInfo(); @@ -183,8 +178,39 @@ type procedure SetPosition(Time: real); function GetPosition: real; - procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); + procedure SetScreen(Screen: integer); + function GetScreen(): integer; + + procedure SetScreenPosition(X, Y, Z: double); + procedure GetScreenPosition(var X, Y, Z: double); + + procedure SetWidth(Width: double); + function GetWidth(): double; + + procedure SetHeight(Height: double); + function GetHeight(): double; + + {** + * Sub-image of the video frame to draw. + * This can be used for zooming or similar purposes. + *} + procedure SetFrameRange(Range: TRectCoords); + function GetFrameRange(): TRectCoords; + + function GetFrameAspect(): real; + + procedure SetAspectCorrection(AspectCorrection: TAspectCorrection); + function GetAspectCorrection(): TAspectCorrection; + + procedure SetAlpha(Alpha: double); + function GetAlpha(): double; + + procedure SetReflectionSpacing(Spacing: double); + function GetReflectionSpacing(): double; + + procedure GetFrame(Time: Extended); + procedure Draw(); + procedure DrawReflection(); end; TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback ) @@ -498,6 +524,22 @@ begin fPboId := 0; fAspectCorrection := acoCrop; + + fScreen := 1; + + fPosX := 0; + fPosY := 0; + fPosZ := 0; + fWidth := RenderW; + fHeight := RenderH; + + fFrameRange.Left := 0; + fFrameRange.Right := 1; + fFrameRange.Upper := 0; + fFrameRange.Lower := 1; + + fAlpha := 1; + fReflectionSpacing := 0; end; procedure TVideo_FFmpeg.Close; @@ -889,72 +931,142 @@ procedure TVideo_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) + // 2. Render aspect (fWidth x fHeight -> variable) // 3. Movie aspect (video frame aspect stored in fAspect) - ScreenAspect := ScreenW / ScreenH; + ScreenAspect := fWidth*((ScreenW/Screens)/RenderW)/(fHeight*(ScreenH/RenderH)); case fAspectCorrection of acoStretch: begin - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH; + ScaledVideoWidth := fWidth; + ScaledVideoHeight := fHeight; end; + acoCrop: begin if (ScreenAspect >= fAspect) then begin - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH * ScreenAspect/fAspect; - end - else + ScaledVideoWidth := fWidth; + ScaledVideoHeight := fHeight * ScreenAspect/fAspect; + end else begin - ScaledVideoHeight := RenderH; - ScaledVideoWidth := RenderW * fAspect/ScreenAspect; + ScaledVideoHeight := fHeight; + ScaledVideoWidth := fWidth * fAspect/ScreenAspect; end; end; + acoLetterBox: begin - ScaledVideoWidth := RenderW; - ScaledVideoHeight := RenderH * ScreenAspect/fAspect; - end - else + if (ScreenAspect <= fAspect) then + begin + ScaledVideoWidth := fWidth; + ScaledVideoHeight := fHeight * ScreenAspect/fAspect; + end else + begin + ScaledVideoHeight := fHeight; + ScaledVideoWidth := fWidth * fAspect/ScreenAspect; + end; + end else raise Exception.Create('Unhandled aspect correction!'); end; - // center video - ScreenRect.Left := (RenderW - ScaledVideoWidth) / 2; + //center video + ScreenRect.Left := (fWidth - ScaledVideoWidth) / 2 + fPosX; ScreenRect.Right := ScreenRect.Left + ScaledVideoWidth; - ScreenRect.Upper := (RenderH - ScaledVideoHeight) / 2; + ScreenRect.Upper := (fHeight - ScaledVideoHeight) / 2 + fPosY; 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; + TexRect.Left := (fCodecContext^.width / fTexWidth) * fFrameRange.Left; + TexRect.Right := (fCodecContext^.width / fTexWidth) * fFrameRange.Right; + TexRect.Upper := (fCodecContext^.height / fTexHeight) * fFrameRange.Upper; + TexRect.Lower := (fCodecContext^.height / fTexHeight) * fFrameRange.Lower; end; -procedure TVideo_FFmpeg.DrawGL(Screen: integer); -var - ScreenRect: TRectCoords; - TexRect: TRectCoords; +procedure TVideo_FFmpeg.DrawBorders(ScreenRect: TRectCoords); + procedure DrawRect(left, right, upper, lower: double); + begin + glColor4f(0, 0, 0, fAlpha); + glBegin(GL_QUADS); + glVertex3f(left, upper, fPosZ); + glVertex3f(right, upper, fPosZ); + glVertex3f(right, lower, fPosZ); + glVertex3f(left, lower, fPosZ); + glEnd; + end; 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 + //upper border + if(ScreenRect.Upper > fPosY) then + DrawRect(fPosX, fPosX+fWidth, fPosY, ScreenRect.Upper); + + //lower border + if(ScreenRect.Lower < fPosY+fHeight) then + DrawRect(fPosX, fPosX+fWidth, ScreenRect.Lower, fPosY+fHeight); + + //left border + if(ScreenRect.Left > fPosX) then + DrawRect(fPosX, ScreenRect.Left, fPosY, fPosY+fHeight); + + //right border + if(ScreenRect.Right < fPosX+fWidth) then + DrawRect(ScreenRect.Right, fPosX+fWidth, fPosY, fPosY+fHeight); +end; + +procedure TVideo_FFmpeg.DrawBordersReflected(ScreenRect: TRectCoords; AlphaUpper, AlphaLower: double); +var + rPosUpper, rPosLower: double; + + procedure DrawRect(left, right, upper, lower: double); + var + AlphaTop: double; + AlphaBottom: double; + 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); + AlphaTop := AlphaUpper+(AlphaLower-AlphaUpper)*(upper-rPosUpper)/(fHeight*ReflectionH); + AlphaBottom := AlphaLower+(AlphaUpper-AlphaLower)*(rPosLower-lower)/(fHeight*ReflectionH); + + glBegin(GL_QUADS); + glColor4f(0, 0, 0, AlphaTop); + glVertex3f(left, upper, fPosZ); + glVertex3f(right, upper, fPosZ); + + glColor4f(0, 0, 0, AlphaBottom); + glVertex3f(right, lower, fPosZ); + glVertex3f(left, lower, fPosZ); + glEnd; end; +begin + rPosUpper := fPosY+fHeight+fReflectionSpacing; + rPosLower := rPosUpper+fHeight*ReflectionH; + + //upper border + if(ScreenRect.Upper > rPosUpper) then + DrawRect(fPosX, fPosX+fWidth, rPosUpper, ScreenRect.Upper); + + //lower border + if(ScreenRect.Lower < rPosLower) then + DrawRect(fPosX, fPosX+fWidth, ScreenRect.Lower, rPosLower); + + //left border + if(ScreenRect.Left > fPosX) then + DrawRect(fPosX, ScreenRect.Left, rPosUpper, rPosLower); + //right border + if(ScreenRect.Right < fPosX+fWidth) then + DrawRect(ScreenRect.Right, fPosX+fWidth, rPosUpper, rPosLower); +end; + + +procedure TVideo_FFmpeg.Draw(); +var + ScreenRect: TRectCoords; + TexRect: TRectCoords; + HeightFactor: double; + WidthFactor: double; + +begin // exit if there's nothing to draw if (not fOpened) then Exit; @@ -966,31 +1078,53 @@ begin // get texture and screen positions GetVideoRect(ScreenRect, TexRect); - // we could use blending for brightness control, but do we need this? - glDisable(GL_BLEND); + WidthFactor := (ScreenW/Screens) / RenderW; + HeightFactor := ScreenH / RenderH; + + glScissor( + round(fPosX*WidthFactor + HeightFactor*(fScreen-1)), + round((RenderH-fPosY-fHeight)*HeightFactor), + round(fWidth*WidthFactor), + round(fHeight*HeightFactor) + ); + + glEnable(GL_SCISSOR_TEST); + glEnable(GL_BLEND); + glDepthRange(0, 10); + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, fFrameTex); - glColor3f(1, 1, 1); + glColor4f(1, 1, 1, fAlpha); glBegin(GL_QUADS); // upper-left coord glTexCoord2f(TexRect.Left, TexRect.Upper); - glVertex2f(ScreenRect.Left, ScreenRect.Upper); + glVertex3f(ScreenRect.Left, ScreenRect.Upper, fPosZ); // lower-left coord glTexCoord2f(TexRect.Left, TexRect.Lower); - glVertex2f(ScreenRect.Left, ScreenRect.Lower); + glVertex3f(ScreenRect.Left, ScreenRect.Lower, fPosZ); // lower-right coord glTexCoord2f(TexRect.Right, TexRect.Lower); - glVertex2f(ScreenRect.Right, ScreenRect.Lower); + glVertex3f(ScreenRect.Right, ScreenRect.Lower, fPosZ); // upper-right coord glTexCoord2f(TexRect.Right, TexRect.Upper); - glVertex2f(ScreenRect.Right, ScreenRect.Upper); + glVertex3f(ScreenRect.Right, ScreenRect.Upper, fPosZ); glEnd; + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + //draw black borders + DrawBorders(ScreenRect); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); {$IFDEF VideoBenchmark} Log.BenchmarkEnd(15); - Log.LogBenchmark('DrawGL', 15); + Log.LogBenchmark('Draw', 15); {$ENDIF} {$IF Defined(Info) or Defined(DebugFrames)} @@ -998,6 +1132,94 @@ begin {$IFEND} end; +procedure TVideo_FFmpeg.DrawReflection(); +var + ScreenRect: TRectCoords; + TexRect: TRectCoords; + HeightFactor: double; + WidthFactor: double; + + AlphaTop: double; + AlphaBottom: double; + + AlphaUpper: double; + AlphaLower: double; + +begin + // exit if there's nothing to draw + if (not fOpened) then + Exit; + + // get texture and screen positions + GetVideoRect(ScreenRect, TexRect); + + WidthFactor := (ScreenW/Screens) / RenderW; + HeightFactor := ScreenH / RenderH; + + glScissor( + round(fPosX*WidthFactor + HeightFactor*(fScreen-1)), + round((RenderH-fPosY-fHeight-fReflectionSpacing-fHeight*ReflectionH)*HeightFactor), + round(fWidth*WidthFactor), + round(fHeight*HeightFactor*ReflectionH) + ); + + glEnable(GL_SCISSOR_TEST); + glEnable(GL_BLEND); + glDepthRange(0, 10); + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, fFrameTex); + + //calculate new ScreenRect coordinates for Reflection + ScreenRect.Lower := fPosY + fHeight + fReflectionSpacing + + (ScreenRect.Upper-fPosY) + (ScreenRect.Lower-ScreenRect.Upper)*ReflectionH; + ScreenRect.Upper := fPosY + fHeight + fReflectionSpacing + + (ScreenRect.Upper-fPosY); + + AlphaUpper := fAlpha-0.3; + AlphaLower := 0; + + AlphaTop := AlphaUpper-(AlphaLower-AlphaUpper)* + (ScreenRect.Upper-fPosY-fHeight-fReflectionSpacing)/fHeight; + AlphaBottom := AlphaLower+(AlphaUpper-AlphaLower)* + (fPosY+fHeight+fReflectionSpacing+fHeight*ReflectionH-ScreenRect.Lower)/fHeight; + + glBegin(GL_QUADS); + //Top Left + glColor4f(1, 1, 1, AlphaTop); + glTexCoord2f(TexRect.Left, TexRect.Lower); + glVertex3f(ScreenRect.Left, ScreenRect.Upper, fPosZ); + + //Bottom Left + glColor4f(1, 1, 1, AlphaBottom); + glTexCoord2f(TexRect.Left, (TexRect.Lower-TexRect.Upper)*(1-ReflectionH)); + glVertex3f(ScreenRect.Left, ScreenRect.Lower, fPosZ); + + //Bottom Right + glColor4f(1, 1, 1, AlphaBottom); + glTexCoord2f(TexRect.Right, (TexRect.Lower-TexRect.Upper)*(1-ReflectionH)); + glVertex3f(ScreenRect.Right, ScreenRect.Lower, fPosZ); + + //Top Right + glColor4f(1, 1, 1, AlphaTop); + glTexCoord2f(TexRect.Right, TexRect.Lower); + glVertex3f(ScreenRect.Right, ScreenRect.Upper, fPosZ); + glEnd; + + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + //draw black borders + DrawBordersReflected(ScreenRect, AlphaUpper, AlphaLower); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); +end; + procedure TVideo_FFmpeg.ShowDebugInfo(); begin {$IFDEF Info} @@ -1107,6 +1329,111 @@ begin Result := fFrameTime; end; +procedure TVideo_FFmpeg.SetScreen(Screen: integer); +begin + fScreen := Screen; +end; + +function TVideo_FFmpeg.GetScreen(): integer; +begin + Result := fScreen; +end; + + +procedure TVideo_FFmpeg.SetScreenPosition(X, Y, Z: double); +begin + fPosX := X; + fPosY := Y; + fPosZ := Z; +end; + +procedure TVideo_FFmpeg.GetScreenPosition(var X, Y, Z: double); +begin + X := fPosX; + Y := fPosY; + Z := fPosZ; +end; + + +procedure TVideo_FFmpeg.SetWidth(Width: double); +begin + fWidth := Width; +end; + +function TVideo_FFmpeg.GetWidth(): double; +begin + Result := fWidth; +end; + + +procedure TVideo_FFmpeg.SetHeight(Height: double); +begin + fHeight := Height; +end; + +function TVideo_FFmpeg.GetHeight(): double; +begin + Result := fHeight; +end; + + +procedure TVideo_FFmpeg.SetFrameRange(Range: TRectCoords); +begin + fFrameRange := Range; +end; + +function TVideo_FFmpeg.GetFrameRange(): TRectCoords; +begin + Result := fFrameRange; +end; + + +function TVideo_FFmpeg.GetFrameAspect(): real; +begin + Result := fAspect; +end; + + +procedure TVideo_FFmpeg.SetAspectCorrection(AspectCorrection: TAspectCorrection); +begin + fAspectCorrection := AspectCorrection; +end; + +function TVideo_FFmpeg.GetAspectCorrection(): TAspectCorrection; +begin + Result := fAspectCorrection; +end; + + + +procedure TVideo_FFmpeg.SetAlpha(Alpha: double); +begin + fAlpha := Alpha; + + if (fAlpha>1) then + fAlpha := 1; + + if (fAlpha<0) then + fAlpha := 0; +end; + +function TVideo_FFmpeg.GetAlpha(): double; +begin + Result := fAlpha; +end; + + +procedure TVideo_FFmpeg.SetReflectionSpacing(Spacing: double); +begin + fReflectionSpacing := Spacing; +end; + +function TVideo_FFmpeg.GetReflectionSpacing(): double; +begin + Result := fReflectionSpacing; +end; + + initialization MediaManager.Add(TVideoPlayback_FFmpeg.Create); diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas index 4f553521..1cdc3500 100644 --- a/src/media/UVisualizer.pas +++ b/src/media/UVisualizer.pas @@ -110,6 +110,8 @@ type fState: TProjectMState; + fScreen: integer; + fVisualTex: GLuint; fPCMData: TPCMData; fRndPCMcount: integer; @@ -144,8 +146,35 @@ type procedure SetLoop(Enable: boolean); function GetLoop(): boolean; + procedure SetScreen(Screen: integer); + function GetScreen(): integer; + + procedure SetScreenPosition(X, Y, Z: double); + procedure GetScreenPosition(var X, Y, Z: double); + + procedure SetWidth(Width: double); + function GetWidth(): double; + + procedure SetHeight(Height: double); + function GetHeight(): double; + + procedure SetFrameRange(Range: TRectCoords); + function GetFrameRange(): TRectCoords; + + function GetFrameAspect(): real; + + procedure SetAspectCorrection(AspectCorrection: TAspectCorrection); + function GetAspectCorrection(): TAspectCorrection; + + procedure SetAlpha(Alpha: double); + function GetAlpha(): double; + + procedure SetReflectionSpacing(Spacing: double); + function GetReflectionSpacing(): double; + procedure GetFrame(Time: Extended); - procedure DrawGL(Screen: integer); + procedure Draw(); + procedure DrawReflection(); end; TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoVisualization ) @@ -262,6 +291,88 @@ begin Result := true; end; +procedure TVideo_ProjectM.SetScreen(Screen: integer); +begin +end; + +function TVideo_ProjectM.GetScreen(): integer; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetScreenPosition(X, Y, Z: double); +begin +end; + +procedure TVideo_ProjectM.GetScreenPosition(var X, Y, Z: double); +begin + X := 0; + Y := 0; + Z := 0; +end; + +procedure TVideo_ProjectM.SetWidth(Width: double); +begin +end; + +function TVideo_ProjectM.GetWidth(): double; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetHeight(Height: double); +begin +end; + +function TVideo_ProjectM.GetHeight(): double; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetFrameRange(Range: TRectCoords); +begin +end; + +function TVideo_ProjectM.GetFrameRange(): TRectCoords; +begin + Result.Left := 0; + Result.Right := 0; + Result.Upper := 0; + Result.Lower := 0; +end; + +function TVideo_ProjectM.GetFrameAspect(): real; +begin + Result := 0; +end; + +procedure TVideo_ProjectM.SetAspectCorrection(AspectCorrection: TAspectCorrection); +begin +end; + +function TVideo_ProjectM.GetAspectCorrection(): TAspectCorrection; +begin + Result := acoStretch; +end; + +procedure TVideo_ProjectM.SetAlpha(Alpha: double); +begin +end; + +function TVideo_ProjectM.GetAlpha(): double; +begin + Result := 1; +end; + +procedure TVideo_ProjectM.SetReflectionSpacing(Spacing: double); +begin +end; + +function TVideo_ProjectM.GetReflectionSpacing(): double; +begin + Result := 0; +end; + {** * Returns the stack depth of the given OpenGL matrix mode stack. *} @@ -485,11 +596,11 @@ end; * Draws the current frame to screen. * TODO: this is not used yet. Data is directly drawn on GetFrame(). *} -procedure TVideo_ProjectM.DrawGL(Screen: integer); +procedure TVideo_ProjectM.Draw(); begin {$IFDEF UseTexture} // have a nice black background to draw on - if (Screen = 1) then + if (fScreen = 1) then begin glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); @@ -521,10 +632,10 @@ begin // 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); + glTexCoord2f(0, 0); glVertex2f((fScreen - 1), 0); + glTexCoord2f(1, 0); glVertex2f(fScreen, 0); + glTexCoord2f(1, 1); glVertex2f(fScreen, 1); + glTexCoord2f(0, 1); glVertex2f((fScreen - 1), 1); glEnd(); glDisable(GL_TEXTURE_2D); @@ -538,6 +649,10 @@ begin {$ENDIF} end; +procedure TVideo_ProjectM.DrawReflection(); +begin +end; + {** * Produces random "sound"-data in case no audio-data is available. * Otherwise the visualization will look rather boring. -- cgit v1.2.3 From 0643ec049ea85731c8f1409f3d40d70131147e5d Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 12 Jun 2010 13:27:28 +0000 Subject: fix seeking in videos git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2500 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index e8cfbbf7..15493881 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -1315,7 +1315,10 @@ begin fEOF := false; fFrameTexValid := false; - if (av_seek_frame(fFormatContext, fStreamIndex, Floor(Time/fTimeBase), SeekFlags) < 0) then + if (av_seek_frame(fFormatContext, + fStreamIndex, + Round(Time / av_q2d(fStream^.time_base)), + SeekFlags) < 0) then begin Log.LogError('av_seek_frame() failed', 'TVideoPlayback_ffmpeg.SetPosition'); Exit; -- cgit v1.2.3 From 0685b966f6b8164c9aa02a24be602a08c34a596b Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 12 Jun 2010 13:44:38 +0000 Subject: fTimeBase is NOT the time-base of the FFmpeg stream -> renamed to fFrameDuration to avoid wrong usage git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2501 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 49 +++++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 15493881..85655e8e 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -145,8 +145,8 @@ type fAspect: real; //**< width/height ratio fAspectCorrection: TAspectCorrection; - fTimeBase: extended; //**< FFmpeg time base per time unit - fFrameTime: extended; //**< video time position (absolute) + fFrameDuration: extended; //**< duration of a video frame in seconds (= 1/fps) + fFrameTime: extended; //**< video time position (absolute) fLoopTime: extended; //**< start time of the current loop fPboEnabled: boolean; @@ -432,19 +432,18 @@ begin fAspect := fAspect * fCodecContext^.width / fCodecContext^.height; - fTimeBase := 1/av_q2d(fStream^.r_frame_rate); + fFrameDuration := 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 + // hack to get reasonable framerate (for divx and others) + if (fFrameDuration < 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; + fFrameDuration := av_q2d(fStream^.r_frame_rate); + while (fFrameDuration > 50) do + fFrameDuration := fFrameDuration/10; + fFrameDuration := 1/fFrameDuration; end; - Log.LogInfo('VideoTimeBase: ' + floattostr(fTimeBase), 'TVideoPlayback_ffmpeg.Open'); - Log.LogInfo('Framerate: '+inttostr(floor(1/fTimeBase))+'fps', 'TVideoPlayback_ffmpeg.Open'); + Log.LogInfo('Framerate: '+inttostr(floor(1/fFrameDuration))+'fps', 'TVideoPlayback_ffmpeg.Open'); {$IFDEF UseSWScale} // if available get a SWScale-context -> faster than the deprecated img_convert(). @@ -510,7 +509,7 @@ begin fOpened := False; fPaused := False; - fTimeBase := 0; + fFrameDuration := 0; fFrameTime := 0; fStream := nil; fStreamIndex := -1; @@ -727,12 +726,6 @@ 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 *} @@ -752,12 +745,12 @@ begin {$IFDEF DebugDisplay} DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak + 'VideoTime: '+inttostr(floor(fFrameTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} // check if time has reached the next frame - if (TimeDiff < fTimeBase) then + if (TimeDiff < fFrameDuration) then begin {$ifdef DebugFrames} // frame delay debug display @@ -768,7 +761,7 @@ begin DebugWriteln('not getting new frame' + sLineBreak + 'Time: '+inttostr(floor(Time*1000)) + sLineBreak + 'VideoTime: '+inttostr(floor(fFrameTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} @@ -787,9 +780,9 @@ begin // check if we have to skip frames // 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 + // Do not skip if the difference is less than fFrameDuration as there is no next frame. + // Note: We assume that fFrameDuration is the length of one frame. + if (TimeDiff >= Max(fFrameDuration, SKIP_FRAME_DIFF)) then begin {$IFDEF DebugFrames} //frame drop debug display @@ -797,13 +790,13 @@ begin {$ENDIF} {$IFDEF DebugDisplay} DebugWriteln('skipping frames' + sLineBreak + - 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak + + 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + 'TimeDiff: '+inttostr(floor(TimeDifference*1000))); {$endif} // update video-time - DropFrameCount := Trunc(TimeDiff / fTimeBase); - fFrameTime := fFrameTime + DropFrameCount*fTimeBase; + DropFrameCount := Trunc(TimeDiff / fFrameDuration); + fFrameTime := fFrameTime + DropFrameCount*fFrameDuration; // skip frames for i := 1 to DropFrameCount do @@ -1223,7 +1216,7 @@ end; procedure TVideo_FFmpeg.ShowDebugInfo(); begin {$IFDEF Info} - if (fFrameTime+fTimeBase < 0) then + if (fFrameTime+fFrameDuration < 0) then begin glColor4f(0.7, 1, 0.3, 1); SetFontStyle (1); -- cgit v1.2.3 From fd0388550ddbe2b817a427b6ee08be9157fff287 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 13 Jun 2010 09:42:50 +0000 Subject: Fix for invalid song lengths git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2511 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 2badb84d..c64d79c2 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -728,7 +728,7 @@ begin begin // seeking failed fErrorState := true; - Log.LogStatus('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); + Log.LogError('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); end else begin -- cgit v1.2.3 From 0017ec10aa9ba57797a4b3e963ccb5d2ce376da2 Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Tue, 15 Jun 2010 04:30:20 +0000 Subject: fixed x-position of glScissor test in UVideo.Draw and UVideo.DrawReflection (video was visible on the first screen only) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2527 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UVideo.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/media') diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas index 85655e8e..add7bdc8 100644 --- a/src/media/UVideo.pas +++ b/src/media/UVideo.pas @@ -1075,7 +1075,7 @@ begin HeightFactor := ScreenH / RenderH; glScissor( - round(fPosX*WidthFactor + HeightFactor*(fScreen-1)), + round(fPosX*WidthFactor + (ScreenW/Screens)*(fScreen-1)), round((RenderH-fPosY-fHeight)*HeightFactor), round(fWidth*WidthFactor), round(fHeight*HeightFactor) @@ -1150,7 +1150,7 @@ begin HeightFactor := ScreenH / RenderH; glScissor( - round(fPosX*WidthFactor + HeightFactor*(fScreen-1)), + round(fPosX*WidthFactor + (ScreenW/Screens)*(fScreen-1)), round((RenderH-fPosY-fHeight-fReflectionSpacing-fHeight*ReflectionH)*HeightFactor), round(fWidth*WidthFactor), round(fHeight*HeightFactor*ReflectionH) -- cgit v1.2.3 From 008c58c6ac1cdbe0fdb01ce79567b7f0e815b83a Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 19 Jun 2010 16:36:25 +0000 Subject: Bugfix: do not call Inc() on PByteArray pointers. It will not increment by one byte, but by SizeOf(ByteArray) bytes which is some KB. As a result the pointer will point to an illegal memory address. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2549 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index c64d79c2..b44c7b11 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -934,7 +934,7 @@ begin Break; end; - Inc(fAudioPaketData, PaketDecodedSize); + Inc(PByte(fAudioPaketData), PaketDecodedSize); Dec(fAudioPaketSize, PaketDecodedSize); // check if avcodec_decode_audio returned data, otherwise fetch more frames -- cgit v1.2.3