MaartenBaert / ssr

SimpleScreenRecorder, a screen recorder for Linux
http://www.maartenbaert.be/simplescreenrecorder/
GNU General Public License v3.0
2.53k stars 286 forks source link

Cannot compile with Clang (FreeBSD) #197

Open iamgreaser opened 10 years ago

iamgreaser commented 10 years ago

Note, g++ doesn't work on my system.

After dummying out the inotify stuff (because that's not in FreeBSD), I eventually get to this step:

GUI/PageOutput.cpp:79:15: error: no viable overloaded '='
    m_containers = {
    ~~~~~~~~~~~~ ^ ~
/usr/include/c++/v1/vector:571:13: note: candidate function not viable: cannot convert initializer list argument to 'const std::__1::vector<PageOutput::ContainerData, std::__1::allocator<PageOutput::ContainerData> >'
vector& operator=(const vector& __x);
        ^
/usr/include/c++/v1/vector:579:13: note: candidate function not viable: cannot convert initializer list argument to 'std::__1::vector<PageOutput::ContainerData, std::__1::allocator<PageOutput::ContainerData> >'
vector& operator=(vector&& __x)
        ^
/usr/include/c++/v1/vector:586:13: note: candidate function not viable: cannot convert initializer list argument to 'PageOutput::ContainerData'
vector& operator=(initializer_list<value_type> __il)
        ^

Stuff like this is why I strongly believe C++ needs to die in a fire, but could someone find out how to make this crap work? Thanks.

Using Qt 4.8.5 if memory serves me well.

MaartenBaert commented 10 years ago

AFAIK what I'm doing is valid C++11. Anyway, I tweaked it a bit to make it less ambiguous and now it compiles fine (with Clang on Linux at least).

You can do without inotify if you don't need OpenGL recording. Does FreeBSD have all the other required APIs, like ALSA?

iamgreaser commented 10 years ago

Oddly enough it does have ALSA (which just routes to PulseAudio so it just sucks), but it's got JACK as well. The native sound API is (kernel-mixed) OSS if you need to wrap that.

Anyway, your fix didn't quite work but it's inspired me enough to get it working. The problematic line was the "Other..." section and it needed a bit of wrapping.

m_containers = {
    ContainerData({"Matroska (MKV)", "matroska", QStringList({"mkv"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("Matroska") + " (*.mkv)",
        {VIDEO_CODEC_H264, VIDEO_CODEC_VP8, VIDEO_CODEC_THEORA},
        {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC, AUDIO_CODEC_UNCOMPRESSED}}),
    ContainerData({"MP4", "mp4", QStringList({"mp4"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("MP4") + " (*.mp4)",
        {VIDEO_CODEC_H264},
        {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC}}),
    ContainerData({"WebM", "webm", QStringList({"webm"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("WebM") + " (*.webm)",
        {VIDEO_CODEC_VP8},
        {AUDIO_CODEC_VORBIS}}),
    ContainerData({"OGG", "ogg", QStringList({"ogg"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("OGG") + " (*.ogg)",
        {VIDEO_CODEC_THEORA},
        {AUDIO_CODEC_VORBIS}}),
    ContainerData({tr("Other..."), "other", QStringList(), "", std::set<enum_video_codec>({}), std::set<enum_audio_codec>({})}),
};

Here's the full patch to get it working on FreeBSD. I strongly encourage you to go through it manually to suit your style. And yes, it works well.

diff --git a/glinject/elfhacks.h b/glinject/elfhacks.h
index d1be8e2..3b9061d 100644
--- a/glinject/elfhacks.h
+++ b/glinject/elfhacks.h
@@ -48,15 +48,20 @@ extern "C" {
 #ifdef __elf64
 # define ELFW_R_SYM ELF64_R_SYM
 # define ElfW_Sword Elf64_Sxword
+# define ElfW(v) Elf64_##v
+# define __ELF_NATIVE_CLASS 64
 #else
 # ifdef __elf32
 #  define ELFW_R_SYM ELF32_R_SYM
 #  define ElfW_Sword Elf32_Sword
+#  define ElfW(v) Elf32_##v
+#  define __ELF_NATIVE_CLASS 32
 # else
 #  error neither __elf32 nor __elf64 is defined
 # endif
 #endif

+
 /**
  *  \defgroup elfhacks elfhacks
  *  Elfhacks is a collection of functions that aim for retvieving
diff --git a/src/AV/Input/SSRVideoStreamWatcher.cpp b/src/AV/Input/SSRVideoStreamWatcher.cpp
index 0033cd0..1f610a1 100644
--- a/src/AV/Input/SSRVideoStreamWatcher.cpp
+++ b/src/AV/Input/SSRVideoStreamWatcher.cpp
@@ -23,7 +23,7 @@ along with SimpleScreenRecorder.  If not, see <http://www.gnu.org/licenses/>.

 #include <dirent.h>
 #include <signal.h>
-#include <sys/inotify.h>
+//include <sys/inotify.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -113,6 +113,7 @@ void SSRVideoStreamWatcher::Init() {
    }

    // initialize inotify
+#ifdef LINUX
    m_fd_notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
    if(m_fd_notify == -1) {
        Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't initialize inotify!", "don't translate 'inotify'"));
@@ -124,6 +125,9 @@ void SSRVideoStreamWatcher::Init() {
        Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't watch shared memory directory!"));
        throw SSRStreamException();
    }
+#else
+   m_fd_notify = -1;
+#endif

    // get all the files that existed already
    DIR *dir = NULL;
@@ -181,7 +185,10 @@ void SSRVideoStreamWatcher::Free() {

 void SSRVideoStreamWatcher::HandleChanges(AddCallback add_callback, RemoveCallback remove_callback, void* userdata) {

+   if(m_fd_notify == -1) return;
+
    // find out how much we can read
+#ifdef LINUX
    int len;
    if(ioctl(m_fd_notify, FIONREAD, &len) == -1) {
        Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Can't get read length from inotify!", "don't translate 'inotify'"));
@@ -246,6 +253,7 @@ void SSRVideoStreamWatcher::HandleChanges(AddCallback add_callback, RemoveCallba
        }

    }
+#endif

    // delete abandoned streams
    for(unsigned int j = m_streams.size(); j > 0; ) {
diff --git a/src/AV/SimpleSynth.cpp b/src/AV/SimpleSynth.cpp
index cdf4891..24a59e3 100644
--- a/src/AV/SimpleSynth.cpp
+++ b/src/AV/SimpleSynth.cpp
@@ -30,6 +30,7 @@ along with SimpleScreenRecorder.  If not, see <http://www.gnu.org/licenses/>.
 static void MakeThreadHighPriority() {
    rlimit limit;

+#ifdef LINUX
    // try to get real-time priority
    getrlimit(RLIMIT_RTPRIO, &limit);
    int rt_limit = (limit.rlim_cur == RLIM_INFINITY)? std::numeric_limits<int>::max() : limit.rlim_cur;
@@ -50,6 +51,7 @@ static void MakeThreadHighPriority() {
            return;
        }
    }
+#endif

    Logger::LogWarning("[MakeThreadHighPriority] " + Logger::tr("Warning: Can't increase the thread priority."));
 }
diff --git a/src/GUI/PageOutput.cpp b/src/GUI/PageOutput.cpp
index b4742ae..2724f9e 100644
--- a/src/GUI/PageOutput.cpp
+++ b/src/GUI/PageOutput.cpp
@@ -77,19 +77,19 @@ PageOutput::PageOutput(MainWindow* main_window)

    // main codecs
    m_containers = {
-       {"Matroska (MKV)", "matroska", QStringList({"mkv"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("Matroska") + " (*.mkv)",
+       ContainerData({"Matroska (MKV)", "matroska", QStringList({"mkv"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("Matroska") + " (*.mkv)",
            {VIDEO_CODEC_H264, VIDEO_CODEC_VP8, VIDEO_CODEC_THEORA},
-           {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC, AUDIO_CODEC_UNCOMPRESSED}},
-       {"MP4", "mp4", QStringList({"mp4"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("MP4") + " (*.mp4)",
+           {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC, AUDIO_CODEC_UNCOMPRESSED}}),
+       ContainerData({"MP4", "mp4", QStringList({"mp4"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("MP4") + " (*.mp4)",
            {VIDEO_CODEC_H264},
-           {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC}},
-       {"WebM", "webm", QStringList({"webm"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("WebM") + " (*.webm)",
+           {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC}}),
+       ContainerData({"WebM", "webm", QStringList({"webm"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("WebM") + " (*.webm)",
            {VIDEO_CODEC_VP8},
-           {AUDIO_CODEC_VORBIS}},
-       {"OGG", "ogg", QStringList({"ogg"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("OGG") + " (*.ogg)",
+           {AUDIO_CODEC_VORBIS}}),
+       ContainerData({"OGG", "ogg", QStringList({"ogg"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("OGG") + " (*.ogg)",
            {VIDEO_CODEC_THEORA},
-           {AUDIO_CODEC_VORBIS}},
-       {tr("Other..."), "other", QStringList(), "", {}, {}},
+           {AUDIO_CODEC_VORBIS}}),
+       ContainerData({tr("Other..."), "other", QStringList(), "", std::set<enum_video_codec>({}), std::set<enum_audio_codec>({})}),
    };
    m_video_codecs = {
        {"H.264"       , "libx264"  },
MaartenBaert commented 10 years ago

Thanks, I will integrate it into the master branch.

Is OSS the 'default' sound API on FreeBSD? Currently the sound notification code only uses ALSA, that probably won't be ideal for FreeBSD?

I noticed you disabled the code that changes the thread priority in SimpleSynth. I know that separate nice values per thread only work on Linux, but what about real-time threads? Is this also unsupported on FreeBSD? How can a system like JACK work without real-time (or at least high priority) threads?

Have you tested OpenGL recording on FreeBSD? SSR won't be able to auto-detect new streams without inotify, but it should still work if you start the OpenGL program first.

iamgreaser commented 10 years ago

OSS is the 'default' sound system, yes.

I disabled the code because getrlimit doesn't support those two parameters. I know that FreeBSD supports setpriority(2), but I think that's for the "nice" level.

Suprisingly enough, JACK runs pretty well at normal priority. I could renice the process if I really wanted to, though.

Trying to record Xonotic I get this:

[PageRecord::StartPage] Starting page ...
[SSRVideoStreamWatcher::Init] Error: Can't create channel directory!
[PageRecord::StartPage] Error: Something went wrong during initialization.
[PageRecord::StartPage] Started page.
[PageRecord::StartInput] Starting input ...
[PageRecord::StartInput] Error: Could not start the GLInject input because it has not been created.
[PageRecord::StartInput] Error: Something went wrong during initialization.
[PageRecord::StartOutput] Starting output ...
[PageRecord::StartOutput] Error: Could not get the size of the OpenGL application because the GLInject input has not been created.
[PageRecord::StartOutput] Error: Something went wrong during initialization.

I did two more things:

  1. Replaced all references to /dev/shm with a reference to /tmp.
  2. Made the injection code look for libc.so rather than libdl.so, because that's where dlsym is located on FreeBSD.

After changing the "channel directory" so it points to /tmp/ssr-* rather than /dev/shm/ssr-*, some of the errors go away but I still can't actually record...

[PageRecord::StartOutput] Starting output ...
[PageRecord::StartOutput] Error: Could not get the size of the OpenGL application. Either the application wasn't started correctly, or the application hasn't created an OpenGL window yet. If you want to start recording before starting the application, you have to enable scaling and enter the video size manually.
[PageRecord::StartOutput] Error: Something went wrong during initialization.

I also tested this with an OpenGL + SDL game I wrote. glxgears complains that it can't find libdl.so (or after patching, libc.so).

Here's the patch so far.

diff --git a/glinject/Hook.cpp b/glinject/Hook.cpp
index e1c0be7..71d8939 100644
--- a/glinject/Hook.cpp
+++ b/glinject/Hook.cpp
@@ -47,8 +47,13 @@ void InitGLInject() {

    // part 1: get dlsym and dlvsym
    eh_obj_t libdl;
+#ifdef LINUX
    if(eh_find_obj(&libdl, "*/libdl.so*")) {
        GLINJECT_PRINT("Error: Can't open libdl.so!");
+#else
+   if(eh_find_obj(&libdl, "*/libc.so*")) {
+       GLINJECT_PRINT("Error: Can't open libc.so!");
+#endif
        exit(1);
    }
    if(eh_find_sym(&libdl, "dlsym", (void**) &g_glinject_real_dlsym)) {
diff --git a/glinject/SSRVideoStreamWriter.cpp b/glinject/SSRVideoStreamWriter.cpp
index aaa3f62..bb07f29 100644
--- a/glinject/SSRVideoStreamWriter.cpp
+++ b/glinject/SSRVideoStreamWriter.cpp
@@ -31,7 +31,11 @@ SSRVideoStreamWriter::SSRVideoStreamWriter(const std::string& channel, const std

    std::string stream_name = NumToString(hrt_time_micro()) + "-" + NumToString(getpid()) + "-" + source + "-" + GetProgramName();

+#ifdef LINUX
    m_channel_directory = "/dev/shm/ssr-" + ((channel.empty())? "channel-" + GetUserName() : channel);
+#else
+   m_channel_directory = "/tmp/ssr-" + ((channel.empty())? "channel-" + GetUserName() : channel);
+#endif
    m_filename_main = m_channel_directory + "/video-" + stream_name;
    m_page_size = sysconf(_SC_PAGE_SIZE);
    m_width = 0;
diff --git a/src/AV/Input/SSRVideoStreamReader.cpp b/src/AV/Input/SSRVideoStreamReader.cpp
index 0307329..0d987c2 100644
--- a/src/AV/Input/SSRVideoStreamReader.cpp
+++ b/src/AV/Input/SSRVideoStreamReader.cpp
@@ -30,7 +30,11 @@ along with SimpleScreenRecorder.  If not, see <http://www.gnu.org/licenses/>.
 SSRVideoStreamReader::SSRVideoStreamReader(const std::string& channel, const SSRVideoStream& stream) {

    m_stream = stream;
+#ifdef LINUX
    m_channel_directory = "/dev/shm/ssr-" + channel;
+#else
+   m_channel_directory = "/tmp/ssr-" + channel;
+#endif
    m_filename_main = m_channel_directory + "/video-" + stream.m_stream_name;
    m_page_size = sysconf(_SC_PAGE_SIZE);

diff --git a/src/AV/Input/SSRVideoStreamWatcher.cpp b/src/AV/Input/SSRVideoStreamWatcher.cpp
index 0033cd0..a805b27 100644
--- a/src/AV/Input/SSRVideoStreamWatcher.cpp
+++ b/src/AV/Input/SSRVideoStreamWatcher.cpp
@@ -58,7 +58,11 @@ static bool SSRVideoStreamParse(const std::string& filename, SSRVideoStream* str

 SSRVideoStreamWatcher::SSRVideoStreamWatcher(const std::string& channel, bool relax_permissions) {

+#ifdef LINUX
    m_channel_directory = "/dev/shm/ssr-" + channel;
+#else
+   m_channel_directory = "/tmp/ssr-" + channel;
+#endif
    m_relax_permissions = relax_permissions;

    m_fd_notify = -1;
MaartenBaert commented 10 years ago

Without inotify, you will have to start the application first (from a terminal, with the 'ssr-glinject' script), and then go to the recording page. You should get a message that says a pre-existing stream was found.

Is /tmp guaranteed to be a ramdisk? I.e. is it safe to use files in /tmp as shared memory? Or should I use $XDG_RUNTIME_DIR (/run/user/1000)? Or even instruct the user to set up a ramdisk?

I should probably rewrite the stream detection code to use a more standard mechanism, like UNIX domain sockets (I've read there's a way to send open file decriptors through sockets, that's how Wayland does shared memory). The current code is just really messy.

iamgreaser commented 10 years ago

ssr-glinject, you say? That just opened up a horrible can of worms resulting in me making a bunch of elf hacks hacks, and trying to make the dynamic linker behave and eventually just giving up.

For starters, I'd change the "#!/bin/bash" line to "#!/usr/bin/env bash". Doing "#!/bin/sh" results in an "invalid substitution" issue, so that substitution would be fine. (FYI, FreeBSD typically puts bash at /usr/local/bin/bash... and if you thought that was bad, I'm pretty sure NetBSD puts it at /usr/pkg/bin/bash.)

Another note is if you have to refer to yourself, it's "/proc/curproc/file" (assuming the user has procfs mounted), not "/proc/self/exe". I think there's another way to do it using some BSD-specific call, but I could be wrong. Note, someone may have been a smartarse and mounted linprocfs there instead, which would result in "/proc/self/exe" working - but this is a STUPID idea.

Next up, said "elf hacks hacks" involves, well, this:

@@ -192,22 +198,38 @@ int eh_init_obj(eh_obj_t *obj)
                        if (obj->strtab)
                                return ENOTSUP;

+#ifdef BSD
+                       obj->strtab = (const char *) (obj->addr + obj->dynamic[p].d_un.d_ptr);
+#else
                        obj->strtab = (const char *) obj->dynamic[p].d_un.d_ptr;
+#endif
                } else if (obj->dynamic[p].d_tag == DT_HASH) {
                        if (obj->hash)
                                return ENOTSUP;

+#ifdef BSD
+                       obj->hash = (ElfW(Word) *) (obj->dynamic[p].d_un.d_ptr + obj->addr);
+#else
                        obj->hash = (ElfW(Word) *) obj->dynamic[p].d_un.d_ptr;
+#endif
                } else if (obj->dynamic[p].d_tag == DT_GNU_HASH) {
                        if (obj->gnu_hash)
                                return ENOTSUP;

+#ifdef BSD
+                       obj->gnu_hash = (Elf32_Word *) (obj->dynamic[p].d_un.d_ptr + obj->addr);
+#else
                        obj->gnu_hash = (Elf32_Word *) obj->dynamic[p].d_un.d_ptr;
+#endif
                } else if (obj->dynamic[p].d_tag == DT_SYMTAB) {
                        if (obj->symtab)
                                return ENOTSUP;

+#ifdef BSD
+                       obj->symtab = (ElfW(Sym) *) (obj->dynamic[p].d_un.d_ptr + obj->addr);
+#else
                        obj->symtab = (ElfW(Sym) *) obj->dynamic[p].d_un.d_ptr;
+#endif
                }
                p++;

As for the shared memory issue...

If it has to be a ramdisk, I'd go with the "instruct the user to set up a ramdisk" approach (mdmfs -s 32m md /mount/point) until you have a better approach. Otherwise, /tmp should be fine. $XDG_RUNTIME_DIR seems to be undefined.

But I'm honestly not sure where to go with respect to the dynamic linker. It might be possible to do something interesting with syscalls but it's probably unwise to hand-craft a new linker. I think the best way to do it, that I can see, is to find out where libGL and libX11 are currently located in memory and use elfhacks on them.

MaartenBaert commented 10 years ago

Sounds like the glinject code needs some serious changes to make it compatible with BSD :(.

What exactly is wrong with the linker? Doesn't it support LD_PRELOAD, or doesn't it override functions like the Linux linker does? Can you explain why these changes to elfhacks are needed?

EDIT: By the way, github is messing up the tabs in your patches, so they don't apply anymore. Can you upload them somewhere else (or just mail them as attachments)?

iamgreaser commented 10 years ago

I'm putting the patches here mostly because they're kinda small and probably best to have a manual look-over. But I could possibly do pull requests if you'd prefer that.

The linker definitely supports LD_PRELOAD. I just don't know why dlsym isn't working, and yes, I have even tried grabbing dlopen.

As for elfhacks, I'm not sure why I have to do that, it just appears to behave a little differently from the Linux linker.

To be honest, the best way to get the OpenGL stuff working on BSD is to actually install it and have a play around yourself. A VM should be fine. For reference, this is my ./configure line:

 ./configure CXX="c++ -std=c++11" CC=cc CFLAGS="-I/usr/local/include -DBSD" LDFLAGS="-L/usr/local/lib" CXXFLAGS="-I/usr/local/include -DBSD" CPPFLAGS="-I/usr/local/include -DBSD"
MaartenBaert commented 10 years ago

Yeah, I will definitely try to install it in a VM once I have a bit more time :).