Playing mp3 file to multiple devices

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Playing mp3 file to multiple devices

JoeJoeTV
Hello there,
I recently found UOS while searching for a sound library I can use in Lazarus.

I looked at the examples and tried to build a small application myself that just plays an audio file(test file is mp3, but should work with any supported by UOS, right?) to multiple output devices.
At first I tried it with only one Device and that worked, but after adding another Output, the audio laggs/skips, like it's struggling to keep up. I saw that there is a way to load a file into a buffer, so I tried that, but only got horrible noises and never got the file to play correctly, like the .flac file from the "consoleplaymemorybuffer" example. Even after trying every sample rate, it didn't work.

How can I achieve this, because in theory it should work right?

Here is my code, but it is basically just the simpleplayer example:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ctypes, uos_flat;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    ComboBox1: TComboBox;
    Edit1: TEdit;
    ListBox1: TListBox;
    ToggleBox1: TToggleBox;
    procedure Button1Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure ToggleBox1Change(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  PA_FileName: String;
  SF_FileName: String;
  MPG_FileName: String;
  LibLoaded: Boolean;
  PlayerIndex, InputIndex, OutputIndex1, OutputIndex2: Integer;
  testbuffer: array of cfloat;
  testbufferinfos: TuosF_BufferInfos;

const
  SoundFileName = 'test.mp3';

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  appDir: String;
  opath: String;
  res: Integer;
begin
  appDir := Application.Location;

  //Load apropriate libraries from 'lib' sub directory

  Form1.Enabled := False;

  {$IFDEF Windows}
    {$if defined(cpu64)}
      PA_FileName := appDir + 'lib\Windows\64bit\LibPortaudio-64.dll';
      SF_FileName := appDir + 'lib\Windows\64bit\LibSndFile-64.dll';
      MPG_FileName := appDir + 'lib\Windows\64bit\LibMpg123-64.dll';
    {$else}
      PA_FileName := appDir + 'lib\Windows\32bit\LibPortaudio-32.dll';
      SF_FileName := appDir + 'lib\Windows\32bit\LibSndFile-32.dll';
      MPG_FileName := appDir + 'lib\Windows\32bit\LibMpg123-32.dll';
   {$endif}
  {$ENDIF}

  {$IFDEF freebsd}
    {$if defined(cpu64)}
      PA_FileName := appDir + 'lib/FreeBSD/64bit/libportaudio-64.so';
      SF_FileName := appDir + 'lib/FreeBSD/64bit/libsndfile-64.so';
      MPG_FileName := appDir + 'lib/FreeBSD/64bit/libmpg123-64.so';
    {$else}
      PA_FileName := appDir + 'lib/FreeBSD/32bit/libportaudio-32.so';
      SF_FileName := appDir + 'lib/FreeBSD/32bit/libsndfile-32.so';
      MPG_FileName := appDir + 'lib/FreeBSD/32bit/libmpg123-32.so';
    {$endif}
  {$ENDIF}

  {$IFDEF Darwin}
    {$IFDEF CPU32}
      opath := ordir;
      opath := copy(opath, 1, Pos('/uos', opath) - 1);
      PA_FileName := appDir + '/lib/Mac/32bit/LibPortaudio-32.dylib';
      SF_FileName := appDir + '/lib/Mac/32bit/LibSndFile-32.dylib';
      MPG_FileName := appDir + '/lib/Mac/32bit/LibMpg123-32.dylib';
    {$ENDIF}
    {$IFDEF CPU64}
      opath := ordir;
      opath := copy(opath, 1, Pos('/uos', opath) - 1);
      PA_FileName := appDir + '/lib/Mac/64bit/LibPortaudio-64.dylib';
      SF_FileName := appDir + '/lib/Mac/64bit/LibSndFile-64.dylib';
      MPG_FileName := appDir + '/lib/Mac/64bit/LibMpg123-64.dylib';
    {$ENDIF}
  {$ENDIF}

  {$if defined(CPUAMD64) and defined(linux) }
    PA_FileName := appDir + 'lib/Linux/64bit/LibPortaudio-64.so';
    SF_FileName := appDir + 'lib/Linux/64bit/LibSndFile-64.so';
    MPG_FileName := appDir + 'lib/Linux/64bit/LibMpg123-64.so';
  {$ENDIF}

  {$if defined(cpu86) and defined(linux)}
    PA_FileName := appDir + 'lib/Linux/32bit/LibPortaudio-32.so';
    SF_FileName := appDir + 'lib/Linux/32bit/LibSndFile-32.so';
    MPG_FileName := appDir + 'lib/Linux/32bit/LibMpg123-32.so';
  {$ENDIF}

  {$if defined(linux) and defined(cpuarm)}
    PA_FileName := appDir + 'lib/Linux/arm_raspberrypi/libportaudio-arm.so';
    SF_FileName := appDir + 'lib/Linux/arm_raspberrypi/libsndfile-arm.so';
    MPG_FileName := appDir + 'lib/Linux/arm_raspberrypi/libmpg123-arm.so';
  {$ENDIF}

  {$if defined(linux) and defined(cpuaarch64)}
    PA_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libportaudio_aarch64.so';
    SF_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libsndfile_aarch64.so';
    MPG_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libmpg123_aarch64.so';
  {$ENDIF}

  res := uos_LoadLib(PChar(PA_FileName), PChar(SF_FileName), PChar(MPG_FileName), nil, nil, nil);

  if res <> 0 then
    begin
      ShowMessage('Error while loading');
      LibLoaded := False;
    end
  else
    LibLoaded := True;

  Form1.Enabled := True;
  ListBox1.Items.Add('Libraries Loaded: ' + BoolToStr(LibLoaded, True));
end;

procedure TForm1.ToggleBox1Change(Sender: TObject);
begin
  if uos_GetStatus(PlayerIndex) > 0 then
    begin
      uos_Stop(PlayerIndex);
    end;
        
  //uos_File2File(PChar(Application.Location + SoundFileName), PChar(Application.Location + 'out.wav'), -1, -1);

  //testbuffer := uos_File2Buffer(PChar(Application.Location + 'out.wav'), -1, testbufferinfos, -1, -1);

  PlayerIndex := 0;

  if uos_CreatePlayer(PlayerIndex) then
    begin
      ListBox1.Items.Add('Sound File Name: ' + Application.Location + SoundFileName);

      InputIndex := uos_AddFromFile(PlayerIndex, PChar(Application.Location + SoundFileName), -1, -1, -1);
      //InputIndex := uos_AddFromMemoryBuffer(PlayerIndex, testbuffer, testbufferinfos, -1, 1024);
      //InputIndex := uos_AddFromFileIntoMemory(PlayerIndex, PChar(Application.Location + SoundFileName));

      ListBox1.Items.Add('Player Index: ' + IntToStr(PlayerIndex));
      ListBox1.Items.Add('Input Index: ' + IntToStr(InputIndex));

      if InputIndex > -1 then
        begin
          uos_InputAddDSPVolume(PlayerIndex, InputIndex, 0.1, 0.1);

          OutputIndex1 := uos_AddIntoDevOut(PlayerIndex, -1, -1, -1, -1, -1, -1, -1);
                                                                           
          ListBox1.Items.Add('Output Index 1: ' + IntToStr(OutputIndex1));
          if OutputIndex1 > -1 then
            begin

              OutputIndex2 := uos_AddIntoDevOut(PlayerIndex, 14, -1, -1, -1, -1, -1, -1);

              ListBox1.Items.Add('Output Index 2: ' + IntToStr(OutputIndex2));
              if OutputIndex2 > -1 then
                begin
                  uos_Play(PlayerIndex);

                  ListBox1.Items.Add('Player Status: ' + IntToStr(uos_GetStatus(PlayerIndex)));
                end;
            end;
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;


begin
  uos_GetInfoDevice();

  for i := 0 to High(uosDeviceInfos) do
    begin
      if uosDeviceInfos[i].DeviceType = 'Out' then
        ComboBox1.Items.Add('[' + InttoStr(uosDeviceInfos[i].DeviceNum) + '] ' + uosDeviceInfos[i].DeviceName);
    end;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
begin

end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  uos_unloadlib();
  uos_free();
end;

end.

I would appreciate any help I can get!
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
Hello and welcome to uos forum.

What OS are you using and what sound cards?

If dev1 and dev2 are working devices, this should work:

    .....
 
 InputIndex := uos_AddFromFile(PlayerIndex, PChar(Application.Location + SoundFileName), -1, -1, -1);

    if InputIndex > -1 then
        begin
         OutputIndex1 := uos_AddIntoDevOut(PlayerIndex, -1, -1, -1, -1, -1, -1, -1);
         OutputIndex2 := uos_AddIntoDevOut(PlayerIndex, dev1, -1, -1, -1, -1, -1, -1);
         OutputIndex3 := uos_AddIntoDevOut(PlayerIndex, dev2, -1, -1, -1, -1, -1, -1);

;;;;

What strange noise do you get?

Did you try each device alone (not all but one by one) ?

Fre;D
       
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
Re-hello.

Not sure it will work good because there are many chance that all the sound cards works with not exactly same timers.

Imho, if you dont need absolute synchro, you will have better result with a player with one output (one of the devices) and one input (the same file for  each player) and start all the players together.

Fre;D
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

JoeJoeTV
In reply to this post by fredvs
Hi,

I'm on Linux Mint Cinnamon 64bit, Kernel 5.4

The sound card I use is the onboard sound of my Motherboard(Asus Prime B350 Plus).

The Strange noises are like when you try to play a file with the wrong playback settings(I tried different sample formats).

The "best" one was when you could hear the sound kind off, but distorted.

I did try each device seperately, one of them is my normal output(headphones) and the other one is a virtual sink.

I tested it now using the device number directly for both of them instead of using -1 for the fisrt one since it's my default device and it works better, but there is still a tiny "skip" at the start, but that may not be a problem of UOS.

This may have solved my problem, but i need to test it.

Thank you for the help!

Just a question, in your example, why dou you use the uos_AddIntoDevOut 3 times?
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

JoeJoeTV
Also, to add to this,

I tried another mp3 file, and it's more noticeable there, there is some kind of noise(not that audible), when hearing the audio compared to playing it in vlc. It's like static noise, but following the pattern of the sound.
I don't really know hoe to explain it.
I could maybe record it and upload it here?
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
In reply to this post by JoeJoeTV
> Just a question, in your example, why dou you use the uos_AddIntoDevOut 3 times?

Maybe I did not understand, I was thinking that you wanted to play a mp3 and output the sound in 3 different sound cards.

So, sorry I dont catch what yo want.

Do you want to have the choice of the different devices of the same audio card and play a mp3 with device wanted ?

Fre;D
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
In reply to this post by JoeJoeTV
> I could maybe record it and upload it here?

Yes but give also the code you used.
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

JoeJoeTV
In reply to this post by fredvs
What I want to do is to play a sound file and output it to 2 devices simultaineusly.

I've attached the recording, but you need to listen carefully to hear it, it is a high pitched static noise and don't worry about the sound, it's the first thing I found to test this. :)
upload.wav

EDIT:The part with the second output is commented out to test if the noise also existed when using only one output and it does.

This is the code I used now(only a few changes from before):

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ctypes, uos_flat;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    ComboBox1: TComboBox;
    Edit1: TEdit;
    ListBox1: TListBox;
    ToggleBox1: TToggleBox;
    procedure Button1Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure ToggleBox1Change(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  PA_FileName: String;
  SF_FileName: String;
  MPG_FileName: String;
  LibLoaded: Boolean;
  PlayerIndex, InputIndex, OutputIndex1, OutputIndex2: Integer;
  testbuffer: array of cfloat;
  testbufferinfos: TuosF_BufferInfos;

const
  SoundFileName = 'test2.mp3';

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  appDir: String;
  opath: String;
  res: Integer;
begin
  appDir := Application.Location;

  //Load apropriate libraries from 'lib' sub directory

  Form1.Enabled := False;

  {$IFDEF Windows}
    {$if defined(cpu64)}
      PA_FileName := appDir + 'lib\Windows\64bit\LibPortaudio-64.dll';
      SF_FileName := appDir + 'lib\Windows\64bit\LibSndFile-64.dll';
      MPG_FileName := appDir + 'lib\Windows\64bit\LibMpg123-64.dll';
    {$else}
      PA_FileName := appDir + 'lib\Windows\32bit\LibPortaudio-32.dll';
      SF_FileName := appDir + 'lib\Windows\32bit\LibSndFile-32.dll';
      MPG_FileName := appDir + 'lib\Windows\32bit\LibMpg123-32.dll';
   {$endif}
  {$ENDIF}

  {$IFDEF freebsd}
    {$if defined(cpu64)}
      PA_FileName := appDir + 'lib/FreeBSD/64bit/libportaudio-64.so';
      SF_FileName := appDir + 'lib/FreeBSD/64bit/libsndfile-64.so';
      MPG_FileName := appDir + 'lib/FreeBSD/64bit/libmpg123-64.so';
    {$else}
      PA_FileName := appDir + 'lib/FreeBSD/32bit/libportaudio-32.so';
      SF_FileName := appDir + 'lib/FreeBSD/32bit/libsndfile-32.so';
      MPG_FileName := appDir + 'lib/FreeBSD/32bit/libmpg123-32.so';
    {$endif}
  {$ENDIF}

  {$IFDEF Darwin}
    {$IFDEF CPU32}
      opath := ordir;
      opath := copy(opath, 1, Pos('/uos', opath) - 1);
      PA_FileName := appDir + '/lib/Mac/32bit/LibPortaudio-32.dylib';
      SF_FileName := appDir + '/lib/Mac/32bit/LibSndFile-32.dylib';
      MPG_FileName := appDir + '/lib/Mac/32bit/LibMpg123-32.dylib';
    {$ENDIF}
    {$IFDEF CPU64}
      opath := ordir;
      opath := copy(opath, 1, Pos('/uos', opath) - 1);
      PA_FileName := appDir + '/lib/Mac/64bit/LibPortaudio-64.dylib';
      SF_FileName := appDir + '/lib/Mac/64bit/LibSndFile-64.dylib';
      MPG_FileName := appDir + '/lib/Mac/64bit/LibMpg123-64.dylib';
    {$ENDIF}
  {$ENDIF}

  {$if defined(CPUAMD64) and defined(linux) }
    PA_FileName := appDir + 'lib/Linux/64bit/LibPortaudio-64.so';
    SF_FileName := appDir + 'lib/Linux/64bit/LibSndFile-64.so';
    MPG_FileName := appDir + 'lib/Linux/64bit/LibMpg123-64.so';
  {$ENDIF}

  {$if defined(cpu86) and defined(linux)}
    PA_FileName := appDir + 'lib/Linux/32bit/LibPortaudio-32.so';
    SF_FileName := appDir + 'lib/Linux/32bit/LibSndFile-32.so';
    MPG_FileName := appDir + 'lib/Linux/32bit/LibMpg123-32.so';
  {$ENDIF}

  {$if defined(linux) and defined(cpuarm)}
    PA_FileName := appDir + 'lib/Linux/arm_raspberrypi/libportaudio-arm.so';
    SF_FileName := appDir + 'lib/Linux/arm_raspberrypi/libsndfile-arm.so';
    MPG_FileName := appDir + 'lib/Linux/arm_raspberrypi/libmpg123-arm.so';
  {$ENDIF}

  {$if defined(linux) and defined(cpuaarch64)}
    PA_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libportaudio_aarch64.so';
    SF_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libsndfile_aarch64.so';
    MPG_FileName := appDir + 'lib/Linux/aarch64_raspberrypi/libmpg123_aarch64.so';
  {$ENDIF}

  res := uos_LoadLib(PChar(PA_FileName), PChar(SF_FileName), PChar(MPG_FileName), nil, nil, nil);

  if res <> 0 then
    begin
      ShowMessage('Error while loading');
      LibLoaded := False;
    end
  else
    LibLoaded := True;

  Form1.Enabled := True;
  ListBox1.Items.Add('Libraries Loaded: ' + BoolToStr(LibLoaded, True));
end;

procedure TForm1.ToggleBox1Change(Sender: TObject);
begin
  if uos_GetStatus(PlayerIndex) > 0 then
    begin
      uos_Stop(PlayerIndex);
    end;

  PlayerIndex := 0;

  if uos_CreatePlayer(PlayerIndex) then
    begin
      ListBox1.Items.Add('Sound File Name: ' + Application.Location + SoundFileName);

      InputIndex := uos_AddFromFile(PlayerIndex, PChar(Application.Location + SoundFileName), -1, -1, -1);

      ListBox1.Items.Add('Player Index: ' + IntToStr(PlayerIndex));
      ListBox1.Items.Add('Input Index: ' + IntToStr(InputIndex));

      if InputIndex > -1 then
        begin
          //uos_InputAddDSPVolume(PlayerIndex, InputIndex, 1, 1);

          OutputIndex1 := uos_AddIntoDevOut(PlayerIndex, 11, -1, -1, -1, -1, -1, -1);
                                                                           
          ListBox1.Items.Add('Output Index 1: ' + IntToStr(OutputIndex1));
          if OutputIndex1 > -1 then
            begin

              //OutputIndex2 := uos_AddIntoDevOut(PlayerIndex, 14, -1, -1, -1, -1, -1, -1);

              //ListBox1.Items.Add('Output Index 2: ' + IntToStr(OutputIndex2));
              //if OutputIndex2 > -1 then
                //begin
                  uos_Play(PlayerIndex);

                  ListBox1.Items.Add('Player Status: ' + IntToStr(uos_GetStatus(PlayerIndex)));
                //end;
            end;
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;


begin
  uos_GetInfoDevice();

  for i := 0 to High(uosDeviceInfos) do
    begin
      if uosDeviceInfos[i].DeviceType = 'Out' then
        ComboBox1.Items.Add('[' + InttoStr(uosDeviceInfos[i].DeviceNum) + '] ' + uosDeviceInfos[i].DeviceName);
    end;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
begin

end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  uos_unloadlib();
  uos_free();
end;

end.
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
In reply to this post by fredvs
Hello.

I think with 2 players it will work (if your sound card can play 2 devices together):

PlayerIndex1 := 0;
 PlayerIndex2 := 1;

  uos_CreatePlayer(PlayerIndex1);
  uos_CreatePlayer(PlayerIndex2);
 
  InputIndex1 := uos_AddFromFile(PlayerIndex1, PChar(Application.Location + SoundFileName), -1, -1, -1);
  InputIndex2 := uos_AddFromFile(PlayerIndex2, PChar(Application.Location + SoundFileName), -1, -1, -1)
  OutputIndex1 := uos_AddIntoDevOut(PlayerIndex1, -1, -1, -1, -1, -1, -1, -1);
  OutputIndex2 := uos_AddIntoDevOut(PlayerIndex2, devx, -1, -1, -1, -1, -1, -1);

  uos_Play(PlayerIndex1);
  uos_Play(PlayerIndex2);
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
Ooops sorry, we are very synchro.

OK, I can hear a scratch in middle of sound.

Does it the same sound withe the SimplePlayer uos demo?

Fre;D
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

JoeJoeTV
In reply to this post by fredvs
Thanks, I'll try that.

Do you have a solution for the background noise or know what causes it?
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
> Do you have a solution for the background noise or know what causes it?

At the moment no, it is strange because it is not constant.

Anyway, you may play with the uos_AddIntoDevOut parameters, mainly with latency ( try with 0.3 ) and size of buffers.
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

JoeJoeTV
In reply to this post by fredvs
Ok, I found a solution:

I tried it with the SimplePlayer example and there was no noise, so I treid different settings in the SimplePlayer GUI and noticed that the nise appeared again after selecting int16 or int32 for the sample format, so I changed the sample format in my program to float32 and the noise is gone

Thank you for helping me so fast!
Reply | Threaded
Open this post in threaded view
|

Re: Playing mp3 file to multiple devices

fredvs
Administrator
I am happy that you found a solution.

By default the sample format it set to int16 because old sound cards only accept it.
But yes, now all recent audio card can deal with float32.

Fre;D