My Resolve Dashcam Workflow

As I moved to Resolve I was forced to change my Vegas Movie Studio dashcam processing workflow a bit. Not only you cannot use MP4 under Linux at all, but MP4 presents challenges to the free Resolve under Windows too.

The first step I take for all dashcam footage is to convert it using ffmpeg to DNxHR LB. Not only it’s a well-supported intermediary codec that increases performance significantly, but it also get’s rid of any nonsense my dashcam puts in the clip. And 36 Mbps is more than enough for anything my dashcam can throw at it. Instead of converting clip-by-clip, I opted to merge them all into a single file - that’s the reason behind weird syntax:

ls *.MP4 | awk '{print "file \x27" $1 "\x27"}' | ffmpeg \
    -f concat -safe 0 -protocol_whitelist pipe,file -i - \
    -c:v dnxhd -profile:v dnxhr_lb -q:v 1 -pix_fmt yuv422p -an \
    ^^dashcam.mov^^

Once all these videos are imported into Resolve, I go over them removing any clip portions when car is not moving. For any stops where state around car changes (e.g. waiting for traffic light), I use smooth cut to transition from one state to another. Other than that, I leave footage as is.

Once I’m done with editing I export the whole video into DNxHR SQ VBR. If I hadn’t done any editing, exporting to DNxHR LB would be fine as generational loss is quite acceptable. However, with all smooth cuts I’ve made, a temporary bump in video quality is beneficial. Especially since this is not the final output.

As I don’t expect to edit these clips again, the final output is H.264 as it’s size savings cannot be ignored. I usually use two-pass encoding with 6 Mbps average rate. You can use veryslow preset to increase quality at the cost of speed but improvement is minimal so I simply go with the default of medium:

ffmpeg -i ^^render.mov^^ \
   -c:v libx264 -pix_fmt yuv420p -b:v 6M \
   -an -y -pass 1 -f mp4 ^^render^^.mp4

ffmpeg -i ^^render.mov^^ \
   -c:v libx264 -pix_fmt yuv420p -b:v 6M \
   -an -y -pass 2 -f mp4 ^^render^^.mp4

rm ffmpeg2pass-0.log*

And that’s it - final video is similar enough in quality while not taking extreme amounts of disk space.

PS: I am not using H.265 at this time because I find it even more trouble to work with than H.264 is. I might think about it in the future as support for it increases.

Implementing Global Hotkey Support in QT under X11

High-level description of global hotkey support is easy enough:

bool registerHotkey(QKeySequence keySequence) {
  auto key = Qt::Key(keySequence[0] & static_cast<int>(~Qt::KeyboardModifierMask));
  auto modifiers = Qt::KeyboardModifiers(keySequence[0] & static_cast<int>(Qt::KeyboardModifierMask));

  return nativeRegisterHotkey(key, modifiers);
}

Essentially one has to split key sequence into a key and modifiers and get platform-specific code to do the actual work. For X11 this is a bit more involved and full of traps.

Inevitably, X11-specific code will have a section with conversion of key and modifiers into a X11-compatible values. For key value this has to be additionally converted from key symbols into 8-bit key codes:

bool Hotkey::nativeRegisterHotkey(Qt::Key key, Qt::KeyboardModifiers modifiers) {
  uint16_t modValue = 0;
  if (modifiers & Qt::AltModifier)     { modValue |= XCB_MOD_MASK_1; }
  if (modifiers & Qt::ControlModifier) { modValue |= XCB_MOD_MASK_CONTROL; }
  if (modifiers & Qt::ShiftModifier)   { modValue |= XCB_MOD_MASK_SHIFT; }

  KeySym keySymbol;
  if (((key >= Qt::Key_A) && (key <= Qt::Key_Z)) || ((key >= Qt::Key_0) && (key <= Qt::Key_9))) {
    keySymbol = key;
  } else if ((key >= Qt::Key_F1) && (key <= Qt::Key_F35)) {
    keySymbol = XK_F1 + (key - Qt::Key_F1);
  } else {
    return false; //unsupported key
  }
  xcb_keycode_t keyValue = XKeysymToKeycode(QX11Info::display(), keySymbol);

  xcb_connection_t* connection = QX11Info::connection();
  auto cookie = xcb_grab_key_checked(connection, 1,
                static_cast<xcb_window_t>(QX11Info::appRootWindow()),
                modValue, keyValue, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
  auto cookieError = xcb_request_check(connection, cookie);
  if (cookieError == nullptr) {
    return true;
  } else {
    free(cookieError);
    return false;
  }
}

With key code and modifier bitmask ready, a call to xcb_grab_key_checked will actually do the deed, followed by some boiler plate code for error detection.

At last, we can use event filter to actually capture the key press and emit activated signal:

bool Hotkey::nativeEventFilter(const QByteArray&, void* message, long*) {
  xcb_generic_event_t* e = static_cast<xcb_generic_event_t*>(message);
  if ((e->response_type & ~0x80) == XCB_KEY_PRESS) {
    emit activated();
    return true;
  }
  return false;
}

Mind you, this is a rather incomplete and simplified example. Full code (supporting both Windows and Linux) is available for download.

To use it, just assign instance to a long living variable, register a key sequence, and hook into activated signal:

_hotkey = new Hotkey();
_hotkey->registerHotkey(QKeySequence { "Ctrl+Shift+F1" });
connect(_hotkey, SIGNAL(activated()), this, SLOT(^^onActivated()^^));

PS: Windows variant of this code is available here.

Implementing Global Hotkey Support in QT on Windows

If your application needs a global hotkey support, QT will leave you hanging. Fortunately, on high-level this is a rather simple function:

bool registerHotkey(QKeySequence keySequence) {
  auto key = Qt::Key(keySequence[0] & static_cast<int>(~Qt::KeyboardModifierMask));
  auto modifiers = Qt::KeyboardModifiers(keySequence[0] & static_cast<int>(Qt::KeyboardModifierMask));

  return nativeRegisterHotkey(key, modifiers);
}

Essentially the only work is splitting key and modifiers into their own variables and then having platform-specific code handling the nasty bits.

For Windows, this is just a simple conversion of modifiers and keys followed by a call to RegisterHotKey API:

bool nativeRegisterHotkey(Qt::Key key, Qt::KeyboardModifiers modifiers) {
  uint modValue = 0;
  if (modifiers &  Qt::AltModifier) { modValue += MOD_ALT; }
  if (modifiers &  Qt::ControlModifier) { modValue += MOD_CONTROL; }
  if (modifiers &  Qt::ShiftModifier) { modValue += MOD_SHIFT; }

  uint keyValue;
  if (((key >= Qt::Key_A) && (key <= Qt::Key_Z)) || ((key >= Qt::Key_0) && (key <= Qt::Key_9))) {
    keyValue = key;
  } else if ((key >= Qt::Key_F1) && (key <= Qt::Key_F24)) {
    keyValue = VK_F1 + (key - Qt::Key_F1);
  } else {
    return false; //unsupported key
  }

  return RegisterHotKey(nullptr, _hotkeyId, modValue, keyValue);
}

But this alone is nothing without actual QAbstractNativeEventFilter-based event filter. Here we need to intercept message and emit the signal:

bool nativeEventFilter(const QByteArray&, void* message, long*) {
  MSG* msg = static_cast<MSG*>(message);
  if (msg->message == WM_HOTKEY) {
    if (msg->wParam == static_cast<WPARAM>(_hotkeyId)) {
      emit activated();
      return true;
    }
  }
  return false;
}

Mind you, this is a rather incomplete and simplified example. Full code (supporting both Windows and Linux) is available for download.

To use it, just assign instance to a long living variable, register a key sequence, and hook into activated signal:

_hotkey = new Hotkey();
_hotkey->registerHotkey(QKeySequence { "Ctrl+Shift+F1" });
connect(_hotkey, SIGNAL(activated()), this, SLOT(^^onActivated()^^));

PS: X11 variant of this code is available here.

Black Frames in Garmin Dashcam Video

Illustration

Editing video files produced by my Garmin dashcam is sometimes really annoying. Not only it splits every darn trip into 60 second chunks forcing you to edit bunch of small files, but it will also occasionally produce video with the first frame completely black. At least that’s how it looks in Vegas Movie Studio 15 and, while annoying, was easy enough to remove. In DaVinci Resolve these videos would have a few seconds worth of corrupted data and that’s a bigger problem.

As I’m moving most of my video editing to Resolve due to it’s cross-platform capabilities, I decided to figure it out. One way to see what’s going on with MP4 is by looking at video file in MediaInfo.

The first confusing thing was that MP4 contained only streams with ID 2 and 3. What happened to stream with ID 1 is anybody’s guess. The second source of confusion was that all streams combined amounted to smidgen over 60 MB. The whole video file was more than 70 MB. While one can expect MP4 container format to take some space, overhead is generally measured in KB - not MB.

However, both these things were present in both valid and invalid video file. It took going into Advanced mode to reveal more curiosities. At last it let me know where remaining 10 MB were - in the header. And more interestingly it has shown multiple stream size calculations for video stream. File that contained black frame had one of it’s six video stream sizes listed as 59.97 MB while fully working file had all video stream sizes set to 60.00 MB.

Either due to crappy encoder or bad coding, Garmin not only bloats header to unreasonable level but it can also miscalculate stream stream sizes. Because MP4 contains stream size data at multiple places, it was dependent on decoder whether error would be noticeable or not.

Knowing I am dealing with the corrupt container and seemingly correct stream (albeit one frame shorter), I decided to simply repackage MP4 without recompression using ffmpeg:

ffmpeg -i ^^input.mp4^^ -c:v copy -c:a copy ^^fixed.mp4^^

This copies both video and audio stream (irrelevant if there is no audio stream) into a new file. For normal videos this results in a direct stream copy. Videos where one frame was corrupted end up with 59.967 seconds worth of frames. Essentially the broken frame will be removed. And this repackaging solved the black frame issue for both Vegas Movie Studio and DaVinci Resolve.

Unfortunately, while DaVinci Resolve did recognize files now, exported result had a stutter. For some reason all these files were recognized as 15 fps. And no, this wasn’t due to stream copy as original videos were misidentified too. It took me a while to give up and ask the question about it on Blackmagic forum only to find out I stumbled upon a bug.

As a workaround before bug is resolved, I went onto converting the stream to DNxHD LB codec:

ffmpeg -i ^^input.mp4^^ -c:v dnxhd -profile:v dnxhr_lb -pix_fmt yuv420p -c:a copy ^^fixed.mov^^

Not only this also removes invalid frames but it also helps editing speed as DNxHD is much more CPU-friendly format.

Being too lazy to deal with these files on case-by-case basis, a bit of Bash magic to repackage multiple files comes in handy:

mkdir out
find . -type f -name '*.MP4' -exec \
    ffmpeg -i {} -c:v dnxhd -profile:v dnxhr_lb -q:v 1 -pix_fmt yuv422p -c:a copy \
    out/{}.mov \;

Single Instance Application in QT

Single instance applications are fun in any programming language. Let’s take QT as example. One could just create a server and depending on whether it can listen, determine if another instance is running. Something like this:

server = new QLocalServer();
bool serverListening = server.listen("SomeName");

if (!serverListening) {
  //hey, I'm walkin over here
}

And that’s it. Only if it would be this easy.

This code might fail on Unix with AddressInUseError if there was a previous application crash. This means we need to complicate code a bit:

server = new QLocalServer();
bool serverListening = server.listen("SomeName");
if (!serverListening && (server->serverError() == QAbstractSocket::AddressInUseError)) {
  QLocalServer::removeServer(serverName); //cleanup
  serverListening = _server->listen(serverName); //try again
}

if (!serverListening) {
  //hey, I'm walkin over here
}

But fun wouldn’t be complete if that was all. You see, Windows have issues of their own. As implemented in QT, you can actually have multiple listeners at the same time. Failure to listen will never happen there.

Unfortunately this is a bit more complicated and you can really go wild solving this issue - even so far as to involve QSystemSemaphore with it’s portability and thread blocking issues.

Or you can go with solution that works 99% of the time - directly calling into CreateMutexW API.

Modifying code in the following manner will do the trick:

server = new QLocalServer();
bool serverListening = server.listen("SomeName");
if (!serverListening && (server->serverError() == QAbstractSocket::AddressInUseError)) {
  QLocalServer::removeServer(serverName); //cleanup
  serverListening = _server->listen(serverName); //try again
}

#if defined(Q_OS_WIN)
  if (serverListening) {
    CreateMutexW(nullptr, true, reinterpret_cast<LPCWSTR>(serverName.utf16()));
    if (GetLastError() == ERROR_ALREADY_EXISTS) { //someone has this Mutex
      server->close(); //disable server
      serverListening = false;
    }
  }
#endif

if (!serverListening) {
  //hey, I'm walkin over here
}

Now on Windows we try to create our very own mutex. If that succeeds, all is normal. If that returns an error, we simply close our server because we know some other instance owns the mutex.

Not ideal but it covers single-instance scenario reasonably well.

If you want to use this in application, you can download the example and use it as follows:

if (!SingleInstance::attach()) {
  return static_cast<int>(0x80004004); //exit immediately if another instance is running
}