From 5142d64ca5edc5499098513912959834b971e75b Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 20 Feb 2008 18:30:46 +0000 Subject: - Resampling support added - DecodeStreams are closed now if they are not used anymore - Fixed the crash that occured when USDX was closed git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@875 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Game/Code/Classes/UAudioDecoder_FFMpeg.pas | 170 +++-- Game/Code/Classes/UAudioInput_Bass.pas | 85 ++- Game/Code/Classes/UAudioInput_Portaudio.pas | 2 +- Game/Code/Classes/UAudioPlayback_Portaudio.pas | 881 +++++-------------------- Game/Code/Classes/UAudioPlayback_SDL.pas | 113 ++++ Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 809 +++++++++++++++++++++++ Game/Code/Classes/UMusic.pas | 34 +- Game/Code/UltraStar.dpr | 8 + Game/Code/switches.inc | 4 +- 9 files changed, 1262 insertions(+), 844 deletions(-) create mode 100644 Game/Code/Classes/UAudioPlayback_SDL.pas create mode 100644 Game/Code/Classes/UAudioPlayback_SoftMixer.pas (limited to 'Game/Code') diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas index 646e9eef..1bb9208a 100644 --- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas +++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas @@ -22,18 +22,12 @@ interface uses Classes, - {$IFDEF win32} - windows, - {$ENDIF} SysUtils, UMusic; implementation uses - {$ifndef win32} - libc, - {$endif} UIni, UMain, avcodec, // FFMpeg Audio file decoding @@ -84,8 +78,8 @@ type _EOF: boolean; // end-of-stream flag _EOF_lock : PSDL_Mutex; - lock : PSDL_Mutex; - resumeCond : PSDL_Cond; + internalLock : PSDL_Mutex; + resumeCond : PSDL_Cond; quitRequest : boolean; @@ -96,6 +90,8 @@ type parseThread: PSDL_Thread; packetQueue: TPacketQueue; + formatInfo : TAudioFormatInfo; + // FFMpeg internal data pFormatCtx : PAVFormatContext; pCodecCtx : PAVCodecContext; @@ -113,6 +109,11 @@ type audio_buf_size : cardinal; audio_buf : TAudioBuffer; + procedure Lock(); inline; + procedure Unlock(); inline; + function GetLockMutex(): PSDL_Mutex; inline; + + procedure ParseAudio(); function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer; procedure SetEOF(state: boolean); public @@ -143,7 +144,7 @@ type function Open(const Filename: string): TAudioDecodeStream; end; -function ParseAudio(streamPtr: Pointer): integer; cdecl; forward; +function DecodeThreadMain(streamPtr: Pointer): integer; cdecl; forward; var singleton_AudioDecoderFFMpeg : IAudioDecoder; @@ -173,29 +174,42 @@ begin Self.ffmpegStreamIndex := ffmpegStreamIndex; Self.ffmpegStream := ffmpegStream; + formatInfo := TAudioFormatInfo.Create( + pCodecCtx^.channels, + pCodecCtx^.sample_rate, + // pCodecCtx^.sample_fmt not yet used by FFMpeg -> use FFMpeg's standard format + asfS16 + ); + _EOF := false; _EOF_lock := SDL_CreateMutex(); - lock := SDL_CreateMutex(); + internalLock := SDL_CreateMutex(); resumeCond := SDL_CreateCond(); - parseThread := SDL_CreateThread(@ParseAudio, Self); + parseThread := SDL_CreateThread(@DecodeThreadMain, Self); end; destructor TFFMpegDecodeStream.Destroy(); begin - //Close(); - //packetQueue.Free(); + Close(); inherited; end; procedure TFFMpegDecodeStream.Close(); +var + status: integer; begin - // TODO: abort thread - //quitRequest := true; - //SDL_WaitThread(parseThread, nil); + Lock(); + quitRequest := true; + SDL_CondSignal(resumeCond); + Unlock(); + + if (parseThread <> nil) then + begin + SDL_WaitThread(parseThread, status); + end; - (* // Close the codec if (pCodecCtx <> nil) then begin @@ -209,26 +223,40 @@ begin av_close_input_file(pFormatCtx); pFormatCtx := nil; end; - *) + + FreeAndNil(packetQueue); + FreeAndNil(formatInfo); +end; + +procedure TFFMpegDecodeStream.Lock(); +begin + SDL_mutexP(internalLock); +end; + +procedure TFFMpegDecodeStream.Unlock(); +begin + SDL_mutexV(internalLock); +end; + +function TFFMpegDecodeStream.GetLockMutex(): PSDL_Mutex; +begin + Result := internalLock; end; function TFFMpegDecodeStream.GetLength(): real; begin - result := pFormatCtx^.duration / AV_TIME_BASE; + Result := pFormatCtx^.duration / AV_TIME_BASE; end; function TFFMpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo; begin - result.Channels := pCodecCtx^.channels; - result.SampleRate := pCodecCtx^.sample_rate; - //result.Format := pCodecCtx^.sample_fmt; // sample_fmt not yet used by FFMpeg - result.Format := asfS16; // use FFMpeg's standard format + Result := formatInfo; end; function TFFMpegDecodeStream.IsEOF(): boolean; begin SDL_mutexP(_EOF_lock); - result := _EOF; + Result := _EOF; SDL_mutexV(_EOF_lock); end; @@ -251,89 +279,94 @@ procedure TFFMpegDecodeStream.SetPosition(Time: real); var bytes: integer; begin - SDL_mutexP(lock); + Lock(); seekPos := Trunc(Time * AV_TIME_BASE); // FIXME: seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0 seekFlags := 0;//AVSEEK_FLAG_BACKWARD; seekRequest := true; SDL_CondSignal(resumeCond); - SDL_mutexV(lock); + Unlock(); end; -function ParseAudio(streamPtr: Pointer): integer; cdecl; +function DecodeThreadMain(streamPtr: Pointer): integer; cdecl; var - packet: TAVPacket; stream: TFFMpegDecodeStream; +begin + stream := TFFMpegDecodeStream(streamPtr); + stream.ParseAudio(); + result := 0; +end; + +procedure TFFMpegDecodeStream.ParseAudio(); +var + packet: TAVPacket; seekTarget: int64; eofState: boolean; pbIOCtx: PByteIOContext; begin - stream := TFFMpegDecodeStream(streamPtr); eofState := false; while (true) do begin - //SafeWriteLn('Hallo'); - - SDL_mutexP(stream.lock); + Lock(); // wait if end-of-file reached if (eofState) then begin - if (not (stream.seekRequest or stream.quitRequest)) then + if (not (seekRequest or quitRequest)) then begin // signal end-of-file - stream.packetQueue.put(@EOFPacket); + packetQueue.put(@EOFPacket); // wait for reuse or destruction of stream repeat - SDL_CondWait(stream.resumeCond, stream.lock); - until (stream.seekRequest or stream.quitRequest); + SDL_CondWait(resumeCond, GetLockMutex()); + until (seekRequest or quitRequest); end; eofState := false; - stream.SetEOF(false); + SetEOF(false); end; - if (stream.quitRequest) then + if (quitRequest) then begin break; end; // handle seek-request - if(stream.seekRequest) then + if(seekRequest) then begin // TODO: Do we need this? // The position is converted to AV_TIME_BASE and then to the stream-specific base. // Why not convert to the stream-specific one from the beginning. - seekTarget := av_rescale_q(stream.seekPos, AV_TIME_BASE_Q, stream.ffmpegStream^.time_base); - if(av_seek_frame(stream.pFormatCtx, stream.ffmpegStreamIndex, - seekTarget, stream.seekFlags) < 0) then + seekTarget := av_rescale_q(seekPos, AV_TIME_BASE_Q, ffmpegStream^.time_base); + if(av_seek_frame(pFormatCtx, ffmpegStreamIndex, + seekTarget, seekFlags) < 0) then begin // this will crash in FPC due to a bug - //Log.LogStatus({stream.pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg'); + //Log.LogStatus({pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg'); end else begin - stream.packetQueue.Flush(); - stream.packetQueue.Put(@FlushPacket); + packetQueue.Flush(); + packetQueue.Put(@FlushPacket); end; - stream.seekRequest := false; + seekRequest := false; end; - SDL_mutexV(stream.lock); + Unlock(); - if(stream.packetQueue.size > MAX_AUDIOQ_SIZE) then + if(packetQueue.size > MAX_AUDIOQ_SIZE) then begin SDL_Delay(10); continue; end; - if(av_read_frame(stream.pFormatCtx, packet) < 0) then + 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 := stream.pFormatCtx^.pb; + pbIOCtx := pFormatCtx^.pb; {$ELSE} - pbIOCtx := @stream.pFormatCtx^.pb; + pbIOCtx := @pFormatCtx^.pb; {$IFEND} if(url_feof(pbIOCtx) <> 0) then @@ -362,20 +395,16 @@ begin //SafeWriteLn( 'ffmpeg - av_read_frame' ); - if(packet.stream_index = stream.ffmpegStreamIndex) then + if(packet.stream_index = ffmpegStreamIndex) then begin //SafeWriteLn( 'packet_queue_put' ); - stream.packetQueue.put(@packet); + packetQueue.put(@packet); end else begin av_free_packet(@packet); end; end; - - SafeWriteLn('Done: ' + inttostr(stream.packetQueue.nbPackets)); - - result := 0; end; function TFFMpegDecodeStream.DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer; @@ -409,7 +438,7 @@ begin if(len1 < 0) then begin - // if error, skip frame + // if error, skip frame SafeWriteLn( 'Skip audio frame' ); audio_pkt_size := 0; break; @@ -420,11 +449,11 @@ begin if (data_size <= 0) then begin - // No data yet, get more frames + // no data yet, get more frames continue; end; - // We have data, return it and come back for more later + // we have data, return it and come back for more later result := data_size; exit; end; @@ -469,7 +498,6 @@ var outStream : TFFMpegDecodeStream; len1, audio_size : integer; - pSrc : Pointer; len : integer; begin len := BufSize; @@ -482,13 +510,13 @@ begin while (len > 0) do begin if (audio_buf_index >= audio_buf_size) then begin - // We have already sent all our data; get more + // we have already sent all our data; get more audio_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer)); //SafeWriteLn('audio_decode_frame : '+ inttostr(audio_size)); if(audio_size < 0) then begin - // If error, output silence + // if error, output silence audio_buf_size := 1024; FillChar(audio_buf, audio_buf_size, #0); //SafeWriteLn( 'Silence' ); @@ -504,15 +532,10 @@ begin if (len1 > len) then len1 := len; - pSrc := PChar(@audio_buf) + audio_buf_index; - {$ifdef WIN32} - CopyMemory(Buffer, pSrc , len1); - {$else} - memcpy(Buffer, pSrc , len1); - {$endif} + Move(audio_buf[audio_buf_index], Buffer[0], len1); Dec(len, len1); - Inc(PChar(Buffer), len1); + Inc(Buffer, len1); Inc(audio_buf_index, len1); end; @@ -550,7 +573,7 @@ var streamIndex: integer; stream : PAVStream; begin - // Find the first audio stream + // find the first audio stream streamIndex := -1; for i := 0 to pFormatCtx^.nb_streams-1 do @@ -588,11 +611,11 @@ begin exit; end; - // Open audio file + // open audio file if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then exit; - // Retrieve stream information + // retrieve stream information if (av_find_stream_info(pFormatCtx) < 0) then exit; @@ -641,6 +664,7 @@ end; destructor TPacketQueue.Destroy(); begin + Flush(); SDL_DestroyMutex(mutex); SDL_DestroyCond(cond); inherited; diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas index 6d661258..f99b0885 100644 --- a/Game/Code/Classes/UAudioInput_Bass.pas +++ b/Game/Code/Classes/UAudioInput_Bass.pas @@ -144,43 +144,58 @@ begin if (Descr = nil) then break; - SetLength(AudioInputProcessor.Device, DeviceIndex+1); - - // TODO: free object on termination - BassDevice := TBassInputDevice.Create(); - AudioInputProcessor.Device[DeviceIndex] := BassDevice; - - BassDevice.DeviceIndex := DeviceIndex; - BassDevice.BassDeviceID := BassDeviceID; - BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex); - - // get input sources - SourceIndex := 0; - BASS_RecordInit(BassDeviceID); - BassDevice.MicInput := 0; - - // process each input - while true do + // try to intialize the device + if not BASS_RecordInit(BassDeviceID) then begin - SourceName := BASS_RecordGetInputName(SourceIndex); - if (SourceName = nil) then - break; - - SetLength(BassDevice.Source, SourceIndex+1); - BassDevice.Source[SourceIndex].Name := - UnifyDeviceSourceName(SourceName, BassDevice.Description); - - // set mic index - Flags := BASS_RecordGetInput(SourceIndex); - if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then - BassDevice.MicInput := SourceIndex; - - Inc(SourceIndex); + Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']', + 'TAudioInput_Bass.InitializeRecord'); + end + else + begin + SetLength(AudioInputProcessor.Device, DeviceIndex+1); + + // TODO: free object on termination + BassDevice := TBassInputDevice.Create(); + AudioInputProcessor.Device[DeviceIndex] := BassDevice; + + BassDevice.DeviceIndex := DeviceIndex; + BassDevice.BassDeviceID := BassDeviceID; + BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex); + + // get input sources + SourceIndex := 0; + BassDevice.MicInput := 0; + + // process each input + while true do + begin + SourceName := BASS_RecordGetInputName(SourceIndex); + if (SourceName = nil) then + break; + + SetLength(BassDevice.Source, SourceIndex+1); + BassDevice.Source[SourceIndex].Name := + UnifyDeviceSourceName(SourceName, BassDevice.Description); + + // set mic index + Flags := BASS_RecordGetInput(SourceIndex); + if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then + begin + BassDevice.MicInput := SourceIndex; + end; + + Inc(SourceIndex); + end; + + //Writeln('BASS_RecordFree'); + // FIXME: this call hangs in FPC (windows) every 2nd time USDX is called. + // Maybe because the sound-device was not released properly? + BASS_RecordFree; + //Writeln('BASS_RecordFree - Done'); + + Inc(DeviceIndex); end; - - BASS_RecordFree; - - Inc(DeviceIndex); + Inc(BassDeviceID); end; diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas index 753c69f6..665f1972 100644 --- a/Game/Code/Classes/UAudioInput_Portaudio.pas +++ b/Game/Code/Classes/UAudioInput_Portaudio.pas @@ -63,7 +63,7 @@ var *} const paDefaultApi = -1; -var +const ApiPreferenceOrder: {$IF Defined(WIN32)} // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas index 59571d3d..96fff957 100644 --- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas +++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas @@ -1,728 +1,153 @@ -unit UAudioPlayback_Portaudio; - -interface - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - - -uses - Classes, - SysUtils, - UMusic; - -implementation - -uses - {$IFNDEF Win32} - libc, - {$ENDIF} - sdl, - portaudio, - ULog, - UIni, - UMain; - -type - TPortaudioPlaybackStream = class(TAudioPlaybackStream) - private - Status: TStreamStatus; - Loop: boolean; - - _volume: integer; - - procedure Reset(); - public - DecodeStream: TAudioDecodeStream; - - constructor Create(); - destructor Destroy(); override; - - function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; - - procedure Play(); override; - procedure Pause(); override; - procedure Stop(); override; - procedure Close(); override; - function GetLoop(): boolean; override; - procedure SetLoop(Enabled: boolean); override; - function GetLength(): real; override; - function GetStatus(): TStreamStatus; override; - - function IsLoaded(): boolean; - - function GetVolume(): integer; override; - procedure SetVolume(volume: integer); override; - - // functions delegated to the decode stream - function GetPosition: real; - procedure SetPosition(Time: real); - function ReadData(Buffer: PChar; BufSize: integer): integer; - end; - -type - TAudioMixerStream = class - private - activeStreams: TList; - mixerBuffer: PChar; - internalLock: PSDL_Mutex; - - _volume: integer; - - procedure Lock(); inline; - procedure Unlock(); inline; - - function GetVolume(): integer; - procedure SetVolume(volume: integer); - public - constructor Create(); - destructor Destroy(); override; - procedure AddStream(stream: TAudioPlaybackStream); - procedure RemoveStream(stream: TAudioPlaybackStream); - function ReadData(Buffer: PChar; BufSize: integer): integer; - - property Volume: integer READ GetVolume WRITE SetVolume; - end; - -type - TAudioPlayback_Portaudio = class( TInterfacedObject, IAudioPlayback ) - private - MusicStream: TPortaudioPlaybackStream; - - MixerStream: TAudioMixerStream; - paStream: PPaStream; - - FrameSize: integer; - - function InitializePortaudio(): boolean; - function StartPortaudioStream(): boolean; - - function InitializeSDLAudio(): boolean; - function StartSDLAudioStream(): boolean; - procedure StopSDLAudioStream(); - public - function GetName: String; - - function InitializePlayback(): boolean; - destructor Destroy; override; - - function Load(const Filename: String): TPortaudioPlaybackStream; - - procedure SetVolume(Volume: integer); - procedure SetMusicVolume(Volume: integer); - procedure SetLoop(Enabled: boolean); - function Open(Filename: string): boolean; // true if succeed - procedure Rewind; - procedure SetPosition(Time: real); - procedure Play; - procedure Pause; - - procedure Stop; - procedure Close; - function Finished: boolean; - function Length: real; - function GetPosition: real; - - // Equalizer - procedure GetFFTData(var data: TFFTData); - - // Interface for Visualizer - function GetPCMData(var data: TPCMData): Cardinal; - - // Sounds - function OpenSound(const Filename: String): TAudioPlaybackStream; - procedure PlaySound(stream: TAudioPlaybackStream); - procedure StopSound(stream: TAudioPlaybackStream); - end; - - -function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword; - timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - userData: Pointer): Integer; cdecl; forward; - -var - singleton_AudioPlaybackPortaudio : IAudioPlayback; - - -{ TAudioMixerStream } - -constructor TAudioMixerStream.Create(); -begin - activeStreams := TList.Create; - internalLock := SDL_CreateMutex(); - _volume := 100; -end; - -destructor TAudioMixerStream.Destroy(); -begin - if assigned(mixerBuffer) then - Freemem(mixerBuffer); - activeStreams.Free; - SDL_DestroyMutex(internalLock); -end; - -procedure TAudioMixerStream.Lock(); -begin - SDL_mutexP(internalLock); -end; - -procedure TAudioMixerStream.Unlock(); -begin - SDL_mutexV(internalLock); -end; - -function TAudioMixerStream.GetVolume(): integer; -begin - Lock(); - result := _volume; - Unlock(); -end; - -procedure TAudioMixerStream.SetVolume(volume: integer); -begin - Lock(); - _volume := 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; - -procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream); -begin - Lock(); - activeStreams.Remove(Pointer(stream)); - Unlock(); -end; - -function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer; -var - i: integer; - size: integer; - stream: TPortaudioPlaybackStream; - appVolume: single; -begin - result := BufSize; - - // zero target-buffer (silence) - FillChar(Buffer^, BufSize, 0); - - // resize mixer-buffer if necessary - ReallocMem(mixerBuffer, BufSize); - if not assigned(mixerBuffer) then - Exit; - - Lock(); - - //writeln('Mix: ' + inttostr(activeStreams.Count)); - - // use _volume instead of Volume to prevent recursive locking - appVolume := _volume / 100 * SDL_MIX_MAXVOLUME; - - for i := 0 to activeStreams.Count-1 do - begin - stream := TPortaudioPlaybackStream(activeStreams[i]); - if (stream.GetStatus() = ssPlaying) then - begin - // 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)); - end; - end; - end; - - Unlock(); -end; - - -{ TPortaudioPlaybackStream } - -constructor TPortaudioPlaybackStream.Create(); -begin - inherited Create(); - Reset(); -end; - -destructor TPortaudioPlaybackStream.Destroy(); -begin - Close(); - inherited Destroy(); -end; - -procedure TPortaudioPlaybackStream.Reset(); -begin - Status := ssStopped; - Loop := false; - DecodeStream := nil; - _volume := 0; -end; - -function TPortaudioPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; -begin - result := false; - - Reset(); - - if not assigned(decodeStream) then - Exit; - Self.DecodeStream := decodeStream; - - _volume := 100; - - result := true; -end; - -procedure TPortaudioPlaybackStream.Close(); -begin - Reset(); -end; - -procedure TPortaudioPlaybackStream.Play(); -begin - if (status <> ssPaused) then - begin - // rewind - if assigned(DecodeStream) then - DecodeStream.Position := 0; - end; - status := ssPlaying; - //MixerStream.AddStream(Self); -end; - -procedure TPortaudioPlaybackStream.Pause(); -begin - status := ssPaused; -end; - -procedure TPortaudioPlaybackStream.Stop(); -begin - status := ssStopped; -end; - -function TPortaudioPlaybackStream.IsLoaded(): boolean; -begin - result := assigned(DecodeStream); -end; - -function TPortaudioPlaybackStream.GetLoop(): boolean; -begin - result := Loop; -end; - -procedure TPortaudioPlaybackStream.SetLoop(Enabled: boolean); -begin - Loop := Enabled; -end; - -function TPortaudioPlaybackStream.GetLength(): real; -begin - if assigned(DecodeStream) then - result := DecodeStream.Length - else - result := -1; -end; - -function TPortaudioPlaybackStream.GetStatus(): TStreamStatus; -begin - result := status; -end; - -function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; -begin - if not assigned(DecodeStream) then - begin - result := -1; - Exit; - end; - result := DecodeStream.ReadData(Buffer, BufSize); - // end-of-file reached -> stop playback - if (DecodeStream.EOF) then - begin - status := ssStopped; - end; -end; - -function TPortaudioPlaybackStream.GetPosition: real; -begin - if assigned(DecodeStream) then - result := DecodeStream.Position - else - result := -1; -end; - -procedure TPortaudioPlaybackStream.SetPosition(Time: real); -begin - if assigned(DecodeStream) then - DecodeStream.Position := Time; -end; - -function TPortaudioPlaybackStream.GetVolume(): integer; -begin - result := _volume; -end; - -procedure TPortaudioPlaybackStream.SetVolume(volume: integer); -begin - // clamp volume - if (volume > 100) then - _volume := 100 - else if (volume < 0) then - _volume := 0 - else - _volume := volume; -end; - - -{ TAudioPlayback_Portaudio } - -function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword; - timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - userData: Pointer): Integer; cdecl; -var - playback: TAudioPlayback_Portaudio; -begin - playback := TAudioPlayback_Portaudio(userData); - with playback do - begin - MixerStream.ReadData(output, frameCount * FrameSize); - end; - result := paContinue; -end; - -procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl; -var - playback: TAudioPlayback_Portaudio; -begin - playback := TAudioPlayback_Portaudio(userdata); - with playback do - begin - MixerStream.ReadData(stream, len); - end; -end; - -function TAudioPlayback_Portaudio.GetName: String; -begin - result := 'Portaudio_Playback'; -end; - -function TAudioPlayback_Portaudio.InitializePortaudio(): boolean; -var - paApi : TPaHostApiIndex; - paApiInfo : PPaHostApiInfo; - paOutParams : TPaStreamParameters; - paOutDevice : TPaDeviceIndex; - paOutDeviceInfo : PPaDeviceInfo; - err : TPaError; -begin - result := false; - - Pa_Initialize(); - - // FIXME: determine automatically - {$IFDEF WIN32} - paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound); - {$ELSE} - paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA); - {$ENDIF} - if (paApi < 0) then - begin - Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio'); - exit; - end; - - paApiInfo := Pa_GetHostApiInfo(paApi); - paOutDevice := paApiInfo^.defaultOutputDevice; - paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice); - - with paOutParams do begin - device := paOutDevice; - channelCount := 2; - sampleFormat := paInt16; - suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency; - hostApiSpecificStreamInfo := nil; - end; - - // set the size of one audio frame (2channel 16bit uint sample) - FrameSize := 2 * sizeof(Smallint); - - err := Pa_OpenStream(paStream, nil, @paOutParams, 44100, - paFramesPerBufferUnspecified, - paNoFlag, @AudioCallback, Self); - if(err <> paNoError) then begin - Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio'); - exit; - end; - - Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio'); - - result := true; -end; - -function TAudioPlayback_Portaudio.StartPortaudioStream(): boolean; -var - err: TPaError; -begin - result := false; - - err := Pa_StartStream(paStream); - if(err <> paNoError) then - begin - Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio'); - exit; - end; - - result := true; -end; - -function TAudioPlayback_Portaudio.InitializeSDLAudio(): boolean; -var - desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec; - err: integer; -begin - result := false; - - SDL_InitSubSystem(SDL_INIT_AUDIO); - - FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0); - with desiredAudioSpec do - begin - freq := 44100; - format := AUDIO_S16SYS; - channels := 2; - samples := 1024; // latency: 23 ms - callback := @SDLAudioCallback; - userdata := Self; - end; - - // set the size of one audio frame (2channel 16bit uint sample) - FrameSize := 2 * sizeof(Smallint); - - if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then - begin - Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL'); - exit; - end; - - Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL'); - - result := true; -end; - -function TAudioPlayback_Portaudio.StartSDLAudioStream(): boolean; -begin - SDL_PauseAudio(0); - result := true; -end; - -procedure TAudioPlayback_Portaudio.StopSDLAudioStream(); -begin - SDL_CloseAudio(); -end; - -function TAudioPlayback_Portaudio.InitializePlayback: boolean; -begin - result := false; - - //Log.LogStatus('InitializePlayback', 'UAudioPlayback_Portaudio'); - - //if(not InitializePortaudio()) then - if(not InitializeSDLAudio()) then - Exit; - - MixerStream := TAudioMixerStream.Create; - - //if(not StartPortaudioStream()) then; - if(not StartSDLAudioStream()) then - Exit; - - result := true; -end; - -destructor TAudioPlayback_Portaudio.Destroy; -begin - StopSDLAudioStream(); - - MixerStream.Free(); - MusicStream.Free(); - - inherited Destroy(); -end; - -function TAudioPlayback_Portaudio.Load(const Filename: String): TPortaudioPlaybackStream; -var - decodeStream: TAudioDecodeStream; - playbackStream: TPortaudioPlaybackStream; -begin - Result := nil; - - decodeStream := AudioDecoder.Open(Filename); - if not assigned(decodeStream) then - begin - Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_Portaudio'); - Exit; - end; - - playbackStream := TPortaudioPlaybackStream.Create(); - if (not playbackStream.SetDecodeStream(decodeStream)) then - Exit; - - // FIXME: remove this line - MixerStream.AddStream(playbackStream); - - result := playbackStream; -end; - -procedure TAudioPlayback_Portaudio.SetVolume(Volume: integer); -begin - // sets volume only for this application - MixerStream.Volume := Volume; -end; - -procedure TAudioPlayback_Portaudio.SetMusicVolume(Volume: Integer); -begin - if assigned(MusicStream) then - MusicStream.Volume := Volume; -end; - -procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean); -begin - if assigned(MusicStream) then - MusicStream.SetLoop(Enabled); -end; - -function TAudioPlayback_Portaudio.Open(Filename: string): boolean; -var - decodeStream: TAudioDecodeStream; -begin - Result := false; - - // free old MusicStream - MusicStream.Free(); - - MusicStream := Load(Filename); - if not assigned(MusicStream) then - Exit; - - //Set Max Volume - SetMusicVolume(100); - - Result := true; -end; - -procedure TAudioPlayback_Portaudio.Rewind; -begin - SetPosition(0); -end; - -procedure TAudioPlayback_Portaudio.SetPosition(Time: real); -begin - if assigned(MusicStream) then - MusicStream.SetPosition(Time); -end; - -function TAudioPlayback_Portaudio.GetPosition: real; -begin - if assigned(MusicStream) then - Result := MusicStream.GetPosition() - else - Result := -1; -end; - -function TAudioPlayback_Portaudio.Length: real; -begin - if assigned(MusicStream) then - Result := MusicStream.GetLength() - else - Result := -1; -end; - -procedure TAudioPlayback_Portaudio.Play; -begin - if assigned(MusicStream) then - MusicStream.Play(); -end; - -procedure TAudioPlayback_Portaudio.Pause; -begin - if assigned(MusicStream) then - MusicStream.Pause(); -end; - -procedure TAudioPlayback_Portaudio.Stop; -begin - if assigned(MusicStream) then - MusicStream.Stop(); -end; - -procedure TAudioPlayback_Portaudio.Close; -begin - if assigned(MusicStream) then - begin - MixerStream.RemoveStream(MusicStream); - MusicStream.Close(); - end; -end; - -function TAudioPlayback_Portaudio.Finished: boolean; -begin - if assigned(MusicStream) then - Result := (MusicStream.GetStatus() = ssStopped) - else - Result := true; -end; - -//Equalizer -procedure TAudioPlayback_Portaudio.GetFFTData(var data: TFFTData); -begin - //Get Channel Data Mono and 256 Values -// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512); -end; - -// Interface for Visualizer -function TAudioPlayback_Portaudio.GetPCMData(var data: TPCMData): Cardinal; -begin - result := 0; -end; - -function TAudioPlayback_Portaudio.OpenSound(const Filename: String): TAudioPlaybackStream; -begin - result := Load(Filename); -end; - -procedure TAudioPlayback_Portaudio.PlaySound(stream: TAudioPlaybackStream); -begin - if assigned(stream) then - stream.Play(); -end; - -procedure TAudioPlayback_Portaudio.StopSound(stream: TAudioPlaybackStream); -begin - if assigned(stream) then - stream.Stop(); -end; - - -initialization - singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create(); - AudioManager.add( singleton_AudioPlaybackPortaudio ); - -finalization - AudioManager.Remove( singleton_AudioPlaybackPortaudio ); - - -end. +unit UAudioPlayback_Portaudio; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + + +uses + Classes, + SysUtils, + UMusic; + +implementation + +uses + portaudio, + UAudioPlayback_SoftMixer, + ULog, + UIni, + UMain; + +type + TAudioPlayback_Portaudio = class(TAudioPlayback_SoftMixer) + private + paStream: PPaStream; + protected + function InitializeAudioPlaybackEngine(): boolean; override; + function StartAudioPlaybackEngine(): boolean; override; + procedure StopAudioPlaybackEngine(); override; + public + function GetName: String; override; + end; + +var + singleton_AudioPlaybackPortaudio : IAudioPlayback; + + +{ 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); + engine.AudioCallback(output, frameCount * engine.FormatInfo.FrameSize); + result := paContinue; +end; + +function TAudioPlayback_Portaudio.GetName: String; +begin + result := 'Portaudio_Playback'; +end; + +function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean; +var + paApi : TPaHostApiIndex; + paApiInfo : PPaHostApiInfo; + paOutParams : TPaStreamParameters; + paOutDevice : TPaDeviceIndex; + paOutDeviceInfo : PPaDeviceInfo; + err : TPaError; +const + sampleFreq = 44100; +begin + result := false; + + Pa_Initialize(); + + // FIXME: determine automatically + {$IFDEF WIN32} + paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound); + {$ELSE} + paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA); + {$ENDIF} + if (paApi < 0) then + begin + Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio'); + exit; + end; + + paApiInfo := Pa_GetHostApiInfo(paApi); + paOutDevice := paApiInfo^.defaultOutputDevice; + paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice); + + with paOutParams do begin + device := paOutDevice; + channelCount := 2; + sampleFormat := paInt16; + suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency; + hostApiSpecificStreamInfo := nil; + end; + + err := Pa_OpenStream(paStream, nil, @paOutParams, sampleFreq, + paFramesPerBufferUnspecified, + paNoFlag, @PortaudioAudioCallback, Self); + if(err <> paNoError) then begin + Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio'); + exit; + end; + + FormatInfo := TAudioFormatInfo.Create( + paOutParams.channelCount, + sampleFreq, + asfS16 // FIXME: is paInt16 system-dependant or -independant? + ); + + Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio'); + + 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; + + + +initialization + singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create(); + AudioManager.add( singleton_AudioPlaybackPortaudio ); + +finalization + AudioManager.Remove( singleton_AudioPlaybackPortaudio ); + + +end. diff --git a/Game/Code/Classes/UAudioPlayback_SDL.pas b/Game/Code/Classes/UAudioPlayback_SDL.pas new file mode 100644 index 00000000..5dc664fa --- /dev/null +++ b/Game/Code/Classes/UAudioPlayback_SDL.pas @@ -0,0 +1,113 @@ +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) + protected + function InitializeAudioPlaybackEngine(): boolean; override; + function StartAudioPlaybackEngine(): boolean; override; + procedure StopAudioPlaybackEngine(); override; + public + function GetName: String; override; + end; + +var + singleton_AudioPlaybackSDL : IAudioPlayback; + + +{ 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.InitializeAudioPlaybackEngine(): boolean; +var + desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec; + err: integer; +begin + result := false; + + SDL_InitSubSystem(SDL_INIT_AUDIO); + + FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0); + with desiredAudioSpec do + begin + freq := 44100; + format := AUDIO_S16SYS; + channels := 2; + samples := 1024; // latency: 23 ms + callback := @SDLAudioCallback; + userdata := Self; + end; + + if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then + begin + Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL'); + exit; + end; + + FormatInfo := TAudioFormatInfo.Create( + obtainedAudioSpec.channels, + obtainedAudioSpec.freq, + asfS16 + ); + + Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL'); + + result := true; +end; + +function TAudioPlayback_SDL.StartAudioPlaybackEngine(): boolean; +begin + SDL_PauseAudio(0); + result := true; +end; + +procedure TAudioPlayback_SDL.StopAudioPlaybackEngine(); +begin + SDL_CloseAudio(); +end; + + + +initialization + singleton_AudioPlaybackSDL := TAudioPlayback_SDL.create(); + AudioManager.add( singleton_AudioPlaybackSDL ); + +finalization + AudioManager.Remove( singleton_AudioPlaybackSDL ); + + +end. diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas new file mode 100644 index 00000000..1dc28dcd --- /dev/null +++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas @@ -0,0 +1,809 @@ +unit UAudioPlayback_SoftMixer; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + + +uses + Classes, + SysUtils, + sdl, + UMusic; + +type + TAudioPlayback_SoftMixer = class; + + TSoftMixerPlaybackStream = class(TAudioPlaybackStream) + private + Engine: TAudioPlayback_SoftMixer; + + DecodeStream: TAudioDecodeStream; + SampleBuffer : PChar; + SampleBufferPos : cardinal; + BytesAvail: integer; + cvt: TSDL_AudioCVT; + + Status: TStreamStatus; + Loop: boolean; + _volume: integer; + + InternalLock: PSDL_Mutex; + + procedure Reset(); + + class function ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16; + function InitFormatConversion(): boolean; + + procedure Lock(); inline; + procedure Unlock(); inline; + public + constructor Create(Engine: TAudioPlayback_SoftMixer); + destructor Destroy(); override; + + function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; + + procedure Play(); override; + procedure Pause(); override; + procedure Stop(); override; + procedure Close(); override; + function GetLoop(): boolean; override; + procedure SetLoop(Enabled: boolean); override; + function GetLength(): real; override; + function GetStatus(): TStreamStatus; override; + + function IsLoaded(): boolean; + + function GetVolume(): integer; override; + procedure SetVolume(Volume: integer); override; + + // functions delegated to the decode stream + function GetPosition: real; + procedure SetPosition(Time: real); + function ReadData(Buffer: PChar; BufSize: integer): integer; + end; + + TAudioMixerStream = class + private + activeStreams: TList; + mixerBuffer: PChar; + internalLock: PSDL_Mutex; + + _volume: integer; + + procedure Lock(); inline; + procedure Unlock(); inline; + + function GetVolume(): integer; + procedure SetVolume(volume: integer); + public + constructor Create(); + destructor Destroy(); override; + procedure AddStream(stream: TAudioPlaybackStream); + procedure RemoveStream(stream: TAudioPlaybackStream); + function ReadData(Buffer: PChar; BufSize: integer): integer; + + property Volume: integer READ GetVolume WRITE SetVolume; + end; + + TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback ) + private + MusicStream: TSoftMixerPlaybackStream; + MixerStream: TAudioMixerStream; + protected + FormatInfo: TAudioFormatInfo; + + function InitializeAudioPlaybackEngine(): boolean; virtual; abstract; + function StartAudioPlaybackEngine(): boolean; virtual; abstract; + procedure StopAudioPlaybackEngine(); virtual; abstract; + procedure AudioCallback(buffer: PChar; size: integer); inline; + public + function GetName: String; virtual; abstract; + + function InitializePlayback(): boolean; + destructor Destroy; override; + + function Load(const Filename: String): TSoftMixerPlaybackStream; + + procedure SetVolume(Volume: integer); + procedure SetMusicVolume(Volume: integer); + procedure SetLoop(Enabled: boolean); + function Open(Filename: string): boolean; // true if succeed + procedure Rewind; + procedure SetPosition(Time: real); + procedure Play; + procedure Pause; + + procedure Stop; + procedure Close; + function Finished: boolean; + function Length: real; + function GetPosition: real; + + // Equalizer + procedure GetFFTData(var data: TFFTData); + + // Interface for Visualizer + function GetPCMData(var data: TPCMData): Cardinal; + + function GetMixer(): TAudioMixerStream; inline; + function GetAudioFormatInfo(): TAudioFormatInfo; + + // Sounds + function OpenSound(const Filename: String): TAudioPlaybackStream; + procedure PlaySound(stream: TAudioPlaybackStream); + procedure StopSound(stream: TAudioPlaybackStream); + end; + +implementation + +uses + Math, + //samplerate, + UFFT, + ULog, + UIni, + UMain; + +{ TAudioMixerStream } + +constructor TAudioMixerStream.Create(); +begin + activeStreams := TList.Create; + internalLock := SDL_CreateMutex(); + _volume := 100; +end; + +destructor TAudioMixerStream.Destroy(); +begin + if assigned(mixerBuffer) then + Freemem(mixerBuffer); + activeStreams.Free; + SDL_DestroyMutex(internalLock); +end; + +procedure TAudioMixerStream.Lock(); +begin + SDL_mutexP(internalLock); +end; + +procedure TAudioMixerStream.Unlock(); +begin + SDL_mutexV(internalLock); +end; + +function TAudioMixerStream.GetVolume(): integer; +begin + Lock(); + result := _volume; + Unlock(); +end; + +procedure TAudioMixerStream.SetVolume(volume: integer); +begin + Lock(); + _volume := 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; BufSize: integer): integer; +var + i: integer; + size: integer; + stream: TSoftMixerPlaybackStream; + appVolume: single; + needsPacking: boolean; +begin + result := BufSize; + + // zero target-buffer (silence) + FillChar(Buffer^, BufSize, 0); + + // resize mixer-buffer if necessary + ReallocMem(mixerBuffer, BufSize); + if not assigned(mixerBuffer) then + Exit; + + 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 + for i := 0 to activeStreams.Count-1 do + begin + if (activeStreams[i] = nil) then + begin + needsPacking := true; + continue; + end; + + stream := TSoftMixerPlaybackStream(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)); + end; + end; + + // remove nil-pointers from list + if (needsPacking) then + begin + activeStreams.Pack(); + end; + + Unlock(); +end; + + +{ TSoftMixerPlaybackStream } + +constructor TSoftMixerPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer); +begin + inherited Create(); + Self.Engine := Engine; + internalLock := SDL_CreateMutex(); + Reset(); +end; + +destructor TSoftMixerPlaybackStream.Destroy(); +begin + Close(); + SDL_DestroyMutex(internalLock); + inherited Destroy(); +end; + +procedure TSoftMixerPlaybackStream.Reset(); +begin + Stop(); + Loop := false; + // TODO: use DecodeStream.Unref() instead of Free(); + FreeAndNil(DecodeStream); + FreeMem(SampleBuffer); + SampleBuffer := nil; + SampleBufferPos := 0; + BytesAvail := 0; + _volume := 0; +end; + +procedure TSoftMixerPlaybackStream.Lock(); +begin + SDL_mutexP(internalLock); +end; + +procedure TSoftMixerPlaybackStream.Unlock(); +begin + SDL_mutexV(internalLock); +end; + +class function TSoftMixerPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16; +begin + case fmt of + asfU8: Result := AUDIO_U8; + asfS8: Result := AUDIO_S8; + asfU16LSB: Result := AUDIO_U16LSB; + asfS16LSB: Result := AUDIO_S16LSB; + asfU16MSB: Result := AUDIO_U16MSB; + asfS16MSB: Result := AUDIO_S16MSB; + asfU16: Result := AUDIO_U16; + asfS16: Result := AUDIO_S16; + else Result := 0; + end; +end; + +function TSoftMixerPlaybackStream.InitFormatConversion(): boolean; +var + err: integer; + srcFormat: UInt16; + dstFormat: UInt16; + srcFormatInfo: TAudioFormatInfo; + dstFormatInfo: TAudioFormatInfo; +begin + Result := false; + + srcFormatInfo := DecodeStream.GetAudioFormatInfo(); + dstFormatInfo := Engine.GetAudioFormatInfo(); + + srcFormat := ConvertAudioFormatToSDL(srcFormatInfo.Format); + dstFormat := ConvertAudioFormatToSDL(dstFormatInfo.Format); + + if ((srcFormat = 0) or (dstFormat = 0)) then + begin + Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion'); + Exit; + end; + + if (SDL_BuildAudioCVT(@cvt, + srcFormat, srcFormatInfo.Channels, srcFormatInfo.SampleRate, + dstFormat, dstFormatInfo.Channels, dstFormatInfo.SampleRate) = -1) then + begin + Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion'); + Exit; + end; + + Result := true; +end; + +function TSoftMixerPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean; +begin + result := false; + + Reset(); + + if not assigned(decodeStream) then + Exit; + Self.DecodeStream := decodeStream; + if not InitFormatConversion() then + Exit; + + _volume := 100; + + result := true; +end; + +procedure TSoftMixerPlaybackStream.Close(); +begin + Reset(); +end; + +procedure TSoftMixerPlaybackStream.Play(); +var + mixer: TAudioMixerStream; +begin + if (status <> ssPaused) then + begin + // rewind + if assigned(DecodeStream) then + DecodeStream.Position := 0; + end; + status := ssPlaying; + + mixer := Engine.GetMixer(); + if (mixer <> nil) then + mixer.AddStream(Self); +end; + +procedure TSoftMixerPlaybackStream.Pause(); +var + mixer: TAudioMixerStream; +begin + status := ssPaused; + + mixer := Engine.GetMixer(); + if (mixer <> nil) then + mixer.RemoveStream(Self); +end; + +procedure TSoftMixerPlaybackStream.Stop(); +var + mixer: TAudioMixerStream; +begin + status := ssStopped; + + mixer := Engine.GetMixer(); + if (mixer <> nil) then + mixer.RemoveStream(Self); +end; + +function TSoftMixerPlaybackStream.IsLoaded(): boolean; +begin + result := assigned(DecodeStream); +end; + +function TSoftMixerPlaybackStream.GetLoop(): boolean; +begin + result := Loop; +end; + +procedure TSoftMixerPlaybackStream.SetLoop(Enabled: boolean); +begin + Loop := Enabled; +end; + +function TSoftMixerPlaybackStream.GetLength(): real; +begin + if assigned(DecodeStream) then + result := DecodeStream.Length + else + result := -1; +end; + +function TSoftMixerPlaybackStream.GetStatus(): TStreamStatus; +begin + result := status; +end; + +{* + * Note: 44.1kHz to 48kHz conversion or vice versa is not supported + * by SDL at the moment. 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. Although this is not + * audible in most cases it needs synchronization with the video + * or the lyrics timer. + * Using libsamplerate might give better results. + *} +function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; +var + decodeBufSize: integer; + sampleBufSize: integer; + nBytesDecoded: integer; + frameSize: integer; + remFrameBytes: integer; + copyCnt: integer; + BytesNeeded: integer; +begin + Result := -1; + + BytesNeeded := BufSize; + + // copy remaining data from the last call to the result-buffer + if (BytesAvail > 0) then + begin + copyCnt := Min(BufSize, BytesAvail); + Move(SampleBuffer[SampleBufferPos], Buffer[0], copyCnt); + Dec(BytesAvail, copyCnt); + Dec(BytesNeeded, copyCnt); + if (BytesNeeded = 0) then + begin + // Result-Buffer is full -> no need to decode more data. + // The sample-buffer might even contain some data for the next call + Inc(SampleBufferPos, copyCnt); + Result := BufSize; + Exit; + end; + end; + + if not assigned(DecodeStream) then + Exit; + + // calc number of bytes to decode + decodeBufSize := Ceil(BufSize / cvt.len_ratio); + // assure that the decode-size is a multiple of the frame size + frameSize := DecodeStream.GetAudioFormatInfo().FrameSize; + remFrameBytes := decodeBufSize mod frameSize; + if (remFrameBytes > 0) then + decodeBufSize := decodeBufSize + (frameSize - remFrameBytes); + + // calc buffer size + sampleBufSize := decodeBufSize * cvt.len_mult; + + // resize buffer if necessary. + // The required buffer-size will be smaller than the result-buffer + // in most cases (if the decoded signal is mono or has a lesser bitrate). + // If the output-rate is 44.1kHz and the decode-rate is 48kHz or 96kHz it + // will be ~1.09 or ~2.18 times bigger. Those extra memory consumption + // should be reasonable. If not we should call TDecodeStream.ReadData() + // multiple times. + // Note: we do not decrease the buffer by the count of bytes used from + // the previous call of this function (bytesAvail). Otherwise the + // buffer will be reallocated each time this function is called just to + // add or remove a few bytes from the buffer. + // By not doing this the buffer's size should be rather stable and it + // will not be reallocated/resized at all if the BufSize params does not + // change in consecutive calls. + ReallocMem(SampleBuffer, sampleBufSize); + if not assigned(SampleBuffer) then + Exit; + + // decode data + nBytesDecoded := DecodeStream.ReadData(SampleBuffer, decodeBufSize); + if (nBytesDecoded = -1) then + Exit; + + // end-of-file reached -> stop playback + if (DecodeStream.EOF) then + Stop(); + + // resample decoded data + cvt.buf := PUint8(SampleBuffer); + cvt.len := nBytesDecoded; + if (SDL_ConvertAudio(@cvt) = -1) then + Exit; + + BytesAvail := cvt.len_cvt; + SampleBufferPos := 0; + + // copy data to result buffer + copyCnt := Min(BytesNeeded, BytesAvail); + Move(SampleBuffer[0], Buffer[BufSize - BytesNeeded], copyCnt); + Dec(BytesAvail, copyCnt); + Dec(BytesNeeded, copyCnt); + Inc(SampleBufferPos, copyCnt); + + Result := BufSize - BytesNeeded; +end; + +(* TODO: libsamplerate support +function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer; +var + convState: PSRC_STATE; + convData: SRC_DATA; + error: integer; +begin + // Note: needs mono->stereo conversion, multi-channel->stereo, etc. + // maybe we should use SDL for the channel-conversion stuff + // and use libsamplerate afterwards for the frequency-conversion + + //convState := src_new(SRC_SINC_MEDIUM_QUALITY, 2, @error); + //src_short_to_float_array(input, output, len); + convData. + if (src_process(convState, @convData) <> 0) then + begin + Log.LogError(src_strerror(src_error(convState)), 'TSoftMixerPlaybackStream.ReadData'); + Exit; + end; + src_float_to_short_array(); + //src_delete(convState); +end; +*) + +function TSoftMixerPlaybackStream.GetPosition: real; +begin + if assigned(DecodeStream) then + result := DecodeStream.Position + else + result := -1; +end; + +procedure TSoftMixerPlaybackStream.SetPosition(Time: real); +begin + if assigned(DecodeStream) then + DecodeStream.Position := Time; +end; + +function TSoftMixerPlaybackStream.GetVolume(): integer; +begin + result := _volume; +end; + +procedure TSoftMixerPlaybackStream.SetVolume(volume: integer); +begin + // clamp volume + if (volume > 100) then + _volume := 100 + else if (volume < 0) then + _volume := 0 + else + _volume := volume; +end; + + +{ TAudioPlayback_SoftMixer } + +function TAudioPlayback_SoftMixer.InitializePlayback: boolean; +begin + result := false; + + //Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer'); + + if(not InitializeAudioPlaybackEngine()) then + Exit; + + MixerStream := TAudioMixerStream.Create; + + if(not StartAudioPlaybackEngine()) then + Exit; + + result := true; +end; + +destructor TAudioPlayback_SoftMixer.Destroy; +begin + StopAudioPlaybackEngine(); + + FreeAndNil(MusicStream); + FreeAndNil(MixerStream); + FreeAndNil(FormatInfo); + + inherited Destroy(); +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.Load(const Filename: String): TSoftMixerPlaybackStream; +var + decodeStream: TAudioDecodeStream; + playbackStream: TSoftMixerPlaybackStream; +begin + Result := nil; + + decodeStream := AudioDecoder.Open(Filename); + if not assigned(decodeStream) then + begin + Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_SoftMixer'); + Exit; + end; + + playbackStream := TSoftMixerPlaybackStream.Create(Self); + if (not playbackStream.SetDecodeStream(decodeStream)) then + Exit; + + result := playbackStream; +end; + +procedure TAudioPlayback_SoftMixer.SetVolume(Volume: integer); +begin + // sets volume only for this application + MixerStream.Volume := Volume; +end; + +procedure TAudioPlayback_SoftMixer.SetMusicVolume(Volume: Integer); +begin + if assigned(MusicStream) then + MusicStream.Volume := Volume; +end; + +procedure TAudioPlayback_SoftMixer.SetLoop(Enabled: boolean); +begin + if assigned(MusicStream) then + MusicStream.SetLoop(Enabled); +end; + +function TAudioPlayback_SoftMixer.Open(Filename: string): boolean; +var + decodeStream: TAudioDecodeStream; +begin + Result := false; + + // free old MusicStream + MusicStream.Free(); + // and load new one + MusicStream := Load(Filename); + if not assigned(MusicStream) then + Exit; + + //Set Max Volume + SetMusicVolume(100); + + Result := true; +end; + +procedure TAudioPlayback_SoftMixer.Rewind; +begin + SetPosition(0); +end; + +procedure TAudioPlayback_SoftMixer.SetPosition(Time: real); +begin + if assigned(MusicStream) then + MusicStream.SetPosition(Time); +end; + +function TAudioPlayback_SoftMixer.GetPosition: real; +begin + if assigned(MusicStream) then + Result := MusicStream.GetPosition() + else + Result := -1; +end; + +function TAudioPlayback_SoftMixer.Length: real; +begin + if assigned(MusicStream) then + Result := MusicStream.GetLength() + else + Result := -1; +end; + +procedure TAudioPlayback_SoftMixer.Play; +begin + if assigned(MusicStream) then + MusicStream.Play(); +end; + +procedure TAudioPlayback_SoftMixer.Pause; +begin + if assigned(MusicStream) then + MusicStream.Pause(); +end; + +procedure TAudioPlayback_SoftMixer.Stop; +begin + if assigned(MusicStream) then + MusicStream.Stop(); +end; + +procedure TAudioPlayback_SoftMixer.Close; +begin + if assigned(MusicStream) then + begin + MusicStream.Close(); + end; +end; + +function TAudioPlayback_SoftMixer.Finished: boolean; +begin + if assigned(MusicStream) then + Result := (MusicStream.GetStatus() = ssStopped) + else + Result := true; +end; + +//Equalizer +procedure TAudioPlayback_SoftMixer.GetFFTData(var data: TFFTData); +begin + //Get Channel Data Mono and 256 Values +// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512); +end; + +// Interface for Visualizer +function TAudioPlayback_SoftMixer.GetPCMData(var data: TPCMData): Cardinal; +begin + result := 0; +end; + +function TAudioPlayback_SoftMixer.OpenSound(const Filename: String): TAudioPlaybackStream; +begin + result := Load(Filename); +end; + +procedure TAudioPlayback_SoftMixer.PlaySound(stream: TAudioPlaybackStream); +begin + if assigned(stream) then + stream.Play(); +end; + +procedure TAudioPlayback_SoftMixer.StopSound(stream: TAudioPlaybackStream); +begin + if assigned(stream) then + stream.Stop(); +end; + + +end. diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas index 8bbd297a..9b8cd606 100644 --- a/Game/Code/Classes/UMusic.pas +++ b/Game/Code/Classes/UMusic.pas @@ -109,10 +109,27 @@ type asfFloat // float ); - TAudioFormatInfo = record - Channels: byte; - SampleRate: integer; - Format: TAudioSampleFormat; +const + // Size of one sample (one channel only) in bytes + AudioSampleSize: array[TAudioSampleFormat] of integer = ( + 1, 1, // asfU8, asfS8 + 2, 2, // asfU16LSB, asfS16LSB + 2, 2, // asfU16MSB, asfS16MSB + 2, 2, // asfU16, asfS16 + 3, // asfS24 + 4, // asfS32 + 4 // asfFloat + ); + +type + TAudioFormatInfo = class + public + Channels : byte; + SampleRate : integer; + Format : TAudioSampleFormat; + FrameSize : integer; // calculated on construction + + constructor Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat); end; type @@ -307,6 +324,14 @@ var singleton_AudioManager : TInterfaceList = nil; +constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat); +begin + Self.Channels := Channels; + Self.SampleRate := SampleRate; + Self.Format := Format; + Self.FrameSize := AudioSampleSize[Format] * Channels; +end; + function AudioManager: TInterfaceList; begin if singleton_AudioManager = nil then @@ -404,7 +429,6 @@ begin AssignSingletonObjects(); - if VideoPlayback <> nil then begin end; diff --git a/Game/Code/UltraStar.dpr b/Game/Code/UltraStar.dpr index c0825d2f..55046af3 100644 --- a/Game/Code/UltraStar.dpr +++ b/Game/Code/UltraStar.dpr @@ -167,9 +167,17 @@ uses {$IFDEF UsePortaudioInput} UAudioInput_Portaudio in 'Classes\UAudioInput_Portaudio.pas', {$ENDIF} +{$IF Defined(UsePortaudioPlayback) or Defined(UseSDLPlayback)} + UFFT in 'lib\fft\UFFT.pas', + //samplerate in 'lib\samplerate\samplerate.pas', + UAudioPlayback_Softmixer in 'Classes\UAudioPlayback_SoftMixer.pas', +{$IFEND} {$IFDEF UsePortaudioPlayback} UAudioPlayback_Portaudio in 'Classes\UAudioPlayback_Portaudio.pas', {$ENDIF} +{$IFDEF UseSDLPlayback} + UAudioPlayback_SDL in 'Classes\UAudioPlayback_SDL.pas', +{$ENDIF} //------------------------------ diff --git a/Game/Code/switches.inc b/Game/Code/switches.inc index d0f187bb..33f00ce3 100644 --- a/Game/Code/switches.inc +++ b/Game/Code/switches.inc @@ -42,8 +42,8 @@ {$DEFINE UseBASSInput} {$ELSE} {$DEFINE UseFFMpegDecoder} - {$DEFINE UsePortaudioPlayback} - //{$DEFINE UseSDLPlayback} + //{$DEFINE UsePortaudioPlayback} + {$DEFINE UseSDLPlayback} {$DEFINE UsePortaudioInput} {$DEFINE UsePortmixer} {$ENDIF} -- cgit v1.2.3