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.