Skip to content

Commit

Permalink
Fixes #1093: High bitrate files are lagging
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël committed Jan 12, 2020
1 parent 79b2403 commit bb2d29a
Show file tree
Hide file tree
Showing 19 changed files with 169 additions and 42 deletions.
120 changes: 83 additions & 37 deletions Dopamine.Core/Audio/CSCorePlayer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using CSCore;
using CSCore.Codecs;
using CSCore.CoreAudioAPI;
using CSCore.DSP;
using CSCore.Ffmpeg;
using CSCore.MediaFoundation;
using CSCore.SoundOut;
using CSCore.Streams;
Expand Down Expand Up @@ -39,6 +39,7 @@ public class CSCorePlayer : IPlayer, IDisposable
private SingleBlockNotificationStream notificationSource;
private float volume = 1.0F;
private ISoundOut soundOut;
Stream audioStream;

private MMDevice selectedMMDevice;
IList<MMDevice> mmDevices = new List<MMDevice>();
Expand All @@ -49,15 +50,13 @@ public class CSCorePlayer : IPlayer, IDisposable

// Flags
private bool isPlaying;
private bool hasMediaFoundationSupport = false;

// To detect redundant calls
private bool disposedValue = false;

public CSCorePlayer()
{
// Register the NVorbis new codec
CodecFactory.Instance.Register("ogg-vorbis", new CodecFactoryEntry((s) => new NVorbisSource(s).ToWaveSource(), ".ogg"));

this.canPlay = true;
this.canPause = false;
this.canStop = false;
Expand All @@ -77,6 +76,12 @@ public static CSCorePlayer Instance

public SingleBlockNotificationStream NotificationSource => this.notificationSource;

public bool HasMediaFoundationSupport
{
get { return this.hasMediaFoundationSupport; }
set { this.hasMediaFoundationSupport = value; }
}

public string Filename
{
get { return this.filename; }
Expand Down Expand Up @@ -262,19 +267,35 @@ public void Play(string filename, AudioDevice audioDevice)

private IWaveSource GetCodec(string filename)
{
IWaveSource waveSource;
IWaveSource waveSource = null;
bool useFfmpegDecoder = true;

if (Path.GetExtension(this.filename.ToLower()) == FileFormats.MP3)
// FfmpegDecoder doesn't support WMA lossless. If Windows Media Foundation is available,
// we can use MediaFoundationDecoder for WMA files, which supports WMA lossless.
if (this.hasMediaFoundationSupport && Path.GetExtension(filename).ToLower().Equals(FileFormats.WMA))
{
// For MP3's, we force usage of MediaFoundationDecoder. CSCore uses DmoMp3Decoder
// by default. DmoMp3Decoder however is very slow at playing MP3's from a NAS.
// So we're using MediaFoundationDecoder until DmoMp3Decoder has improved.
waveSource = new MediaFoundationDecoder(this.filename);
try
{
waveSource = new MediaFoundationDecoder(filename);
useFfmpegDecoder = false;
}
catch (Exception)
{
}
}
else

if (useFfmpegDecoder)
{
// Other file formats are using the default decoders
waveSource = CodecFactory.Instance.GetCodec(this.filename);
// waveSource = new FfmpegDecoder(this.filename);

// On some systems, files with special characters (e.g. "æ", "ø") can't be opened by FfmpegDecoder.
// This exception is thrown: avformat_open_input returned 0xfffffffe: No such file or directory.
// StackTrace: at CSCore.Ffmpeg.FfmpegCalls.AvformatOpenInput(AVFormatContext** formatContext, String url)
// This issue can't be reproduced for now, so we're using a stream as it works in all cases.
// See: https://github.com/digimezzo/Dopamine/issues/746
// And: https://github.com/filoe/cscore/issues/344
this.audioStream = File.OpenRead(filename);
waveSource = new FfmpegDecoder(this.audioStream);
}

// If the SampleRate < 32000, make it 32000. The Equalizer's maximum frequency is 16000Hz.
Expand Down Expand Up @@ -340,37 +361,49 @@ public void Stop()
private void InitializeSoundOut(IWaveSource soundSource)
{
// Create SoundOut
this.soundOut = new WasapiOut(this.eventSync, this.audioClientShareMode, this.latency, ThreadPriority.Highest);
if (this.hasMediaFoundationSupport)
{
this.soundOut = new WasapiOut(this.eventSync, this.audioClientShareMode, this.latency, ThreadPriority.Highest);

// Map stereo or mono file to all channels
((WasapiOut)this.soundOut).UseChannelMixingMatrices = this.useAllAvailableChannels;
// Map stereo or mono file to all channels
((WasapiOut)this.soundOut).UseChannelMixingMatrices = this.useAllAvailableChannels;

if (this.selectedMMDevice == null)
{
// If no output device was provided, we're playing on the default device.
// In such case, we want to detect when the default device changes.
// This is done by setting stream routing options
((WasapiOut)this.soundOut).StreamRoutingOptions = StreamRoutingOptions.All;
}
else
{
// If an output device was provided, assign it to soundOut.Device.
// Only allow stream routing when the device was disconnected.
((WasapiOut)this.soundOut).StreamRoutingOptions = StreamRoutingOptions.OnDeviceDisconnect;
((WasapiOut)this.soundOut).Device = this.selectedMMDevice;
}
if (this.selectedMMDevice == null)
{
// If no output device was provided, we're playing on the default device.
// In such case, we want to detect when the default device changes.
// This is done by setting stream routing options
((WasapiOut)this.soundOut).StreamRoutingOptions = StreamRoutingOptions.All;
}
else
{
// If an output device was provided, assign it to soundOut.Device.
// Only allow stream routing when the device was disconnected.
((WasapiOut)this.soundOut).StreamRoutingOptions = StreamRoutingOptions.OnDeviceDisconnect;
((WasapiOut)this.soundOut).Device = this.selectedMMDevice;
}

// Initialize SoundOut
this.notificationSource = new SingleBlockNotificationStream(soundSource.ToSampleSource());
this.soundOut.Initialize(this.notificationSource.ToWaveSource(16));
// Initialize SoundOut
this.notificationSource = new SingleBlockNotificationStream(soundSource.ToSampleSource());
this.soundOut.Initialize(this.notificationSource.ToWaveSource(16));

if (inputStreamList.Count != 0)
{
foreach (var inputStream in inputStreamList)
if (inputStreamList.Count != 0)
{
this.notificationSource.SingleBlockRead += inputStream;
foreach (var inputStream in inputStreamList)
{
this.notificationSource.SingleBlockRead += inputStream;
}
}
}
else
{
this.soundOut = new DirectSoundOut(this.latency, ThreadPriority.Highest);

// Initialize SoundOut
// Spectrum analyzer performance is only acceptable with WasapiOut,
// so we're not setting a notificationSource for DirectSoundOut
this.soundOut.Initialize(soundSource);
}

this.soundOut.Stopped += this.SoundOutStoppedHandler;
this.soundOut.Volume = this.volume;
Expand Down Expand Up @@ -413,6 +446,19 @@ private void CloseSoundOut()
//Swallow
}
}

// audioStream
if (this.audioStream != null)
{
try
{
this.audioStream.Dispose();
}
catch (Exception)
{
//Swallow
}
}
}

public CSCore.Streams.Effects.Equalizer Create10BandEqualizer(ISampleSource source)
Expand Down
2 changes: 2 additions & 0 deletions Dopamine.Core/Audio/IPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface IPlayer

string Filename { get; }

bool HasMediaFoundationSupport { get; set; }

void Stop();

void Play(string filename, AudioDevice audioDevice);
Expand Down
2 changes: 1 addition & 1 deletion Dopamine.Core/Audio/IPlayerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public interface IPlayerFactory
{
IPlayer Create();
IPlayer Create(bool hasMediaFoundationSupport);
}
}
7 changes: 5 additions & 2 deletions Dopamine.Core/Audio/PlayerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
{
public class PlayerFactory : IPlayerFactory
{
public IPlayer Create()
public IPlayer Create(bool hasMediaFoundationSupport)
{
return CSCorePlayer.Instance;
IPlayer player = CSCorePlayer.Instance;
player.HasMediaFoundationSupport = hasMediaFoundationSupport;

return player;
}
}
}
7 changes: 7 additions & 0 deletions Dopamine.Core/Base/ProductInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public sealed class ProductInformation
LicenseUrl = "https://opensource.org/licenses/MIT"
},
new ExternalComponent
{
Name = "FFmpeg",
Description = "A collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata.",
Url = "https://github.com/FFmpeg/FFmpeg",
LicenseUrl = "https://github.com/FFmpeg/FFmpeg/blob/master/LICENSE.md"
},
new ExternalComponent
{
Name = "Font Awesome",
Description = "Font Awesome by Dave Gandy.",
Expand Down
3 changes: 3 additions & 0 deletions Dopamine.Packager/PackagerConfiguration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<Directory>Languages</Directory>
<Directory>x64</Directory>
<Directory>x86</Directory>
<Directory>FFmpeg\bin\windows\x64</Directory>
<Directory>FFmpeg\bin\windows\x86</Directory>
</Directories>
<Files>
<File>Dopamine.exe</File>
Expand All @@ -34,6 +36,7 @@
<File>Dopamine.Icons.dll</File>
<File>Dopamine.Services.dll</File>
<File>CSCore.dll</File>
<File>CSCore.Ffmpeg.dll</File>
<File>Ionic.Zip.dll</File>
<File>SQLite-net.dll</File>
<File>SQLitePCLRaw.batteries_green.dll</File>
Expand Down
2 changes: 2 additions & 0 deletions Dopamine.Services/Playback/IPlaybackService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public interface IPlaybackService

bool IsSavingPlaybackCounters { get; }

bool HasMediaFoundationSupport { get; }

IList<TrackViewModel> Queue { get; }

bool Shuffle { get; }
Expand Down
11 changes: 9 additions & 2 deletions Dopamine.Services/Playback/PlaybackService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Dopamine.Core.Audio;
using Dopamine.Core.Base;
using Dopamine.Core.Extensions;
using Dopamine.Core.Helpers;
using Dopamine.Data;
using Dopamine.Data.Entities;
using Dopamine.Data.Metadata;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class PlaybackService : IPlaybackService
private bool mute;
private bool isPlayingPreviousTrack;
private IPlayer player;
private bool hasMediaFoundationSupport = false;

private bool isLoadingSettings;

Expand Down Expand Up @@ -78,6 +80,8 @@ public class PlaybackService : IPlaybackService

public bool IsSavingPlaybackCounters => this.isSavingPLaybackCounters;

public bool HasMediaFoundationSupport => this.hasMediaFoundationSupport;

public bool IsStopped
{
get
Expand Down Expand Up @@ -898,14 +902,17 @@ public async Task<EnqueueResult> AddAlbumsToQueueAsync(IList<AlbumViewModel> alb

private async void Initialize()
{
// Media Foundation
this.hasMediaFoundationSupport = MediaFoundationHelper.HasMediaFoundationSupport();

// Settings
this.SetPlaybackSettings();

// PlayerFactory
this.playerFactory = new PlayerFactory();

// Player (default for now, can be changed later when playing a file)
this.player = this.playerFactory.Create();
this.player = this.playerFactory.Create(this.hasMediaFoundationSupport);

// Audio device
await this.SetAudioDeviceAsync();
Expand Down Expand Up @@ -1042,7 +1049,7 @@ private async Task StartPlaybackAsync(TrackViewModel track, bool silent = false)
this.SetPlaybackSettings();

// Play the Track from its runtime path (current or temporary)
this.player = this.playerFactory.Create();
this.player = this.playerFactory.Create(this.hasMediaFoundationSupport);

this.player.SetPlaybackSettings(this.Latency, this.EventMode, this.ExclusiveMode, this.activePreset.Bands, this.UseAllAvailableChannels);
this.player.SetVolume(silent | this.Mute ? 0.0f : this.Volume);
Expand Down
25 changes: 25 additions & 0 deletions Dopamine/Dopamine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,30 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SubType>Designer</SubType>
</Content>
<Content Include="FFmpeg\bin\windows\x64\avcodec-57.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x64\avformat-57.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x64\avutil-55.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x64\swresample-2.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x86\avcodec-57.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x86\avformat-57.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x86\avutil-55.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="FFmpeg\bin\windows\x86\swresample-2.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Icons\Dopamine - White.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -1410,6 +1434,7 @@
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.9\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.9\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
Expand Down
26 changes: 26 additions & 0 deletions Dopamine/Dopamine.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@
<!-- Program Files -->
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="INSTALLFOLDER" Name="$(var.Product)">
<!-- Ffmpeg -->
<Directory Id="FFMPEGFOLDER" Name="FFmpeg">
<Directory Id="FFMPEGBINFOLDER" Name="bin">
<Directory Id="FFMPEGBINWINDOWSFOLDER" Name="windows">
<Directory Id="FFMPEGBINWINDOWSX64FOLDER" Name="x64">
<Component Id="FFMPEGX64CMP" DiskId="1" Guid="A212950C-30F5-4130-9BC0-431DE13E9A40">
<File Id="AVCODECX64" Source="FFmpeg\bin\windows\x64\avcodec-57.dll" />
<File Id="AVFORMATX64" Source="FFmpeg\bin\windows\x64\avformat-57.dll" />
<File Id="AVUTILX64" Source="FFmpeg\bin\windows\x64\avutil-55.dll" />
<File Id="SWRESAMPLEX64" Source="FFmpeg\bin\windows\x64\swresample-2.dll" />
</Component>
</Directory>
<Directory Id="FFMPEGBINWINDOWSX86FOLDER" Name="x86">
<Component Id="FFMPEGX86CMP" DiskId="1" Guid="DCB59B57-7456-4F78-A919-5F0B9D35FF1C">
<File Id="AVCODECX86" Source="FFmpeg\bin\windows\x86\avcodec-57.dll" />
<File Id="AVFORMATX86" Source="FFmpeg\bin\windows\x86\avformat-57.dll" />
<File Id="AVUTILX86" Source="FFmpeg\bin\windows\x86\avutil-55.dll" />
<File Id="SWRESAMPLEX86" Source="FFmpeg\bin\windows\x86\swresample-2.dll" />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
<!-- SQLite -->
<Directory Id="X64FOLDER" Name="x64">
<Component Id="X64CMP" DiskId="1" Guid="89332F0B-AF41-49AC-9B8D-D1E8AD9E50F1">
Expand Down Expand Up @@ -160,6 +183,7 @@
<File Source="Dopamine.Icons.dll" />
<File Source="Dopamine.Services.dll" />
<File Source="CSCore.dll" />
<File Source="CSCore.Ffmpeg.dll" />
<File Source="GongSolutions.WPF.DragDrop.dll" />
<File Source="Ionic.Zip.dll" />
<File Source="NVorbis.dll" />
Expand Down Expand Up @@ -240,6 +264,8 @@
<!-- Components to add -->
<!-- ================= -->
<Feature Id="DefaultFeature" Title="Main Feature" Level="1">
<ComponentRef Id="FFMPEGX64CMP" />
<ComponentRef Id="FFMPEGX86CMP" />
<ComponentRef Id="ICONSCMP" />
<ComponentRef Id="LANGUAGESCMP" />
<ComponentRef Id="EQUALIZERCMP" />
Expand Down
Binary file added Dopamine/FFmpeg/bin/windows/x64/avcodec-57.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x64/avformat-57.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x64/avutil-55.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x64/swresample-2.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x86/avcodec-57.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x86/avformat-57.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x86/avutil-55.dll
Binary file not shown.
Binary file added Dopamine/FFmpeg/bin/windows/x86/swresample-2.dll
Binary file not shown.
Loading

0 comments on commit bb2d29a

Please sign in to comment.