Raspberry Pi Encoding script

I finally had the time to work on my hardware assisted h264 encoding appliance. Followers of my blog (are there any) know that I got a Leopard Board DM368 (LI-TB-02) to use for my encoding project, but due to limitations of the hardware (the encoder only encodes material that is divisable by 32pixels width) I could not recode my recorded tv shows – german public tv stations air their shows in 720×576 PAL-DVD dimensions.

I also own a raspberry pi ARM computer which has a hardware assisted encoding capability. Using „raspbian“ (an arm „hardfloat“ linux distribution based on debian), mediainfo (to extract the number of streams within a recording) and a lua script, I start a GStreamer to recode the transport stream and encapsulate the resulting h264 (video) and aac (audio) materialn in a matroska container.

I’ve chosen matroska because the format seems to deal well with input (transport stream) implications. The audio stream of a mpeg transport stream is not always (or, to be more precise, almost always) not in time synchronization with the video stream (to make matters worse, the timing between the audio and video stream can shift with time). MPEG-4 containers require (as far as I understand) the streams to be in sync, which is problematic with most stream-based recoding.

Playback of the resulting mkv seems to be shaky on my laptop with vlc – playback with Raspbmc (a raspberry pi based XBMC distribution) is perfectly fine.

Helper function for luaxpath:
/mnt/media/src/recode/split.lua:

function split(str, pat)
  local t = {} -- NOTE: use {n = 0} in Lua-5.0
  local fpat = "(.-)" .. pat
  local last_end = 1
  local s, e, cap = str:find(fpat, 1)
  while s do
    if s ~= 1 or cap ~= "" then
      table.insert(t,cap)
    end
    last_end = e+1
    s, e, cap = str:find(fpat, last_end)
  end
  if last_end <= #str then
    cap = str:sub(last_end)
    table.insert(t, cap)
  end
  return t
end

Extracting the transport stream information:
/mnt/media/src/recode/mediainfo.lua:

package.path = '/mnt/media/src/recode/?.lua;' .. package.path

require "xpath" 
require "lxp.lom"
require "split"

function mediainfo(filename) 
  local ret = {}
  local path = string.match(filename, "(.-)([^\\/]-%.?([^%.\\/]*))$")
  os.execute("mediainfo --Output=XML " .. filename .. " > " .. filename .. ".info.xml")
  local xmlfile = assert(io.open(filename .. ".info.xml"), "XML Info file could not be openend")
  local xmlblob = xmlfile:read("*all")
  xmlblob = string.gsub(xmlblob, "\n", "")
  local xmltab  = lxp.lom.parse(xmlblob)
  ret["fileformat"] = xpath.selectNodes(xmltab, "//Format")[1][1]
  ret["audio"]      = xpath.selectNodes(xmltab, '/Mediainfo/File/track[@type="Audio"]')
  os.remove(filename .. ".info.xml")
  return ret, xmltab
end

The script calling gstreamer:
/mnt/media/src/recode/gstrecode.lua:

#!/usr/bin/env lua

package.path = '/mnt/media/src/recode/?.lua;' .. package.path

require "mediainfo"

local sourcefile = string.gsub(arg[1], "^\./", "")
local destfile   = string.gsub(arg[2], "^\./", "")

print("Source:      " .. sourcefile)
print("Destination: " .. destfile)

local lockPrevious = io.open(sourcefile .. ".lck", "r")
if lockPrevious ~= nil then
  print("Already transcoding or stale lockfile found")
  os.exit()
end

local lock = io.open(sourcefile .. ".lck", "w")
lock:write("LOCKED")
lock:flush()
lock:close()

local info = mediainfo(sourcefile)

local command = ""

command = command .. "gst-launch-1.0 filesrc location=" .. sourcefile .. " ! progressreport ! "
if info["fileformat"] == "MPEG-TS" then
  command = command .. "tsparse ! "
end
command = command .. " decodebin name=demux "
for i = 1, table.getn(info["audio"]) do
  command = command .. "demux. ! queue ! audioconvert ! voaacenc bitrate=128000 ! queue ! mux. "
end
command = command .. 'demux. ! queue ! '
if os.getenv("HOME") == "/home/pi" then
  command = command .. 'omxh264enc control-rate=1 target-bitrate=1250000 ! '
else
  command = command .. 'x264enc bitrate=1250 ! '
end

command = command .. '"video/x-h264,profile=high" ! queue ! h264parse ! mux. '
command = command .. "matroskamux name=mux ! filesink location=" .. destfile

print("Command:     " .. command)
local retcode = os.execute(command)

if retcode == 0 then
  os.remove(sourcefile .. ".lck")
else
  os.remove(destfile)
  local failed = io.open(sourcefile .. ".failedencode", "w")
  failed:write("EPIC FAIL")
  failed:close()
end

Call: /mnt/media/src/recode/gstrecode.lua source.ts destination.mkv

I use the same script for encoding on my x86 machines and the pi – that’s why I check for the home directory of the calling user. If you want to use the script, you might want to change the paths and this check. The script requires luaexpat to be installed.

A problem still occurs, if the aspect-ratio within the transport stream suddenly changes (i.e., a 16:9 show preceded by a 4:3 show) – the encoding process just stalls.