How to write a Class in C++ with pthread and libvlc

Rocco Galati
Rocco Galati used Ask the Experts™
on
My application (C++, WxWidgets, Ubuntu) have to play different mp3 files depending on user actions. At the moment, I use vlc library and I always call a new function to reproduce the audio file, but this requires too much code and I think it's not so professional. Since I do not want to stop the flow of the application while the mp3 is playing, I use threads.

I tried to write a class for the mp3, but I think it is not correct since I get this error:

  /home/isola/Documents/Isola02/secondpanel.cpp:68:102: error: invalid use of void expression
  pthread_create(&thread, NULL, mp3->play_mp3("/home/user/Project/audio/scegli-rifiuto.mp3"), NULL);

Open in new window


This is the code of my class:

rePlay.cpp
#include "rePlay.h"
#include <vlc/vlc.h>

rePlay::rePlay()
{
    //ctor
}

rePlay::~rePlay()
{
    //dtor
}

void rePlay::play_mp3(const char* path){
  // load the vlc engine
    inst = libvlc_new(0, NULL);
    printf("apro il file %d\n", inst);
    // create a new item
    m = libvlc_media_new_path(inst, path);
    // create a media play playing environment
    mp = libvlc_media_player_new_from_media(m);
    // no need to keep the media now
    libvlc_media_release(m);
    // play the media_player
    libvlc_media_player_play(mp);
    printf("Done.\n");
}

void rePlay::stop_mp3(){
  // stop playing
    libvlc_media_player_stop(mp);
    // free the media_player
    libvlc_media_player_release(mp);
    libvlc_release(inst);
}

Open in new window

and the header rePlay.h
#ifndef REPLAY_H
#define REPLAY_H
#include <vlc/vlc.h>

class rePlay
{
    public:
        rePlay();
        virtual ~rePlay();
        void play_mp3(const char*);
        void stop_mp3();
    protected:
        libvlc_instance_t *inst;
        libvlc_media_player_t *mp;
        libvlc_media_t *m;
    private:
};

#endif // REPLAY_H

Open in new window

My idea is to call:

pthread_t thread;
rePlay *mp3;
pthread_create(&thread, NULL, mp3->play_mp3("/home/user/Project/audio/scegli-rifiuto.mp3"), NULL);

Open in new window


by passing the path of the file each time I want to reproduce a mp3 and then to call:

pthread_create(&thread, NULL, mp3->stop_mp3, NULL);

Open in new window


when I want to stop it.

At the moment, I get this error from the compiler regarding the pthread_create, but I think there should be other problems since I do not know if the play_mp3() and stop_mp3() could work.

Can you help me, please?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Hi Rocco Galati,

creating the thread this way can't work, in the first you pass the return value from the call 'mp3->play_mp3( ... )' (where mp3 is an uninitialized pointer), in the second call you try to pass a none-static member function of a class, which is not possible, coz there's no (good) way to pass the object, the thread needs to be able to call the class member function.

The thread procedure passed to pthread has to be a global function (or a static class mamber function) which takes one void-pointer as argument, any data you want to pass to the thread procedure has to be casted to/from this void-pointer ... which isn't really typesafe.

I, in your place, would consider using STL's std::thread (needs C++ 11 or newer), IMO they are much easier and convenient for multi-threading than pthreads, pthread in fact is a C threading API.

With std::thread you have multiple benefits, it's i.e. possible to use a lambda as thread procedure, where it's simple to pass any kind of data to it, it's even possible to i.e. derive you own thread-class with data as members - and there are a lot of other classes which allow a fairly easy way to implement even complicated multi-threading functionality, i.e. std::atomic, std::mutex and std::thread_local, std::future, ...

If you're interested take a look at https://en.cppreference.com/w/cpp/thread/thread - in the internet you can find a lot of tutorials for C++ 11 multi-threading, i.e. https://thispointer.com/c-11-multithreading-part-1-three-different-ways-to-create-threads/

Best regards,

ZOPPO
Top Expert 2016

Commented:
pthread_create(&thread, NULL, mp3->play_mp3("/home/user/Project/audio/scegli-rifiuto.mp3"), NULL);

the third argument is a function pointer of a global function or a static member function. the fourth argument is a pointer to a data or class object that was passed as argument to the start function:

// mythread.h
....

class MyThread
{
      std::string strmp3path;
      volatile bool stopflag;     // volatile makes the variable to be always read newly from memory
                                                 // otherwise a compiler may not recognize when a variable was changed asynchronously
public:
      MyThread(const char * szPath) : strmp3path(szPath), stopflag(false) {}

      // declare static function 
      // with static it is a global function which can be called by MyThread::mythread_startfunc(..);
      static void mythread_startfunc(void *);

      // helpers
      const char * GetMp3Path() const {  return strmp3path.c_str(); }
      void SetStopFlag() { stopflag = true; }
      bool Stopped() const { return stopflag; }
};
....

// mythread.cpp

....
#include "mythread.h"

// define startfunc
void MyThread::mythread_startfunc(void * pArg)
{
      // cast the void pointer to the class or struct you passed as fourth argument
      MyThreadClass * pMyThread = ( MyThread * )pArg;
      
      // put here your thread-safe code using pMyThread for to get all needed parameters
      MP3 mp3(....);

      // without stop check do
      mp3.play_mp3(pMyThread->GetMp3Path());

      // Note, if you want the thread to be able to recognize a stop flag ...
      // ... you would need an endless while loop and the function must be able to run in small intervals - say 1 second   
      // then you can check the stopflag each second 

      while (true)
      {
              if(pMyThread->Stopped())
                   break;
              mp3.play_mp3(pMyThread->GetMp3Path(), 1);
      }    
      // if coming here the thread would exit
       
}

Open in new window


the above would be used like

#include <unistd.h>

int main()
{
       MyThread  myThread("/home/user/Project/audio/scegli-rifiuto.mp3"); 

       int threadhandle = pthread_create(&thread, NULL, &MyThread::mythread_startfunc, &myThread);

       // at this point the thread is running asynchronously

       // you can run your gui or any other i/o without being blocked
       .....
 
       // if you return from main the thread was killed (if still running)
       // a better way is to set a stop flag such that the thread could return from start and exit
       MyThread.SetStopFlag();
       unsigned int microseconds = 2000;  
       // wait 2 seconds to
       usleep(microseconds);
        
       return 0;
} 

Open in new window


(note, the code is not tested and may have typing errors)

Sara
Rocco GalatiR&D Engineer

Author

Commented:
Thank you for your suggestions! In the meanwhile, I was trying to make my code works and I did few changes: I switched to <thread> and I corrected some methods in my previous class.
It seems to work, but I cannot correctly call stop_mp3() from external classes.

// replay.h
#include <vlc/vlc.h>

class rePlay
{
    public:
        rePlay();
        virtual ~rePlay();
        void play_mp3(const char*);
        void stop_mp3();
    protected:
        libvlc_instance_t *inst;
        libvlc_media_player_t *mp;
        libvlc_media_t *m;
    private:
};

// rePlay.cpp

#include "rePlay.h"
#include <vlc/vlc.h>

rePlay::rePlay()
{
    //ctor
}

rePlay::~rePlay()
{
    //dtor
}

void rePlay::play_mp3(const char* path){

  // load the vlc engine
    inst = libvlc_new(0, NULL);
    printf("apro il file %d\n", inst);
    // create a new item
    m = libvlc_media_new_path(inst, path);
    // create a media play playing environment
    mp = libvlc_media_player_new_from_media(m);
    // no need to keep the media now
    libvlc_media_release(m);
    // play the media_player
    libvlc_media_player_play(mp);
    printf("Done.\n");
}

void rePlay::stop_mp3(){
  // stop playing
    libvlc_media_player_stop(mp);
    // free the media_player
    libvlc_media_player_release(mp);
    libvlc_release(inst);
}

// firstpanel.h

class firstpanel: public wxPanel
{
	public:
		firstpanel(wxWindow* parent, Isola02Frame*, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
		virtual ~firstpanel();
		void checkValue(wxCommandEvent& event);
		void check_cf(wxTimerEvent& event);
                rePlay *mp3_apertura_porta = new rePlay();    // <-- I DECLARED THE pointer here
		//(*Declarations(firstpanel)
		wxStaticText* assistenza;
		wxStaticText* first_panel;
		wxStaticText* identificazione;
		wxTextCtrl* tessera;
		//*)
...
}

// firstpanel.cpp
 std::thread second = std::thread([this]() noexcept {
        this->mp3_apertura_porta->play_mp3("/home/robodyne/Project/audio/scegli-rifiuto.mp3"); });
        second.join();

// secondpanel.cpp

void secondpanel::OnBitmapButton2Click(wxCommandEvent& event)
{
 firstpanel *ptr;
 ptr->mp3_apertura_porta->stop_mp3();
}

Open in new window


the mp3 plays fine but when I try to stop it in the secondpanel class, it get a segmentation error.

@Sarabande, I checked your example and I'm trying it right now, but do you think mine could be ok if we solve the problem with the stop_mp3() function?
11/26 Forrester Webinar: Savings for Enterprise

How can your organization benefit from savings just by replacing your legacy backup solutions with Acronis' #CyberProtection? Join Forrester's Joe Branca and Ryan Davis from Acronis live as they explain how you can too.

Here you call something with an uninitialized pointer 'ptr':
 firstpanel *ptr;
 ptr->mp3_apertura_porta->stop_mp3();

Open in new window

Rocco GalatiR&D Engineer

Author

Commented:
I usually use this procedure to access the functions in a class.
The compiler gives me a warning about an uninitialized ptr but then it runs fine.

How can I correctly initialize the pointer?
Hm - this can only work with static class member functions, but for this is meaningless to use a pointer (no matter if initialized or not).

Somewhere in your code you should have an instance of type firstpanel, which represents your widget ... you need to use this instance.

I think I would add a member with a copy of the pointer to firstpanel::rePlay in secondpanel - BTW, I don't see, why you use a pointer in firstpanel, the way you use it you could even use an instance.

In pseudo-code I would do it somehow like this:
class firstpanel : public wxPanel
{
	rePlay mp3_apertura_porta;    // the instance used with this panel
public:
	// ...
	rePlay* getPlayer()
	{
		return mp3_apertura_porta;
	}
};

class secondpanel : public wxPanel
{
	rePlay* mp3_apertura_porta = nullptr;    // pointer to the instance in firstpanel, must be set from outside
public:
	// ...
	void setPlayer( rePlay* player )
	{
		mp3_apertura_porta = player;
	}
};

// when you created the panels you'll have to set the pointer
firstpanel p1 = /*...*/;
secondpanel p2 = /*...*/;
p2.setPlayer( p1.getPlayer() );

Open in new window

Top Expert 2016

Commented:
How can I correctly initialize the pointer? 

Open in new window


don't use a pointer if you can do it with a class object as well:

 firstpanel fp;// check wether the constructor requires arguments
 fp.mp3_apertura_porta->stop_mp3();

Open in new window


or (less recommendable)

 firstpanel fp = new firstpanel();  // check wether the constructor requires arguments
 fp.mp3_apertura_porta->stop_mp3();
delete fp; 
fp = NULL;  // generally, initialize pointers and reset to NULL after delete

Open in new window


Sara
Rocco GalatiR&D Engineer

Author

Commented:
Sara, I already tried to do that, but I get thir error from the compiler:
void secondpanel::OnBitmapButton2Click(wxCommandEvent& event)
{
 firstpanel ptr;
 ptr.mp3_apertura_porta->stop_mp3();
}

home/isola/Documents/Isola02/secondpanel.cpp: In member function ‘void secondpanel::OnBitmapButton2Click(wxCommandEvent&)’:
/home/isola/Documents/Isola02/secondpanel.cpp:94:13: error: no matching function for call to ‘firstpanel::firstpanel()’
  firstpanel ptr;
             ^~~
In file included from /home/isola/Documents/Isola02/secondpanel.cpp:3:0:
/home/isola/Documents/Isola02/firstpanel.h:16:3: note: candidate: firstpanel::firstpanel(wxWindow*, Isola02Frame*, wxWindowID, const wxPoint&, const wxSize&)
   firstpanel(wxWindow* parent, Isola02Frame*, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
   ^~~~~~~~~~

Open in new window


@Zoppo: what is the meaning of Player, Setplayer and getplayer?
'@Zoppo: what is the meaning of Player, Setplayer and getplayer?'

This is what you mean with 'mp3_apertura_porta', I don't know what this means so I used a more abstract name 'player' ... sorry, probably I should have named the functions 'set_mp3_apertura_porta()' and 'get_mp3_apertura_porta()'.
Rocco GalatiR&D Engineer

Author

Commented:
Thank you, now it's more clear to me!

Just another question:

firstpanel p1 = /*...*/;
secondpanel p2 = /*...*/;
p2.setPlayer( p1.getPlayer() );

Open in new window


where these declarations should be placed?

in firstpanel, I have:

  std::thread first = std::thread([this]() noexcept {
        this->mp3_apertura_porta->play_mp3("/home/isola/Documents/Isola02/audio/errore-ripetere-la-strisciata.mp3"); });
        first.join();

Open in new window

Sorry, I have no experience with wxWidgets, but you already must have some code somewhere which creates your two panels, I guess you there have a possibility to retrieve pointers to their instances ... this are the pointers you need instead of 'p1' and 'p2' from my pseudo-code.
Rocco GalatiR&D Engineer

Author

Commented:
yes, for example, I have this one for firstpanel:

// firstpanel.cpp

BEGIN_EVENT_TABLE(firstpanel,wxPanel)
	//(*EventTable(firstpanel)
	//*)
END_EVENT_TABLE()

firstpanel::firstpanel(wxWindow* parent, Isola02Frame *mainFrame, wxWindowID id,const wxPoint& pos,const wxSize& size)
{
    pointer = mainFrame;
	//(*Initialize(firstpanel)
	Create(parent, wxID_ANY, wxDefaultPosition, wxSize(1323,768), wxTAB_TRAVERSAL, _T("wxID_ANY"));
	SetMaxSize(wxSize(-1,-1));
	SetBackgroundColour(wxColour(176,253,203));
	first_panel = new wxStaticText(this, ID_STATICTEXT1, _("STRISCIA LA TESSERA PER INIZIARE"), wxPoint(528,350), wxSize(296,38), wxALIGN_CENTRE|wxSIMPLE_BORDER, _T("ID_STATICTEXT1"));
	wxFont first_panelFont(28,wxFONTFAMILY_SWISS,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Sans"),wxFONTENCODING_DEFAULT);
	first_panel->SetFont(first_panelFont);
	tessera = new wxTextCtrl(this, ID_TEXTCTRL1, wxEmptyString, wxPoint(808,72), wxSize(360,27), 0, wxDefaultValidator, _T("ID_TEXTCTRL1"));
	tessera->SetFocus();
	identificazione = new wxStaticText(this, ID_STATICTEXT2, _("IDENTIFICAZIONE NON RIUSCITA"), wxPoint(440,480), wxSize(826,35), wxALIGN_CENTRE, _T("ID_STATICTEXT2"));
	identificazione->Hide();
	identificazione->SetForegroundColour(wxColour(255,48,0));
	wxFont identificazioneFont(22,wxFONTFAMILY_SWISS,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Sans"),wxFONTENCODING_DEFAULT);
	identificazione->SetFont(identificazioneFont);
	assistenza = new wxStaticText(this, ID_STATICTEXT3, _("RIVOLGERSI IN ASSISTENZA"), wxPoint(536,528), wxSize(766,38), wxALIGN_CENTRE, _T("ID_STATICTEXT3"));
	assistenza->Hide();
	assistenza->SetForegroundColour(wxColour(255,48,0));
	wxFont assistenzaFont(24,wxFONTFAMILY_SWISS,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Sans"),wxFONTENCODING_DEFAULT);
	assistenza->SetFont(assistenzaFont);
	//*)
	Connect(ID_TEXTCTRL1, wxEVT_TEXT,(wxObjectEventFunction)&firstpanel::checkValue);
	tessera_timer.Bind(wxEVT_TIMER, &firstpanel::check_cf, this);

}

firstpanel::~firstpanel()
{
	//(*Destroy(firstpanel)
	//*)
}

//firstpanel.h
class firstpanel: public wxPanel
{
	rePlay mp3_apertura_porta;
	public:
		firstpanel(wxWindow* parent, Isola02Frame*, wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
		virtual ~firstpanel();
		void checkValue(wxCommandEvent& event);
		void check_cf(wxTimerEvent& event);
        rePlay* getPlayer()
	    {
		return mp3_apertura_porta;
	    }
		//(*Declarations(firstpanel)
		wxStaticText* assistenza;
		wxStaticText* first_panel;
		wxStaticText* identificazione;
		wxTextCtrl* tessera;
		//*)

	protected:
	    wxString surname = "Utente";   // now it should be "URSO"
        wxString name = "Utente";
        Isola02Frame *pointer;
        wxTimer tessera_timer;

        //(*Identifiers(firstpanel)
		static const long ID_STATICTEXT1;
		static const long ID_TEXTCTRL1;
		static const long ID_STATICTEXT2;
		static const long ID_STATICTEXT3;
		//*)

	private:

		//(*Handlers(firstpanel)
		void OnButton1Click(wxCommandEvent& event);
		void OnTextCtrl1Text(wxCommandEvent& event);
		//*)

		DECLARE_EVENT_TABLE()
};

Open in new window


do you mean this code?
Rocco GalatiR&D Engineer

Author

Commented:
Hello Sarabande,

I'm trying your example with the MyThread class but I'm having some questions, I hope you will have the time to answer me.

// define startfunc
void MyThread::mythread_startfunc(void * pArg)
{
      // cast the void pointer to the class or struct you passed as fourth argument
      MyThreadClass * pMyThread = ( MyThread * )pArg;
      
      // put here your thread-safe code using pMyThread for to get all needed parameters
      MP3 mp3(....);

      // without stop check do
      mp3.play_mp3(pMyThread->GetMp3Path());

      // Note, if you want the thread to be able to recognize a stop flag ...
      // ... you would need an endless while loop and the function must be able to run in small intervals - say 1 second   
      // then you can check the stopflag each second 

      while (true)
      {
              if(pMyThread->Stopped())
                   break;
              mp3.play_mp3(pMyThread->GetMp3Path(), 1);
      }    
      // if coming here the thread would exit
       
}

Open in new window


When you write
 MP3 mp3(....);

Open in new window

do you refer to the class I already wrote?

rePlay::play_mp3(const char* path)

Open in new window


and how do I need to declare mp3 used in
mp3.play_mp3(pMyThread->GetMp3Path());

Open in new window


Thank you a lot!
Top Expert 2016
Commented:
error: no matching function for call to ‘firstpanel::firstpanel()

that is because i used the default constructor firstpanel::firstpanel() with the statement

firstpanel fp;

Open in new window



you get the error because the firstpanel has no default constructor. there are two choices then:

either you need a new firstpanel object here. then look which constructors are available for to create (construct) a new firstpanel object.
or, you already have a firstpanel object created somewhere in the code. then, you should use this instead of a new fp. you also could use a pointer to the existing firstpanel object for example like

firstpanel * ptr = &my_alreadyexisting_firstpanel;

Open in new window


or, if the firstpanel object was created by 'new' operator, what means you have already a pointer , then you could do

firstpanel * ptr = ptr_to_alreadyexisting_firstpanel;

Open in new window


what simply is a copy. note, if you can avoid pointers it normally is the better and simpler code cause an object not created with 'new' was deleted automatically if it goes out of scope. with pointers you must guarantee there is something like an owner of the (original) pointer, normally some sort of a parent class object, which cares for creation and deletion. Never delete a copy of a pointer, because that destroys the object where also the original pointer was pointing to. so, the original pointer and all further copies would be invalid as well.



1. do you refer to the class I already wrote?

probably. as you posted only a part of your code, i thought that there must be something like an MP3 class because you called a member function of it.

However, if you call it like

rePlay::play_mp3(...); 

Open in new window


, it means that there is a class rePlay which has a static member function play_mp3. static member function can be called without class object because they are independent of the non-static data members of the class (if any). nevertheless, if the class rePlay has a default constructor (a constructor with no arguments) you also can do

rePlay mp3; mp3.play_mp3(...)

Open in new window


both calls are equivalent. the first is somewhat clearer as you see from syntax that it is a static member function, which can be called from everywhere without a bound class object.

how do I need to declare mp3 used in ...

as told you can simply change to

rePlay::play_mp3(pMyThread->GetMp3Path());

Open in new window


Sara
Rocco GalatiR&D Engineer

Author

Commented:
Thank you a lot for your time and your support!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial