diff options
-rw-r--r-- | mediaplugin/src/media/UAudioConverter_SRC.pas | 335 | ||||
-rw-r--r-- | mediaplugin/src/ultrastardx.dpr | 3 |
2 files changed, 336 insertions, 2 deletions
diff --git a/mediaplugin/src/media/UAudioConverter_SRC.pas b/mediaplugin/src/media/UAudioConverter_SRC.pas new file mode 100644 index 00000000..c4bf4358 --- /dev/null +++ b/mediaplugin/src/media/UAudioConverter_SRC.pas @@ -0,0 +1,335 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + *} + +unit UAudioConverter_SRC; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + UMusic, + ULog, + samplerate, + UMediaPlugin, + SysUtils, + Math; + +type + TAudioConverter_SRC = class(TInterfacedObject, IAudioConverter) + public + function GetName(): string; + function GetPriority(): integer; + function Init(): boolean; + function Finalize(): boolean; + + function Open(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo): TAudioConvertStream; + end; + + TAudioConvertStream_SRC = class(TAudioConvertStream) + private + ConverterState: PSRC_STATE; + ConversionData: SRC_DATA; + FormatConverter: TAudioConvertStream; + function Init(): boolean; + protected + constructor Create(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo); + public + destructor Destroy(); override; + + class function Open(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo): TAudioConvertStream_SRC; + + function Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; override; + function GetOutputBufferSize(InputSize: integer): integer; override; + function GetRatio(): double; override; + end; + + // Note: SRC (=libsamplerate) provides several converters with different quality + // speed trade-offs. The SINC-types are slow but offer best quality. + // The SRC_SINC_* converters are too slow for realtime conversion, + // (SRC_SINC_FASTEST is approx. ten times slower than SRC_LINEAR) resulting + // in audible clicks and pops. + // SRC_LINEAR is very fast and should have a better quality than SRC_ZERO_ORDER_HOLD + // because it interpolates the samples. Normal "non-audiophile" users should not + // be able to hear a difference between the SINC_* ones and LINEAR. Especially + // if people sing along with the song. + // But FFmpeg might offer a better quality/speed ratio than SRC_LINEAR. + const + SRC_CONVERTER_TYPE = SRC_LINEAR; + +implementation + +uses + ctypes, + UAudioConverter_SDL; + +{ TAudioConverter_SRC } + +function TAudioConverter_SRC.GetName(): string; +begin + Result := 'AudioConverter_SRC'; +end; + +function TAudioConverter_SRC.GetPriority(): integer; +begin + Result := 30; +end; + +function TAudioConverter_SRC.Init(): boolean; +begin + Result := true; +end; + +function TAudioConverter_SRC.Finalize(): boolean; +begin + Result := true; +end; + +function TAudioConverter_SRC.Open(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo): TAudioConvertStream; +begin + Result := TAudioConvertStream_SRC.Open(SrcFormatInfo, DstFormatInfo); +end; + +{ TAudioConvertStream_SRC } + +constructor TAudioConvertStream_SRC.Create(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo); +begin + inherited Create(SrcFormatInfo, DstFormatInfo); +end; + +destructor TAudioConvertStream_SRC.Destroy(); +begin + if (ConverterState <> nil) then + src_delete(ConverterState); + FormatConverter.Free; + inherited; +end; + +class function TAudioConvertStream_SRC.Open(SrcFormatInfo: TAudioFormatInfo; + DstFormatInfo: TAudioFormatInfo): TAudioConvertStream_SRC; +var + Stream: TAudioConvertStream_SRC; +begin + Result := nil; + Stream := TAudioConvertStream_SRC.Create(SrcFormatInfo, DstFormatInfo); + if (not Stream.Init()) then + begin + Stream.Free; + Exit; + end; + Result := Stream; +end; + +function TAudioConvertStream_SRC.Init(): boolean; +var + error: integer; + TempSrcFormatInfo: TAudioFormatInfo; + TempDstFormatInfo: TAudioFormatInfo; +begin + Result := false; + + FormatConverter := nil; + + // SRC does not handle channel or format conversion + if ((SrcFormatInfo.Channels <> DstFormatInfo.Channels) or + not (SrcFormatInfo.Format in [asfS16, asfFloat])) then + begin + // SDL can not convert to float, so we have to convert to SInt16 first + TempSrcFormatInfo := TAudioFormatInfo.Create( + SrcFormatInfo.Channels, SrcFormatInfo.SampleRate, SrcFormatInfo.Format); + TempDstFormatInfo := TAudioFormatInfo.Create( + DstFormatInfo.Channels, SrcFormatInfo.SampleRate, asfS16); + + // init format/channel conversion + FormatConverter := TAudioConvertStream_SDL.Open(TempSrcFormatInfo, TempDstFormatInfo); + + // this info was copied so we do not need it anymore + TempSrcFormatInfo.Free; + TempDstFormatInfo.Free; + + // leave if the format is not supported + if (FormatConverter = nil) then + Exit; + + // adjust our copy of the input audio-format for SRC conversion + Self.SrcFormatInfo.Channels := DstFormatInfo.Channels; + Self.SrcFormatInfo.Format := asfS16; + end; + + if ((DstFormatInfo.Format <> asfS16) and + (DstFormatInfo.Format <> asfFloat)) then + begin + Log.LogError('Unsupported output format', 'TAudioConverter_SRC.Init'); + Exit; + end; + + ConversionData.src_ratio := DstFormatInfo.SampleRate / SrcFormatInfo.SampleRate; + if (src_is_valid_ratio(ConversionData.src_ratio) = 0) then + begin + Log.LogError('Invalid samplerate ratio', 'TAudioConverter_SRC.Init'); + Exit; + end; + + ConverterState := src_new(SRC_CONVERTER_TYPE, DstFormatInfo.Channels, @error); + if (ConverterState = nil) then + begin + Log.LogError('src_new() failed: ' + src_strerror(error), 'TAudioConverter_SRC.Init'); + Exit; + end; + + Result := true; +end; + +function TAudioConvertStream_SRC.Convert(InputBuffer: PByteArray; OutputBuffer: PByteArray; var InputSize: integer): integer; +var + FloatInputBuffer: PSingle; + FloatOutputBuffer: PSingle; + TempBuffer: PByteArray; + TempSize: integer; + NumSamples: integer; + OutputSize: integer; + error: integer; +begin + Result := -1; + + TempBuffer := nil; + + // format conversion with external converter (to correct number of channels and format) + if (assigned(FormatConverter)) then + begin + TempSize := FormatConverter.GetOutputBufferSize(InputSize); + GetMem(TempBuffer, TempSize); + InputSize := FormatConverter.Convert(InputBuffer, TempBuffer, InputSize); + InputBuffer := TempBuffer; + end; + + if (InputSize <= 0) then + begin + // avoid div-by-zero problems + if (InputSize = 0) then + Result := 0; + if (TempBuffer <> nil) then + FreeMem(TempBuffer); + Exit; + end; + + if (SrcFormatInfo.Format = asfFloat) then + begin + FloatInputBuffer := PSingle(InputBuffer); + end else begin + NumSamples := InputSize div AudioSampleSize[SrcFormatInfo.Format]; + GetMem(FloatInputBuffer, NumSamples * SizeOf(Single)); + src_short_to_float_array(PCshort(InputBuffer), PCfloat(FloatInputBuffer), NumSamples); + end; + + // calculate approx. output size + OutputSize := Ceil(InputSize * ConversionData.src_ratio); + + if (DstFormatInfo.Format = asfFloat) then + begin + FloatOutputBuffer := PSingle(OutputBuffer); + end else begin + NumSamples := OutputSize div AudioSampleSize[DstFormatInfo.Format]; + GetMem(FloatOutputBuffer, NumSamples * SizeOf(Single)); + end; + + with ConversionData do + begin + data_in := PCFloat(FloatInputBuffer); + input_frames := InputSize div SrcFormatInfo.FrameSize; + data_out := PCFloat(FloatOutputBuffer); + output_frames := OutputSize div DstFormatInfo.FrameSize; + // TODO: set this to 1 at end of file-playback + end_of_input := 0; + end; + + error := src_process(ConverterState, @ConversionData); + if (error <> 0) then + begin + Log.LogError(src_strerror(error), 'TAudioConverter_SRC.Convert'); + if (SrcFormatInfo.Format <> asfFloat) then + FreeMem(FloatInputBuffer); + if (DstFormatInfo.Format <> asfFloat) then + FreeMem(FloatOutputBuffer); + if (TempBuffer <> nil) then + FreeMem(TempBuffer); + Exit; + end; + + if (SrcFormatInfo.Format <> asfFloat) then + FreeMem(FloatInputBuffer); + + if (DstFormatInfo.Format <> asfFloat) then + begin + NumSamples := ConversionData.output_frames_gen * DstFormatInfo.Channels; + src_float_to_short_array(PCfloat(FloatOutputBuffer), PCshort(OutputBuffer), NumSamples); + FreeMem(FloatOutputBuffer); + end; + + // free format conversion buffer if used + if (TempBuffer <> nil) then + FreeMem(TempBuffer); + + if (assigned(FormatConverter)) then + InputSize := ConversionData.input_frames_used * FormatConverter.SrcFormatInfo.FrameSize + else + InputSize := ConversionData.input_frames_used * SrcFormatInfo.FrameSize; + + // set result to output size according to SRC + Result := ConversionData.output_frames_gen * DstFormatInfo.FrameSize; +end; + +function TAudioConvertStream_SRC.GetOutputBufferSize(InputSize: integer): integer; +begin + Result := Ceil(InputSize * GetRatio()); +end; + +function TAudioConvertStream_SRC.GetRatio(): double; +begin + // if we need additional channel/format conversion, use this ratio + if (assigned(FormatConverter)) then + Result := FormatConverter.GetRatio() + else + Result := 1.0; + + // now the SRC ratio (Note: the format might change from SInt16 to float) + Result := Result * + ConversionData.src_ratio * + (DstFormatInfo.FrameSize / SrcFormatInfo.FrameSize); +end; + +initialization + MediaManager.add(TAudioConverter_SRC.Create); + +end.
\ No newline at end of file diff --git a/mediaplugin/src/ultrastardx.dpr b/mediaplugin/src/ultrastardx.dpr index 393c7c71..f0b3cfc6 100644 --- a/mediaplugin/src/ultrastardx.dpr +++ b/mediaplugin/src/ultrastardx.dpr @@ -283,9 +283,8 @@ uses UAudioPlayback_Portaudio in 'media\UAudioPlayback_Portaudio.pas', {$ENDIF} UAudioConverter_SDL in 'media\UAudioConverter_SDL.pas', -// TODO {$IFDEF UseSRCResample} - //UAudioConverter_SRC in 'media\UAudioConverter_SRC.pas', + UAudioConverter_SRC in 'media\UAudioConverter_SRC.pas', {$ENDIF} // fallback dummy, must be last UMedia_dummy in 'media\UMedia_dummy.pas', |