From 78ea8d5b1d7608255de7a776d98032fa14d47b76 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 9 Mar 2008 19:05:37 +0000 Subject: new MixBuffers() function as replacement for SDL_MixAudio. Now portaudio works without crackling. MixBuffers is not optimized yet (no MMX, etc.) but I think it is fast enough for now. If SDL is used for audio output SDL_MixAudio() is used instead because it is more optimized. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@948 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Game/Code/Classes/UAudioDecoder_FFMpeg.pas | 4 +- Game/Code/Classes/UAudioPlayback_SDL.pas | 7 ++ Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 144 +++++++++++++++++-------- 3 files changed, 108 insertions(+), 47 deletions(-) (limited to 'Game') diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas index e692df6f..1ac677ce 100644 --- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas +++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas @@ -362,13 +362,13 @@ begin if(av_read_frame(pFormatCtx, packet) < 0) then begin - // check for end-of-file (eof is not an error) {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} pbIOCtx := pFormatCtx^.pb; {$ELSE} pbIOCtx := @pFormatCtx^.pb; {$IFEND} - + + // check for end-of-file (eof is not an error) if(url_feof(pbIOCtx) <> 0) then begin {$IFDEF DebugFFMpegDecode} diff --git a/Game/Code/Classes/UAudioPlayback_SDL.pas b/Game/Code/Classes/UAudioPlayback_SDL.pas index 1ea0c5b7..4c9200b2 100644 --- a/Game/Code/Classes/UAudioPlayback_SDL.pas +++ b/Game/Code/Classes/UAudioPlayback_SDL.pas @@ -31,6 +31,7 @@ type procedure StopAudioPlaybackEngine(); override; public function GetName: String; override; + procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); override; end; var @@ -100,6 +101,12 @@ begin SDL_CloseAudio(); end; +procedure TAudioPlayback_SDL.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); +begin + // Note: (volume * SDL_MIX_MAXVOLUME) may exceed High(Integer) + // if SDL_MIX_MAXVOLUME (=128 at the moment) changes + SDL_MixAudio(PUInt8(dst), PUInt8(src), size, volume * SDL_MIX_MAXVOLUME div 100); +end; initialization diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas index 75fddf95..9fd9028a 100644 --- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas +++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas @@ -18,7 +18,7 @@ uses type TAudioPlayback_SoftMixer = class; - TSoftMixerPlaybackStream = class(TAudioPlaybackStream) + TGenericPlaybackStream = class(TAudioPlaybackStream) private Engine: TAudioPlayback_SoftMixer; @@ -73,6 +73,8 @@ type TAudioMixerStream = class private + Engine: TAudioPlayback_SoftMixer; + activeStreams: TList; mixerBuffer: PChar; internalLock: PSDL_Mutex; @@ -85,7 +87,7 @@ type function GetVolume(): integer; procedure SetVolume(volume: integer); public - constructor Create(); + constructor Create(Engine: TAudioPlayback_SoftMixer); destructor Destroy(); override; procedure AddStream(stream: TAudioPlaybackStream); procedure RemoveStream(stream: TAudioPlaybackStream); @@ -96,7 +98,7 @@ type TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback ) private - MusicStream: TSoftMixerPlaybackStream; + MusicStream: TGenericPlaybackStream; MixerStream: TAudioMixerStream; protected FormatInfo: TAudioFormatInfo; @@ -111,7 +113,7 @@ type function InitializePlayback(): boolean; destructor Destroy; override; - function Load(const Filename: String): TSoftMixerPlaybackStream; + function Load(const Filename: String): TGenericPlaybackStream; procedure SetVolume(Volume: integer); procedure SetMusicVolume(Volume: integer); @@ -137,6 +139,8 @@ type function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF} function GetAudioFormatInfo(): TAudioFormatInfo; + procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); virtual; + // Sounds function OpenSound(const Filename: String): TAudioPlaybackStream; procedure PlaySound(stream: TAudioPlaybackStream); @@ -155,8 +159,10 @@ uses { TAudioMixerStream } -constructor TAudioMixerStream.Create(); +constructor TAudioMixerStream.Create(Engine: TAudioPlayback_SoftMixer); begin + Self.Engine := Engine; + activeStreams := TList.Create; internalLock := SDL_CreateMutex(); _volume := 100; @@ -232,8 +238,7 @@ function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer; var i: integer; size: integer; - stream: TSoftMixerPlaybackStream; - appVolume: single; + stream: TGenericPlaybackStream; needsPacking: boolean; begin result := BufSize; @@ -248,10 +253,6 @@ begin Lock(); - // calc application volume - // use _volume instead of Volume to prevent recursive locking - appVolume := _volume / 100 * SDL_MIX_MAXVOLUME; - needsPacking := false; // mix streams to one stream @@ -263,13 +264,14 @@ begin continue; end; - stream := TSoftMixerPlaybackStream(activeStreams[i]); + stream := TGenericPlaybackStream(activeStreams[i]); // fetch data from current stream size := stream.ReadData(mixerBuffer, BufSize); if (size > 0) then begin - SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size, - Trunc(appVolume * stream.Volume / 100)); + // mix stream-data with mixer-buffer + // Note: use _volume (Application-Volume) instead of Volume to prevent recursive locking + Engine.MixBuffers(Buffer, mixerBuffer, size, _volume * stream.Volume div 100); end; end; @@ -283,9 +285,9 @@ begin end; -{ TSoftMixerPlaybackStream } +{ TGenericPlaybackStream } -constructor TSoftMixerPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer); +constructor TGenericPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer); begin inherited Create(); Self.Engine := Engine; @@ -293,14 +295,14 @@ begin Reset(); end; -destructor TSoftMixerPlaybackStream.Destroy(); +destructor TGenericPlaybackStream.Destroy(); begin Close(); SDL_DestroyMutex(internalLock); inherited Destroy(); end; -procedure TSoftMixerPlaybackStream.Reset(); +procedure TGenericPlaybackStream.Reset(); begin Stop(); Loop := false; @@ -313,17 +315,17 @@ begin _volume := 0; end; -procedure TSoftMixerPlaybackStream.Lock(); +procedure TGenericPlaybackStream.Lock(); begin SDL_mutexP(internalLock); end; -procedure TSoftMixerPlaybackStream.Unlock(); +procedure TGenericPlaybackStream.Unlock(); begin SDL_mutexV(internalLock); end; -class function TSoftMixerPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16; +class function TGenericPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16; begin case fmt of asfU8: Result := AUDIO_U8; @@ -338,7 +340,7 @@ begin end; end; -function TSoftMixerPlaybackStream.InitFormatConversion(): boolean; +function TGenericPlaybackStream.InitFormatConversion(): boolean; var err: integer; srcFormat: UInt16; @@ -371,7 +373,7 @@ begin Result := true; end; -function TSoftMixerPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; +function TGenericPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; begin result := false; @@ -388,12 +390,12 @@ begin result := true; end; -procedure TSoftMixerPlaybackStream.Close(); +procedure TGenericPlaybackStream.Close(); begin Reset(); end; -procedure TSoftMixerPlaybackStream.Play(); +procedure TGenericPlaybackStream.Play(); var mixer: TAudioMixerStream; begin @@ -410,7 +412,7 @@ begin mixer.AddStream(Self); end; -procedure TSoftMixerPlaybackStream.Pause(); +procedure TGenericPlaybackStream.Pause(); var mixer: TAudioMixerStream; begin @@ -421,7 +423,7 @@ begin mixer.RemoveStream(Self); end; -procedure TSoftMixerPlaybackStream.Stop(); +procedure TGenericPlaybackStream.Stop(); var mixer: TAudioMixerStream; begin @@ -432,22 +434,22 @@ begin mixer.RemoveStream(Self); end; -function TSoftMixerPlaybackStream.IsLoaded(): boolean; +function TGenericPlaybackStream.IsLoaded(): boolean; begin result := assigned(DecodeStream); end; -function TSoftMixerPlaybackStream.GetLoop(): boolean; +function TGenericPlaybackStream.GetLoop(): boolean; begin result := Loop; end; -procedure TSoftMixerPlaybackStream.SetLoop(Enabled: boolean); +procedure TGenericPlaybackStream.SetLoop(Enabled: boolean); begin Loop := Enabled; end; -function TSoftMixerPlaybackStream.GetLength(): real; +function TGenericPlaybackStream.GetLength(): real; begin if assigned(DecodeStream) then result := DecodeStream.Length @@ -455,7 +457,7 @@ begin result := -1; end; -function TSoftMixerPlaybackStream.GetStatus(): TStreamStatus; +function TGenericPlaybackStream.GetStatus(): TStreamStatus; begin result := status; end; @@ -467,9 +469,9 @@ end; * So the result might not be that accurate. Although this is not * audible in most cases it needs synchronization with the video * or the lyrics timer. - * Using libsamplerate might give better results. + * Using libsamplerate might give better results. *} -function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; +function TGenericPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; var decodeBufSize: integer; sampleBufSize: integer; @@ -568,7 +570,7 @@ begin end; (* TODO: libsamplerate support -function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; +function TGenericPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; var convState: PSRC_STATE; convData: SRC_DATA; @@ -591,7 +593,7 @@ begin end; *) -function TSoftMixerPlaybackStream.GetPCMData(var data: TPCMData): Cardinal; +function TGenericPlaybackStream.GetPCMData(var data: TPCMData): Cardinal; var nBytes: integer; begin @@ -621,7 +623,7 @@ begin Result := nBytes div SizeOf(TPCMStereoSample); end; -procedure TSoftMixerPlaybackStream.GetFFTData(var data: TFFTData); +procedure TGenericPlaybackStream.GetFFTData(var data: TFFTData); var i: integer; Frames: integer; @@ -667,7 +669,7 @@ begin end; end; -function TSoftMixerPlaybackStream.GetPosition: real; +function TGenericPlaybackStream.GetPosition: real; begin if assigned(DecodeStream) then result := DecodeStream.Position @@ -675,18 +677,18 @@ begin result := -1; end; -procedure TSoftMixerPlaybackStream.SetPosition(Time: real); +procedure TGenericPlaybackStream.SetPosition(Time: real); begin if assigned(DecodeStream) then DecodeStream.Position := Time; end; -function TSoftMixerPlaybackStream.GetVolume(): integer; +function TGenericPlaybackStream.GetVolume(): integer; begin result := _volume; end; -procedure TSoftMixerPlaybackStream.SetVolume(volume: integer); +procedure TGenericPlaybackStream.SetVolume(volume: integer); begin // clamp volume if (volume > 100) then @@ -709,7 +711,7 @@ begin if(not InitializeAudioPlaybackEngine()) then Exit; - MixerStream := TAudioMixerStream.Create; + MixerStream := TAudioMixerStream.Create(Self); if(not StartAudioPlaybackEngine()) then Exit; @@ -743,10 +745,10 @@ begin Result := FormatInfo; end; -function TAudioPlayback_SoftMixer.Load(const Filename: String): TSoftMixerPlaybackStream; +function TAudioPlayback_SoftMixer.Load(const Filename: String): TGenericPlaybackStream; var decodeStream: TAudioDecodeStream; - playbackStream: TSoftMixerPlaybackStream; + playbackStream: TGenericPlaybackStream; begin Result := nil; @@ -757,7 +759,7 @@ begin Exit; end; - playbackStream := TSoftMixerPlaybackStream.Create(Self); + playbackStream := TGenericPlaybackStream.Create(Self); if (not playbackStream.SetDecodeStream(decodeStream)) then Exit; @@ -862,6 +864,58 @@ begin Result := true; end; +procedure TAudioPlayback_SoftMixer.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); +var + SampleIndex: Cardinal; + SampleInt: Integer; + SampleFlt: Single; +begin + + // TODO: optimize this code, e.g. with assembler (MMX) + + SampleIndex := 0; + case FormatInfo.Format of + asfS16: + begin + while (SampleIndex < size) do + begin + // apply volume and sum with previous mixer value + SampleInt := PSmallInt(@dst[SampleIndex])^ + PSmallInt(@src[SampleIndex])^ * volume div 100; + // clip result + if (SampleInt > High(SmallInt)) then + SampleInt := High(SmallInt) + else if (SampleInt < Low(SmallInt)) then + SampleInt := Low(SmallInt); + // assign result + PSmallInt(@dst[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(@dst[SampleIndex])^ + PSingle(@src[SampleIndex])^ * volume/100; + // clip result + if (SampleFlt > 1.0) then + SampleFlt := 1.0 + else if (SampleFlt < -1.0) then + SampleFlt := -1.0; + // assign result + PSingle(@dst[SampleIndex])^ := SampleFlt; + // increase index by one sample + Inc(SampleIndex, SizeOf(Single)); + end; + end; + else + begin + Log.LogError('Incompatible format', 'TAudioMixerStream.MixAudio'); + end; + end; +end; + //Equalizer procedure TAudioPlayback_SoftMixer.GetFFTData(var data: TFFTData); begin -- cgit v1.2.3