This website uses cookies on its adverts and sponsored links. By clicking the "Accept" button you are consenting to their use.

Read more

Accept


Mobile Tech Tracker
≡ sections
Home

Tech Advice

Tech Thoughts

Apps

Tech News

About

Welcome to Mobile Tech Tracker. Our mission is to help technically-minded people to become better versions of themselves and to help ordinary people to use modern smart technologies to their own advantage. If you want to support us, please consider visiting the pages of our advertisers.


Building .NET Core sound application - part 2

This is the second part in our series of tutorials on building audio capabilities into .NET Core, which the platform doesn't have out of the box. In the first tutorial of this series, we have set up a basic project structure and have added a class that enabled us to play audio on Windows.

However, .NET Core wasn't created for Windows alone. Therefore, in this tutorial, we will add Linux playback capabilities to our application.

Although there is already a reliable way of playing audio on .NET Core in a cross-platform fashion, it relies on a number of dependencies and inter-operability with Node.js, which is achieved by NodeServices. The solution presented in this series of tutorials, however, doesn't rely on any third party dependencies at all. It doesn't even require any additional standard libraries from .NET Core or ASP.NET Core.


Introducing ALSA

Advanced Linux Sound Architecture (ALSA) is de-facto standard suite of software that enables audio playback on Linux operating systems. Of course, being an open-source OS, there is no 100% guarantee that a particular distribution of Linux will have ALSA. However, all of the mainstream distributions, such as Debian, Ubuntu and Fedora do. Therefore, if you write any Linux software that specifically relies on ALSA audio playback capabilities, chances are that it will work on the vast majority of Linux machines.

ALSA comes with a handy command line utility called aplay, which is used to play audio from specified files. The utility comes with an intuitive command syntax. For example, to play an MP3 file called "audio.mp3" that is located inside of the currently selected folder, you can use the following command:

aplay audio.mp3

Another useful utility that comes with ALSA is amixer. This utility allows to adjust playback volumes on different hardware devices on any available audio cards. In this context, a device is something that is connected to a particular hardware output. For example, there will be a distinct device used by HDMI output and another device used by a standard audio jack.

To adjust volume on the first device from the default master device to 75%, you can execute the following command:

amixer sset Master 75%

There are many other things that you can do with various ALSA command line utilities, which are beyond the scope of this article. However, one thing that is relevant is the fact that .NET Core allows you to execute any arbitrary commands directly from the code, regardless of what OS it's running on.


Execute Linux shell commands from your code

Remember the IPlayer interface that we implemented in the first part or our tutorial that looked like this?

using System.Threading.Tasks;

namespace NetCoreAudio.Interfaces {
  public interface IPlayer   {
    Task Play(string fileName);
    Task Pause();
    Task Resume();
    Task Stop();
  }
}

We will how add a Linux implementation of it as a separate class. Let's call it LinuxPlayer.

Once the class is created with placeholders for the methods, we can add the following private field into it:

private Process _process = null;

SystemDiagnostics.Process is an in-built class from the standard library that allows you to start any new processes. In this case, it will enable us to use aplay on Linux. This will be achieved by the following private method:

private Process StartAplayPlayback(string fileName)
{
  var escapedArgs = fileName.Replace("\"", "\\\"");

  var process = new Process()
  {
    StartInfo = new ProcessStartInfo
    {
      FileName = "/bin/bash",
      Arguments = $"-c \"aplay {escapedArgs} -i\"",
      RedirectStandardOutput = true,
      RedirectStandardInput = true,
      UseShellExecute = false,
      CreateNoWindow = true,
    }
  };
  process.Start();
  return process;
}

I'll have to explain what are we doing here.

First, we need to escape slashes in our file name. This is so they are actually processes as slash symbols by the process and not as escape characters.

FileName field of ProcessStartInfo is set to the standard Linux Bash (equivalent of cmd on Windows). This allows us to run any commands that you would normally be able to from Linux terminal.

Arguments field consists of the actual "aplay" command with the file name set as the parameter. "i" flag allows us to play audio in interactive mode, so we will be able to pause and resume it from the standard input from within the process.

As we are not using the default shell from the operating system, we are setting UseShellExecute field to false. We are running the command in a completely headless mode inside of the background process. Therefore we are setting CreateNoWindow field to true.

Finally, to enable us to inject input into the process while it's running and read the output right from the code, we are setting RedirectStandardOutput and RedirectStandardInput fields to true.

We can now call StartAplayPlayback method from the implementation of Play() method inside the LinuxPlayer class.

Implementation of Stop() method can be done as follows:

public Task Stop()
{
  if (_process != null)
  {
    _process.Dispose();
    _process = null;
  }
  return Task.CompletedTask;
}

This will clear out any unmanaged resources used by the Process object and will set its value to null.

Additional call to Stop() method can be made from the start of Play() method. This will stop any current playback before starting a new playback from the beginning.

Finally, we will add the ability to pause and resume the playback. The aplay documentation states that this can be achieved by inputting space or return while a file is being played in the interactive mode. Therefore, the following line should both pause and resume the playback:

_process.StandardInput.Write(' ');


Dynamically choose IPlayer implementation

The library that we are building should just work, regardless of the OS that you would run it on. Therefore, we will need to be able to identify the OS and dynamically apply the correct implementation of IPlayer interface accordingly.

This is easily achievable in .NET Core. Assuming that we have called our classes WindowsPlayer and LinuxPlayer and that we have declared a private field of a type IPlayer called _internalPlayer, the following code will apply the correct implementation:

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  _internalPlayer = new WindowsPlayer();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  _internalPlayer = new LinuxPlayer();


Wrapping up

So, if you have been following this tutorial starting from part one, you should now be able to build a .NET Core app that can play, pause, resume and stop audio on Windows and Linux.

In both cases, I have provided an overview of how this is done, instead of guiding you through all of the details line by line. Therefore, if you got confused at any point, you will be able to see how it all fits together inside NetCoreAudio project.

In the next part of the tutorial, I will describe how to enable the same capabilities on Mac OS.



Written by

Posted on 27 Aug 2018

Fiodar Sazanavets is a full stack software developer with several years of experience working in various industries. In his professional career, he has mainly worked with a number of different Microsoft stack technologies, both back-end and front-end, and Java for Android. Fiodar has an Honours degree in Environmental Biology and a Masters degree in Environmental Informatics.



More from Tech Advice


Building .NET Core sound application - part 3

Building .NET Core sound application - part 3


Building .NET Core audio application - part 1

Building .NET Core audio application - part 1


Building .NET Core desktop application

Building .NET Core desktop application


How to play sound on .NET Core

How to play sound on .NET Core


How to protect your website from spammers

How to protect your website from spammers


Proven way to make programming fun

Proven way to make programming fun


Why you should care about functional programming

Why you should care about functional programming


What the heck is WebAssembly

What the heck is WebAssembly


Desktop apps are not dead. Here is why

Desktop apps are not dead. Here is why


Popular misconceptions about Node.js

Popular misconceptions about Node.js


Share this:

Facebook Google LinkedIn Twitter Become a Patron!


More from Tech Advice











Privacy Policy

© Mobile Tech Tracker. All rights reserved. Unauthorised copying of any of this website's content is prohibited under international law.

For any queries, comments or suggestions, please write to info@mobiletechtracker.co.uk.