// blog

Mednafen 1.22.2's Joystick Detection and FreeBSD 12

Posted 2020-01-07. Permalink. Tagged freebsd, hardware, photo, software, video games.

Photo of a gamepad next to the game Wipeout XL

A few days ago I bought a Logitech F310 gamepad at my local thrift store. FreeBSD detected it immediately and created the appropriate uhid entry in /dev. SDL2 also saw the gamepad, which I was able to verify with Joytran. The Mednafen game console emulator did not see the device, however. Here's why:

Mednafen 1.22.2 supports three different joystick backends: Linux event devices (evdev), SDL2, and MS Windows DirectInput / XInput devices. The backend is selected when the autoconf ./configure script is run, and then that backend selection is compiled into the Mednafen binary (see the ifdef statement at line 178 in src/drivers/Joystick.cpp).

Mednafen's autoconf script selects the evdev backend if it finds the file linux/joystick.h in the header search path. The FreeBSD port devel/evdev-proto installs that joystick.h header file. So, if you have evdev-proto installed (it's required by x11-toolkits/qt5-gui) and then compile emulators/mednafen it will use evdev and not SDL2 for its joystick support.

I worked around this by commenting out parts of src/drivers/Joystick.cpp (again around line 178) to force Mednafen to use SDL2 for joysticks. I should probably post on FreeBSD's bug tracker; could submit a patch too, it's simple enough.

This whole situation is definitely a corner case. The joystick backend selection in Mednafen could be better, but I'm also doing something a little unusual here.

Posting because this took me hours to figure out, and so I'll remember next time I encounter this problem.

Renoise 3.2 on FreeBSD

Posted 2019-10-09. Permalink. Tagged freebsd, linux, music, renoise, screenshot.

Renoise 3.2 (Linux x86_64 build) running on FreeBSD 12.0:

Renoise 3.2 running on FreeBSD

The procedure to achieve this is the same that I outlined in this 2017 blog post. This time I was able to get the 64-bit build to work (which is a good thing, because there will be no more 32-bit builds going forward).

To repeat the instructions, in short: enable the FreeBSD linux binary compat feature as per the handbook, install the linux-c7 metaport, find or compile a version of jack1 compatible with CentOS 7 x86_64, install that copy of jack in your /compat/linux directory, edit your $HOME/.jackdrc (see here for an example), then renoise should work with jack sound. I used the jack1 git master version and compiled it myself in a CentOS 7 virtual machine.

It's annoying to do (I spent four hours on this and hit multiple roadblocks), but works perfectly once done. With the same catch as before, no MIDI features are available.

Posting to report that at least one person has gotten this to work. Twice, even.

Renoise on FreeBSD

Posted 2017-02-11. Permalink. Tagged freebsd, linux, music, renoise, screenshot.

Renoise 3.1 (Linux x86 Build) running on FreeBSD 11:

Renoise 3.1 running on FreeBSD

It's been known for some time that Renoise runs under the FreeBSD Linux compatibility layer. However, no-one (to my knowledge) had gotten audio to work. With the release of FreeBSD 11 and its Linux compat changes, I decided to give it a shot, and got it to work - with JACK audio.

In short:


I used the linux-c7 meta-port to install CentOS 7.2.1511 compatibility. At this point Renoise Linux x86 runs, but without audio. Renoise Linux x64 didn't work, but I don't remember for what reason.

I tried to get ALSA sound working and simply could not. Then I tried getting Renoise (Linux) to connect to a FreeBSD native JACK server, and that didn't work either. It turns out that JACK clients won't connect to a JACK server built on a different operating system. So you have to run a JACK server compiled for Linux.

I built a 32bit CentOS VM, installed the necessary development tools, compiled the JACK server, packaged it and deployed it into my FreeBSD system's /compat/linux directory.

There are a few things to be aware of when building this Linux JACK server. First, make sure you enable the OSS driver (so it can talk to FreeBSD's OSS devices). Second, JACK requires a memory filesystem (tmpfs(5)) mounted at /dev/shm, or at another location configured at compile-time. Make sure to set that up. Finally be sure to compile JACK 1 and not 2; I used version 0.124.1.

Now JACK, and by extension Renoise, has to be made aware of the location of this alternate server. The standard method for this is $HOME/.jackdrc; see jackdrc(5). In my case, I added the line:

/compat/linux/usr/bin/jackd -r -d oss -C /dev/dsp2 -P /dev/dsp6

and that worked. dsp2 for capture, dsp6 for playback.

Unfortunately there is no MIDI support with this setup. I tried jack_umidi, but of course that program wouldn't talk to the JACK server compiled for Linux. Unless I can hack a version of jack_umidi that compiles in CentOS 7, or some kind of jack_umidi-like program that simulates an alsa midi device, then I think MIDI is simply off the table.

I think that's everything. It's been several months since I did this, so I may have forgotten a step. Hopefully this helps someone.

FFmpeg, bktr, and frame read timing

Posted 2016-12-16. Permalink. Tagged freebsd, screenshot, vhs, video.

An incorrectly captured video frame Real video frame A
Real video frame B

There's something wrong with the picture on the left.

Freebsd bktr(4), as configured by libavdevice/bktr.c, reads data coming off the card, fills a memory buffer, then signals libavdevice/bktr.c that a frame is complete. libavdevice is then supposed to copy the frame and process it, while bktr(4) reads new data.

It turns out that libavdevice doesn't wait for that signal before reading a frame. It sleeps for a period of time calculated from the video FPS; a signal from bktr(4) will interrupt this sleep. If no signal is received, meaning the video frame is taking longer to be delivered than expected, then libavdevice will complete the sleep and copy the video buffer anyway.

The above image on the left is the result of copying the video buffer early, without waiting for that frame-complete signal. An image composed of one video frame partially overwritten with another. In this case a fully interlaced frame (above right top) and a non-interlaced frame (above right bottom).

If you've ever tried to record video with ffmpeg's bktr device, this is what that "SLEPT NO signals" message means. It means that libavdevice/bktr.c bktr_getframe() called usleep() for the expected amount of time, didn't receive a signal, and proceeded to read a frame early.

This infuriated me for about a month and a half. After reading the bktr(4) driver and libavdevice/bktr.c about 50 times, I came to the conclusion above. I "fixed" it by altering bktr_getframe() in libavdevice to fully block until bktr(4) sends the frame-complete signal. Which should force a kind of hard frame synchronisation.

My bktr_getframe() looks like this:

static void bktr_getframe(uint64_t per_frame)
uint64_t curtime;
curtime = av_gettime();

while (nsignals == 0) {
/* 834 is 1/40th the duration of an NTSC frame.
* More likely I'm an idiot and it's an arbitrary number. */

nsignals = 0;
last_frame_time = curtime;

And that works for me to record interlaced NTSC video without broken frames.

To be clear: this is over my head. I'm not entirely sure my explanation above correctly describes what is going wrong. If I said I fully understood what's happening here I'd be lying.

FreeBSD, FFmpeg, and a bt848 capture card

Posted 2016-07-31. Permalink. Tagged freebsd, photo, vhs, video.

FreeBSD supports the (now old) Brooktree 848 series of video capture devices with the bktr(4) driver. I've owned one of these since the early 2000s, for recording VHS tapes and watching analog video in general.

FFmpeg supports the FreeBSD bktr driver, but for the longest time I couldn't figure out how to select the correct video input. Recording with FFmpeg always produced a video of the noise present at the (unused) coaxial input of my tuner card. There's no mention of input selection in the ffmpeg documentation. But a careful reading of libavdevice/bktr.c revealed that FFmpeg reads several (undocumented) environment variables to set options on the capture card. Notably, BKTR_DEV.

BKTR_DEV is used to select the analog video input on the tuner card. The value of BKTR_DEV is an index into an array defined at line ~83 of libavdevice/bktr.c. Some experimentation is needed to find the correct value; in my case it was '0'. The default is '1'.

In case this isn't completely obvious, a working bourne shell one-liner looks something like:

BKTR_DEV=0 ffmpeg -f bktr -i /dev/bktr0 -c:v libx264 -preset fast test.mkv

And that's it. I've written this up so I won't forget, and on the off chance it might help somebody else. Happy recording.

Blurry Photo of a Hauppage bt848 Card