diff options
Diffstat (limited to 'Game/Code')
-rw-r--r-- | Game/Code/Classes/UAudioDecoder_FFMpeg.pas | 170 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioInput_Bass.pas | 85 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioInput_Portaudio.pas | 2 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_Portaudio.pas | 881 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_SDL.pas | 113 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 809 | ||||
-rw-r--r-- | Game/Code/Classes/UMusic.pas | 34 | ||||
-rw-r--r-- | Game/Code/UltraStar.dpr | 8 | ||||
-rw-r--r-- | Game/Code/switches.inc | 4 |
9 files changed, 1262 insertions, 844 deletions
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}
|