From b8f87b0bf8795dbf815311f825a7f9f6ca5f42ff Mon Sep 17 00:00:00 2001 From: Gerasim Troeglazov <3dEyes@gmail.com> Date: Sat, 18 Dec 2021 21:36:25 +1000 Subject: Add haiku support diff --git a/CMakeLists.txt b/CMakeLists.txt index 210b1fa..7cfa9ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") elseif(ANDROID) set(DEFAULT_ALSA OFF) set(DEFAULT_PORTAUDIO OFF) +elseif(HAIKU) + set(DEFAULT_ALSA OFF) + set(DEFAULT_PORTAUDIO OFF) else() set(DEFAULT_ALSA OFF) set(DEFAULT_PORTAUDIO ON) @@ -122,7 +125,7 @@ endif() option(USE_GLSLC "Compile Vulkan shaders" OFF) add_feature_info(GLSLC USE_GLSLC "Compile Vulkan shaders") -if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) +if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND NOT HAIKU) option(USE_FREEDESKTOP_NOTIFICATIONS "Use Freedesktop notifications" ON) add_feature_info("Freedesktop notifications" USE_FREEDESKTOP_NOTIFICATIONS "Use Freedesktop notifications") @@ -166,7 +169,7 @@ else() set(USE_YOUTUBEDL OFF) endif() -if(USE_EXTENSIONS AND NOT WIN32 AND NOT APPLE AND NOT ANDROID) +if(USE_EXTENSIONS AND NOT WIN32 AND NOT APPLE AND NOT ANDROID AND NOT HAIKU) option(USE_MPRIS2 "Build Extensions with MPRIS2 support" ON) add_feature_info(MPRIS2 USE_MPRIS2 "Build Extensions with MPRIS2 support") endif() diff --git a/src/gui/Main.cpp b/src/gui/Main.cpp index 1360cba..73d4cd8 100644 --- a/src/gui/Main.cpp +++ b/src/gui/Main.cpp @@ -606,6 +606,10 @@ int main(int argc, char *argv[]) qputenv("QT_QPA_UPDATE_IDLE_TIME", "0"); +#ifdef Q_OS_HAIKU + setenv("HOME", "/boot/home", 1); +#endif + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifndef USE_OPENGL QGuiApplication::setAttribute(Qt::AA_ForceRasterWidgets); @@ -707,7 +711,7 @@ int main(int argc, char *argv[]) } if (!cmakeBuildFound) { -#if !defined Q_OS_WIN && !defined Q_OS_MACOS && !defined Q_OS_ANDROID +#if !defined Q_OS_WIN && !defined Q_OS_MACOS && !defined Q_OS_ANDROID && !defined Q_OS_HAIKU sharePath = QCoreApplication::applicationDirPath() + "/../share/qmplay2"; libPath = QMPlay2CoreClass::getLibDir(); if (libPath.isEmpty() || !QDir(libPath).exists("qmplay2")) @@ -913,5 +917,9 @@ int main(int argc, char *argv[]) if (canDeleteApp) #endif delete qApp; + +#ifdef Q_OS_HAIKU + kill(::getpid(), SIGKILL); +#endif return 0; } diff --git a/src/gui/MainWidget.cpp b/src/gui/MainWidget.cpp index 7aa180a..a50e01c 100644 --- a/src/gui/MainWidget.cpp +++ b/src/gui/MainWidget.cpp @@ -150,7 +150,7 @@ MainWidget::MainWidget(QList> &arguments) setIconSize({22, 22}); SettingsWidget::InitSettings(); -#ifndef Q_OS_ANDROID +#if !defined Q_OS_ANDROID && !defined Q_OS_HAIKU settings.init("MainWidget/WidgetsLocked", false); #else settings.init("MainWidget/WidgetsLocked", true); diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 832b774..216e162 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -7,6 +7,9 @@ if(WIN32) elseif(APPLE) set(MODULES_INSTALL_PATH "${CMAKE_INSTALL_LIBDIR}/modules") set(QMPLAY2_MODULE SHARED) # otherwise CMake uses ".so" extension +elseif(HAIKU) + set(MODULES_INSTALL_PATH "modules") + set(QMPLAY2_MODULE MODULE) else() set(MODULES_INSTALL_PATH "${CMAKE_INSTALL_LIBDIR}/qmplay2/modules") set(QMPLAY2_MODULE MODULE) @@ -58,6 +61,10 @@ if(USE_PIPEWIRE) add_subdirectory(PipeWire) endif() +if(HAIKU) + add_subdirectory(MediaKit) +endif() + if(USE_XVIDEO) add_subdirectory(XVideo) endif() diff --git a/src/modules/MediaKit/CMakeLists.txt b/src/modules/MediaKit/CMakeLists.txt new file mode 100644 index 0000000..f94e365 --- /dev/null +++ b/src/modules/MediaKit/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.1) +project(MediaKit) + +set(MediaKit_HDR + MediaKit.hpp + MediaKitWriter.hpp + RingBuffer.hpp + SndPlayer.hpp +) + +set(MediaKit_SRC + MediaKit.cpp + MediaKitWriter.cpp + RingBuffer.cpp + SndPlayer.cpp +) + +set(MediaKit_RESOURCES + icon.qrc +) + +include_directories(../../qmplay2/headers) + +add_library(${PROJECT_NAME} ${QMPLAY2_MODULE} + ${MediaKit_HDR} + ${MediaKit_SRC} + ${MediaKit_RESOURCES} +) + +target_link_libraries(${PROJECT_NAME} + be + media + libqmplay2 +) + +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${MODULES_INSTALL_PATH}) diff --git a/src/modules/MediaKit/MediaKit.cpp b/src/modules/MediaKit/MediaKit.cpp new file mode 100644 index 0000000..2fee2d5 --- /dev/null +++ b/src/modules/MediaKit/MediaKit.cpp @@ -0,0 +1,65 @@ +#include +#include + +MediaKit::MediaKit() : + Module( "MediaKit" ) +{ + m_icon = QIcon( ":/MediaKit" ); + + init( "WriterEnabled", true ); + init( "Delay", 0.2 ); +} + +QList< MediaKit::Info > MediaKit::getModulesInfo( const bool showDisabled ) const +{ + QList< Info > modulesInfo; + if ( showDisabled || getBool( "WriterEnabled" ) ) + modulesInfo += Info( MediaKitWriterName, WRITER, QStringList( "audio" ) ); + return modulesInfo; +} +void *MediaKit::createInstance( const QString &name ) +{ + if ( name == MediaKitWriterName && getBool( "WriterEnabled" ) ) + return new MediaKitWriter( *this ); + return NULL; +} + +MediaKit::SettingsWidget *MediaKit::getSettingsWidget() +{ + return new ModuleSettingsWidget( *this ); +} + +QMPLAY2_EXPORT_MODULE( MediaKit ) + +/**/ + +#include +#include +#include +#include + +ModuleSettingsWidget::ModuleSettingsWidget( Module &module ) : + Module::SettingsWidget( module ) +{ + enabledB = new QCheckBox( tr( "Enabled" ) ); + enabledB->setChecked( sets().getBool( "WriterEnabled" ) ); + + QLabel *delayL = new QLabel( tr( "Delay" ) + ": " ); + + delayB = new QDoubleSpinBox; + delayB->setRange( 0.01, 1.0 ); + delayB->setSingleStep( 0.01 ); + delayB->setSuffix( " " + tr( "sec" ) ); + delayB->setValue( sets().getDouble( "Delay" ) ); + + QGridLayout *layout = new QGridLayout( this ); + layout->addWidget( enabledB, 0, 0, 1, 2 ); + layout->addWidget( delayL, 1, 0, 1, 1 ); + layout->addWidget( delayB, 1, 1, 1, 1 ); +} + +void ModuleSettingsWidget::saveSettings() +{ + sets().set( "WriterEnabled", enabledB->isChecked() ); + sets().set( "Delay", delayB->value() ); +} diff --git a/src/modules/MediaKit/MediaKit.hpp b/src/modules/MediaKit/MediaKit.hpp new file mode 100644 index 0000000..ed89e32 --- /dev/null +++ b/src/modules/MediaKit/MediaKit.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +class MediaKit : public Module +{ +public: + MediaKit(); +private: + QList< Info > getModulesInfo( const bool ) const; + void *createInstance( const QString & ); + + SettingsWidget *getSettingsWidget(); +}; + +/**/ + +#include + +class QDoubleSpinBox; +class QCheckBox; + +class ModuleSettingsWidget : public Module::SettingsWidget +{ + Q_DECLARE_TR_FUNCTIONS( ModuleSettingsWidget ) +public: + ModuleSettingsWidget( Module & ); +private: + void saveSettings(); + + QCheckBox *enabledB; + QDoubleSpinBox *delayB; +}; diff --git a/src/modules/MediaKit/MediaKitWriter.cpp b/src/modules/MediaKit/MediaKitWriter.cpp new file mode 100644 index 0000000..a86e584 --- /dev/null +++ b/src/modules/MediaKit/MediaKitWriter.cpp @@ -0,0 +1,85 @@ +#include +#include + +MediaKitWriter::MediaKitWriter( Module &module ) : + err( false ) +{ + addParam( "delay" ); + addParam( "chn" ); + addParam( "rate" ); + + SetModule( module ); +} + +bool MediaKitWriter::set() +{ + if ( player.delay != sets().getDouble( "Delay" ) ) + { + player.delay = sets().getDouble( "Delay" ); + return false; + } + return sets().getBool( "WriterEnabled" ); +} + +bool MediaKitWriter::readyWrite() const +{ + return !err && player.isOpen(); +} + +bool MediaKitWriter::processParams( bool * ) +{ + bool resetAudio = false; + + uchar chn = getParam( "chn" ).toUInt(); + if ( player.channels != chn ) + { + resetAudio = true; + player.channels = chn; + } + uint rate = getParam( "rate" ).toUInt(); + if ( player.sample_rate != rate ) + { + resetAudio = true; + player.sample_rate = rate; + } + + if ( resetAudio || err ) + { + player.stop(); + err = !player.start(); + if ( !err ) + modParam( "delay", player.delay ); + else + QMPlay2Core.logError( "MediaKitWriter :: " + tr ( "Cannot open audio output stream" ) ); + } + + return readyWrite(); +} +qint64 MediaKitWriter::write( const QByteArray &arr ) +{ + if ( !arr.size() || !readyWrite() ) + return 0; + + err = !player.write( arr ); + if ( err ) + { + QMPlay2Core.logError( "MediaKitWriter :: " + tr ( "Playback error" ) ); + return 0; + } + + return arr.size(); +} + +qint64 MediaKitWriter::size() const +{ + return -1; +} +QString MediaKitWriter::name() const +{ + return MediaKitWriterName; +} + +bool MediaKitWriter::open() +{ + return player.isOK(); +} diff --git a/src/modules/MediaKit/MediaKitWriter.hpp b/src/modules/MediaKit/MediaKitWriter.hpp new file mode 100644 index 0000000..28fa249 --- /dev/null +++ b/src/modules/MediaKit/MediaKitWriter.hpp @@ -0,0 +1,30 @@ +#include +#include + +#include + +class MediaKitWriter : public Writer +{ + Q_DECLARE_TR_FUNCTIONS( MediaKitWriter ) +public: + MediaKitWriter( Module & ); +private: + bool set(); + + bool readyWrite() const; + + bool processParams( bool *paramsCorrected ); + qint64 write( const QByteArray & ); + + qint64 size() const; + QString name() const; + + bool open(); + + /**/ + + SndPlayer player; + bool err; +}; + +#define MediaKitWriterName "MediaKit Writer" diff --git a/src/modules/MediaKit/RingBuffer.cpp b/src/modules/MediaKit/RingBuffer.cpp new file mode 100644 index 0000000..915becc --- /dev/null +++ b/src/modules/MediaKit/RingBuffer.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "RingBuffer.hpp" + +RingBuffer::RingBuffer( int size ) +{ + initialized = false; + Buffer = new unsigned char[size]; + if(Buffer!=NULL) { + memset( Buffer, 0, size ); + BufferSize = size; + } else { + BufferSize = 0; + } + reader = 0; + writer = 0; + writeBytesAvailable = size; + if((locker=create_sem(1,"locker")) >= B_OK) { + initialized = true; + } else { + if(Buffer!=NULL) { + delete[] Buffer; + } + } +} + +RingBuffer::~RingBuffer( ) +{ + if(initialized) { + delete[] Buffer; + delete_sem(locker); + } +} + +bool +RingBuffer::Empty( void ) +{ + memset( Buffer, 0, BufferSize ); + reader = 0; + writer = 0; + writeBytesAvailable = BufferSize; + return true; +} + +int +RingBuffer::Read( unsigned char *data, int size ) +{ + acquire_sem(locker); + + if( data == 0 || size <= 0 || writeBytesAvailable == BufferSize ) { + release_sem(locker); + return 0; + } + + int readBytesAvailable = BufferSize - writeBytesAvailable; + + if( size > readBytesAvailable ) { + size = readBytesAvailable; + } + + if(size > BufferSize - reader) { + int len = BufferSize - reader; + memcpy(data, Buffer + reader, len); + memcpy(data + len, Buffer, size-len); + } else { + memcpy(data, Buffer + reader, size); + } + + reader = (reader + size) % BufferSize; + writeBytesAvailable += size; + + release_sem(locker); + return size; +} + +int +RingBuffer::Write( unsigned char *data, int size ) +{ + acquire_sem(locker); + + if( data == 0 || size <= 0 || writeBytesAvailable == 0 ) { + release_sem(locker); + return 0; + } + + if( size > writeBytesAvailable ) { + size = writeBytesAvailable; + } + + if(size > BufferSize - writer) { + int len = BufferSize - writer; + memcpy(Buffer + writer, data, len); + memcpy(Buffer, data+len, size-len); + } else { + memcpy(Buffer + writer, data, size); + } + + writer = (writer + size) % BufferSize; + writeBytesAvailable -= size; + + release_sem(locker); + return size; +} + +int +RingBuffer::GetSize( void ) +{ + return BufferSize; +} + +int +RingBuffer::GetWriteAvailable( void ) +{ + return writeBytesAvailable; +} + +int +RingBuffer::GetReadAvailable( void ) +{ + return BufferSize - writeBytesAvailable; +} + +status_t +RingBuffer::InitCheck( void ) +{ + return initialized?B_OK:B_ERROR; +} diff --git a/src/modules/MediaKit/RingBuffer.hpp b/src/modules/MediaKit/RingBuffer.hpp new file mode 100644 index 0000000..4715632 --- /dev/null +++ b/src/modules/MediaKit/RingBuffer.hpp @@ -0,0 +1,31 @@ +#ifndef __RING_BUFFER_H__ +#define __RING_BUFFER_H__ + +#include + +class RingBuffer { + +public: + RingBuffer(int size); + ~RingBuffer(); + int Read( unsigned char* dataPtr, int numBytes ); + int Write( unsigned char *dataPtr, int numBytes ); + + bool Empty( void ); + int GetSize( ); + int GetWriteAvailable( ); + int GetReadAvailable( ); + status_t InitCheck( ); +private: + unsigned char *Buffer; + int BufferSize; + int reader; + int writer; + int writeBytesAvailable; + + sem_id locker; + + bool initialized; +}; + +#endif diff --git a/src/modules/MediaKit/SndPlayer.cpp b/src/modules/MediaKit/SndPlayer.cpp new file mode 100644 index 0000000..dafa355 --- /dev/null +++ b/src/modules/MediaKit/SndPlayer.cpp @@ -0,0 +1,103 @@ +#include + +#include +#include +#include +#include + + +static void proc(void *cookie, void *buffer, size_t len, const media_raw_audio_format &format) +{ + RingBuffer *ring = (RingBuffer*)cookie; + unsigned char* ptr = (unsigned char*)buffer; + + int readed = ring->Read(ptr,len); + + if(readed InitCheck() != B_OK) { + delete ring; ring = 0; + return false; + } + + player = new BSoundPlayer(&form, "QMPlay2_BSoundPlayer", proc, NULL, (void*)ring); + + if(player->InitCheck() != B_OK) { + delete player; + player = NULL; + return false; + } + + player->Start(); + player->SetHasData(true); + + _isOK = true; + + return player; +} +void SndPlayer::stop() +{ + if ( player ) + { + if(player) { + player->Stop(); + delete player; + delete ring; + } + + player = NULL; + ring = NULL; + } +} + +double SndPlayer::getLatency() +{ + double lat = player->Latency() / (ring->GetSize()*4.0); + + return lat; +} + +bool SndPlayer::write( const QByteArray &arr ) +{ + int s = arr.size(); + while ( s > 0 && s % 4 ) + s--; + if ( s <= 0 ) + return false; + + int len=s; + + unsigned char *src_ptr = (unsigned char *)arr.data(); + + for(;;) { + int len2 = ring->Write(src_ptr,len); + if(len2 == len)break; + len -= len2; + src_ptr += len2; + snooze(100); + } + + return true; +} diff --git a/src/modules/MediaKit/SndPlayer.hpp b/src/modules/MediaKit/SndPlayer.hpp new file mode 100644 index 0000000..63709b4 --- /dev/null +++ b/src/modules/MediaKit/SndPlayer.hpp @@ -0,0 +1,49 @@ +#ifndef PULSE_HPP +#define PULSE_HPP + +#include + +#include + +#include +#include +#include + +#include "RingBuffer.hpp" + +class SndPlayer +{ +public: + SndPlayer(); + inline ~SndPlayer() + { + stop(); + } + + inline bool isOK() const + { + return _isOK; + } + inline bool isOpen() const + { + return player; + } + + bool start(); + void stop(); + + double getLatency(); + + bool write( const QByteArray & ); + + double delay; + uchar channels; + float sample_rate; + +private: + bool _isOK; + BSoundPlayer *player; + RingBuffer *ring; +}; + +#endif diff --git a/src/modules/MediaKit/icon.qrc b/src/modules/MediaKit/icon.qrc new file mode 100644 index 0000000..24b4ebd --- /dev/null +++ b/src/modules/MediaKit/icon.qrc @@ -0,0 +1,3 @@ + + MediaKit.png + diff --git a/src/qmplay2/IPC_Unix.cpp b/src/qmplay2/IPC_Unix.cpp index bac1b16..2bc3175 100644 --- a/src/qmplay2/IPC_Unix.cpp +++ b/src/qmplay2/IPC_Unix.cpp @@ -98,7 +98,7 @@ bool IPCSocket::open(QIODevice::OpenMode mode) if (m_priv->fd > 0) { - const unsigned long on = 1; + unsigned long on = 1; ioctl(m_priv->fd, FIONBIO, &on); m_priv->socketNotifier = new QSocketNotifier(m_priv->fd, QSocketNotifier::Read, this); connect(m_priv->socketNotifier, SIGNAL(activated(int)), this, SLOT(socketReadActive())); diff --git a/src/qmplay2/QMPlay2Core.cpp b/src/qmplay2/QMPlay2Core.cpp index 5cf15b9..aacaade 100644 --- a/src/qmplay2/QMPlay2Core.cpp +++ b/src/qmplay2/QMPlay2Core.cpp @@ -45,7 +45,7 @@ #include #elif defined Q_OS_MACOS #include -#elif !defined Q_OS_ANDROID +#elif !defined Q_OS_ANDROID && !defined Q_OS_HAIKU #include #include #endif @@ -243,7 +243,7 @@ void QMPlay2CoreClass::init(bool loadModules, bool modulesInSubdirs, const QStri } else { -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) || defined(Q_OS_HAIKU) settingsDir = QFileInfo(QSettings(QSettings::IniFormat, QSettings::UserScope, QString()).fileName()).absolutePath() + "/QMPlay2/"; #elif defined(Q_OS_MACOS) settingsDir = Functions::cleanPath(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0)); @@ -491,6 +491,11 @@ QStringList QMPlay2CoreClass::getModules(const QString &type, int typeLen) const #elif defined Q_OS_WIN if (type == "videoWriters") defaultModules << "OpenGL 2" << "DirectDraw"; +#elif defined Q_OS_HAIKU + if ( type == "videoWriters" ) + defaultModules << "QPainter"; + else if ( type == "audioWriters" ) + defaultModules << "MediaKit"; #endif if (type == "decoders") defaultModules << "FFmpeg Decoder"; diff --git a/src/qmplay2/YouTubeDL.cpp b/src/qmplay2/YouTubeDL.cpp index 5659018..63256fa 100644 --- a/src/qmplay2/YouTubeDL.cpp +++ b/src/qmplay2/YouTubeDL.cpp @@ -37,9 +37,13 @@ static QMutex g_mutex(QMutex::Recursive); QString YouTubeDL::getFilePath() { +#ifdef Q_OS_HAIKU + return "/bin/yt-dlp" +#else return QMPlay2Core.getSettingsDir() + "yt-dlp" #ifdef Q_OS_WIN "_x86.exe" +#endif #endif ; } @@ -258,6 +262,9 @@ void YouTubeDL::abort() bool YouTubeDL::prepare() { +#ifdef Q_OS_HAIKU + return true; +#endif #ifdef Q_OS_ANDROID return false; #endif @@ -304,6 +311,9 @@ bool YouTubeDL::prepare() bool YouTubeDL::download() { +#if defined(Q_OS_HAIKU) + return true; +#endif // Mutex must be locked here const QString downloadUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" @@ -357,6 +367,9 @@ bool YouTubeDL::download() } bool YouTubeDL::update() { +#if defined(Q_OS_HAIKU) + return true; +#endif // Mutex must be locked here qDebug() << "\"youtube-dl\" updates will be checked"; @@ -408,7 +421,7 @@ bool YouTubeDL::update() void YouTubeDL::ensureExecutable() { -#ifndef Q_OS_WIN +#if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU) if (!QFileInfo(m_ytDlPath).isExecutable()) { QFile file(m_ytDlPath); -- 2.30.2