Added local music playback, cleaned macOS cmake support, and reworked gstreamer player logic

This commit is contained in:
2026-03-17 12:20:46 +10:00
parent 6de7ae441d
commit dcd4720f4c
10 changed files with 558 additions and 15 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
include_directories(${GSTREAMER_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIRS} /usr/X11R7/include /usr/pkg/include/ffmpeg8)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message("--- Detected macOS ---")
target_link_directories(ossp PRIVATE /opt/ossp/lib /opt/homebrew/lib)
target_link_directories(ossp PRIVATE /opt/ossp/lib)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)
pkg_check_modules(BZ2 REQUIRED bzip2)
+7 -5
View File
@@ -66,21 +66,23 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
if ((*discordrpc_struct)->state == DISCORDRPC_STATE_IDLE) {
asprintf(&detailsString, "Idle");
presence.details = detailsString;
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_OPENSUBSONIC) {
// Playing a song
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_OPENSUBSONIC ||
((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_LOCALFILE)) {
// Playing a song from an OpenSubsonic server
time_t currentTime = time(NULL);
asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle);
asprintf(&stateString, "by %s", (*discordrpc_struct)->songArtist);
presence.details = detailsString;
presence.state = stateString;
presence.largeImageKey = (*discordrpc_struct)->coverArtUrl;
if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_OPENSUBSONIC) {
// TODO As of now, local file playback does NOT deal with cover art
presence.largeImageKey = (*discordrpc_struct)->coverArtUrl;
}
presence.startTimestamp = (long)currentTime;
presence.endTimestamp = (long)currentTime + (*discordrpc_struct)->songLength;
if (configObj->discordrpc_showSysDetails) {
presence.largeImageText = discordrpc_osString;
}
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_LOCALFILE) {
//
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_INTERNETRADIO) {
// Playing an internet radio station
time_t currentTime = time(NULL);
+365 -4
View File
@@ -5,6 +5,11 @@
* Info: Debug / Prototype graphical interface
*/
/*
* THIS IS SPECIFICALLY THE DEVELOPMENT INTERFACE
* IT IS HORRIFICIALLY UNOPTIMIZED BUT IT WORKS
*/
#include "../external/imgui/imgui.h"
#include "../external/imgui/backends/imgui_impl_sdl2.h"
#include "../external/imgui/backends/imgui_impl_opengl2.h"
@@ -15,15 +20,25 @@
#include "../configHandler.h"
#include "../libopensubsonic/httpclient.h"
#include "../libopensubsonic/endpoint_getStarred.h"
#include "../libopensubsonic/endpoint_getAlbum.h"
#include "../player/player.h"
#include "../libopensubsonic/endpoint_getInternetRadioStations.h"
extern configHandler_config_t* configObj;
bool bLikedSongsShow = false;
bool bAudioSettingsShow = false;
bool bPlayQueueShow = false;
bool bShowRadioStations = false;
bool bShowNowPlaying = false;
bool bShowLikedAlbums = false;
bool bShowLocalSongs = false;
void showLikedSongs();
void showAudioSettings();
void showPlayQueue();
void showRadioStations();
void showNowPlaying();
void showLikedAlbums();
void showLocalSongs();
int gui_entry() {
// Initialize SDL
@@ -112,6 +127,26 @@ int gui_entry() {
bPlayQueueShow = true;
}
if (ImGui::Button("Radio")) {
bShowRadioStations = true;
}
ImGui::SameLine();
if (ImGui::Button("Now Playing")) {
bShowNowPlaying = true;
}
ImGui::SameLine();
if (ImGui::Button("Liked Albums")) {
bShowLikedAlbums = true;
}
if (ImGui::Button("Local Songs")) {
bShowLocalSongs = true;
}
ImGui::End();
}
@@ -127,6 +162,22 @@ int gui_entry() {
showPlayQueue();
}
if (bShowRadioStations) {
showRadioStations();
}
if (bShowNowPlaying) {
showNowPlaying();
}
if (bShowLikedAlbums) {
showLikedAlbums();
}
if (bShowLocalSongs) {
showLocalSongs();
}
// Render
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
@@ -198,10 +249,30 @@ void showLikedSongs() {
}
if (selectedSong != -1) {
OSSPlayer_QueueAppend(starredStruct->songs[selectedSong].title,
starredStruct->songs[selectedSong].artist,
starredStruct->songs[selectedSong].id,
starredStruct->songs[selectedSong].duration);
// Form URL
opensubsonic_httpClient_URL_t* song_url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&song_url);
song_url->endpoint = OPENSUBSONIC_ENDPOINT_STREAM;
song_url->id = strdup(starredStruct->songs[selectedSong].id);
opensubsonic_httpClient_formUrl(&song_url);
opensubsonic_httpClient_URL_t* coverart_url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&coverart_url);
coverart_url->endpoint = OPENSUBSONIC_ENDPOINT_GETCOVERART;
coverart_url->id = strdup(starredStruct->songs[selectedSong].coverArt);
opensubsonic_httpClient_formUrl(&coverart_url);
OSSPQ_AppendToEnd(starredStruct->songs[selectedSong].title,
starredStruct->songs[selectedSong].album,
starredStruct->songs[selectedSong].artist,
starredStruct->songs[selectedSong].id,
song_url->formedUrl,
coverart_url->formedUrl,
starredStruct->songs[selectedSong].duration,
OSSPQ_MODE_OPENSUBSONIC);
opensubsonic_httpClient_URL_cleanup(&song_url);
opensubsonic_httpClient_URL_cleanup(&coverart_url);
selectedSong = -1;
}
@@ -242,6 +313,10 @@ void showAudioSettings() {
OSSPlayer_GstECont_Pitch_Set(pitch_val * 100.0f); // Convert semitones to cents
}
if(ImGui::Button("Skip")) {
OSSPlayer_GstECont_Playbin3_Stop();
}
ImGui::End();
}
@@ -263,3 +338,289 @@ void showPlayQueue() {
ImGui::End();
}
bool haveRadioStations = false;
opensubsonic_getInternetRadioStations_struct* irsStruct;
opensubsonic_httpClient_URL_t* radioUrl;
void getRadioStations() {
radioUrl = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&radioUrl);
radioUrl->endpoint = OPENSUBSONIC_ENDPOINT_GETINTERNETRADIOSTATIONS;
opensubsonic_httpClient_formUrl(&radioUrl);
opensubsonic_httpClient_fetchResponse(&radioUrl, (void**)&irsStruct);
if (irsStruct->errorCode != 0) {
// Error happened
}
haveRadioStations = true;
}
void showRadioStations() {
if (!haveRadioStations) { getRadioStations(); }
ImGui::Begin("Radio Stations");
static int selectedSong = -1;
if (haveRadioStations) {
if (ImGui::BeginChild("Radio Stations", ImVec2(0, 200), ImGuiChildFlags_Border)) {
for (int i = 0; i < irsStruct->radioStationCount; i++) {
if (ImGui::Selectable(irsStruct->radioStations[i].name, selectedSong == i)) {
selectedSong = i;
}
}
ImGui::EndChild();
}
}
if (selectedSong != -1) {
//OSSPlayer_QueueAppend_Radio(irsStruct->radioStations[selectedSong].name,
// irsStruct->radioStations[selectedSong].id,
// irsStruct->radioStations[selectedSong].streamUrl);
selectedSong = -1;
}
ImGui::End();
}
void showNowPlaying() {
ImGui::Begin("Now Playing");
/*
* Okay so I need:
* - Current and final position of song
* - Play, Pause, Next buttons (Needs DiscordRPC expansion)
*/
ImGui::End();
}
void showAlbum(char* id);
int likedAlbumsSelectedSong = -1;
void showLikedAlbums() {
// /getStarred is all of the liked info, not just songs
if (!haveLikedSongsInfo) { getLikedSongsInfo(); }
ImGui::Begin("Liked Albums");
ImGui::Text("Liked Albums");
if (ImGui::Button("Close")) {
bShowLikedAlbums = false;
}
if (ImGui::Button("Refresh")) {
opensubsonic_getStarred_struct_free(&starredStruct);
opensubsonic_httpClient_URL_cleanup(&starredUrl);
haveLikedSongsInfo = false;
}
if (haveLikedSongsInfo) {
if (ImGui::BeginChild("Liked Albums", ImVec2(0, 200), ImGuiChildFlags_Border)) {
for (int i = 0; i < starredStruct->albumCount; i++) {
if (ImGui::Selectable(starredStruct->albums[i].title, likedAlbumsSelectedSong == i)) {
likedAlbumsSelectedSong = i;
}
}
ImGui::EndChild();
}
}
if (likedAlbumsSelectedSong != -1) {
showAlbum(starredStruct->albums[likedAlbumsSelectedSong].id);
}
ImGui::End();
}
bool hasAlbum = false;
opensubsonic_getAlbum_struct* getAlbumStruct;
void getAlbum(char* id) {
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&url);
url->endpoint = OPENSUBSONIC_ENDPOINT_GETALBUM;
url->id = strdup(id);
opensubsonic_httpClient_formUrl(&url);
opensubsonic_httpClient_fetchResponse(&url, (void**)&getAlbumStruct);
//opensubsonic_getAlbum_struct_free(&getAlbumStruct);
//opensubsonic_httpClient_URL_cleanup(&url);
hasAlbum = true;
}
static int rand_int(int n) {
int limit = RAND_MAX - RAND_MAX % n;
int rnd;
do {
rnd = rand();
} while (rnd >= limit);
return rnd % n;
}
void shuffle(int *array, int n) {
int i, j, tmp;
for (i = n - 1; i > 0; i--) {
j = rand_int(i + 1);
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
}
void shuffleAlbum() {
int n = getAlbumStruct->songCount;
int arr[1] = { 0 };
for (int i = 0; i < n; i++) {
arr[i] = i;
}
shuffle(arr, n);
for (int i = 0; i < n; i++) {
//OSSPlayer_QueueAppend_Song(getAlbumStruct->songs[arr[i]].title,
// getAlbumStruct->songs[arr[i]].artist,
// getAlbumStruct->songs[arr[i]].id,
// getAlbumStruct->songs[arr[i]].duration);
}
}
#include <ctime>
void showAlbum(char* id) {
ImGui::Begin("Album");
if (!hasAlbum) { getAlbum(id); }
ImGui::Text("Album");
if (ImGui::Button("Close")) {
likedAlbumsSelectedSong = -1;
hasAlbum = false;
}
ImGui::SameLine();
if (ImGui::Button("Play all")) {
for (int i = 0; i < getAlbumStruct->songCount; i++) {
/*
// Form URL
opensubsonic_getAlbum_struct*
opensubsonic_httpClient_URL_t* song_url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&song_url);
song_url->endpoint = OPENSUBSONIC_ENDPOINT_STREAM;
song_url->id = strdup(starredStruct->songs[selectedSong].id);
opensubsonic_httpClient_formUrl(&song_url);
opensubsonic_httpClient_URL_t* coverart_url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&coverart_url);
coverart_url->endpoint = OPENSUBSONIC_ENDPOINT_GETCOVERART;
coverart_url->id = strdup(starredStruct->songs[selectedSong].coverArt);
opensubsonic_httpClient_formUrl(&coverart_url);
OSSPQ_AppendToEnd(starredStruct->songs[selectedSong].title,
starredStruct->songs[selectedSong].album,
starredStruct->songs[selectedSong].artist,
starredStruct->songs[selectedSong].id,
song_url->formedUrl,
coverart_url->formedUrl,
starredStruct->songs[selectedSong].duration,
OSSPQ_MODE_OPENSUBSONIC);
opensubsonic_httpClient_URL_cleanup(&song_url);
opensubsonic_httpClient_URL_cleanup(&coverart_url);
selectedSong = -1; getAlbumStruct->songs[i].duration);
*/
}
}
ImGui::SameLine();
if (ImGui::Button("Shuffle")) {
srand(time(NULL));
shuffleAlbum();
}
static int selectedSong = -1;
if (hasAlbum) {
if (ImGui::BeginChild("Album", ImVec2(0, 200), ImGuiChildFlags_Border)) {
for (int i = 0; i < getAlbumStruct->songCount; i++) {
if (ImGui::Selectable(getAlbumStruct->songs[i].title, selectedSong == i)) {
selectedSong = i;
}
}
ImGui::EndChild();
}
}
ImGui::End();
}
#include "../localMusicHandler.hpp"
bool hasLocalSongs = false;
localMusicHandler_songReq_t* songReq;
void getLocalSongs() {
songReq = localMusicHandler_test();
hasLocalSongs = true;
}
void showLocalSongs() {
ImGui::Begin("Local Songs");
if (!hasLocalSongs) { getLocalSongs(); }
ImGui::Text("Local Songs");
static int selectedSong = -1;
if (hasLocalSongs) {
if (ImGui::BeginChild("LocalSongs", ImVec2(0, 200), ImGuiChildFlags_Border)) {
for (int i = 0; i < songReq->songCount; i++) {
if (ImGui::Selectable(songReq->songs[i].title, selectedSong == i)) {
selectedSong = i;
}
}
ImGui::EndChild();
}
}
if (selectedSong != -1) {
// Treat it as radio station for testing
char* newPath = NULL;
asprintf(&newPath, "file://%s", songReq->songs[selectedSong].path);
//OSSPlayer_QueueAppend_Radio(songReq->songs[selectedSong].title,
// songReq->songs[selectedSong].uid,
// newPath);
OSSPQ_AppendToEnd(songReq->songs[selectedSong].title,
NULL,
NULL,
songReq->songs[selectedSong].uid,
newPath,
NULL,
0,
OSSPQ_MODE_LOCALFILE);
selectedSong = -1;
}
ImGui::End();
}
+65
View File
@@ -84,6 +84,9 @@ void localMusicHandler_scan() {
} else if (rc == 1) {
// Table was already made, assume songs were loaded in before
}
//localMusicHandler_test();
}
void localMusicHandler_scanDirectory(char* directory) {
@@ -255,3 +258,65 @@ void localMusicHandler_moveSongsToDatabase(int idx) {
sqlite3_finalize(sqlite_stmt);
}
// P.S. Sqlite searching directly is fucking useless
// Have to use something on top, and just load base shit into memory completely I think
// Plus how many fucking songs would it take to actually become an issue?
std::deque<localMusicHandler_AudioObject> localMusicHandler_songReqDeque;
localMusicHandler_songReq_t* localMusicHandler_test() {
// Test
sqlite3_stmt* sqlite_stmt;
//const char* sqlQuery = "SELECT * FROM local_songs WHERE artistTitle LIKE ?;";
const char* sqlQuery = "SELECT * FROM local_songs;";
if (sqlite3_prepare_v2(sqlite_db, sqlQuery, -1, &sqlite_stmt, NULL) != SQLITE_OK) {
printf("fuck\n");
//return;
}
//sqlite3_bind_text(sqlite_stmt, 1, "ch", -1, SQLITE_STATIC);
// %text% to prevent SQL injection --> %% for % with asprintf, %%%s%%. fucking ridiculous but its it
// Also rename these fuckass fields. 'artistTitle' what am I high??
static int rc = 0;
while ((rc = sqlite3_step(sqlite_stmt)) == SQLITE_ROW) {
printf("here\n");
localMusicHandler_AudioObject audioObject;
// TODO THIS IS NOT SAFE HOLY FUCK TESTING ONLY LIKE MEGA ONLY
// Could load directly into struct if I know how many songs there will be
// Seems to be only able to do that by issuing yet another SQL request
audioObject.path = (char*)sqlite3_column_text(sqlite_stmt, 6);
audioObject.songTitle = (char*)sqlite3_column_text(sqlite_stmt, 1);
audioObject.uid = (char*)sqlite3_column_text(sqlite_stmt, 0);
localMusicHandler_songReqDeque.push_back(audioObject);
}
if (sqlite3_step(sqlite_stmt) != SQLITE_DONE) {
printf("[LocalMusicHandler] Execution error: %s\n", sqlite3_errmsg(sqlite_db));
}
sqlite3_finalize(sqlite_stmt);
// Load into actual struct
int songCount = localMusicHandler_songReqDeque.size();
localMusicHandler_songReq_t* songReq = (localMusicHandler_songReq_t*)malloc(sizeof(localMusicHandler_songReq_t));
songReq->songCount = songCount;
songReq->songs = (localMusicHandler_songReq_songs_t*)malloc(sizeof(localMusicHandler_songReq_songs_t) * songCount);
for (int i = 0; i < songCount; i++) {
localMusicHandler_AudioObject audioObject;
audioObject = localMusicHandler_songReqDeque.front();
localMusicHandler_songReqDeque.pop_front();
songReq->songs[i].uid = strdup(audioObject.uid.c_str());
songReq->songs[i].title = strdup(audioObject.songTitle.c_str());
songReq->songs[i].path = strdup(audioObject.path.c_str());
}
return songReq;
}
+19
View File
@@ -19,6 +19,25 @@ void localMusicHandler_generateUid(int idx);
int localMusicHandler_initDatabase();
void localMusicHandler_moveSongsToDatabase(int idx);
typedef struct {
char* uid;
char* title;
char* album;
char* artist;
char* path;
} localMusicHandler_songReq_songs_t;
typedef struct {
int songCount;
localMusicHandler_songReq_songs_t* songs;
} localMusicHandler_songReq_t;
localMusicHandler_songReq_t* localMusicHandler_test();
#ifdef __cplusplus
}
#endif // __cplusplus
+61
View File
@@ -0,0 +1,61 @@
/*
* OpenSubsonicPlayer
* Goldenkrew3000 2026
* License: GNU General Public License 3.0
* Info: Local (Not from OpenSubsonic server) Internet Radio Sqlite3 Database Handler
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "external/sqlite3/sqlite3.h"
#include "localRadioDBHandler.h"
static sqlite3* db = NULL;
static int rc = 0;
static char* errorMsg = NULL;
int localRadioDBHandler_CreateDB(char* dbPath);
int localRadioDBHandler_Init() {
// Form path
char* homePath = getenv("HOME");
char* dbPath = NULL;
rc = asprintf(&dbPath, "%s/.config/ossp/radio.db", homePath);
if (rc == -1) { printf("asprintf() failed.\n"); return 1; }
// Check if file exists
struct stat st;
if (stat(dbPath, &st) == 0) {
printf("[LocalRadio] Database found, is %ld bytes.\n", st.st_size);
} else {
printf("[LocalRadio] Database does not exist, creating.\n");
localRadioDBHandler_CreateDB(dbPath);
}
}
int localRadioDBHandler_CreateDB(char* dbPath) {
// Create database
rc = sqlite3_open(dbPath, &db);
if (rc) {
printf("Could not create database: %s\n", sqlite3_errmsg(db));
} else { printf("Created database.\n"); }
// Create table 'stations'
char* sqlQuery = "CREATE TABLE stations(id INT, name TEXT, url TEXT)";
rc = sqlite3_exec(db, sqlQuery, NULL, 0, &errorMsg);
if (rc != SQLITE_OK) {
printf("could not make table: %s\n", errorMsg);
sqlite3_free(errorMsg);
} else {
printf("made table\n");
}
sqlite3_close(db);
}
int localRadioDBHandler_AddStation(char* name, char* url) {
//
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef _LOCAL_RADIO_DB_HANDLER_H
#define _LOCAL_RADIO_DB_HANDLER_H
int localRadioDBHandler_Init();
#endif
+11
View File
@@ -17,12 +17,20 @@
#include "player/player.h"
#include "discordrpc.h"
#include "localRadioDBHandler.h"
#include "libopensubsonic/endpoint_getInternetRadioStations.h"
#include "libopensubsonic/httpclient.h"
#include "socket.h"
#include "localMusicHandler.hpp"
static int rc = 0;
configHandler_config_t* configObj = NULL;
int checkConfigFile();
int validateConnection();
int main(int argc, char** argv) {
//localRadioDBHandler_Init();
// Read config file
rc = configHandler_Read(&configObj);
if (rc != 0) {
@@ -67,8 +75,11 @@ int main(int argc, char** argv) {
discordrpc_update(&discordrpc);
discordrpc_struct_deinit(&discordrpc);
//localMusicHandler_scan();
// Launch QT frontend
gui_entry();
//socketHandler_init();
// Cleanup and exit
configHandler_Free(&configObj);
+17 -2
View File
@@ -44,7 +44,7 @@ static gboolean gst_bus_call(GstBus* bus, GstMessage* message, gpointer data) {
case GST_MESSAGE_BUFFERING: {
gint percent = 0;
gst_message_parse_buffering(message, &percent);
printf("Buffering (%d%%)...\n", (int)percent);
//printf("Buffering (%d%%)...\n", (int)percent);
break;
}
case GST_MESSAGE_ERROR: {
@@ -57,7 +57,7 @@ static gboolean gst_bus_call(GstBus* bus, GstMessage* message, gpointer data) {
break;
}
case GST_MESSAGE_STATE_CHANGED:
printf("State changed\n");
//printf("State changed\n");
break;
case GST_MESSAGE_NEW_CLOCK:
//
@@ -361,6 +361,8 @@ int OSSPlayer_GstInit() {
}
// Initialize reverb
}
int OSSPlayer_GstDeInit() {
@@ -432,6 +434,19 @@ void OSSPlayer_GstECont_Playbin3_Stop() {
isPlaying = false; // Notify player thread to attempt to load next song
}
void OSSPlayer_GstECont_Playbin3_PlayPause() {
GstState state;
gst_element_get_state (pipeline, &state, NULL, 0);
if (state == GST_STATE_PLAYING) {
gst_element_set_state (pipeline, GST_STATE_PAUSED);
g_print ("Paused\n");
} else {
gst_element_set_state (pipeline, GST_STATE_PLAYING);
g_print ("Playing\n");
}
}
/*
* Utility Functions
*/
+6 -3
View File
@@ -13,10 +13,11 @@ extern "C" {
#include "playQueue.hpp"
void* OSSPlayer_GMainLoop(void*);
void* OSSPlayer_ThrdInit(void*);
void* OSSPlayer_GMainLoop(void* arg);
void* OSSPlayer_ThrdInit(void* arg);
int OSSPlayer_GstInit();
int OSSPlayer_QueueAppend(char* title, char* artist, char* id, long duration);
int OSSPlayer_QueueAppend_Song(char* title, char* artist, char* id, long duration);
int OSSPlayer_QueueAppend_Radio(char* name, char* id, char* radioUrl);
OSSPQ_SongStruct* OSSPlayer_QueuePopFront();
float OSSPlayer_GstECont_InVolume_Get();
@@ -25,6 +26,8 @@ float OSSPlayer_GstECont_OutVolume_Get();
void OSSPlayer_GstECont_OutVolume_set(float val);
float OSSPlayer_GstECont_Pitch_Get();
void OSSPlayer_GstECont_Pitch_Set(float cents);
void OSSPlayer_GstECont_Playbin3_Stop();
void OSSPlayer_GstECont_Playbin3_PlayPause();
float OSSPlayer_DbLinMul(float db);
float OSSPlayer_PitchFollow(float freq, float semitone);