diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/src/scripts/MemoryReferenceInfo.lua b/src/scripts/MemoryReferenceInfo.lua new file mode 100644 index 0000000..0575c65 --- /dev/null +++ b/src/scripts/MemoryReferenceInfo.lua @@ -0,0 +1,1073 @@ +-- https://github.com/yaukeywang/LuaMemorySnapshotDump +-- +-- Collect memory reference info. +-- +-- @filename MemoryReferenceInfo.lua +-- @author WangYaoqi +-- @date 2016-02-03 + +-- The global config of the mri. +local cConfig = +{ + m_bAllMemoryRefFileAddTime = true, + m_bSingleMemoryRefFileAddTime = true, + m_bComparedMemoryRefFileAddTime = true +} + +-- Get the format string of date time. +local function FormatDateTimeNow() + local cDateTime = os.date("*t") + local strDateTime = string.format("%04d%02d%02d-%02d%02d%02d", tostring(cDateTime.year), tostring(cDateTime.month), tostring(cDateTime.day), + tostring(cDateTime.hour), tostring(cDateTime.min), tostring(cDateTime.sec)) + return strDateTime +end + +-- Get the string result without overrided __tostring. +local function GetOriginalToStringResult(cObject) + if not cObject then + return "" + end + + local cMt = getmetatable(cObject) + if not cMt then + return tostring(cObject) + end + + -- Check tostring override. + local strName = "" + local cToString = rawget(cMt, "__tostring") + if cToString then + rawset(cMt, "__tostring", nil) + strName = tostring(cObject) + rawset(cMt, "__tostring", cToString) + else + strName = tostring(cObject) + end + + return strName +end + +-- Create a container to collect the mem ref info results. +local function CreateObjectReferenceInfoContainer() + -- Create new container. + local cContainer = {} + + -- Contain [table/function] - [reference count] info. + local cObjectReferenceCount = {} + setmetatable(cObjectReferenceCount, {__mode = "k"}) + + -- Contain [table/function] - [name] info. + local cObjectAddressToName = {} + setmetatable(cObjectAddressToName, {__mode = "k"}) + + -- Set members. + cContainer.m_cObjectReferenceCount = cObjectReferenceCount + cContainer.m_cObjectAddressToName = cObjectAddressToName + + -- For stack info. + cContainer.m_nStackLevel = -1 + cContainer.m_strShortSrc = "None" + cContainer.m_nCurrentLine = -1 + + return cContainer +end + +-- Create a container to collect the mem ref info results from a dumped file. +-- strFilePath - The file path. +local function CreateObjectReferenceInfoContainerFromFile(strFilePath) + -- Create a empty container. + local cContainer = CreateObjectReferenceInfoContainer() + cContainer.m_strShortSrc = strFilePath + + -- Cache ref info. + local cRefInfo = cContainer.m_cObjectReferenceCount + local cNameInfo = cContainer.m_cObjectAddressToName + + -- Read each line from file. + local cFile = assert(io.open(strFilePath, "rb")) + for strLine in cFile:lines() do + local strHeader = string.sub(strLine, 1, 2) + if "--" ~= strHeader then + local _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)") + if strAddr then + cRefInfo[strAddr] = strRefCount + cNameInfo[strAddr] = strName + end + end + end + + -- Close and clear file handler. + io.close(cFile) + cFile = nil + + return cContainer +end + +-- Create a container to collect the mem ref info results from a dumped file. +-- strObjectName - The object name you need to collect info. +-- cObject - The object you need to collect info. +local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject) + -- Create new container. + local cContainer = {} + + -- Contain [address] - [true] info. + local cObjectExistTag = {} + setmetatable(cObjectExistTag, {__mode = "k"}) + + -- Contain [name] - [true] info. + local cObjectAliasName = {} + + -- Contain [access] - [true] info. + local cObjectAccessTag = {} + setmetatable(cObjectAccessTag, {__mode = "k"}) + + -- Set members. + cContainer.m_cObjectExistTag = cObjectExistTag + cContainer.m_cObjectAliasName = cObjectAliasName + cContainer.m_cObjectAccessTag = cObjectAccessTag + + -- For stack info. + cContainer.m_nStackLevel = -1 + cContainer.m_strShortSrc = "None" + cContainer.m_nCurrentLine = -1 + + -- Init with object values. + cContainer.m_strObjectName = strObjectName + cContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject) + cContainer.m_cObjectExistTag[cObject] = true + + return cContainer +end + +-- Collect memory reference info from a root table or function. +-- strName - The root object name that start to search, default is "_G" if leave this to nil. +-- cObject - The root object that start to search, default is _G if leave this to nil. +-- cDumpInfoContainer - The container of the dump result info. +local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer) + if not cObject then + return + end + + if not strName then + strName = "" + end + + -- Check container. + if (not cDumpInfoContainer) then + cDumpInfoContainer = CreateObjectReferenceInfoContainer() + end + + -- Check stack. + if cDumpInfoContainer.m_nStackLevel > 0 then + local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl") + if cStackInfo then + cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src + cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline + end + + cDumpInfoContainer.m_nStackLevel = -1 + end + + -- Get ref and name info. + local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCount + local cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToName + + local strType = type(cObject) + if "table" == strType then + -- Check table with class name. + if rawget(cObject, "__cname") then + if "string" == type(cObject.__cname) then + strName = strName .. "[class:" .. cObject.__cname .. "]" + end + elseif rawget(cObject, "class") then + if "string" == type(cObject.class) then + strName = strName .. "[class:" .. cObject.class .. "]" + end + elseif rawget(cObject, "_className") then + if "string" == type(cObject._className) then + strName = strName .. "[class:" .. cObject._className .. "]" + end + end + + -- Check if table is _G. + if cObject == _G then + strName = strName .. "[_G]" + end + + -- Get metatable. + local bWeakK = false + local bWeakV = false + local cMt = getmetatable(cObject) + if cMt then + -- Check mode. + local strMode = rawget(cMt, "__mode") + if strMode then + if "k" == strMode then + bWeakK = true + elseif "v" == strMode then + bWeakV = true + elseif "kv" == strMode then + bWeakK = true + bWeakV = true + end + end + end + + -- Add reference and name. + cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + if cNameInfoContainer[cObject] then + return + end + + -- Set name. + cNameInfoContainer[cObject] = strName + + -- Dump table key and value. + for k, v in pairs(cObject) do + -- Check key type. + local strKeyType = type(k) + if "table" == strKeyType then + if not bWeakK then + CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "function" == strKeyType then + if not bWeakK then + CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "thread" == strKeyType then + if not bWeakK then + CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "userdata" == strKeyType then + if not bWeakK then + CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + else + CollectObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer) + end + end + + -- Dump metatable. + if cMt then + CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer) + end + elseif "function" == strType then + -- Get function info. + local cDInfo = debug.getinfo(cObject, "Su") + + -- Write this info. + cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + if cNameInfoContainer[cObject] then + return + end + + -- Set name. + cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]" + + -- Get upvalues. + local nUpsNum = cDInfo.nups + for i = 1, nUpsNum do + local strUpName, cUpValue = debug.getupvalue(cObject, i) + local strUpValueType = type(cUpValue) + --print(strUpName, cUpValue) + if "table" == strUpValueType then + CollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "function" == strUpValueType then + CollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "thread" == strUpValueType then + CollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "userdata" == strUpValueType then + CollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + end + end + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer) + end + end + elseif "thread" == strType then + -- Add reference and name. + cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + if cNameInfoContainer[cObject] then + return + end + + -- Set name. + cNameInfoContainer[cObject] = strName + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer) + end + end + + -- Dump metatable. + local cMt = getmetatable(cObject) + if cMt then + CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer) + end + elseif "userdata" == strType then + -- Add reference and name. + cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + if cNameInfoContainer[cObject] then + return + end + + -- Set name. + cNameInfoContainer[cObject] = strName + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer) + end + end + + -- Dump metatable. + local cMt = getmetatable(cObject) + if cMt then + CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer) + end + elseif "string" == strType then + -- Add reference and name. + cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + if cNameInfoContainer[cObject] then + return + end + + -- Set name. + cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]" + else + -- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.) + + -- -- Add reference and name. + -- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1 + -- if cNameInfoContainer[cObject] then + -- return + -- end + + -- -- Set name. + -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]" + end +end + +-- Collect memory reference info of a single object from a root table or function. +-- strName - The root object name that start to search, can not be nil. +-- cObject - The root object that start to search, can not be nil. +-- cDumpInfoContainer - The container of the dump result info. +local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer) + if not cObject then + return + end + + if not strName then + strName = "" + end + + -- Check container. + if (not cDumpInfoContainer) then + cDumpInfoContainer = CreateObjectReferenceInfoContainer() + end + + -- Check stack. + if cDumpInfoContainer.m_nStackLevel > 0 then + local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl") + if cStackInfo then + cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src + cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline + end + + cDumpInfoContainer.m_nStackLevel = -1 + end + + local cExistTag = cDumpInfoContainer.m_cObjectExistTag + local cNameAllAlias = cDumpInfoContainer.m_cObjectAliasName + local cAccessTag = cDumpInfoContainer.m_cObjectAccessTag + + local strType = type(cObject) + if "table" == strType then + -- Check table with class name. + if rawget(cObject, "__cname") then + if "string" == type(cObject.__cname) then + strName = strName .. "[class:" .. cObject.__cname .. "]" + end + elseif rawget(cObject, "class") then + if "string" == type(cObject.class) then + strName = strName .. "[class:" .. cObject.class .. "]" + end + elseif rawget(cObject, "_className") then + if "string" == type(cObject._className) then + strName = strName .. "[class:" .. cObject._className .. "]" + end + end + + -- Check if table is _G. + if cObject == _G then + strName = strName .. "[_G]" + end + + -- Get metatable. + local bWeakK = false + local bWeakV = false + local cMt = getmetatable(cObject) + if cMt then + -- Check mode. + local strMode = rawget(cMt, "__mode") + if strMode then + if "k" == strMode then + bWeakK = true + elseif "v" == strMode then + bWeakV = true + elseif "kv" == strMode then + bWeakK = true + bWeakV = true + end + end + end + + -- Check if the specified object. + if cExistTag[cObject] and (not cNameAllAlias[strName]) then + cNameAllAlias[strName] = true + end + + -- Add reference and name. + if cAccessTag[cObject] then + return + end + + -- Get this name. + cAccessTag[cObject] = true + + -- Dump table key and value. + for k, v in pairs(cObject) do + -- Check key type. + local strKeyType = type(k) + if "table" == strKeyType then + if not bWeakK then + CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "function" == strKeyType then + if not bWeakK then + CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "thread" == strKeyType then + if not bWeakK then + CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + elseif "userdata" == strKeyType then + if not bWeakK then + CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer) + end + + if not bWeakV then + CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer) + end + else + CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer) + end + end + + -- Dump metatable. + if cMt then + CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer) + end + elseif "function" == strType then + -- Get function info. + local cDInfo = debug.getinfo(cObject, "Su") + local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]" + + -- Check if the specified object. + if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) then + cNameAllAlias[cCombinedName] = true + end + + -- Write this info. + if cAccessTag[cObject] then + return + end + + -- Set name. + cAccessTag[cObject] = true + + -- Get upvalues. + local nUpsNum = cDInfo.nups + for i = 1, nUpsNum do + local strUpName, cUpValue = debug.getupvalue(cObject, i) + local strUpValueType = type(cUpValue) + --print(strUpName, cUpValue) + if "table" == strUpValueType then + CollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "function" == strUpValueType then + CollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "thread" == strUpValueType then + CollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + elseif "userdata" == strUpValueType then + CollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer) + end + end + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer) + end + end + elseif "thread" == strType then + -- Check if the specified object. + if cExistTag[cObject] and (not cNameAllAlias[strName]) then + cNameAllAlias[strName] = true + end + + -- Add reference and name. + if cAccessTag[cObject] then + return + end + + -- Get this name. + cAccessTag[cObject] = true + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer) + end + end + + -- Dump metatable. + local cMt = getmetatable(cObject) + if cMt then + CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer) + end + elseif "userdata" == strType then + -- Check if the specified object. + if cExistTag[cObject] and (not cNameAllAlias[strName]) then + cNameAllAlias[strName] = true + end + + -- Add reference and name. + if cAccessTag[cObject] then + return + end + + -- Get this name. + cAccessTag[cObject] = true + + -- Dump environment table. + local getfenv = debug.getfenv + if getfenv then + local cEnv = getfenv(cObject) + if cEnv then + CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer) + end + end + + -- Dump metatable. + local cMt = getmetatable(cObject) + if cMt then + CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer) + end + elseif "string" == strType then + -- Check if the specified object. + if cExistTag[cObject] and (not cNameAllAlias[strName]) then + cNameAllAlias[strName] = true + end + + -- Add reference and name. + if cAccessTag[cObject] then + return + end + + -- Get this name. + cAccessTag[cObject] = true + else + -- For "number" and "boolean" type, they are not object type, skip. + end +end + +-- The base method to dump a mem ref info result into a file. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- strRootObjectName - The header info to show the root object name, can be nil. +-- cRootObject - The header info to show the root object address, can be nil. +-- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults. +-- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase. +local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults) + -- Check results. + if not cDumpInfoResults then + return + end + + -- Get time format string. + local strDateTime = FormatDateTimeNow() + + -- Collect memory info. + local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nil + local cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nil + local cRefInfo = cDumpInfoResults.m_cObjectReferenceCount + local cNameInfo = cDumpInfoResults.m_cObjectAddressToName + + -- Create a cache result to sort by ref count. + local cRes = {} + local nIdx = 0 + for k in pairs(cRefInfo) do + nIdx = nIdx + 1 + cRes[nIdx] = k + end + + -- Sort result. + table.sort(cRes, function (l, r) + return cRefInfo[l] > cRefInfo[r] + end) + + -- Save result to file. + local bOutputFile = strSavePath and (string.len(strSavePath) > 0) + local cOutputHandle = nil + local cOutputEntry = print + + if bOutputFile then + -- Check save path affix. + local strAffix = string.sub(strSavePath, -1) + if ("/" ~= strAffix) and ("\\" ~= strAffix) then + strSavePath = strSavePath .. "/" + end + + -- Combine file name. + local strFileName = strSavePath .. "LuaMemRefInfo-All" + if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then + if cDumpInfoResultsBase then + if cConfig.m_bComparedMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "].txt" + else + strFileName = strFileName .. ".txt" + end + else + if cConfig.m_bAllMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "].txt" + else + strFileName = strFileName .. ".txt" + end + end + else + if cDumpInfoResultsBase then + if cConfig.m_bComparedMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt" + else + strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt" + end + else + if cConfig.m_bAllMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt" + else + strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt" + end + end + end + + local cFile = assert(io.open(strFileName, "w")) + cOutputHandle = cFile + cOutputEntry = cFile.write + end + + local cOutputer = function (strContent) + if cOutputHandle then + cOutputEntry(cOutputHandle, strContent) + else + cOutputEntry(strContent) + end + end + + -- Write table header. + if cDumpInfoResultsBase then + cOutputer("--------------------------------------------------------\n") + cOutputer("-- This is compared memory information.\n") + + cOutputer("--------------------------------------------------------\n") + cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n") + cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n") + else + cOutputer("--------------------------------------------------------\n") + cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n") + end + + cOutputer("--------------------------------------------------------\n") + cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n") + cOutputer("--------------------------------------------------------\n") + + if strRootObjectName and cRootObject then + if "string" == type(cRootObject) then + cOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n") + else + cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n") + end + end + + -- Save each info. + for i, v in ipairs(cRes) do + if (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) then + if (nMaxRescords > 0) then + if (i <= nMaxRescords) then + if "string" == type(v) then + local strOrgString = tostring(v) + local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"") + if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then + local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n") + cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + else + cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + end + else + cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + end + end + else + if "string" == type(v) then + local strOrgString = tostring(v) + local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"") + if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then + local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n") + cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + else + cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + end + else + cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n") + end + end + end + end + + if bOutputFile then + io.close(cOutputHandle) + cOutputHandle = nil + end +end + +-- The base method to dump a mem ref info result of a single object into a file. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- cDumpInfoResults - The dumped results. +local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults) + -- Check results. + if not cDumpInfoResults then + return + end + + -- Get time format string. + local strDateTime = FormatDateTimeNow() + + -- Collect memory info. + local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName + + -- Save result to file. + local bOutputFile = strSavePath and (string.len(strSavePath) > 0) + local cOutputHandle = nil + local cOutputEntry = print + + if bOutputFile then + -- Check save path affix. + local strAffix = string.sub(strSavePath, -1) + if ("/" ~= strAffix) and ("\\" ~= strAffix) then + strSavePath = strSavePath .. "/" + end + + -- Combine file name. + local strFileName = strSavePath .. "LuaMemRefInfo-Single" + if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then + if cConfig.m_bSingleMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "].txt" + else + strFileName = strFileName .. ".txt" + end + else + if cConfig.m_bSingleMemoryRefFileAddTime then + strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt" + else + strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt" + end + end + + local cFile = assert(io.open(strFileName, "w")) + cOutputHandle = cFile + cOutputEntry = cFile.write + end + + local cOutputer = function (strContent) + if cOutputHandle then + cOutputEntry(cOutputHandle, strContent) + else + cOutputEntry(strContent) + end + end + + -- Write table header. + cOutputer("--------------------------------------------------------\n") + cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n") + cOutputer("--------------------------------------------------------\n") + + -- Calculate reference count. + local nCount = 0 + for k in pairs(cObjectAliasName) do + nCount = nCount + 1 + end + + -- Output reference count. + cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n") + cOutputer("--------------------------------------------------------\n") + + -- Save each info. + for k in pairs(cObjectAliasName) do + if (nMaxRescords > 0) then + if (i <= nMaxRescords) then + cOutputer(k .. "\n") + end + else + cOutputer(k .. "\n") + end + end + + if bOutputFile then + io.close(cOutputHandle) + cOutputHandle = nil + end +end + +-- Fileter an existing result file and output it. +-- strFilePath - The existing result file. +-- strFilter - The filter string. +-- bIncludeFilter - Include(true) or exclude(false) the filter. +-- bOutputFile - Output to file(true) or console(false). +local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile) + if (not strFilePath) or (0 == string.len(strFilePath)) then + print("You need to specify a file path.") + return + end + + if (not strFilter) or (0 == string.len(strFilter)) then + print("You need to specify a filter string.") + return + end + + -- Read file. + local cFilteredResult = {} + local cReadFile = assert(io.open(strFilePath, "rb")) + for strLine in cReadFile:lines() do + local nBegin, nEnd = string.find(strLine, strFilter) + if nBegin and nEnd then + if bIncludeFilter then + nBegin, nEnd = string.find(strLine, "[\r\n]") + if nBegin and nEnd and (string.len(strLine) == nEnd) then + table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1)) + else + table.insert(cFilteredResult, strLine) + end + end + else + if not bIncludeFilter then + nBegin, nEnd = string.find(strLine, "[\r\n]") + if nBegin and nEnd and (string.len(strLine) == nEnd) then + table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1)) + else + table.insert(cFilteredResult, strLine) + end + end + end + end + + -- Close and clear read file handle. + io.close(cReadFile) + cReadFile = nil + + -- Write filtered result. + local cOutputHandle = nil + local cOutputEntry = print + + if bOutputFile then + -- Combine file name. + local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt") + strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt" + + local cFile = assert(io.open(strResFileName, "w")) + cOutputHandle = cFile + cOutputEntry = cFile.write + end + + local cOutputer = function (strContent) + if cOutputHandle then + cOutputEntry(cOutputHandle, strContent) + else + cOutputEntry(strContent) + end + end + + -- Output result. + for i, v in ipairs(cFilteredResult) do + cOutputer(v .. "\n") + end + + if bOutputFile then + io.close(cOutputHandle) + cOutputHandle = nil + end +end + +-- Dump memory reference at current time. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil. +-- cRootObject - The root object that start to search, default is _G if leave this to nil. +local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject) + -- Get time format string. + local strDateTime = FormatDateTimeNow() + + -- Check root object. + if cRootObject then + if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then + strRootObjectName = tostring(cRootObject) + end + else + cRootObject = debug.getregistry() + strRootObjectName = "registry" + end + + -- Create container. + local cDumpInfoContainer = CreateObjectReferenceInfoContainer() + local cStackInfo = debug.getinfo(2, "Sl") + if cStackInfo then + cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src + cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline + end + + -- Collect memory info. + CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer) + + -- Dump the result. + OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer) +end + +-- Dump compared memory reference results generated by DumpMemorySnapshot. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- cResultBefore - The base dumped results. +-- cResultAfter - The compared dumped results. +local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter) + -- Dump the result. + OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter) +end + +-- Dump compared memory reference file results generated by DumpMemorySnapshot. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- strResultFilePathBefore - The base dumped results file. +-- strResultFilePathAfter - The compared dumped results file. +local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter) + -- Read results from file. + local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore) + local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter) + + -- Dump the result. + OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter) +end + +-- Dump memory reference of a single object at current time. +-- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does. +-- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "". +-- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result. +-- strObjectName - The object name reference you want to dump. +-- cObject - The object reference you want to dump. +local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject) + -- Check object. + if not cObject then + return + end + + if (not strObjectName) or (0 == string.len(strObjectName)) then + strObjectName = GetOriginalToStringResult(cObject) + end + + -- Get time format string. + local strDateTime = FormatDateTimeNow() + + -- Create container. + local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject) + local cStackInfo = debug.getinfo(2, "Sl") + if cStackInfo then + cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src + cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline + end + + -- Collect memory info. + CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer) + + -- Dump the result. + OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer) +end + +-- Return methods. +local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}} + +cPublications.m_cConfig = cConfig + +cPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshot +cPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotCompared +cPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFile +cPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObject + +cPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNow +cPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResult + +cPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainer +cPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFile +cPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainer +cPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemory +cPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemory +cPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshot +cPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObject +cPublications.m_cBases.OutputFilteredResult = OutputFilteredResult + +return cPublications diff --git a/src/scripts/class.lua b/src/scripts/class.lua new file mode 100644 index 0000000..9911c04 --- /dev/null +++ b/src/scripts/class.lua @@ -0,0 +1,61 @@ +--https://github.com/chukong/quick-cocos2d-x/blob/master/framework/functions.lua + +function class(classname, super) + local superType = type(super) + local cls + + if superType ~= "function" and superType ~= "table" then + superType = nil + super = nil + end + + if superType == "function" or (super and super.__ctype == 1) then + -- inherited from native C++ Object + cls = {} + + if superType == "table" then + -- copy fields from super + for k,v in pairs(super) do cls[k] = v end + cls.__create = super.__create + cls.super = super + else + cls.__create = super + cls.ctor = function() end + end + + cls.__cname = classname + cls.__ctype = 1 + + function cls.new(...) + local instance = cls.__create(...) + -- copy fields from class to native object + for k,v in pairs(cls) do instance[k] = v end + instance.class = cls + instance:ctor(...) + return instance + end + + else + -- inherited from Lua Object + if super then + cls = {} + setmetatable(cls, {__index = super}) + cls.super = super + else + cls = {ctor = function() end} + end + + cls.__cname = classname + cls.__ctype = 2 -- lua + cls.__index = cls + + function cls.new(...) + local instance = setmetatable({}, cls) + instance.class = cls + instance:ctor(...) + return instance + end + end + + return cls +end \ No newline at end of file diff --git a/src/scripts/codec/.codec_h264.lua.swp b/src/scripts/codec/.codec_h264.lua.swp new file mode 100644 index 0000000..be63892 Binary files /dev/null and b/src/scripts/codec/.codec_h264.lua.swp differ diff --git a/src/scripts/codec/codec_aac.lua b/src/scripts/codec/codec_aac.lua new file mode 100644 index 0000000..0152b26 --- /dev/null +++ b/src/scripts/codec/codec_aac.lua @@ -0,0 +1,384 @@ +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + +local ADTS_ID = { + MPEG4 = 0, + MPEG2 = 1, +} + +local ADTS_ID_STR = { + [ADTS_ID.MPEG4] = "MPEG4", + [ADTS_ID.MPEG2] = "MPEG2", +} + +local ADTS_PROFILE = { + MAIN = 0, --main profile + LC = 1, --low complexity profile + SSR = 2, --scalable sampling rate profile + RESERVED = 3, +} + +local ADTS_PROTECT = { + CRC = 0, + NOCRC = 1, +} + +local ADTS_PROTECT_STR = { + [ADTS_PROTECT.CRC] = "CRC", + [ADTS_PROTECT.NOCRC] = "NOCRC", +} + +local ADTS_PROFILE_STR = { + [ADTS_PROFILE.MAIN] = "main", + [ADTS_PROFILE.LC] = "low complexity profile", + [ADTS_PROFILE.SSR] = "scalable sampling rate profile", + [ADTS_PROFILE.RESERVED] = "reserved" +} + +local ADTS_PROFILE_BRIEF = { + [ADTS_PROFILE.MAIN] = "MAIN", + [ADTS_PROFILE.LC] = "LC", + [ADTS_PROFILE.SSR] = "SSR", + [ADTS_PROFILE.RESERVED] = "RESERVED" +} + +local ADTS_SAMPLING = { + [0] = 96000, + [1] = 88200, + [2] = 64000, + [3] = 48000, + + [4] = 44100, + [5] = 32000, + [6] = 24000, + [7] = 22050, + + [8] = 16000, + [9] = 12000, + [10] = 11025, + [11] = 8000, + + [12] = 7350, + [13] = 0, --reserved, + [14] = 0, --reserved, + [15] = -1, --escape value +} + +local AAC_CHANNEL = { + [0] = "Defined in AOT Specifc Config", + [1] = "1 channel: front-center", + [2] = "2 channels: front-left, front-right", + [3] = "3 channels: front-center, front-left, front-right", + [4] = "4 channels: front-center, front-left, front-right, back-center", + [5] = "5 channels: front-center, front-left, front-right, back-left, back-right", + [6] = "6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel", + [7] = "8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel", + [8] = "reserved", --0x8 + [9] = "reserved", --0x9 + [10] = "reserved", --0xA + [11] = "reserved", --0xB + [12] = "reserved", --0xC + [13] = "reserved", --0xD + [14] = "reserved", --0xE + [15] = "reserved", --0xF +} + +local AAC_CHANNEL_COUNT = { + [1] = 1, + [2] = 2, + [3] = 3, + [4] = 4, + [5] = 5, + [6] = 6, + [7] = 8, +} + +local ID_SYN_ELE_TYPE = { + SCE = 0, --single_channel_element + CPE = 1, --channel_pair_element + CCE = 2, --coupling_channel_element + LFE = 3, --lfe_channel_element + DSE = 4, --data_stream_element + PCE = 5, --program_config_element + FIL = 6, --fill_element + END = 7, +} + +local ID_SYN_ELE_STR = { + [ID_SYN_ELE_TYPE.SCE] = "SCE", + [ID_SYN_ELE_TYPE.CPE] = "CPE", + [ID_SYN_ELE_TYPE.CCE] = "CCE", + [ID_SYN_ELE_TYPE.LFE] = "LFE", + [ID_SYN_ELE_TYPE.DSE] = "DSE", + [ID_SYN_ELE_TYPE.PCE] = "PCE", + [ID_SYN_ELE_TYPE.FIL] = "FIL", + [ID_SYN_ELE_TYPE.END] = "END", +} + +local aac_summary_t = class("aac_summary_t") +function aac_summary_t:ctor() + self.channels = 0 + self.duration = 0 + self.sample_rate = 0 + self.bitrate = 0 + self.frames = 0 + + self.total_frame_len = 0 +end +local aac_summary = nil + +local cb_desc_frame = function(self) + local brief = self:get_child_brief() + return string.format("%s L:%d %s", self.name, self.len, brief) +end + +local field_id_syn_ele = function() + local f = field.ubits("id_syn_ele", 3, fh.mkdesc(ID_SYN_ELE_STR), fh.mkbrief("ELE", ID_SYN_ELE_STR)) + return f +end + +local cb_element_brief = function(self) + local brief = self:get_child_brief() + return string.format("%sRAWL:%d", brief, self.len) +end + +local function field_syn_single_channel_element() + + local f = field.bit_list("single_channel_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + + end, nil, cb_element_brief) + return f +end + +local function field_syn_channel_pair_element() + local f = field.bit_list("channel_pair_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + + end, nil, cb_element_brief) + return f +end + +local function field_syn_coupling_channel_element() + local f = field.bit_list("coupling_channel_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + + end, nil, cb_element_brief) + return f +end + +local function field_syn_lfe_channel_element() + local f = field.bit_list("lfe_channel_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + + end, nil, cb_element_brief) + return f +end + +local function field_syn_data_stream_element() + local f = field.bit_list("data_stream_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + + end, nil, cb_element_brief) + return f +end + +local function field_syn_program_config_element() + local f = field.bit_list("program_config_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + --TODO + end, nil, cb_element_brief) + return f +end + +local EXT_TYPE = { + FIL = 0, + FILL_DATA = 1, + DATA_ELEMENT = 2, + DYNAMIC_RANGE = 11, +} + +local function field_syn_fill_element() + local f = field.bit_list("fill_element", nil, function(self, ba) + self:append( field_id_syn_ele() ) + + local count = 0 + local f_count = self:append( field.ubits("count", 4) ) + if f_count.value == 15 then + local f_count2 = self:append( field.ubits("count2", 8) ) + count = f_count.value + f_count2.value - 1 + end + + if count <= 0 then return end + + --TODO + while count > 0 do + local f_extension_type = self:append( field.ubits("extension_type", 4) ) + local etype = f_extension_type.value + + if etype == EXT_TYPE.DYNAMIC_RANGE then + + elseif etype == EXT_TYPE.FILL_DATA then + + elseif etype == EXT_TYPE.DATA_ELEMENT then + + else--if etype == EXT_TYPE.FIL then + + end + + break + + end + end, nil, cb_element_brief) + return f +end + +local function field_syn_end_element() + local f = field.bit_list("end_element", nil, function(self, ba) + end) + return f +end + +local syn_ele_fields = { + [ID_SYN_ELE_TYPE.SCE] = field_syn_single_channel_element, + [ID_SYN_ELE_TYPE.CPE] = field_syn_channel_pair_element, + [ID_SYN_ELE_TYPE.CCE] = field_syn_coupling_channel_element, + [ID_SYN_ELE_TYPE.LFE] = field_syn_lfe_channel_element, + [ID_SYN_ELE_TYPE.DSE] = field_syn_data_stream_element, + [ID_SYN_ELE_TYPE.PCE] = field_syn_program_config_element, + [ID_SYN_ELE_TYPE.FIL] = field_syn_fill_element, + [ID_SYN_ELE_TYPE.END] = field_syn_end_element, +} + +local function field_frame(index) + local f = field.list(string.format("frame[%d]", index), nil, function(self, ba) + --test protection + local protection = ba:peek_uint16() & 0x1 + local hlen = 7 + if protection == 0 then + hlen = 9 + end + + local f_frame_length = field.ubits("frame_length", 13, nil, cb_brief_frame_length) + local f_protection = field.ubits("protection_absent", 1, fh.mkdesc(ADTS_PROTECT_STR), fh.mkbrief("PRT", ADTS_PROTECT_STR)) + local f_header = field.bit_list("adts_header", hlen, function(self, ba) + self:append( field.ubits("syncword", 12) ) + self:append( field.ubits("id", 1, fh.mkdesc(ADTS_ID_STR), fh.mkbrief("ID", ADTS_ID_STR) ) ) + self:append( field.ubits("layer", 2) ) + self:append( f_protection ) + self:append( field.ubits("profile", 2, fh.mkdesc(ADTS_PROFILE_STR), fh.mkbrief("PRF", ADTS_PROFILE_BRIEF) ) ) + local f_sampling = field.ubits("sampling_frequency_index", 4, fh.mkdesc(ADTS_SAMPLING), fh.mkbrief("SMP", ADTS_SAMPLING)) + self:append( f_sampling ) + self:append( field.ubits("private_bit", 1) ) + local f_ch = self:append( field.ubits("channel_configuration", 3, fh.mkdesc(AAC_CHANNEL), fh.mkbrief("CH", AAC_CHANNEL_COUNT )) ) + self:append( field.ubits("origin", 1) ) + self:append( field.ubits("home", 1) ) + + self:append( field.ubits("copyright_identification_bit", 1) ) + self:append( field.ubits("copyright_identification_start", 1) ) + self:append( f_frame_length ) + self:append( field.ubits("adts_buffer_fullness", 11) ) + self:append( field.ubits("number_of_raw_data_blocks_in_frame", 2) ) + + if f_protection.value == 0 then + self:append( field.ubits("crc_check", 16) ) + end + + if aac_summary then + if f_frame_length.len > 7 then + aac_summary.total_frame_len = aac_summary.total_frame_len + f_frame_length.value + end + if aac_summary.channels == 0 then + aac_summary.sample_rate = ADTS_SAMPLING[f_sampling.value] or "??" + aac_summary.channels = f_ch.value + end + end + end, nil, fh.child_brief) + + self:append( f_header ) + + + self:append( field.select("raw_data", f_frame_length.value - hlen, function(self, ba) + local id_syn_ele = ba:peek_ubits(3) + + local cb_field = syn_ele_fields[id_syn_ele] + if nil == cb_field then return nil end + local f = cb_field() + return f + end) ) + end, cb_desc_frame) + return f +end + + +local function decode_aac( ba, len ) + aac_summary = aac_summary_t.new() + + local f_aac = field.list("AAC", len, function(self, ba) + + --ADIF 0x41 0x44 0x49 0x46 + local peek = ba:peek_uint32() + if peek == 0x46494441 then + --TODO parse ADIF + return + end + + --ADTS + local index = 0 + while ba:length() > 0 do + index = index + 1 + + self:append(field_frame(index)) + + local pos = ba:position() + bi.set_progress_value(pos) + end + + aac_summary.frames = index + + end) + + return f_aac +end + +local function build_summary() + if nil == aac_summary then return end + + local duration = 1 + local bytes_per_frame = 0 + + local frames_per_sec = aac_summary.sample_rate/1024 + if aac_summary.frames > 0 then + bytes_per_frame = aac_summary.total_frame_len / aac_summary.frames / 1000 + end + + local bitrate = math.floor(8 * bytes_per_frame * frames_per_sec + 0.5) + if frames_per_sec ~= 0 then + duration = aac_summary.frames / frames_per_sec + end + + aac_summary.duration = duration + aac_summary.bitrate = bitrate + + bi.append_summary("channels", tostring(aac_summary.channels)) + bi.append_summary("sample_rate", string.format("%s Hz", aac_summary.sample_rate)) + bi.append_summary("bitrate", string.format("%d Kbps", aac_summary.bitrate)) + bi.append_summary("frames", tostring(aac_summary.frames)) + bi.append_summary("duration", string.format("%s (%.3f secs) ", helper.ms2time(duration*1000), duration)) +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_ext = "aac", + decode = decode_aac, + build_summary = build_summary, + +} + +return codec diff --git a/src/scripts/codec/codec_default.lua b/src/scripts/codec/codec_default.lua new file mode 100644 index 0000000..b33e7e4 --- /dev/null +++ b/src/scripts/codec/codec_default.lua @@ -0,0 +1,31 @@ +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + + +local function decode_default( ba, len ) + + bi.log("decode_default") + local f_pcap = field.list("BinInspector", len, function(self, ba) + end) + + return f_pcap +end + +local function clear() +end + +local function build_summary() +end + +local codec = { +-- authors = { {name="author1", mail="mail1"}, {name="author2", mail="mail2" } }, + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "All files", + file_ext = "*", + decode = decode_default, + clear = clear, + build_summary = build_summary, +} + +return codec diff --git a/src/scripts/codec/codec_flv.lua b/src/scripts/codec/codec_flv.lua new file mode 100644 index 0000000..5897f20 --- /dev/null +++ b/src/scripts/codec/codec_flv.lua @@ -0,0 +1,527 @@ +require("class") +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + +local FLV_TYPE = { + AUDIO = 8, + VIDEO = 9, + SCRIPT = 18, +} + +local FLV_TYPE_STR = { + [FLV_TYPE.AUDIO] = "AUDIO", + [FLV_TYPE.VIDEO] = "VIDEO", + [FLV_TYPE.SCRIPT] = "SCRIPT", +} + +local AMF_TYPE = { + NUMBER = 0x00, + BOOLEAN= 0x01, + STRING = 0x02, + OBJECT = 0x03, + NULL = 0x05, + ARRAY = 0x08, + OBJ_END_MARK = 0x09, +} + +local AMF_TYPE_STR = { + [0] = "number", + [1] = "boolean", + [2] = "string", + [3] = "object", + [5] = "null", + [8] = "array", + [9] = "obj_end_mark", +} + +local SOUND_FORMAT = { + PCM = 0, + ADPCM = 1, + MP3 = 2, + PCM_LE = 3, +-- [4] = "Nellymoser 16-kHz mono", +-- [5] = "Nellymoser 8-kHz mono", +-- [6] = "Nellymoser", +-- [7] = "G.711 A-law logarithmic PCM", +-- [8] = "G.711 mu-law logarithmic PCM 9 = reserved", + AAC = 10, + SPEEX = 11, + MP3_8KHZ = 14, + DEVICE_SPECIFIC_SOUND = 15, +} + + +local SOUND_FORMAT_STR = { + [0] = "Linear PCM, platform endian", + [1] = "ADPCM", + [2] = "MP3", + [3] = "Linear PCM, little endian", + [4] = "Nellymoser 16-kHz mono", + [5] = "Nellymoser 8-kHz mono", + [6] = "Nellymoser", + [7] = "G.711 A-law logarithmic PCM", + [8] = "G.711 mu-law logarithmic PCM 9 = reserved", + [10] = "AAC", + [11] = "Speex", + [14] = "MP3 8-Khz", + [15] = "Device-specific sound", +} + +local SAMPLE_RATE_STR = { + [0] = "5.5kHz", + [1] = "11kHz", + [2] = "22kHz", + [3] = "44kHz", +} + +local SAMPLE_BITS_STR = { + [0] = "8bit", + [1] = "16bit" +} + +local CHANNEL_STR = { + [0] = "mono", + [1] = "stereo", +} + +local AAC_PACKET_TYPE = { + SEQUENCE_HEADER = 0, --AudioSpecificConfig + RAW = 1, --audio data +} + +local AAC_PACKET_TYPE_STR = { + [0] = "AudioSpecificConfig", + [1] = "AudioRawData", +} + +local VIDEO_FRAME_TYPE = { + KEY_FRAME = 1, + INTER_FRAME = 2, + DISPOSABLE_INTER_FARME = 3, + GENERATED_KEY_FRAME = 4, + VIDEO_INFO = 5, +} + +local VIDEO_FRAME_TYPE_STR = { + [1] = "key frame", -- (for AVC, a seekable frame) + [2] = "inter frame", -- (for AVC, a non-seekable frame) + [3] = "disposable inter frame", -- (H.263 only) + [4] = "generated key frame", -- (reserved for server use only) + [5] = "video info/command frame", +} + +local VIDEO_CODEC_ID = { + JPEG = 1, + H263 = 2, + SCREEN_VIDEO = 3, + VP6 = 4, + VP6_ALPHA = 5, + SCREEN_VIDEO_V2 = 6, + AVC = 7 +} + + +local VIDEO_CODEC_ID_STR = { + [1] = "JPEG", + [2] = "Sorenson H.263", + [3] = "Screen video", + [4] = "On2 VP6", + [5] = "On2 VP6 with alpha channel", + [6] = "Screen video version 2", + [7] = "AVC", +} + +local AVC_PACKET_TYPE = { + SEQUENCE_HEADER = 0, + NALU = 1, + END_OF_SEQUENCE = 2, +} + +local AVC_PACKET_TYPE_STR = { + [0] = "AVC sequence header", + [1] = "AVC NALU", + [2] = "AVC end of sequence", -- (lower level NALU sequence ender is not required or supported) +} + +local flv_summary_t = class("flv_summary") +function flv_summary_t:ctor() + self.amf = {} --key:value + +end + + + +local swap_endian = true +local flv_summary = nil + +local field_flv_header = function() + local f = field.array("header", 9, { + field.string("signature", 3, fh.str_desc, fh.mkbrief("SIG")), + field.uint8("version", nil, fh.mkbrief("V")), + field.bit_array("flags", { + field.ubits("type_flags_reversed", 5), + field.ubits("type_flags_audio", 1, nil, fh.mkbrief("audio")), + field.ubits("type_flags_reversed", 1), + field.ubits("type_flags_video", 1, nil, fh.mkbrief("video")), + }, nil, fh.child_brief), + field.uint32("data_offset", swap_endian, nil, fh.mkbrief("HLEN")), + }) + + return f +end + +local function field_meta_data(len) + local f = field.list("metadata", len, function(self, ba) + local pos = ba:position() + + while ba:length() > 0 do + local remain = len - (ba:position() - pos) + if remain <= 3 then + self:append( field.uint24("end_mark", swap_endian) ) + break + end + + local stype = nil + local key = nil + local value = nil + local vtype = nil + self:append(field.list("amf", nil, function(self, ba) + + local f_len = self:append(field.uint16("len", swap_endian)) + local f_key = self:append(field.string("key", f_len.value, fh.str_desc, fh.mkbrief())) + local f_value = nil + + local f_vtype = self:append(field.uint8("vtype", fh.mkdesc(AMF_TYPE_STR))) + + vtype = f_vtype.value + if vtype == AMF_TYPE.NUMBER then + f_value = self:append(field.double("value", swap_endian, nil, fh.mkbrief())) + value = f_value.value + elseif vtype == AMF_TYPE.BOOLEAN then + f_value = self:append(field.uint8("value")) + value = f_value.value + elseif vtype == AMF_TYPE.STRING then + local f_vlen = self:append(field.uint16("len", swap_endian)) + f_value = self:append(field.string("value", f_vlen.value, fh.str_desc)) + value = f_value.value + elseif vtype == AMF_TYPE.OBJECT then + bi.log("todo amf object") + elseif vtype == AMF_TYPE.NULL then + value = "" + elseif vtype == AMF_TYPE.ARRAY then + bi.log("todo amf array") + elseif vtype == AMF_TYPE.OBJ_END_MARK then + end + + stype = AMF_TYPE_STR[vtype] or "??" + key = f_key.value + + if f_value then + flv_summary.amf[key] = f_value.value + end + end, function(self) + return string.format("amf %s %s: %s", stype, key, value) + end)) + + if vtype == AMF_TYPE.OBJECT or + vtype == AMF_TYPE.ARRAY or + vtype == AMF_TYPE.OBJ_END_MARK then + bi.log(string.format("todo amf %s", stype)) + break + end + end + + local remain = len - (ba:position() - pos) + if remain > 0 then + self:append( field.string("amf", remain) ) + end + + local nread = ba:position() - pos + if nread > len then + ba:back_pos( nread - len ) + end + end) + return f +end + +local function field_script(len) + + local f = field.list("script", len, function(self, ba) + + self:append( field.list("amf1", nil, function(self, ba) + self:append( field.uint8("type", nil, fh.mkbrief("T")) ) + self:append( field.uint16("len", swap_endian, nil, fh.mkbrief("L")) ) + self:append( field.string("value", 10, fh.str_desc, fh.mkbrief("V")) ) + end)) + + self:append( field.list("amf2", nil, function(self, ba) + self:append( field.uint8("type", nil, fh.mkbrief("T")) ) + self:append( field.uint32("len", swap_endian, nil, fh.mkbrief("L")) ) + local value_len = len - 13 - 5 + + --self:append( field.string("value", value_len) ) + self:append( field_meta_data(value_len) ) + end)) + end) + return f +end + +local function field_audio(len) + local f = field.list("audio", len, function(self, ba) + + local f_fmt = field.ubits("sound_format", 4, fh.mkdesc(SOUND_FORMAT_STR), fh.mkbrief("FMT", SOUND_FORMAT_STR)) + + self:append( field.bit_list("header", 1, function(self, ba) + self:append( f_fmt ) + self:append( field.ubits("sound_rate", 2, fh.mkdesc(SAMPLE_RATE_STR), fh.mkbrief("RATE", SAMPLE_RATE_STR)) ) + self:append( field.ubits("sound_size", 1, fh.mkdesc(SAMPLE_BITS_STR), fh.mkbrief("BITS", SAMPLE_BITS_STR)) ) + self:append( field.ubits("sound_type", 1, fh.mkdesc(CHANNEL_STR), fh.mkbrief("CH", CHANNEL_STR)) ) + end)) + + local data_len = len -1 + if f_fmt.value == SOUND_FORMAT.AAC then + self:append( field.list("aac", data_len, function(self, ba) + local f_type = self:append( field.uint8("packet_type" + , fh.mkdesc(AAC_PACKET_TYPE_STR), fh.mkbrief("T", AAC_PACKET_TYPE_STR)) ) + + if f_type.value == AAC_PACKET_TYPE.SEQUENCE_HEADER then + self:append( field.string("audio_specific_config", data_len-1) ) + else + self:append( field.string("raw", data_len-1) ) + end + end)) + else + self:append( field.string("data", data_len) ) + end + + end) + return f +end + +local function field_video_header(video) + local f = field.list("video_tag_header", nil, function(self, ba) + + local f_frame_type = field.ubits("frame_type", 4, fh.mkdesc(VIDEO_FRAME_TYPE_STR), fh.mkbrief("", VIDEO_FRAME_TYPE_STR)) + local f_codec_id = field.ubits("codec_id", 4, fh.mkdesc(VIDEO_CODEC_ID_STR), fh.mkbrief("", VIDEO_CODEC_ID_STR)) + self:append( field.bit_list("bits", 1, function(self, ba) + self:append( f_frame_type ) + self:append( f_codec_id ) + end, nil, fh.child_brief)) + + + if f_codec_id.value == VIDEO_CODEC_ID.AVC then + local f_avc_packet_type = field.uint8("avc_packet_type" + , fh.mkdesc(AVC_PACKET_TYPE_STR), fh.mkbrief("T", AVC_PACKET_TYPE_STR)) + + local cb_brief_pts = function(self) + return string.format("PTS:%d ", video.dts + self.value) + end + local f_composition_time = field.int24("composition_time", swap_endian, nil, cb_brief_pts) + self:append( f_avc_packet_type ) + self:append( f_composition_time ) + + video.avc_packet_type = f_avc_packet_type.value + video.composition_time = f_composition_time.value + end + + video.frame_type = f_frame_type.value + video.codec_id = f_codec_id.value + end, nil, fh.child_brief) + return f +end +local function field_video(len, video) + local f = field.list("video", len, function(self, ba) + local pos = ba:position() + + if video.frame_type == VIDEO_FRAME_TYPE.VIDEO_INFO then + self:append( field.string("video_info", len) ) + return + end + + if video.codec_id == VIDEO_CODEC_ID.AVC then + if video.avc_packet_type == 0 then + self:append( field.string("avc_decoder_configuration_record", len) ) + elseif video.avc_packet_type == 1 then +--[[ + local codec_h264 = helper.get_codec("h264") + local nalu_data = ba:peek_bytes(len) + self:append( codec_h264.field_nalu_header(), ba ) +--]] + local remain = len - (ba:position() - pos) + self:append( field.string("nalus", remain) ) + else + self:append( field.string("data", len) ) + end + return + end + self:append( field.string("data", len) ) + end) + return f +end + +local function field_tag( index ) + local f_tag = field.list(string.format("tag[%d]", index), nil, function(self, ba) + + self:append( field.uint32("previous_tag_size", swap_endian) ) + + if ba:length() <= 0 then + return + end + + local f_filter = field.ubits("filter", 1) + local f_type = field.ubits("tag_type", 5, fh.mkdesc(FLV_TYPE_STR), fh.mkbrief("T", FLV_TYPE_STR)) + + local f_data_size = field.uint24("data_size", swap_endian, nil, fh.mkbrief("DL")) + local f_timestamp = field.uint24("timestamp", swap_endian) + local f_timestamp_ex = field.uint8("timestamp_ex", nil, function(self) + local dts = f_timestamp.value | (self.value << 24) + return string.format("DTS:%d ", dts) + end) + local f_stream_id = field.uint24("stream_id", swap_endian) + + local f_header = self:append(field.list("header", nil, function(self, ba) + + self:append( field.bit_list("flags", 1, function(self, ba) + self:append( field.ubits("reserved", 2) ) + self:append( f_filter ) + self:append( f_type ) + end, nil, fh.child_brief)) + + self:append( f_data_size ) + self:append( f_timestamp ) + self:append( f_timestamp_ex ) + self:append( f_stream_id ) + end, nil, fh.child_brief)) + + local pos = ba:position() + + local ftype = f_type.value + + local dts = f_timestamp.value | (f_timestamp_ex.value << 24) + local audio = {} + local video = {} + video.dts = dts + + if ftype == FLV_TYPE.AUDIO then + --audio tag header + end + if ftype == FLV_TYPE.VIDEO then + --video tag header + self:append( field_video_header( video ) ) + end + if f_filter.value ~= 0 then + --TODO + --EncryptionTagHeader + --FilterParams + end + + local data_len = f_data_size.value - (ba:position() - pos) + self:append( field.select("data", data_len, function() + if ftype == FLV_TYPE.VIDEO then return field_video(data_len, video) end + if ftype == FLV_TYPE.AUDIO then return field_audio(data_len, audio) end + if ftype == FLV_TYPE.SCRIPT then return field_script(data_len) end + + return field.string("data", data_len) + end)) + end) + return f_tag +end + +local function decode_flv( ba, len ) + flv_summary = flv_summary_t.new() + + local f_flv = field.list("FLV", len, function(self, ba) + self:append( field_flv_header() ) + + local index = 0 + while ba:length() > 0 do + index = index + 1 + + self:append( field_tag(index) ) + + local pos = ba:position() + bi.set_progress_value(pos) + end + + end) + + return f_flv +end + +local function build_summary() + if not flv_summary then return end + + local function is_fixed_key(k) + if k == "width" then return true end + if k == "height" then return true end + if k == "duration" then return true end + if k == "filesize" then return true end + if k == "framerate" then return true end + return false + end + + local amf = flv_summary.amf + + if amf["width"] and amf["height"] then + bi.append_summary("resolution", string.format("%.fx%.f", amf["width"], amf["height"])) + end + if amf["duration"] then + local v = amf["duration"] + bi.append_summary("duration", string.format("%s (%s)", tostring(v), helper.ms2time(v*1000))) + end + if amf["framerate"] then + local v = amf["framerate"] + bi.append_summary("framerate", string.format("%s", tostring(v))) + end + + local keys = {} + for k, _ in pairs(amf) do + if not is_fixed_key(k) then + table.insert(keys, k) + end + end + + table.sort(keys) + + local tdict = { + ["videocodecid"] = VIDEO_CODEC_ID_STR, + ["audiocodecid"] = SOUND_FORMAT_STR, + ["stereo"] = CHANNEL_STR, + } + + for _, k in ipairs(keys) do + local v = amf[k] + local dict = tdict[k] + if dict then + v = dict[v] or "??" + elseif k == "audiosize" or k == "videosize" then + v = string.format("%.f (%s)", v, helper.size_format(math.floor(v))) + else + local vtype = type(v) + if vtype == "number" then + if (v*100) % 100 == 0 then + v = string.format("%.f", v) + else + v = tostring(v) + end + elseif vtype == "function" then + v = "??" + end + end + + bi.log(string.format("key %s", k)) + bi.append_summary(k, v) + end + +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_ext = "flv", + decode = decode_flv, + build_summary = build_summary, + +} + +return codec diff --git a/src/scripts/codec/codec_h264.lua b/src/scripts/codec/codec_h264.lua new file mode 100644 index 0000000..d374c8d --- /dev/null +++ b/src/scripts/codec/codec_h264.lua @@ -0,0 +1,1163 @@ +require("class") +local helper = require("helper") +local field = require("field") +local fh = require("field_helper") +local json = require("cjson") + +--reference +--https://www.ietf.org/rfc/rfc3984.txt +--https://github.com/ChristianFeldmann/h264Bitstream.git +--https://www.zzsin.com/article/sps_width_height.html + +--nal start with [00 00 01] or [00 00 00 01] +--eg.: 00 00 00 01 67 42 80 0c + +--| start code(4byte) | nal header(1byte) | nal ebsp(nbyte) | + +local NALU_TYPE = { + SLICE = 1, --Coded slice of a non-IDR picture + DPA = 2, --Coded slice data partition A + DPB = 3, --Coded slice data partition B + DPC = 4, --Coded slice data partition C + IDR = 5, --Coded slice of an IDR picture + SEI = 6, --Supplemental enhancement information (SEI) + SPS = 7, --Sequence parameter set + PPS = 8, --Picture parameter set + AUD = 9, --Access unit delimiter + EOSEQ = 10, --End of sequence + EOSTREAM = 11, --End of stream + FILL = 12, --Filler data + SPS_EXT = 13, --Sequence parameter set extension + + AUX = 19, + + STAP_A = 24, --multiple nalu in a rtp packet + STAP_B = 25, + MTAP_16= 26, + MTAP_24= 27, + FU_A = 28, --partial nalu over many rtp packets + FU_B = 29 +} + +local NALU_TYPE_STR = { + [NALU_TYPE.SLICE] = "SLICE", + [NALU_TYPE.DPA] = "DPA", + [NALU_TYPE.DPB] = "DPB", + [NALU_TYPE.DPC] = "DPC", + [NALU_TYPE.IDR] = "IDR", + [NALU_TYPE.SEI] = "SEI", + [NALU_TYPE.SPS] = "SPS", + [NALU_TYPE.PPS] = "PPS", + [NALU_TYPE.AUD] = "AUD", + [NALU_TYPE.EOSEQ] = "EOSEQ", + [NALU_TYPE.EOSTREAM] = "EOSTREAM", + [NALU_TYPE.FILL] = "FILL", + [NALU_TYPE.SPS_EXT] = "SPS_EXT", + + [NALU_TYPE.AUX] = "AUX", + + [NALU_TYPE.STAP_A] = "STAP_A", + [NALU_TYPE.STAP_B] = "STAP_B", + [NALU_TYPE.MTAP_16] = "MTAP_16", + [NALU_TYPE.MTAP_24] = "MTAP_24", + [NALU_TYPE.FU_A] = "FU_A", + [NALU_TYPE.FU_B] = "FU_B", +} + +local SLICE_TYPE = { + P = 0, + B = 1, + I = 2, + SP= 3, + SI= 4, + + P_ONLY = 5, + B_ONLY = 6, + I_ONLY = 7, + SP_ONLY = 8, + SI_ONLY = 9, +} + +local SLICE_TYPE_STR = { + [SLICE_TYPE.P] = "P", + [SLICE_TYPE.B] = "B", + [SLICE_TYPE.I] = "I", + [SLICE_TYPE.SP] = "SP", + [SLICE_TYPE.SI] = "SI", + [SLICE_TYPE.P_ONLY] = "P_ONLY", + [SLICE_TYPE.B_ONLY] = "B_ONLY", + [SLICE_TYPE.I_ONLY] = "I_ONLY", + [SLICE_TYPE.SP_ONLY] = "SP_ONLY", + [SLICE_TYPE.SI_ONLY] = "SI_ONLY", +} + +local YUV_FORMAT = { + YUV_Y = 0, + YUV_420 = 1, + YUV_422 = 2, + YUV_444 = 3, +} + +local YUV_FORMAT_STR = { + [YUV_FORMAT.YUV_Y] = "YUV_Y", + [YUV_FORMAT.YUV_420] = "YUV_420", + [YUV_FORMAT.YUV_422] = "YUV_422", + [YUV_FORMAT.YUV_444] = "YUV_444", +} + +local NALU_SAR = { + UNSPECIFIED = 0, + _1_1 = 1, + _12_11 = 2, + _10_11 = 3, + _16_11 = 4, + _40_33 = 5, + _24_11 = 6, + _20_11 = 7, + _32_11 = 8, + _80_33 = 9, + _18_11 = 10, + _15_11 = 11, + _64_33 = 12, + _160_99 = 13, + + EXTENDED = 255 +} + +local nal_t = class("nal_t") +function nal_t:ctor() + self.nal_unit_type = 0 +end + +local sps_t = class("sps_t") +function sps_t:ctor() + self.log2_max_frame_num_minus4 = 0 + self.frame_mbs_only_flag = 0 + self.pic_order_cnt_type = 0 + self.log2_max_pic_order_cnt_lsb_minus4 = 0 + self.delta_pic_order_always_zero_flag = 0 + self.chroma_format_idc = 1 + self.separate_colour_plane_flag = 0 + + self.frame_cropping_flag = 0 + self.frame_crop_left_offset = 0 + self.frame_crop_right_offset = 0 + self.frame_crop_top_offset = 0 + self.frame_crop_bottom_offset = 0 + + self.pic_width_in_mbs_minus1 = 0 + self.pic_height_in_map_units_minus1 = 0 +end + +function sps_t:calc_width_height() + local chroma_array_type = 0 + if self.separate_colour_plane_flag == 0 then + chroma_array_type = self.chroma_format_idc; + end + + local sub_width_c = 0 + local sub_height_c = 0 + if chroma_array_type == 1 then + sub_width_c = 2 + sub_height_c= 2 + elseif ChromaArrayType == 2 then + sub_width_c = 2 + sub_height_c= 1 + elseif ChromaArrayType == 3 then + sub_width_c = 1 + sub_height_c= 1 + end + + local width = (self.pic_width_in_mbs_minus1 + 1) * 16 + local height = (2 - self.frame_mbs_only_flag) * (self.pic_height_in_map_units_minus1 + 1) * 16 + + if self.frame_cropping_flag ~= 0 then + local crop_unit_x = 0 + local crop_unit_y = 0 + + if chroma_array_type == 0 then + crop_unit_x = 1 + crop_unit_y = 2 - self.frame_mbs_only_flag + elseif chroma_array_type == 1 or chroma_array_type == 2 or chroma_array_type == 3 then + crop_unit_x = sub_width_c; + crop_unit_y = sub_height_c * (2 - self.frame_mbs_only_flag); + end + + width = width - crop_unit_x * (self.frame_crop_left_offset + self.frame_crop_right_offset); + height = height - crop_unit_y * (self.frame_crop_top_offset + self.frame_crop_bottom_offset); + end + return width, height +end + +local pps_t = class("pps_t") +function pps_t:ctor() + self.seq_parameter_set_id = 0 + self.pic_order_present_flag = 0 + self.redundant_pic_cnt_present_flag = 0 + self.weighted_pred_flag = 0 + self.weighted_bipred_idc = 0 + self.entropy_coding_mode_flag = 0 + self.deblocking_filter_control_present_flag = 0 + self.num_slice_groups_minus1 = 0 + self.slice_group_map_type = 0 + self.pic_size_in_map_units_minus1 = 0 + self.slice_group_change_rate_minus1 = 0 + self.num_ref_idx_l0_active_minus1 = 0 + self.num_ref_idx_l1_active_minus1 = 0 +end + +local h264_t = class("h264_t") +function h264_t:ctor() + self.sps = {} --sps[seq_parameter_set_id] = SPS + self.pps = {} --pps[pps_id] = PPS + self.nal = nil --current nal + self.fps = 0 + + self.nalus = {} + self.fnalus = {} +end + +local h264_summary_t = class("h264_summary_t") +function h264_summary_t:ctor() + self.frame_count = 0 + self.width = 0 + self.height = 0 + self.yuv_format = "??" + self.fps = 0 +end + +local g_h264 = nil + +local function intlog2(x) + local log = 0 + if x < 0 then x = 0 end + + while ((x >> log) > 0) do + log = log+1 + end + if log > 0 and x == 1<<(log-1) then + log = log-1 + end + return log; +end + +local function is_slice_type(slice_type, cmp_type) + if slice_type >= 5 then slice_type = slice_type - 5 end + if cmp_type >= 5 then cmp_type = cmp_type - 5 end + if slice_type == cmp_type then return true end + return false +end + +local function enable_decode_video(slice_type) + if slice_type == NALU_TYPE.SPS then return true end + if slice_type == NALU_TYPE.PPS then return true end + if slice_type == NALU_TYPE.SLICE then return true end + if slice_type == NALU_TYPE.IDR then return true end + if slice_type == NALU_TYPE.AUX then return true end + return false +end + +local function field_scaling_list(name, ba, sizeof_scaling_list) + local f_scaling_list = field.bit_list(name, nil, function(self, ba) + + local scaling_list = {} + for i=1, sizeof_scaling_list do + table.insert(scaling_list, 0) + end + + local last_scale = 8; + local next_scale = 8; + for j=1, sizeof_scaling_list do + if next_scale ~= 0 then + local delta_scale = self:append( field.sebits(ba) ) + + next_scale = ( last_scale + delta_scale.value + 256 ) % 256; + --useDefaultScalingMatrixFlag = ( j == 0 && next_scale == 0 ); + end + + if next_scale == 0 then + scaling_list[j] = last_scale + else + scaling_list[j] = next_scale + end + last_scale = scaling_list[j]; + end + end) + return f_scaling_list +end + +local function field_hrd_parameters(ba) + local hrd = field.bit_list("hrd", nil, function(self, ba) + + local f_cpb_cnt_minus1 = self:append( field.uebits("cpb_cnt_minus1") ) + self:append( field.ubits("bit_rate_scale", 4) ) + self:append( field.ubits("cpb_size_scale", 4) ) + + self:append( field.bit_list("cpb_cnt_minus_list", nil, function(self, ba) + for i=0, f_cpb_cnt_minus1.value do + self:append( field.uebits(string.format("bit_rate_value_minus1[%d]", i)) ) + self:append( field.uebits(string.format("cpb_size_value_minus1[%d]", i)) ) + self:append( field.ubits(string.format("cbr_flag[%d]", i)) ) + end + end)) + + self:append( field.ubits("initial_cpb_removal_delay_length_minus1", 5) ) + self:append( field.ubits("cpb_removal_delay_length_minus1", 5) ) + self:append( field.ubits("dpb_output_delay_length_minus1", 5) ) + self:append( field.ubits("time_offset_length", 5) ) + end) + return hrd +end + +local function field_vui_parameters(ba, h264) + + local field_yui = field.bit_list("vui", nil, function(self, ba) + local f_aspect_ratio_info_present_flag = self:append( field.ubits("aspect_ratio_info_present_flag", 1) ) + if f_aspect_ratio_info_present_flag.value ~= 0 then + local f_aspect_ratio_idc = self:append( field.ubits("aspect_ratio_idc", 8) ) + if f_aspect_ratio_idc.value == NALU_SAR.EXTENDED then + self:append( field.ubits("sar_width", 16) ) + self:append( field.ubits("sar_height", 16) ) + end + end + + local f_overscan_info_present_flag = self:append( field.ubits("overscan_info_present_flag", 1) ) + if f_overscan_info_present_flag.value ~= 0 then + self:append( field.ubits("overscan_appropriate_flag", 1) ) + end + + local f_video_signal_type_present_flag = self:append( field.ubits("video_signal_type_present_flag", 1) ) + if f_video_signal_type_present_flag.value ~= 0 then + + self:append( field.ubits("video_format", 3) ) + self:append( field.ubits("video_full_range_flag", 1) ) + local f_colour_description_present_flag = self:append( field.ubits("colour_description_present_flag", 1) ) + if f_colour_description_present_flag.value ~= 0 then + self:append( field.ubits("colour_primaries", 8) ) + self:append( field.ubits("transfer_characteristics", 8) ) + self:append( field.ubits("matrix_coefficients", 8) ) + end + end + + local f_chroma_loc_info_present_flag = self:append( field.ubits("chroma_loc_info_present_flag", 1) ) + if f_chroma_loc_info_present_flag.value ~= 0 then + self:append( field.uebits("chroma_sample_loc_type_top_field") ) + self:append( field.uebits("chroma_sample_loc_type_bottom_field") ) + end + + local f_timing_info_present_flag = self:append( field.ubits("timing_info_present_flag", 1) ) + if f_timing_info_present_flag.value ~= 0 then + self:append( field.bit_list("timing_info", nil, function(self, ba) + local f_num_units_in_tick = self:append(field.ubits("num_units_in_tick", 32)) + local f_time_scale = self:append(field.ubits("time_scale", 32)) + local f_fixed_frame_rate_flag = self:append(field.ubits("fixed_frame_rate_flag", 1)) + + local fps = f_time_scale.value / f_num_units_in_tick.value + --if f_fixed_frame_rate_flag.value == 1 then + fps = fps / 2 + --end + if h264 then + h264.fps = fps + end + end)) + end + + local f_nal_hrd_parameters_present_flag = self:append( field.ubits("nal_hrd_parameters_present_flag", 1) ) + if f_nal_hrd_parameters_present_flag.value ~= 0 then + self:append( field_hrd_parameters(ba) ) + end + + local f_vcl_hrd_parameters_present_flag = self:append( field.ubits("vcl_hrd_parameters_present_flag", 1) ) + if f_vcl_hrd_parameters_present_flag.value ~= 0 then + self:append( field_hrd_parameters(ba) ) + end + + if f_nal_hrd_parameters_present_flag.value ~= 0 or f_vcl_hrd_parameters_present_flag.value ~= 0 then + self:append(field.ubits("low_delay_hrd_flag", 1) ) + end + + self:append( field.ubits("pic_struct_present_flag", 1) ) + + local f_bitstream_restriction_flag = self:append( field.ubits("bitstream_restriction_flag", 1) ) + if f_bitstream_restriction_flag.value ~= 0 then + + self:append( field.bit_list("bitstream_restriction", nil, function(self, ba) + self:append_list({ + field.ubits("motion_vectors_over_pic_boundaries_flag", 1), + field.uebits("max_bytes_per_pic_denom"), + field.uebits("max_bits_per_mb_denom" ), + field.uebits("log2_max_mv_length_horizontal"), + field.uebits("log2_max_mv_length_vertical"), + field.uebits("num_reorder_frames"), + field.uebits("max_dec_frame_buffering"), + }) + end)) + + end + end) + return field_yui +end + +local function field_sps(ebsp_len, h264) + + local f_sps = field.bit_list("SPS", ebsp_len, function(self, ba) --len:unknown + + local f_profile_idc = self:append( field.ubits("profile_idc", 8) ) + + self:append_list({ + field.ubits("constraint_set0_flag", 1), + field.ubits("constraint_set1_flag", 1), + field.ubits("constraint_set2_flag", 1), + field.ubits("constraint_set3_flag", 1), + field.ubits("constraint_set4_flag", 1), + field.ubits("constraint_set5_flag", 1), + field.ubits("reserved_zero_2bits", 2), + field.ubits("level_idc", 8), + }) + local f_seq_parameter_set_id = self:append( field.uebits("seq_parameter_set_id") ) + + local sps = sps_t.new() + if h264 then + h264.sps[f_seq_parameter_set_id.value] = sps + end + + local v_profile_idc = f_profile_idc.value + local cond = (v_profile_idc == 100 or + v_profile_idc == 110 or + v_profile_idc == 122 or + v_profile_idc == 144) + if cond then + local f_chroma_format_idc = self:append( field.uebits("chroma_format_idc", fh.mkdesc(YUV_FORMAT_STR)) ) + sps.chroma_format_idc = f_chroma_format_idc.value + + if f_chroma_format_idc.value == 3 then + local f_separate_colour_plane_flag = self:append( field.ubits("separate_colour_plane_flag", 1) ) + sps.separate_colour_plane_flag = f_separate_colour_plane_flag.value + end + + self:append_list( { + field.uebits("bit_depth_luma_minus8"), + field.uebits("bit_depth_chroma_minus8"), + field.ubits("qpprime_y_zero_transform_bypass_flag", 1), + }) + + local f_seq_scaling_matrix_present_flag = self:append( field.ubits("seq_scaling_matrix_present_flag", 1) ) + + if f_seq_scaling_matrix_present_flag.value ~= 0 then + for i=1, 8 do + + local f_seq_scaling_list_present_flag = self:append( field.ubits("seq_scaling_list_present_flag_" .. i, 1) ) + if f_seq_scaling_matrix_present_flag.value ~= 0 then + if i < 6 then + self:append( field_scaling_list("scaling_list_4x4", ba, 16) ) + else + self:append( field_scaling_list("scaling_list_8x8", ba, 64) ) + end + end + end + end + end + + local f_log2_max_frame_num_minus4 = self:append( field.uebits("log2_max_frame_num_minus4") ) + sps.log2_max_frame_num_minus4 = f_log2_max_frame_num_minus4.value + + + local f_pic_order_cnt_type = self:append( field.uebits("pic_order_cnt_type") ) + sps.pic_order_cnt_type = f_pic_order_cnt_type.value + + if f_pic_order_cnt_type.value == 0 then + local f_log2_max_pic_order_cnt_lsb_minus4 = self:append( field.uebits("log2_max_pic_order_cnt_lsb_minus4") ) + sps.log2_max_pic_order_cnt_lsb_minus4 = f_log2_max_pic_order_cnt_lsb_minus4.value + elseif f_pic_order_cnt_type.value == 1 then + + local f_delta_pic_order_always_zero_flag = self:append( field.ubits( "delta_pic_order_always_zero_flag", 1) ) + sps.delta_pic_order_always_zero_flag = f_delta_pic_order_always_zero_flag.vlaue + + self:append( field.sebits( "offset_for_non_ref_pic" ) ) + + self:append( field.sebits( "offset_for_top_to_bottom_field" ) ) + local f_num_ref_frames_in_pic_order_cnt_cycle = self:append( field.uebits( "num_ref_frames_in_pic_order_cnt_cycle" ) ) + for i=0, f_num_ref_frames_in_pic_order_cnt_cycle.value-1 do + self:append( field.sebits( "offset_for_ref_frame_" .. i ) ) + end + end + + --bi.log("read num_ref_frames") + + self:append( field.uebits("num_ref_frames") ) + self:append( field.ubits("gaps_in_frame_num_value_allowed_flag", 1) ) + + local f_pic_width_in_mbs_minus1 = self:append( field.uebits("pic_width_in_mbs_minus1", + function(self) + return string.format("[%2d bits] %s %d (%d)", self.len, self.name, self.value, (self.value+1)*16) + end, function(self) + local width, _ = sps:calc_width_height() + return string.format("%dx", width) + end) ) + sps.pic_width_in_mbs_minus1 = f_pic_width_in_mbs_minus1.value + + local f_pic_height_in_map_units_minus1 = self:append( field.uebits("pic_height_in_map_units_minus1", + function(self) + return string.format("[%2d bits] %s %d (%d)", self.len, self.name, self.value, (self.value+1)*16) + end, function(self) + local _, height = sps:calc_width_height() + return string.format("%d", height) + end) ) + sps.pic_height_in_map_units_minus1 = f_pic_height_in_map_units_minus1.value + + local f_frame_mbs_only_flag = self:append( field.ubits("frame_mbs_only_flag", 1) ) + sps.frame_mbs_only_flag = f_frame_mbs_only_flag.value + + if f_frame_mbs_only_flag.value == 0 then + self:append( field.ubits("mb_adaptive_frame_field_flag", 1) ) + end + + self:append( field.ubits("direct_8x8_inference_flag", 1) ) + + local f_frame_cropping_flag = self:append( field.ubits("frame_cropping_flag", 1) ) + if f_frame_cropping_flag.value ~= 0 then + sps.frame_cropping_flag = f_frame_cropping_flag.value + + self:append( field.bit_list("frame_crop", nil, function(self, ba) + local f_frame_crop_left_offset = self:append( field.uebits("frame_crop_left_offset") ) + local f_frame_crop_right_offset = self:append( field.uebits("frame_crop_right_offset") ) + local f_frame_crop_top_offset = self:append( field.uebits("frame_crop_top_offset") ) + local f_frame_crop_bottom_offset = self:append( field.uebits("frame_crop_bottom_offset") ) + + sps.frame_crop_left_offset = f_frame_crop_left_offset.value + sps.frame_crop_right_offset = f_frame_crop_right_offset.value + sps.frame_crop_top_offset = f_frame_crop_top_offset.value + sps.frame_crop_bottom_offset = f_frame_crop_bottom_offset.value + end)) + end + + local f_vui_parameters_present_flag = self:append( field.ubits("vui_parameters_present_flag", 1) ) + if f_vui_parameters_present_flag.value ~= 0 then + self:append( field_vui_parameters(ba, h264) ) + end + + --read_rbsp_trailing_bits(h, b); + end) + return f_sps +end + +local function more_rbsp_data(ba) + if ba:length() <= 0 then return 0 end + if ba:peek_ubits(1) == 1 then return 0 end --if next bit is 1, we've reached the stop bit + return 1; +end + +local function field_pps(ebsp_len, h264) + local f_pps = field.bit_list("PPS", ebsp_len, function(self, ba) + + local f_pps_id = self:append( field.uebits("pps_id") ) + + local pps = pps_t.new() + if h264 then + h264.pps[f_pps_id.value] = pps + end + + local f_seq_parameter_set_id = self:append( field.uebits("seq_parameter_set_id") ) + pps.seq_parameter_set_id = f_seq_parameter_set_id.value + + local f_entropy_coding_mode_flag = self:append( field.ubits("entropy_coding_mode_flag", 1) ) + pps.entropy_coding_mode_flag = f_entropy_coding_mode_flag.value + + local f_pic_order_present_flag = self:append( field.ubits("pic_order_present_flag", 1) ) + pps.pic_order_present_flag = f_pic_order_present_flag.value + + local f_num_slice_groups_minus1 = self:append( field.uebits("num_slice_groups_minus1") ) + pps.num_slice_groups_minus1 = f_num_slice_groups_minus1.value + + if f_num_slice_groups_minus1.value > 0 then + local f_slice_group_map_type = self:append( field.uebits("slice_group_map_type") ) + pps.slice_group_map_type = f_slice_group_map_type.value + + local v_slice_group_map_type = f_slice_group_map_type.value + if v_slice_group_map_type == 0 then + + self:append( field.bit_list("run_length_minus1_list", nil, function(self, ba) + for i=0, f_num_slice_groups_minus1.value do + self:append( field.uebits(string.format("run_length_minus1[%d]", i)) ) + end + end)) + end + + elseif v_slice_group_map_type == 2 then + self:append( field.bit_list("run_length_minus1_list", nil, function(self, ba) + for i=0, f_num_slice_groups_minus1.value-1 do + self:append( field.uebits(string.format("top_left[%d]", i)) ) + self:append( field.uebits(string.format("bottom_right[%d]", i)) ) + end + end)) + + elseif v_slice_group_map_type == 3 or v_slice_group_map_type == 4 or v_slice_group_map_type == 5 then + self:append( field.ubits("slice_group_change_direction_flag", 1) ) + local f_slice_group_change_rate_minus1 = self:append( field.uebits("slice_group_change_rate_minus1" ) ) + pps.slice_group_change_rate_minus1 = f_slice_group_change_rate_minus1.value + + elseif v_slice_group_map_type == 6 then + local f_pic_size_in_map_units_minus1 = self:append( field.uebits("pic_size_in_map_units_minus1") ) + pps.pic_size_in_map_units_minus1 = f_pic_size_in_map_units_minus1.value + + for i=0, f_pic_size_in_map_units_minus1.value do + local nbits = intlog2( f_num_slice_groups_minus1.value + 1 ) + self:append( field.ubits(string.format("slice_group_id[%d]", i), nbits )) + end + end + + local f_num_ref_idx_l0_active_minus1 = self:append( field.uebits("num_ref_idx_l0_active_minus1") ) + pps.num_ref_idx_l0_active_minus1 = f_num_ref_idx_l0_active_minus1.value + + local f_num_ref_idx_l1_active_minus1 = self:append( field.uebits("num_ref_idx_l1_active_minus1") ) + pps.num_ref_idx_l1_active_minus1 = f_num_ref_idx_l1_active_minus1.value + + local f_weighted_pred_flag = self:append( field.ubits("weighted_pred_flag", 1) ) + pps.weighted_pred_flag = f_weighted_pred_flag.value + + local f_weighted_bipred_idc = self:append( field.ubits("weighted_bipred_idc", 2) ) + pps.weighted_bipred_idc = f_weighted_bipred_idc.value + + self:append( field.sebits("pic_init_qp_minus26") ) + self:append( field.sebits("pic_init_qs_minus26") ) + self:append( field.sebits("chroma_qp_index_offset") ) + local f_deblocking_filter_control_present_flag = self:append( field.ubits("deblocking_filter_control_present_flag", 1) ) + pps.deblocking_filter_control_present_flag = f_deblocking_filter_control_present_flag.value + + self:append( field.ubits("constrained_intra_pred_flag", 1) ) + local f_redundant_pic_cnt_present_flag = self:append( field.ubits("redundant_pic_cnt_present_flag", 1) ) + pps.redundant_pic_cnt_present_flag = f_redundant_pic_cnt_present_flag.value + + local more_rbsp_data_present = more_rbsp_data(ba) + if more_rbsp_data_present ~= 0 then + local f_transform_8x8_mode_flag = self:append( field.ubits("transform_8x8_mode_flag", 1) ) + local f_pic_scaling_matrix_present_flag = self:append( field.ubits("pic_scaling_matrix_present_flag", 1) ) + if f_pic_scaling_matrix_present_flag.value ~= 0 then + + self:append( field.bit_list("pic_scaling_list", nil, function(self, ba) + + local loop = 6 + 2*f_transform_8x8_mode_flag.value - 1 + + for i=0, loop do + + local f_pic_scaling_list_present_flag = self:append( + field.ubits(string.format("pic_scaling_list_present_flag[%d]", i), 1) ) + + if f_pic_scaling_list_present_flag.value ~= 0 then + if i < 6 then + self:append( field_scaling_list("scaling_list_4x4", ba, 16) ) + else + self:append( field_scaling_list("scaling_list_4x4", ba, 64) ) + end + end + end + + end)) + + end + self:append( field.sebits("second_chroma_qp_index_offset") ) + + end + + end) + return f_pps +end + + +local function field_ref_pic_list_reordering(h264, ba, slice_type) + + if false == is_slice_type( slice_type, SLICE_TYPE.I ) and false == is_slice_type( slice_type, SLICE_TYPE.SI ) then + local field_ref_pic_list_reordering = field.bit_list("ref_pic_list_reordering", nil, function(self, ba) + + + local f_ref_pic_list_reordering_flag_l0 = self:append( field.ubits("ref_pic_list_reordering_flag_l0", 1) ) + if f_ref_pic_list_reordering_flag_l0.value ~= 0 then + + local f_reordering_of_pic_nums_idc = self:append( field.uebits("reordering_of_pic_nums_idc") ) + local index = 0 + while f_reordering_of_pic_nums_idc.value ~= 3 do + if f_reordering_of_pic_nums_idc.value == 0 or f_reordering_of_pic_nums_idc.value == 1 then + self:append( field.uebits(string.format("abs_diff_pic_num_minus1[%d]", index))) + elseif f_reordering_of_pic_nums_idc.value == 2 then + self:append( field.uebits(string.format("long_term_pic_num[%d]", index))) + end + + index = index + 1 + + if ba:length() <= 0 then break end + f_reordering_of_pic_nums_idc = self:append( field.uebits("reordering_of_pic_nums_idc") ) + end + + end + end) + + return field_ref_pic_list_reordering + end + + if is_slice_type( slice_type, SLICE_TYPE.B ) then + + local field_ref_pic_list_reordering = field.bit_list("ref_pic_list_reordering", nil, function(self, ba) + local f_ref_pic_list_reordering_flag_l1 = self:append(field.ubits("ref_pic_list_reordering_flag_l1", 1)) + if f_ref_pic_list_reordering_flag_l1.value ~= 0 then + + local index = 0 + local f_reordering_of_pic_nums_idc = self:append( field.uebits("reordering_of_pic_nums_idc") ) + while f_reordering_of_pic_nums_idc.value ~= 3 do + if f_reordering_of_pic_nums_idc.value == 0 or f_reordering_of_pic_nums_idc.value == 1 then + self:append( field.uebits(string.format("abs_diff_pic_num_minus1[%d]", index)) ) + elseif f_reordering_of_pic_nums_idc.value == 2 then + self:append( field.uebits(string.format("long_term_pic_num[%d]", index) )) + end + + index = index + 1 + + if ba:length() <= 0 then break end + f_reordering_of_pic_nums_idc = self:append( field.uebits("reordering_of_pic_nums_idc") ) + end + + end + end) + return field_ref_pic_list_reordering + end + return nil +end + +local function field_pred_weight_table(h264, ba, slice_type, sps, pps) + local f = field.bit_list("pred_weight_table", nil, function(self, ba) + + self:append( field.uebits("luma_log2_weight_denom") ) + if sps.chroma_format_idc ~= 0 then + self:append( field.uebits("chroma_log2_weight_denom") ) + end + for i=0, pps.num_ref_idx_l0_active_minus1 do + local f_luma_weight_l0_flag = self:append(field.ubits(string.format("luma_weight_l0_flag[%d]", i), 1)) + + if f_luma_weight_l0_flag.value ~= 0 then + self:append( field.sebits(string.format("luma_weight_l0[%d]", i)) ) + self:append( field.sebits(string.format("luma_offset_l0[%d]", i)) ) + end + if sps.chroma_format_idc ~= 0 then + local f_chroma_weight_l0_flag = self:append(field.ubits(string.format("chroma_weight_l0_flag[%d]", i), 1)) + if f_chroma_weight_l0_flag.value ~= 0 then + for j =0, 1 do + self:append(field.sebits(string.format("chroma_weight_l0[%d][%d]", i, j))) + self:append(field.sebits(string.format("chroma_offset_l0[%d][%d]", i, j))) + end + end + end + end + + if is_slice_type( slice_type, SLICE_TYPE.B ) then + for i=0, pps.num_ref_idx_l1_active_minus1 do + local f_luma_weight_l1_flag = self:append(field.ubits(string.format("luma_weight_l1_flag[%d]", i), 1)) + if f_luma_weight_l1_flag.value ~= 0 then + self:append(field.sebits(string.format("luma_weight_l1[%d]", i))) + self:append(field.sebits(string.format("luma_offset_l1[%d]", i))) + end + if sps.chroma_format_idc ~= 0 then + local f_chroma_weight_l1_flag = self:append(field.ubits(string.format("chroma_weight_l1_flag[%d]", i), 1)) + + if f_chroma_weight_l1_flag.value ~= 0 then + for j=0, 1 do + self:append(field.sebits(string.format("chroma_weight_l1[%d][%d]", i, j))) + self:append(field.sebits(string.format("chroma_offset_l1[%d][%d]", i, j))) + end + end + end + end + end + end) + return f +end + +local function field_slice_header(h264) + + local hdr = field.bit_list("SLICE_HEADER", nil, function(self, ba) + + self:append( field.uebits("first_mb_in_slice") ) + local f_slice_type = self:append( field.uebits("slice_type", fh.mkdesc(SLICE_TYPE_STR) ) ) + local slice_type = f_slice_type.value + self.slice_type = slice_type + local f_pic_parameter_set_id = self:append( field.uebits("pic_parameter_set_id") ) + + local pps = h264.pps[f_pic_parameter_set_id.value] + if nil == pps then return end + + local sps = h264.sps[pps.seq_parameter_set_id] + if nil == sps then return end + + local nal = h264.nal + if nil == nal then return end + + local f_frame_num = self:append( field.ubits("frame_num", sps.log2_max_frame_num_minus4 + 4 ) ) + local v_field_pic_flag = 0 + if sps.frame_mbs_only_flag == 0 then + local f_field_pic_flag = self:append( field.ubits("field_pic_flag", 1) ) + v_field_pic_flag = f_field_pic_flag.value + if f_field_pic_flag.value ~= 0 then + self:append( field.ubits("bottom_field_flag", 1) ) + end + end + if nal.nal_unit_type == 5 then + self:append( field.uebits("idr_pic_id") ) + end + + if sps.pic_order_cnt_type == 0 then + self:append( field.ubits("pic_order_cnt_lsb", sps.log2_max_pic_order_cnt_lsb_minus4+4 ) ) + if pps.pic_order_present_flag ~= 0 and v_field_pic_flag == 0 then + self:append( field.sebits("delta_pic_order_cnt_bottom") ) + end + end + + if sps.pic_order_cnt_type == 1 and sps.delta_pic_order_always_zero_flag == 0 then + self:append( field.sebits("delta_pic_order_cnt[0]") ) + if( pps.pic_order_present_flag ~= 0 and v_field_pic_flag == 0 ) then + self:append( field.sebits("delta_pic_order_cnt[1]") ) + end + end + + if pps.redundant_pic_cnt_present_flag ~= 0 then + self:append( field.uebits("redundant_pic_cnt") ) + end + + if( is_slice_type( slice_type, SLICE_TYPE.B ) ) then + self:append( field.ubits("direct_spatial_mv_pred_flag", 1) ) + end + + if is_slice_type( slice_type, SLICE_TYPE.P ) or + is_slice_type( slice_type, SLICE_TYPE.SP ) or + is_slice_type( slice_type, SLICE_TYPE.B ) then + local f_num_ref_idx_active_override_flag = field.ubits("num_ref_idx_active_override_flag", 1) + if f_num_ref_idx_active_override_flag.value ~= 0 then + self:append( field.uebits("num_ref_idx_l0_active_minus1") ) + if is_slice_type( slice_type, SLICE_TYPE.B ) then + self:append( field.uebits("num_ref_idx_l1_active_minus1") ) + end + end + end + + local f_ref_pic_list_reordering = field_ref_pic_list_reordering(h264, ba, slice_type) + if nil ~= f_ref_pic_list_reordering then + self:append( f_ref_pic_list_reordering ) + end + + local is_p = is_slice_type(slice_type, SLICE_TYPE.P) or is_slice_type(slice_type, SLICE_TYPE.SP) + local is_b = is_slice_type(slice_type, SLICE_TYPE.B) + if (pps.weighted_pred_flag ~= 0 and is_p) or (pps.weighted_bipred_idc == 1 and is_b) then + self:append( field_pred_weight_table(h264, ba, slice_type, sps, pps) ) + end + + if nal.nal_ref_idc ~= 0 then + --TODO read_dec_ref_pic_marking + end + + --[[ + if pps.entropy_coding_mode_flag ~= 0 and + false == is_slice_type(slice_type, SLICE_TYPE.I) and + false == is_slice_type(slice_type, SLICE_TYPE.SI) then + + self:append( field.uebits("cabac_init_idc") ) + end + + if is_slice_type(slice_type, SLICE_TYPE.SP) or is_slice_type(slice_type, SLICE_TYPE.SI) then + if is_slice_type( slice_type, SLICE_TYPE.SP) then + self:append( field.ubits("sp_for_switch_flag", 1) ) + end + self:append( field.sebits("slice_qs_delta") ) + end + --]] + + --TODO + end) + return hdr +end + +local function field_slice(h264, typ) + + local name = NALU_TYPE_STR[typ] or "??" + + local f_slice_hdr = field_slice_header(h264) + + local slice = field.bit_list(name, nil, function(self, ba) + self:append( f_slice_hdr ) + end, nil, function(self) + local stype = SLICE_TYPE_STR[f_slice_hdr.slice_type] or "??" + return string.format("%s", stype) + end) + + return slice +end + +--[[ + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ +--]] + +local function field_nalu_header() + local field_forbidden_bit = field.ubits("forbidden_bit", 1, nil, fh.mkbrief("F")) + local field_nal_reference_idc = field.ubits("nal_reference_idc", 2, nil, fh.mkbrief("N")) + local field_nal_unit_type = field.ubits("nal_unit_type", 5, fh.mkdesc(NALU_TYPE_STR), fh.mkbrief("T", NALU_TYPE_STR)) + + local f = field.bit_list("NALU HEADER", 1, function(self, ba) + self:append( field_forbidden_bit ) + self:append( field_nal_reference_idc ) + self:append( field_nal_unit_type ) + + self.nalu_type = field_nal_unit_type.value + end, nil, fh.child_brief) + return f +end + +local function field_fu_header() + local f = field.bit_list("FU_HEADER", 1, function(self, ba) + local f_S = self:append( field.ubits("S", 1) ) + local f_E = self:append( field.ubits("E", 1) ) + local f_R = self:append( field.ubits("R", 1) ) + local f_T = self:append( field.ubits("T", 5, fh.mkdesc(NALU_TYPE_STR), fh.mkbrief("FUT", NALU_TYPE_STR)) ) + + self.fu = { + S = f_S.value, + E = f_E.value, + R = f_R.value, + T = f_T.value, + } + end) + return f +end + +local function field_nalu(h264, index, pos, raw_data, raw_data_len, ebsp_len) + + local nal = nal_t.new() + h264.nal = nal + + local f_nalu = field.list("NALU", raw_data_len, function(self, ba) + + self:append( field.callback("SYNTAX", function(self, ba) + if ba:peek_uint24() == 0x10000 then + return ba:read_uint24(), 3 + elseif ba:peek_uint32() == 0x1000000 then + return ba:read_uint32(), 4 + end + return nil + end, function(self) + return string.format("%s: 0x%X", self.name, self.value) + end, function(self) + return string.format(" S:0x%X ", self.value) + end) ) + + local f_nalu_header = self:append( field_nalu_header() ) + + local tp = f_nalu_header.nalu_type + if tp == NALU_TYPE.SPS then + self:set_bg_color("#FFFF55") + elseif tp == NALU_TYPE.PPS then + self:set_bg_color("#AAAAFF") + elseif tp == NALU_TYPE.IDR then + self:set_bg_color("#55FF55") + end + + self:append( field.select("EBSP", ebsp_len, function() + local t = f_nalu_header.nalu_type + + nal.nal_unit_type = t + + if t == NALU_TYPE.SPS then + h264.sps_data = raw_data + return field_sps(ebsp_len, h264) + end + if t == NALU_TYPE.PPS then + h264.pps_data = raw_data + return field_pps(ebsp_len, h264) + end + + if t == NALU_TYPE.SLICE then return field_slice(h264, NALU_TYPE.SLICE) end + if t == NALU_TYPE.IDR then return field_slice(h264, NALU_TYPE.IDR) end + if t == NALU_TYPE.AUX then return field_slice(h264, NALU_TYPE.AUX) end + end, nil, nil) ) + + end, function(self) + --NALU cb_desc + local brief = self:get_child_brief() + local bmp_flag = "✓" + if nil == self.ffh then + bmp_flag = " " --"✗" + end + return string.format("[%d] %s %s%s POS:%u LEN:%d", index, bmp_flag, self.name, brief, pos, raw_data_len) + end) + + f_nalu.cb_click = function(self) + bi.clear_bmp() + + if nil ~= self.ffh then + self.ffh:drawBmp() + end + --self.ffh:saveBmp(string.format("%s/d_%d.bmp", bi.get_tmp_dir(), f_nalu.index)) + end + + --field nalu dump self data + f_nalu:set_raw_data(raw_data) + return f_nalu +end + +local nalu_syntax24 = string.pack("I3", 0x10000) +local nalu_syntax32 = string.pack("I4", 0x1000000) + +local function find_nalu_syntax(ba) + local i24_pos = ba:search(nalu_syntax24, 3) + if i24_pos < 0 then + return -1 + end + + if i24_pos > ba:position() and ba:cmp(i24_pos-1, nalu_syntax32, 4) == 0 then + --start with 00 00 00 01 + return i24_pos-1, 4 + end + + --start with 00 00 01 + return i24_pos, 3 +end + +local function find_nalu(ba) + + local data = nil + + local old_pos = ba:position() + + --find first syntax + local pos_syntax, nbyte = find_nalu_syntax(ba) + if pos_syntax < 0 then + return nil + end + + local pos = ba:position() + if pos_syntax > pos then + --skip n bytes + ba:read_bytes(pos_syntax - pos) + end + + if nbyte == 3 then + data = string.pack("I3", ba:read_uint24()) + else + data = string.pack("I4", ba:read_uint32()) + end + + local pos_next_syntax = find_nalu_syntax(ba) + + local ebsp_len = 0 + if pos_next_syntax < 0 then + ebsp_len = ba:length() + elseif pos_next_syntax > 0 then + ebsp_len = pos_next_syntax - ba:position() + end + + if ebsp_len > 0 then + local ebsp = ba:read_bytes(ebsp_len) + local _, rbsp = bi.nalu_unshell(ebsp, ebsp_len) + data = data .. rbsp + end + + return data, pos_syntax, ebsp_len +end +local function decode_h264(ba, len) + g_h264 = h264_t.new() + + local f_h264 = field.list("H264", len, function(self, ba) + + local index = 0 + while ba:length() > 0 do + local pos = ba:position() + + bi.set_progress_value(pos) + + local data, pos_syntax, ebsp_len = find_nalu(ba) + + if data then + table.insert(g_h264.nalus, data) + + --parse unshelled nalu data + local data_len = #data + local nalu_ba = byte_stream(data_len) + nalu_ba:set_data(data, data_len) + + --bi.log(string.format("nalu data len %d pos:%d", data_len, ba:position())) + local fnalu = field_nalu(g_h264, index, pos_syntax, data, data_len, ebsp_len) + self:append( fnalu, nalu_ba ) + + table.insert( g_h264.fnalus, fnalu ) + + local t = g_h264.nal.nal_unit_type + if enable_decode_video(t) then + fnalu.ffh = bi.decode_avframe(AV_CODEC_ID.H264, data, data_len) + end + + fnalu.index = index + + index = index + 1 + else + break + end + end + end) + return f_h264 +end + +local function build_summary() + if nil == g_h264 then return end + --set summary + local summary = h264_summary_t.new() + summary.fps = g_h264.fps + summary.frame_count = index + for _, sps in pairs(g_h264.sps) do + summary.width, summary.height = sps:calc_width_height() + summary.yuv_format = YUV_FORMAT_STR[sps.chroma_format_idc] or "??" + break + end + + bi.append_summary("frame_count", #g_h264.fnalus) + + if summary.width ~= 0 then + bi.append_summary("resolution", string.format("%dx%d", summary.width, summary.height)) + else + bi.append_summary("resolution", "??x??") + end + bi.append_summary("yuv_format", summary.yuv_format) + bi.append_summary("fps", summary.fps) +end + +local function clear() + if nil == g_h264 then return end + + --free all ffh + for i, fnalu in ipairs(g_h264.fnalus) do + if nil ~= fnalu.ffh then + bi.ffmpeg_helper_free(fnalu.ffh) + end + end + g_h264.fnalus = nil + + g_h264 = nil +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "H264 video", + file_ext = "h264 264", + decode = decode_h264, + build_summary = build_summary, + + + clear = clear, + + field_nalu_header = field_nalu_header, + field_fu_header = field_fu_header, + field_sps = field_sps, + field_pps = field_pps, + + NALU_TYPE = NALU_TYPE, + NALU_TYPE_STR = NALU_TYPE_STR +} + +return codec diff --git a/src/scripts/codec/codec_h265.lua b/src/scripts/codec/codec_h265.lua new file mode 100644 index 0000000..bcb3ac1 --- /dev/null +++ b/src/scripts/codec/codec_h265.lua @@ -0,0 +1,672 @@ +require("class") + +local helper = require("helper") +local field = require("field") +local fh = require("field_helper") +local json = require("cjson") + +--reference +--https://github.com/NVIDIA/vdpau-hevc-example/blob/master/gsth265parser.c + +local NALU_TYPE = { + TRAIL_N = 0, + TRAIL_R = 1, + TSA_N = 2, + TSA_R = 3, + STSA_N = 4, + STSA_R = 5, + RADL_N = 6, + RADL_R = 7, + RASL_N = 8, + RASL_R = 9, + RSV_VCL_N10 = 10, + RSV_VCL_N12 = 12, + RSV_VCL_N14 = 14, + RSV_VCL_R11 = 11, + RSV_VCL_R13 = 13, + RSV_VCL_R15 = 15, + BLA_W_LP = 16, + BLA_W_RADL = 17, + BLA_N_LP = 18, + IDR_W_RADL = 19, + IDR_N_LP = 20, + RSV_IRAP_VCL22 = 22, + RSV_IRAP_VCL23 = 23, + RSV_VCL24 = 24, + RSV_VCL25 = 25, + RSV_VCL26 = 26, + RSV_VCL27 = 27, + RSV_VCL28 = 28, + RSV_VCL29 = 29, + RSV_VCL30 = 30, + RSV_VCL31 = 31, + VPS_NUT = 32, + SPS_NUT = 33, + PPS_NUT = 34, + AUD_NUT = 35, + EOS_NUT = 36, + EOB_NUT = 37, + FD_NUT = 38, + PREFIX_SEI_NUT = 39, + SUFFIX_SEI_NUT = 40, + RSV_NVCL41 = 41, + RSV_NVCL42 = 42, + RSV_NVCL43 = 43, + RSV_NVCL44 = 44, + RSV_NVCL45 = 45, + RSV_NVCL46 = 46, + RSV_NVCL47 = 47, + UNSPEC48 = 48, + UNSPEC49 = 49, + UNSPEC50 = 50, + UNSPEC51 = 51, + UNSPEC52 = 52, + UNSPEC53 = 53, + UNSPEC54 = 54, + UNSPEC55 = 55, + UNSPEC56 = 56, + UNSPEC57 = 57, + UNSPEC58 = 58, + UNSPEC59 = 59, + UNSPEC60 = 60, + UNSPEC61 = 61, + UNSPEC62 = 62, + UNSPEC63 = 63, +} + +local NALU_TYPE_STR = { + [0] = "TRAIL_N", + [1] = "TRAIL_R", + [2] = "TSA_N", + [3] = "TSA_R", + [4] = "STSA_N", + [5] = "STSA_R", + [6] = "RADL_N", + [7] = "RADL_R", + [8] = "RASL_N", + [9] = "RASL_R", + [10] = "RSV_VCL_N10", + [12] = "RSV_VCL_N12", + [14] = "RSV_VCL_N14", + [11] = "RSV_VCL_R11", + [13] = "RSV_VCL_R13", + [15] = "RSV_VCL_R15", + [16] = "BLA_W_LP", + [17] = "BLA_W_RADL", + [18] = "BLA_N_LP", + [19] = "IDR_W_RADL", + [20] = "IDR_N_LP", + [22] = "RSV_IRAP_VCL22", + [23] = "RSV_IRAP_VCL23", + [24] = "RSV_VCL24", + [25] = "RSV_VCL25", + [26] = "RSV_VCL26", + [27] = "RSV_VCL27", + [28] = "RSV_VCL28", + [29] = "RSV_VCL29", + [30] = "RSV_VCL30", + [31] = "RSV_VCL31", + [32] = "VPS_NUT", + [33] = "SPS_NUT", + [34] = "PPS_NUT", + [35] = "AUD_NUT", + [36] = "EOS_NUT", + [37] = "EOB_NUT", + [38] = "FD_NUT", + [39] = "PREFIX_SEI_NUT", + [40] = "SUFFIX_SEI_NUT", + [41] = "RSV_NVCL41", + [42] = "RSV_NVCL42", + [43] = "RSV_NVCL43", + [44] = "RSV_NVCL44", + [45] = "RSV_NVCL45", + [46] = "RSV_NVCL46", + [47] = "RSV_NVCL47", + [48] = "UNSPEC48", + [49] = "UNSPEC49", + [50] = "UNSPEC50", + [51] = "UNSPEC51", + [52] = "UNSPEC52", + [53] = "UNSPEC53", + [54] = "UNSPEC54", + [55] = "UNSPEC55", + [56] = "UNSPEC56", + [57] = "UNSPEC57", + [58] = "UNSPEC58", + [59] = "UNSPEC59", + [60] = "UNSPEC60", + [61] = "UNSPEC61", + [62] = "UNSPEC62", + [63] = "UNSPEC63", +} + +local nal_t = class("nal_t") +function nal_t:ctor() + self.nal_unit_type = 0 + self.nuh_layer_id = 0 + self.temporal_id = 0 +end + + +local h265_t = class("h265_t") +function h265_t:ctor() + self.sps = {} --sps[seq_parameter_set_id] = SPS + self.pps = {} --pps[pps_id] = PPS + self.nal = nil --current nal + + self.nalus = {} +end + +local function field_nalu_header() + local field_forbidden_bit = field.ubits("forbidden_bit", 1, nil, fh.mkbrief("F")) + local field_nal_unit_type = field.ubits("nal_unit_type", 6, fh.mkdesc(NALU_TYPE_STR), fh.mkbrief("T", NALU_TYPE_STR)) + local field_nuh_layer_id = field.ubits("nuh_layer_id", 6, nil, fh.mkbrief("LayerId")) + local field_nuh_temporal_id_plus1 = field.ubits("nuh_temporal_id_plus1", 3, nil, function(self) + return string.format("TemporalId:%d ", self.value-1) + end) + + local f = field.bit_list("NALU HEADER", 2, function(self, ba) + self:append( field_forbidden_bit ) + self:append( field_nal_unit_type ) + self:append( field_nuh_layer_id ) + self:append( field_nuh_temporal_id_plus1 ) + + self.nalu_type = field_nal_unit_type.value + self.nuh_layer_id = field_nuh_layer_id.value + self.temporal_id = field_nuh_temporal_id_plus1.value - 1 + end, nil, fh.child_brief) + return f +end + +local function field_vps(ebsp_len, nal) +end + +local function field_profile_tier_level( bit_list, sps_max_sub_layers_minus1 ) + + bit_list:append( field.ubits("profile_space", 2) ) + bit_list:append( field.ubits("tier_flag", 1) ) + bit_list:append( field.ubits("profile_idc", 5) ) + + for i=1,32 do + bit_list:append( field.ubits(string.format("profile_compatibility_flag[%d]", i), 1) ) + end + + bit_list:append( field.ubits("progressive_source_flag", 1) ) + bit_list:append( field.ubits("interlaced_source_flag", 1) ) + bit_list:append( field.ubits("non_packed_constraint_flag", 1) ) + bit_list:append( field.ubits("frame_only_constraint_flag", 1) ) + + bit_list:append( field.ubits("skip", 22) ) + bit_list:append( field.ubits("skip", 22) ) + bit_list:append( field.ubits("level_idc", 8) ) + + local sub_layer_profile_present_flag = {} + local sub_layer_level_present_flag = {} + + for i=1, sps_max_sub_layers_minus1 do + local f_tmp0 = bit_list:append( field.ubits(string.format("sub_layer_profile_present_flag[%d]", i)) ) + local f_tmp1 = bit_list:append( field.ubits(string.format("sub_layer_level_present_flag[%d]", i)) ) + + table.insert( sub_layer_profile_present_flag, f_tmp0.value ) + table.insert( sub_layer_level_present_flag, f_tmp1.value ) + end + + if sps_max_sub_layers_minus1 > 0 then + for i=sps_max_sub_layers_minus1, 7 do + bit_list:append( field.ubits("skip", 2) ) + end + end + + for i=1, sps_max_sub_layers_minus1 do + if sub_layer_profile_present_flag[i] ~= 0 then + bit_list:append( field.ubits(string.format("sub_layer_profile_space[%d]", i), 2 ) ) + bit_list:append( field.ubits(string.format("sub_layer_tier_flag[%d]", i), 2 ) ) + bit_list:append( field.ubits(string.format("sub_layer_profile_idc[%d]", i), 2 ) ) + + for j=1, 32 do + bit_list:append( field.ubits(string.format("sub_layer_profile_idc[%d]", i), 2 ) ) + end + end + + if sub_layer_level_present_flag[i] ~= 0 then + bit_list:append( field.ubits(string.format("sub_layer_profile_compatibility_flag[%d][%d]", i, j), 1 ) ) + end + + bit_list:append( field.ubits(string.format("sub_layer_progressive_source_flag[%d]", i), 1 ) ) + bit_list:append( field.ubits(string.format("sub_layer_interlaced_source_flag[%d]", i), 1 ) ) + bit_list:append( field.ubits(string.format("sub_layer_non_packed_constraint_flag[%d]", i), 1 ) ) + bit_list:append( field.ubits(string.format("sub_layer_frame_only_constraint_flag[%d]", i), 1 ) ) + + bit_list:append( field.ubits("skip", 22) ) + bit_list:append( field.ubits("skip", 22) ) + end + +end + +local function field_scaling_list_data() + + local f = field.bit_list("scaling_list_data", nil, function(self, ba) + + for sizeId=0, 3 do + local step = 1 + if sizeId == 3 then + step = 3 + end + + for matrixId=0, 5, step do + local f_tmp = self:append( field.ubits(string.format("scaling_list_pred_mode_flag[%d][%d]", sizeId, matrixId), 1) ) + if f_tmp.value == 0 then + self:append( field.uebits(string.format("scaling_list_pred_matrix_id_delta[%d][%d]", sizeId, matrixId) ) ) + else + local nextCoef = 8 + local coefNum = math.min( 64, ( 1 << ( 4 + ( sizeId << 1 ) ) ) ) + if sizeId > 1 then + local f_tmp2=self:append(field.sebits(string.format("scaling_list_dc_coef_minus8[%d][%d]", sizeId-2, matrixId))) + nextCoef = f_tmp2.value + 8 + end + + for i=0, coefNum-1 do + local f_scaling_list_delta_coef = self:append(field.sebits("scaling_list_delta_coef")) + nextCoef = ( nextCoef + f_scaling_list_delta_coef.value + 256 ) % 256 + --ScalingList[ sizeId ][ matrixId ][ i ] = nextCoef + end + end + end + end + + end) + return f +end + +local function field_st_ref_pic_set(stRpsIdx, num_short_term_ref_pic_sets ) + local f = field.bit_list(string.format("st_ref_pic_set[%d]", stRpsIdx), nil, function(self, ba) + + local NumNegativePics = {} + local NumPositivePics = {} + local NumDeltaPocs = {} + + local inter_ref_pic_set_prediction_flag = 0 + if stRpsIdx ~= 0 then + local f_tmp = self:append(field.ubits("inter_ref_pic_set_prediction_flag", 1)) + inter_ref_pic_set_prediction_flag = f_tmp.value + end + if 0 ~= inter_ref_pic_set_prediction_flag then + if stRpsIdx == num_short_term_ref_pic_sets then + self:append(field.uebits("delta_idx_minus1")) + end + self:append(field.ubits("delta_rps_sign", 1)) + self:append(field.uebits("abs_delta_rps_minus1")) + for j=0, NumDeltaPocs[stRpsIdx] do + local f_used_by_curr_pic_flag = self:append(field.ubits(string.format("used_by_curr_pic_flag[%d]", j)), 1) + if 0 == f_used_by_curr_pic_flag.value then + self:append(field.ubits(string.format("use_delta_flag[%d]", j), 1)) + end + end + else + local f_num_negative_pics = self:append(field.uebits("num_negative_pics")) + local f_num_positive_pics = self:append(field.uebits("num_positive_pics")) + for i=0, f_num_negative_pics.value-1 do + self:append(field.uebits(string.format("delta_poc_s0_minus1[%d]", i))) + self:append(field.ubits(string.format("used_by_curr_pic_s0_flag[%d]", i), 1)) + end + for i=0, f_num_positive_pics.value-1 do + self:append(field.uebits(string.format("delta_poc_s1_minus1[%d]", i))) + self:append(field.ubits(string.format("used_by_curr_pic_s1_flag[%d]", i), 1)) + end + NumNegativePics[stRpsIdx] = f_num_negative_pics.value + NumPositivePics[stRpsIdx] = f_num_positive_pics.value + NumDeltaPocs[stRpsIdx] = NumNegativePics[stRpsIdx] + NumPositivePics[stRpsIdx] + end + + end) + return f +end + +local function field_sps(ebsp_len, nal) + local f = field.bit_list("SPS", ebsp_len, function(self, ba) + self:append( field.ubits("sps_video_parameter_set_id", 4) ) + + local sps_ext_or_max_sub_layers_minus1 = 0 + local sps_max_sub_layers_minus1 = 0 + if nal.nuh_layer_id == 0 then + local f_tmp = self:append( field.ubits("sps_max_sub_layers_minus1", 3) ) + sps_max_sub_layers_minus1 = f_tmp.value + else + local f_tmp = self:append( field.ubits("sps_ext_or_max_sub_layers_minus1", 3) ) + sps_ext_or_max_sub_layers_minus1 = f_tmp.value + end + + local multi_layer_ext_sps_flag = ( nal.nuh_layer_id ~= 0 and sps_ext_or_max_sub_layers_minus1 == 7 ) + if not multi_layer_ext_sps_flag then + self:append( field.ubits("sps_temporal_id_nesting_flag", 1) ) + field_profile_tier_level( self, sps_max_sub_layers_minus1 ) + end + + self:append( field.uebits("sps_seq_parameter_set_id" ) ) + + if multi_layer_ext_sps_flag then + local f_update_rep_format_flag = self:append( field.ubits("update_rep_format_flag", 1) ) + if f_update_rep_format_flag.value ~= 0 then + self:append( field.ubits("sps_rep_format_idx", 8) ) + end + else + local f_chroma_format_idc = self:append( field.uebits("chroma_format_idc") ) + if f_chroma_format_idc.value == 3 then + self:append( field.ubits("separate_colour_plane_flag", 1) ) + end + self:append( field.uebits("pic_width_in_luma_samples") ) + self:append( field.uebits("pic_height_in_luma_samples") ) + local f_conformance_window_flag = self:append( field.ubits("conformance_window_flag", 1) ) + if f_conformance_window_flag.value ~= 0 then + self:append( field.uebits("conf_win_left_offset") ) + self:append( field.uebits("conf_win_right_offset") ) + self:append( field.uebits("conf_win_top_offset") ) + self:append( field.uebits("conf_win_bottom_offset") ) + end + + self:append( field.uebits("bit_depth_luma_minus8") ) + self:append( field.uebits("bit_depth_chroma_minus8") ) + end + + local f_log2_max_pic_order_cnt_lsb_minus4 = self:append( field.uebits("log2_max_pic_order_cnt_lsb_minus4") ) + + if not multi_layer_ext_sps_flag then + self:append( field.ubits("sps_sub_layer_ordering_info_present_flag", 1) ) + local n = 0 + if sps_sub_layer_ordering_info_present_flag == 0 then + n = sps_max_sub_layers_minus1 + end + for i=n, sps_max_sub_layers_minus1 do + self:append( field.uebits(string.format("sps_max_dec_pic_buffering_minus1[%d]", i)) ) + self:append( field.uebits(string.format("sps_max_num_reorder_pics[%d]", i)) ) + self:append( field.uebits(string.format("sps_max_latency_increase_plus1[%d]", i)) ) + end + end + + self:append( field.uebits("log2_min_luma_coding_block_size_minus3") ) + self:append( field.uebits("log2_diff_max_min_luma_coding_block_size") ) + self:append( field.uebits("log2_min_luma_transform_block_size_minus2") ) + self:append( field.uebits("log2_diff_max_min_luma_transform_block_size") ) + self:append( field.uebits("max_transform_hierarchy_depth_inter") ) + self:append( field.uebits("max_transform_hierarchy_depth_intra") ) + local f_scaling_list_enabled_flag = self:append( field.ubits("scaling_list_enabled_flag", 1) ) + if 0 ~= f_scaling_list_enabled_flag.value then + local f_sps_scaling_list_data_present_flag = self:append( field.ubits("sps_scaling_list_data_present_flag", 1) ) + if 0 ~= f_sps_scaling_list_data_present_flag.value then + self:append( field_scaling_list_data() ) + end + end + + self:append( field.ubits("amp_enabled_flag", 1) ) + self:append( field.ubits("sample_adaptive_offset_enabled_flag", 1) ) + local f_pcm_enabled_flag = self:append( field.ubits("pcm_enabled_flag", 1) ) + + if f_pcm_enabled_flag.value ~= 0 then + self:append(field.ubits("pcm_sample_bit_depth_luma_minus1", 4)) + self:append(field.ubits("pcm_sample_bit_depth_chroma_minus1", 4)) + self:append(field.uebits("log2_min_pcm_luma_coding_block_size_minus3")) + self:append(field.uebits("log2_diff_max_min_pcm_luma_coding_block_size")) + self:append(field.ubits("pcm_loop_filter_disabled_flag", 1)) + end + + local f_num_short_term_ref_pic_sets = self:append(field.uebits("num_short_term_ref_pic_sets")) +--[[ + for i=0, f_num_short_term_ref_pic_sets.value-1 do + self:append( field_st_ref_pic_set(i, f_num_short_term_ref_pic_sets.value) ) + end + + local f_long_term_ref_pics_present_flag = self:append(field.ubits("long_term_ref_pics_present_flag", 1)) + if f_long_term_ref_pics_present_flag.value ~= 0 then + local f_num_long_term_ref_pics_sps = self:append( field.uebits("num_long_term_ref_pics_sps") ) + + for i=0, f_num_long_term_ref_pics_sps.value-1 do + self:append(field.ubits(string.format("lt_ref_pic_poc_lsb_sps[%d]", i), f_log2_max_pic_order_cnt_lsb_minus4.value + 4)) + self:append(field.ubits(string.format("used_by_curr_pic_lt_sps_flag[%d]", i), 1)) + end + end + + self:append( field.ubits("temporal_mvp_enabled_flag", 1) ) + self:append( field.ubits("strong_intra_smoothing_enabled_flag", 1) ) + local f_vui_parameters_present_flag = self:append( field.ubits("vui_parameters_present_flag", 1) ) + + if f_vui_parameters_present_flag.value ~= 0 then + --parse_vui_parameters + --gst_h265_parse_vui_parameters (sps, &nr) + end +--]] + + + --[[ + + READ_UINT8 (&nr, sps->sps_extension_flag, 1); + + /* calculate ChromaArrayType */ + if (sps->separate_colour_plane_flag) + sps->chroma_array_type = 0; + else + sps->chroma_array_type = sps->chroma_format_idc; + + /* Calculate width and height */ + sps->width = sps->pic_width_in_luma_samples; + sps->height = sps->pic_height_in_luma_samples; + if (sps->width < 0 || sps->height < 0) { + GST_WARNING ("invalid width/height in SPS"); + goto error; + } + sps->fps_num = 0; + sps->fps_den = 1; + + if (vui && vui->timing_info_present_flag) { + /* derive framerate for progressive stream if the pic_struct + * syntax element is not present in picture timing SEI messages */ + /* Fixme: handle other cases also */ + if (parse_vui_params && vui->timing_info_present_flag + && !vui->field_seq_flag && !vui->frame_field_info_present_flag) { + sps->fps_num = vui->time_scale; + sps->fps_den = vui->num_units_in_tick; + printf("framerate %d/%d", sps->fps_num, sps->fps_den); + } + } else { + printf("No VUI, unknown framerate"); + } + --]] + + end) + + return f +end + +local function field_pps(ebsp_len, nal) +end + +local function field_nalu(h265, index, pos, raw_data, raw_data_len, ebsp_len) + + local nal = nal_t.new() + h265.nal = nal + + local f_nalu = field.list("NALU", raw_data_len, function(self, ba) + local pos_start = ba:position() + + self:append( field.callback("SYNTAX", function(self, ba) + if ba:peek_uint24() == 0x10000 then + return ba:read_uint24(), 3 + elseif ba:peek_uint32() == 0x1000000 then + return ba:read_uint32(), 4 + end + return nil + end, function(self) + return string.format("%s: 0x%X", self.name, self.value) + end, function(self) + return string.format(" S:0x%X ", self.value) + end) ) + + local f_nalu_header = self:append( field_nalu_header() ) + + local ebsp_len = raw_data_len - (ba:position()-pos_start) + + self:append( field.select("EBSP", ebsp_len, function() + local t = f_nalu_header.nalu_type + + nal.nal_unit_type = t + nal.nuh_layer_id = f_nalu_header.nuh_layer_id + nal.temporal_id = f_nalu_header.temporal_id + + if t == NALU_TYPE.VPS_NUT then return field_vps(ebsp_len, nal) end + if t == NALU_TYPE.SPS_NUT then return field_sps(ebsp_len, nal) end + if t == NALU_TYPE.PPS_NUT then return field_pps(ebsp_len, nal) end + + end, nil, nil) ) + + end, function(self) + --NALU cb_desc + local brief = self:get_child_brief() + local bmp_flag = "✓" + if nil == self.bmp then + bmp_flag = " " --"✗" + end + return string.format("[%d] %s %s%s POS:%u LEN:%d", index, bmp_flag, self.name, brief, pos, raw_data_len) + end) + + f_nalu.cb_click = function(self) + if nil == self.bmp then + bi.clear_bmp() + return + end + local bmp = self.bmp + bi.draw_bmp(bmp.data, bmp.w, bmp.h) + + --bi.save_bmp(string.format("%s/d_%d.bmp", f_nalu.index), bi.get_tmp_dir(), bmp.data, bmp.w, bmp.h) + end + + --field nalu dump self data + f_nalu:set_raw_data(raw_data) + return f_nalu +end + +local nalu_syntax24 = string.pack("I3", 0x10000) +local nalu_syntax32 = string.pack("I4", 0x1000000) + +local function find_nalu_syntax(ba) + local i24_pos = ba:search(nalu_syntax24, 3) + if i24_pos < 0 then + return -1 + end + + if i24_pos > ba:position() and ba:cmp(i24_pos-1, nalu_syntax32, 4) == 0 then + --start with 00 00 00 01 + return i24_pos-1, 4 + end + + --start with 00 00 01 + return i24_pos, 3 +end + +local function find_nalu(ba) + + local data = nil + + local old_pos = ba:position() + + --find first syntax + local pos_syntax, nbyte = find_nalu_syntax(ba) + if pos_syntax < 0 then + return nil + end + + local pos = ba:position() + if pos_syntax > pos then + --skip n bytes + ba:read_bytes(pos_syntax - pos) + end + + if nbyte == 3 then + data = string.pack("I3", ba:read_uint24()) + else + data = string.pack("I4", ba:read_uint32()) + end + + local pos_next_syntax = find_nalu_syntax(ba) + + local ebsp_len = 0 + if pos_next_syntax < 0 then + ebsp_len = ba:length() + elseif pos_next_syntax > 0 then + ebsp_len = pos_next_syntax - ba:position() + end + + if ebsp_len > 0 then + local ebsp = ba:read_bytes(ebsp_len) + local _, rbsp = bi.nalu_unshell(ebsp, ebsp_len) + data = data .. rbsp + end + + return data, pos_syntax, ebsp_len +end +local function decode_h265(ba, len) + local h265 = h265_t.new() + + local f_h265 = field.list("H265", len, function(self, ba) + + local index = 0 + while ba:length() > 0 do + local pos = ba:position() + + bi.set_progress_value(pos) + + local data, pos_syntax, ebsp_len = find_nalu(ba) + + if data then + table.insert(h265.nalus, data) + + --parse unshelled nalu data + local data_len = #data + local nalu_ba = byte_stream(data_len) + nalu_ba:set_data(data, data_len) + + --bi.log(string.format("nalu data len %d pos:%d", data_len, ba:position())) + local fnalu = field_nalu(h265, index, pos_syntax, data, data_len, ebsp_len) + self:append( fnalu, nalu_ba ) +--[[ + local t = h265.nal.nal_unit_type + if enable_decode_video(t) then + --decode data: start with nalu signature + local bmp_size, bmp, w, h = bi.decode_frame_to_bmp(AV_CODEC_ID.H265, data, data_len) + if bmp_size > 0 then + fnalu.bmp = { + data=bmp, + w = w, + h = h, + size = bmp_size + } + --bi.save_bmp(string.format("%s/d_%d.bmp", index), bi.get_tmp_dir(), bmp, w, h) + end + end +--]] + fnalu.index = index + + index = index + 1 + else + break + end + end + end) + + return f_h265 +end + +local function build_summary() +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "H265 video", + file_ext = "h265 265", + decode = decode_h265, + build_summary = build_summary, + + field_nalu_header = field_nalu_header, + field_sps = field_sps, + field_pps = field_pps, +} + +return codec diff --git a/src/scripts/codec/codec_mp3.lua b/src/scripts/codec/codec_mp3.lua new file mode 100644 index 0000000..06b6833 --- /dev/null +++ b/src/scripts/codec/codec_mp3.lua @@ -0,0 +1,473 @@ +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + +local swap_endian = true + +local MP3_LAYER = { + III = 1, + II = 2, + I = 3, +} + +local MP3_MPEG_VER = { + _2_5 = 0, + _2 = 2, + _1 = 3, +} + +local MP3_MPEG_VER_STR = { + [0] = "MPEG-2.5", + [1] = "reserved", + [2] = "MPEG-2", + [3] = "MPEG-1", +} + +local MP3_LAYER_STR = { + [0] = "reserved", + [1] = "Layer3", + [2] = "Layer2", + [3] = "Layer1", +} + +local MP3_PROTECT_STR = { + [0] = "CRC", + [1] = "NOCRC", +} + +local MP3_MODE_CHANNEL_STR = { + [0] = "Stereo", + [1] = "JointStereo", + [2] = "DualChannel", + [3] = "SingleChannel", +} + +local MP3_PADDING_STR = { + [0] = "not padded", + [1] = "padded with one extra slot", +} + +local SAMPLING_RATE_TABLE = { + --V2.5 _ V2 V1 + { 11025, 0, 22050, 44100}, --0x00 + { 12000, 0, 24000, 48000}, --0x01 + { 8000, 0, 16000, 32000}, --0x02 + { 0, 0, 0, 0}, --0x03 +} + +local BITRATE_TABLE = { + --L0 L3 L2 L1 L0 L3 L2 L1 + { 0, 0, 0, 0, 0, 0, 0, 0}, --0000 + { 0, 32, 32, 32, 0, 8, 8, 32}, --0001 + { 0, 40, 48, 64, 0, 16, 16, 48}, --0010 + { 0, 48, 56, 96, 0, 24, 24, 56}, --0011 + { 0, 56, 64, 128, 0, 32, 32, 64}, --0100 + { 0, 64, 80, 160, 0, 40, 40, 80}, --0101 + { 0, 80, 96, 192, 0, 48, 48, 96}, --0110 + { 0, 96, 112, 224, 0, 56, 56, 112}, --0111 + { 0, 112, 128, 256, 0, 64, 64, 128}, --1000 + { 0, 128, 160, 288, 0, 80, 80, 144}, --1001 + { 0, 160, 192, 320, 0, 96, 96, 160}, --1010 + { 0, 192, 224, 352, 0, 112, 112, 176}, --1011 + { 0, 224, 256, 384, 0, 128, 128, 192}, --1100 + { 0, 256, 320, 416, 0, 144, 144, 224}, --1101 + { 0, 320, 384, 448, 0, 160, 160, 256}, --1110 + { -1, -1, -1, -1, 0, -1, -1, -1}, --1111 +} + +local SAMPLER_PER_SECOND = { + --V2.5 _ V2 V1 + { 0, 0, 0, 0}, --00 - reserved + { 576, 0, 576, 1152}, --01 - Layer III + {1152, 0, 1152, 1152}, --10 - Layer II + { 384, 0, 384, 384}, --11 - Layer I +} + +local STR_ENCODE = { + [0] = "iso-8859-1", + [1] = "utf-16", + [2] = "utf-16be", + [3] = "utf-8", +} + +local PIC_TYPE_STR = { + [0] = "Other", + [1] = "32x32 pixels 'file icon' (PNG only)", + [2] = "Other file icon", + [3] = "Cover (front)", + [4] = "Cover (back)", + [5] = "Leaflet page", + [6] = "Media (e.g. lable side of CD)", + [7] = "Lead artist/lead performer/soloist", + [8] = "Artist/performer", + [9] = "Conductor", + [10] = "Band/Orchestra", + [11] = "Composer", + [12] = "Lyricist/text writer", + [13] = "Recording Location", + [14] = "During recording", + [15] = "During performance", + [16] = "Movie/video screen capture", + [17] = "A bright coloured fish", + [18] = "Illustration", + [19] = "Band/artist logotype", + [20] = "Publisher/Studio logotype", +} + +--https://github.com/biril/mp3-parser +local function get_frame_length(kbitrate, sampling_rate, padding, ver, layer) + local sample_length = SAMPLER_PER_SECOND[layer+1][ver+1] + local padding_size = 0 + if padding ~= 0 then + if layer == MP3_LAYER.I then + padding = 4 + else + padding = 1 + end + end + local byte_rate = kbitrate * 1000 / 8 + return math.floor((sample_length * byte_rate / sampling_rate) + padding_size) +end + +local id3v2_hdr_t = class("id3v2_hdr_t") +function id3v2_hdr_t:ctor() + self.flag_unsynchronisation = 0 + self.flag_ext_hdr = 0 + self.flag_exp_ind = 0 + self.flag_footer = 0 + self.size = 0 +end + +local mp3_summary_t = class("mp3_summary_t") +function mp3_summary_t:ctor() + self.ver = 0 + self.layer = 0 + self.channel = 0 + self.total_sample_length = 0 + self.sampling_rate = 0 + self.bitrate = 0 + self.duration = 0 + self.frames = 0 +end + +local mp3_summary = nil + +local function field_id3v2_header( id3v2_hdr ) + local f = field.list("header", nil, function(self) + self:append( field.string("id", 3, fh.str_desc, fh.mkbrief("ID") ) ) + self:append( field.uint16("ver", false, nil, fh.mkbrief("V") ) ) + self:append( field.bit_list("flags", 1, function(self, ba) + local f_unsynchronisation = self:append( field.ubits("unsynchronisation", 1, nil, fh.mkbrief("UNSYCH")) ) + local f_ext_hdr = self:append( field.ubits("extended_header", 1, nil, fh.mkbrief("EXT") ) ) + local f_exp_ind = self:append( field.ubits("experimental_indicator", 1, nil, fh.mkbrief("EXP") ) ) + local f_footer = self:append( field.ubits("footer_present", 1, nil, fh.mkbrief("FOOT") ) ) + self:append( field.ubits("reserved", 4) ) + + id3v2_hdr.flag_unsynchronisation = f_unsynchronisation.value + id3v2_hdr.flag_ext_hdr = f_ext_hdr.value + id3v2_hdr.flag_exp_ind = f_exp_ind.value + id3v2_hdr.flag_footer = f_footer.value + end, nil, fh.child_brief) ) + local f_size = self:append( field.uint32("size", swap_endian, nil, fh.mkbrief("L") ) ) + + id3v2_hdr.size = f_size.value + end, nil, fh.child_brief) + return f +end + +local function field_ext_header() + local f = field.list("ext_header", nil, function(self) + self:append( field.uint32("ext_hdr_size", swap_endian, nil, fh.mkbrief("L") ) ) + self:append( field.uint16("flags") ) + self:append( field.uint32("padding_size") ) + end) + return f +end + +local function field_tag_body_TXXX(len) + +end + +--https://id3.org/id3v2.3.0#Declared_ID3v2_frames +local function field_tag_body_APIC(len) + local f = field.list("APIC", len, function(self, ba) + local pos_start = ba:position() + + local f_encode = self:append( field.uint8("encode", fh.mkdesc(STR_ENCODE)) ) + local pos = ba:position() + local pos_end = ba:search( string.pack("I1", 0x00), 1 ) + local str_len = pos_end - pos + 1 + local f_mime = self:append( field.string("mime", str_len, fh.str_desc) ) + + self:append( field.uint8("pic_type", fh.mkdesc(PIC_TYPE_STR)) ) + + pos = ba:position() + local pos_str_end = ba:search( string.pack("I1", 0x00), 1 ) + str_len = pos_str_end - pos + 1 + + local encode = f_encode.value + if encode == 1 or encode == 2 then + self:append( field.string("desc", str_len) ) + elseif encode == 3 then + self:append( field.string("desc", str_len) ) + end + + pos = ba:position() + local pos_not0 = ba:skip0() + if pos_not0 > 0 then + self:append( field.string("skip", pos_not0 - pos) ) + end + + local remain = len - (ba:position() - pos_start) + local f_pic = self:append( field.string("pic", remain) ) + if string.find(f_mime.value, "image/jpeg") then + bi.draw_jpeg( f_pic:get_data(), remain ) + end + end) + return f +end + +local function field_tag(index) + local f = field.list(string.format("tag[%d]", index), nil, function(self) + + local f_id = field.string("id", 4, fh.str_desc, fh.mkbrief("ID") ) + local f_size = field.uint32("size", swap_endian, nil, fh.mkbrief("L") ) + local f_flag = field.uint16("flag") + + self:append( field.list("header", nil, function(self) + self:append( f_id ) + self:append( f_size ) + self:append( f_flag ) + end, nil, fh.child_brief)) + + self:append( field.select("body", f_size.value, function() + local id = f_id.value + local len = f_size.value + --if string.char(id:byte(1)) == 'T' then return field_tag_body_TXXX(len) end + if id == "APIC" then return field_tag_body_APIC(len) end + + return field.string("body", len) + end)) + end) + return f +end + +local function field_frame(index) + + local f = field.list(string.format("frame[%d]", index), nil, function(self, ba) + local old_pos = ba:position() + + local f_ver = field.ubits("ver", 2, fh.mkdesc(MP3_MPEG_VER_STR), fh.mkbrief("V", MP3_MPEG_VER_STR) ) + local f_layer = field.ubits("layer", 2, fh.mkdesc(MP3_LAYER_STR), fh.mkbrief("L", MP3_LAYER_STR) ) + local f_protect = field.ubits("protect", 1, fh.mkdesc(MP3_PROTECT_STR), fh.mkbrief("PROT", MP3_PROTECT_STR) ) + local f_bitrate_index = field.ubits("bitrate_index", 4) + local f_sampling_rate_index = field.ubits("sampling_rate_index", 2) + local f_padding = field.ubits("padding", 1, fh.mkdesc(MP3_PADDING_STR)) + local f_mode_chan = field.ubits("mode_chan", 2, fh.mkdesc(MP3_MODE_CHANNEL_STR), fh.mkbrief("CH", MP3_MODE_CHANNEL_STR) ) + + local f_audio_header = field.bit_list("header", 4, function(self) + + self:append( field.ubits("sync", 11) ) + self:append( f_ver ) + self:append( f_layer ) + self:append( f_protect ) + + self:append( f_bitrate_index ) + self:append( f_sampling_rate_index ) + self:append( f_padding ) + self:append( field.ubits("private", 1) ) + + self:append( f_mode_chan ) + + self:append( field.ubits("mode_ext", 2) ) + self:append( field.ubits("copyright", 1) ) + self:append( field.ubits("original", 1) ) + self:append( field.ubits("emphasis", 2) ) + + if f_protect.value == 0 then + self:append( field.ubits("crc_check", 16) ) + self.len = 6 + end + end, nil, fh.child_brief) + self:append( f_audio_header ) + + local layer = f_layer.value + local ver = f_ver.value + local br_index = layer + if ver == MP3_MPEG_VER._2 or ver == MP3_MPEG_VER._2_5 then + br_index = br_index + 4 + end + local sampling_rate = SAMPLING_RATE_TABLE[f_sampling_rate_index.value+1][ver+1] + if sampling_rate <= 0 then + return + end + + local kbitrate = BITRATE_TABLE[f_bitrate_index.value+1][br_index+1] + --bi.log(string.format("BITRATE_TABLE[%d][%d] = %d", f_bitrate_index.value+1, br_index+1, kbitrate)) + local frame_len = get_frame_length(kbitrate, sampling_rate, padding, ver, layer) + if frame_len <= 0 then + return + end + + local sample_length = SAMPLER_PER_SECOND[layer+1][ver+1] + local duration = sample_length / sampling_rate * 1000 + + if mp3_summary and sample_length > 0 then + mp3_summary.ver = ver + mp3_summary.layer = layer + mp3_summary.sampling_rate = sampling_rate + mp3_summary.bitrate = kbitrate + mp3_summary.total_sample_length = mp3_summary.total_sample_length + sample_length + end + + local cb_hdr_brief = function(self) + local child_brief = self:get_child_brief() + return string.format("%sSMP:%d %.2fms %d %dkbps", child_brief, sample_length, duration, sampling_rate, kbitrate) + end + f_audio_header.get_brief = cb_hdr_brief + + local data_len = frame_len - (ba:position() - old_pos) + if f_padding.value ~= 0 then + data_len = data_len + 1 + end + + --bi.log(string.format("data_len %s, frame_len:%s sampling_rate:%d", tostring(data_len), tostring(frame_len), sampling_rate)) + self:append( field.string("data", data_len) ) + end) + + f.cb_context_menu = function(self, menu) + + menu:add_action("Extract Frame", function() + bi.log(string.format("Extract Frame %s", self.name)) + end) + + menu:add_action("Extract Frame All", function() + bi.log(string.format("Extract Frame All %s", self.name)) + end) + + end + return f +end + +local function is_syn(ba) + if ba:length() < 2 then return false end + + local syn = ba:peek_ubits(11) + if syn == 0x7FF then + return true + end + + return false +end + +local function decode_mp3( ba, len ) + mp3_summary = mp3_summary_t.new() + + local f_mp3 = field.list("MP3", len, function(self, ba) + + local id3v2_hdr = id3v2_hdr_t.new() + + local f_id3v2 = field.list("id3v2", nil, function(self, ba) + + self:append( field_id3v2_header(id3v2_hdr) ) + + if id3v2_hdr.flag_ext_hdr == 1 then + self:append( field_ext_header() ) + end + + local total_tag_size = id3v2_hdr.size + 10 + + --parse tag frame + local index = 0 + while ba:position() < total_tag_size do + if is_syn(ba) then + break + end + + index = index + 1 + self:append( field_tag(index) ) + + local pos = ba:position() + local pos_not0 = ba:skip0() + if pos_not0 > pos then + self:append( field.string("padding", pos_not0 - pos) ) + end + end + end) + + local id3 = ba:peek_uint24() + if id3 == 0x334449 then --ID3 + self:append( f_id3v2 ) + end + + local pos = ba:position() + + --search audio frame syn + local tmp_len = ba:length() + local tmp = ba:peek_bytes(tmp_len) + local tmp_ba = byte_stream(tmp_len) + tmp_ba:set_data(tmp, tmp_len) + while tmp_ba:length() > 1 do + local u16 = tmp_ba:peek_uint16() + if u16 == 0xFAFF or u16 == 0xFBFF then + if tmp_ba:position() > 0 then + self:append( field.string("skip", tmp_ba:position() ) ) + end + break + end + tmp_ba:read_uint8() + end + + --audio frames + index = 0 + while ba:length() > 0 do + index = index + 1 + + if not is_syn(ba) then + break + end + + self:append(field_frame(index)) + + pos = ba:position() + bi.set_progress_value(pos) + end + + pos = ba:position() + local remain = len - pos + if remain > 0 then + self:append( field.string("remain", remain) ) + end + + mp3_summary.frames = index + end) + + return f_mp3 +end + +local function build_summary() + if nil == mp3_summary then return end + + bi.append_summary("ver", MP3_MPEG_VER_STR[mp3_summary.ver]) + bi.append_summary("layer", MP3_LAYER_STR[mp3_summary.layer]) + bi.append_summary("channel", MP3_MODE_CHANNEL_STR[mp3_summary.channel]) + bi.append_summary("sample_rate", string.format("%s Hz", mp3_summary.sampling_rate)) + bi.append_summary("bitrate", string.format("%d kbps", mp3_summary.bitrate)) + bi.append_summary("frames", mp3_summary.frames) + + if mp3_summary.sampling_rate > 0 then + mp3_summary.duration = mp3_summary.total_sample_length/ mp3_summary.sampling_rate * 1000 + + local ms = mp3_summary.duration + bi.append_summary("duration", string.format("%s (%.3f secs)", helper.ms2time(ms), ms/1000)) + end +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_ext = "mp3", + decode = decode_mp3, + build_summary = build_summary, +} + +return codec diff --git a/src/scripts/codec/codec_mp4.lua b/src/scripts/codec/codec_mp4.lua new file mode 100644 index 0000000..325eac6 --- /dev/null +++ b/src/scripts/codec/codec_mp4.lua @@ -0,0 +1,1550 @@ +require("class") +local field = require("field") +local fh = require("field_helper") +local helper = require("helper") + +--reference +--https://xhelmboyx.tripod.com/formats/mp4-layout.txt +--https://www.cimarronsystems.com/wp-content/uploads/2017/04/Elements-of-the-H.264-VideoAAC-Audio-MP4-Movie-v2_0.pdf +--https://github.com/wlanjie/mp4 +local is_decode_h264 = true + +local pos_moov = 0 +local pos_mdat = 0 +local f_mdat = nil +local f_h264_frames = {} + +local function is_media_track(typ) + if typ == "vide" then return true end + if typ == "soun" then return true end + return false +end + +local mp4_stts_t = class("mp4_stts_t") +function mp4_stts_t:ctor() + self.entries = {} +end + +function mp4_stts_t:add_entry( sample_count, sample_delta ) + table.insert(self.entries, { + sample_count = sample_count, + sample_delta = sample_delta, + }) +end + +local mp4_stsc_t = class("mp4_stsc_t") +function mp4_stsc_t:ctor() + self.entries = {} +end + +function mp4_stsc_t:add_entry( first_chunk, sample_per_chunk, sample_description_index) + table.insert(self.entries, { + first_chunk = first_chunk, + sample_per_chunk = sample_per_chunk, + sample_description_index = sample_description_index + }) +end + +function mp4_stsc_t:chunk_count() + return #self.entries +end + +function mp4_stsc_t:sum_sample() + local sum = 0 + for _, o in ipairs(self.entries) do + sum = sum + o.sample_per_chunk + end + return sum +end + +local mp4_stsz_t = class("mp4_stsz_t") +function mp4_stsz_t:ctor() + self.sample_sizes = {} +end + +function mp4_stsz_t:add_size( size ) + table.insert(self.sample_sizes, size) +end + +local mp4_stco_t = class("mp4_stco_t") +function mp4_stco_t:ctor() + self.offsets = {} +end + +function mp4_stco_t:add_offset( offset ) + table.insert(self.offsets, offset ) +end + +local mp4_ctts_t = class("mp4_ctts_t") +function mp4_ctts_t:ctor() + self.entries = {} +end + +function mp4_ctts_t:add_entry( sample_count, sample_offset ) + table.insert(self.entries, { + sample_count = sample_count, + sample_offset = sample_offset + }) +end + +local mp4_elst_t = class("mp4_elst_t") +function mp4_elst_t:ctor() + self.entries = {} +end + +function mp4_elst_t:add_entry( segment_duration, media_time, media_rate_integer, media_rate_fraction ) + table.insert(self.entries, { + segment_duration = segment_duration, + media_time = media_time, + media_rate_integer = media_rate_integer, + media_rate_fraction = media_rate_fraction + }) +end + +local mp4_frame_t = class("mp4_frame_t") +function mp4_frame_t:ctor() + self.index = 0 + self.pts = 0 + self.dts = 0 + self.cts_delta = 0 + self.duration = 0 + self.offset = 0 + self.size = 0 + + self.ichunk = 0 +end + +local mp4_avcc_t = class("mp4_avcc_t") +function mp4_avcc_t:ctor() + self.length_size_minus_one = 3 + self.arr_sps = {} --0x00 00 01 ... + self.arr_pps = {} --0x00 00 01 ... +end + +local mp4_summary_track_t = class("mp4_summary_track_t") +function mp4_summary_track_t:ctor() + self.id = 0 + self.type = "" + self.time_scale = 0 + self.frame_count = 0 + self.duration = 0 + self.fps = 0 + + self.frame_total_size = 0 + self.bitrate = 0 + + self.codec = nil --codec type, avcC + + --video + self.width = 0 + self.height = 0 + self.avcc = nil + + --audio + self.channel_count = 0 + self.sample_size = 0 + + self.stts = mp4_stts_t.new() + self.stsc = mp4_stsc_t.new() + self.stsz = mp4_stsz_t.new() + self.stco = mp4_stco_t.new() + self.ctts = mp4_ctts_t.new() + self.elst = mp4_elst_t.new() + + self.frames = {} --mp4_frame_t +end + +function mp4_summary_track_t:calc_dts(iframe) + + local sum_count = 0 + local sum_delta = 0 + local dts = 0 + for i, entry in ipairs(self.stts.entries) do + local count = entry.sample_count + local delta = entry.sample_delta + + sum_count = sum_count + count + sum_delta = sum_delta + count * delta + + if iframe <= sum_count then + dts = sum_delta - (sum_count - iframe + 1) * delta + break + end + end + return dts +end + +function mp4_summary_track_t:calc_duration(iframe) + local sum_count = 0 + local duration = 0 + for i, entry in ipairs(self.stts.entries) do + local count = entry.sample_count + local delta = entry.sample_delta + + sum_count = sum_count + count + if iframe <= sum_count then + duration = delta + break + end + end + return duration +end + +function mp4_summary_track_t:calc_cts_delta(iframe) + local cts_delta = 0 + + local sum_count = 0 + local sum_offset = 0 + for i, entry in ipairs(self.ctts.entries) do + local count = entry.sample_count + local offset = entry.sample_offset + + sum_count = sum_count + count + if iframe <= sum_count then + cts_delta = offset + break + end + end + + return cts_delta +end + +function mp4_summary_track_t:frame_to_chunk_index(iframe) + local ichunk = 1 + local sum_count = 0 + local last_first = 0 + local last_per_chunk = 0 + local first_iframe = 0 + + for i, entry in ipairs(self.stsc.entries) do + local first_chunk = entry.first_chunk + local sample_per_chunk = entry.sample_per_chunk + first_iframe = sum_count + 1 + + sum_count = sum_count + (first_chunk - last_first) * last_per_chunk + + if iframe <= sum_count then + local nchunk = math.floor((iframe - first_iframe) / last_per_chunk) + ichunk = last_first + nchunk + return ichunk + end + + if iframe <= (sum_count + sample_per_chunk) then + ichunk = first_chunk + return ichunk + end + + last_first = first_chunk + last_per_chunk = sample_per_chunk + end + return nil +end + +function mp4_summary_track_t:build_frames() + local last_ichunk = 0 + local last_frame = nil + local nchunk = self.stsc:chunk_count() + + local chunk_sum_sample = self.stsc:sum_sample() + local ioffset_start = chunk_sum_sample - nchunk + local noffsets = #self.stco.offsets + + self.frame_total_size = 0 + + for i=1, self.frame_count do + local frame = mp4_frame_t.new() + + frame.index = i + frame.type = self.type + frame.cts_delta = self:calc_cts_delta(i) + frame.duration = self:calc_duration(i) + frame.dts = self:calc_dts(i) + frame.pts = frame.dts + frame.cts_delta + + frame.size = self.stsz.sample_sizes[i] + + self.frame_total_size = self.frame_total_size + frame.size + + local ioffsets = 0 + local ichunk = self:frame_to_chunk_index(i) + if nil ~= ichunk then + --calc offset + if last_ichunk ~= ichunk then + frame.offset = self.stco.offsets[ichunk] + else + frame.offset = last_frame.offset + last_frame.size + end + else + ioffsets = i - ioffset_start + + if ioffsets <= noffsets then + frame.offset = self.stco.offsets[ioffsets] + else + frame.offset = last_frame.offset + last_frame.size + end + end + frame.ichunk = ichunk +-- bi.log(string.format("%s[%d].offset = %s itrunk:%s offset:%s/%s" +-- , self.type, i, tostring(frame.offset), tostring(ichunk) +-- , ioffsets, noffsets +-- )) + + last_ichunk = ichunk + last_frame = frame + + table.insert( self.frames, frame ) + end + return self.frames +end + +local mp4_summary_t = class("mp4_summary_t") +function mp4_summary_t:ctor() + self.time_scale = 0 + self.duration = 0 + + self.tracks = {} +end + +function mp4_summary_t:last_track() + local n = #self.tracks + if n <= 0 then return nil end + return self.tracks[n] +end + +local func_box = nil + +local mp4_summary = nil + +local cb_desc_time = function(self) + if self.value <= 0 then + return string.format("%s %s:%d", self.type, self.name, 0) + end + + --year - 66 (in seconds since midnight, January 1, 1904) + --local k1904_1970s = (66 * 365 + 17) * 24 * 60 * 60 + local k1904_1970s = 2082844800 + local sec = self.value - k1904_1970s + local str = os.date("%Y-%m-%d %H:%M:%S", sec) + return string.format("%s %s:%d %s", self.type, self.name, sec, str) +end + +local cb_desc_duration = function(self, timescale) + return string.format("%s %s:%d (%s)", self.type, self.name, self.value, helper.ms2time(self.value, timescale)) +end + +local cb_desc_resolution = function(self) + return string.format("%s %s:%d (%d)", self.type, self.name, self.value, self.value >> 16) +end + +local get_summary_codec = function(codec_type) + if codec_type == "avcC" then return "avcC (h264)" end + return codec_type +end + +local swap_endian = true +local peek_box_header = function( ba ) + local data = ba:peek_bytes(8) + local ba_hdr = byte_stream(8) + + ba_hdr:set_data(data, 8) + local size = ba_hdr:read_uint32(swap_endian) + local name = ba_hdr:read_bytes(4) + return size, name +end + +local field_box_size = function() + local f = field.uint32("size", swap_endian) + return f +end +local field_box_type = function() + local f = field.string("type", 4, fh.str_desc) + return f +end + +local field_unknown = function() + local f_len = field_box_size() + local f_type = field_box_type() + + local f = field.list("unknown", nil, function(self, ba) + local pos = ba:position() + self:append( f_len ) + self:append( f_type ) + + bi.log(string.format("unknown type %s %d", f_type.value, f_len.value)) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end, function(self) + return string.format("unknown(%s) len:%d", f_type.value, f_len.value) + end) + return f +end + +local append_box = function(flist, ba, pos_start, flen) + local remain = flen - (ba:position() - pos_start) + while remain > 0 do + local size, name = peek_box_header(ba) + + local func = func_box[name] or field_unknown + local f_box = func() + flist:append( f_box ) + + remain = flen - (ba:position() - pos_start) + end +end + +local field_ftyp = function() + local f = field.list("ftyp FileTypeBox", nil, function(self, ba) + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + self:append( field.string("major_brand", 4, fh.str_desc, fh.mkbrief("MAJOR") ) ) + self:append( field.int32("minor_version", swap_endian, nil, fh.mkbrief("V") ) ) + + local nremain = f_len.value - 16 + local n = nremain / 4 + for i=1, n do + self:append( field.string("compatible_brand", 4, fh.str_desc, fh.mkbrief("BRAND") ) ) + end + end) + return f +end + +local field_mdat = function() + + local f_len = field_box_size() + local f = field.list("mdat MediaDataBox", nil, function(self, ba) + local pos = ba:position() + pos_mdat = pos + self:append( f_len ) + local f_type = self:append( field_box_type() ) + + if f_len.value == 1 then + local f_large_size = self:append( field.uint64("large_size", swap_endian) ) + f_len.value = f_large_size.value + --bi.log(string.format("large_size %d 0x%x", f_len.value, f_len.value)) + end + + local tk_video = nil + local tk_audio = nil + --build_frames + local tmp_frames = {} + if mp4_summary then + for i, tk in ipairs(mp4_summary.tracks) do + if is_media_track(tk.type) then + if nil == tk_video and tk.type == "vide" then + tk_video = tk + end + if nil == tk_audio and tk.type == "soun" then + tk_audio = tk + end + + local frames = tk:build_frames() + for _, frame in ipairs(frames) do + table.insert( tmp_frames, { + index = frame.index, + type = frame.type, + offset = frame.offset, + size = frame.size, + }) + end + end + end + end + + local total = #tmp_frames + bi.set_progress_max(total) + if total > 0 then + table.sort( tmp_frames, function(a, b) return a.offset < b.offset end ) + end + + local cb_desc_frame = function(frame) + local cb = function(self) + return string.format("%s[%d] [%d,%d) len:%d" + , frame.type, frame.index, frame.offset, frame.offset+frame.size, frame.size) + end + return cb + end + + local cb_desc_h264 = function(frame) + local cb = function(self) + local bmp_flag = "✓" + if nil == self.ffh then + bmp_flag = " " --"✗" + end + local brief = "" + if self.get_child_brief then + brief = self:get_child_brief() + end + return string.format("%s[%d]%s[%d,%d) len:%d %s" + , frame.type, frame.index, bmp_flag, frame.offset, frame.offset+frame.size, frame.size, brief) + end + return cb + end + + for tmp_index, frame in ipairs(tmp_frames) do +-- bi.log(string.format( "[%d]frame[%s][%d] size %d", tmp_index, frame.type, frame.index, frame.size)) + local pos = ba:position() + if frame.offset > pos then + bi.log(string.format("error: unknwon frame offset index:%d %s [%d,%d] %d", frame.index, frame.type, frame.offset, pos, frame.size)) + self:append( field.string("unknown", frame.offset - pos ) ) + elseif frame.offset < pos then + bi.log(string.format("error: %s[%d].offset < pos, offset:[%d,%d]", frame.type, frame.index, frame.offset, pos)) + break + end + + if mp4_summary then + local is_h264 = frame.type == "vide" and tk_video.codec == "avcC" + if is_h264 then + + local codec_h264 = helper.get_codec("h264") + --length_size_minus_one + local nalu_len = tk_video.avcc.length_size_minus_one + 1 + + local f_frame_h264 = field.list("frame", frame.size, function(self, ba) + local pos = ba:position() + + local nalus = {} + local f_nalu_len = self:append(field.uint32("nalu_len", swap_endian)) + local nalu_data = ba:peek_bytes(f_nalu_len.value) + self:append( codec_h264.field_nalu_header(), ba ) + self:append( field.string("ebsp", f_nalu_len.value-1) ) + + table.insert(nalus, nalu_data) + + --if frame contains multiple nalus, SPS, PPS, SEI & IDR, need decode IDR frame + local remain = frame.size - (ba:position() - pos) + while remain > 0 do + f_nalu_len = self:append(field.uint32("nalu_len", swap_endian)) + nalu_data = ba:peek_bytes(f_nalu_len.value) + self:append( codec_h264.field_nalu_header(), ba ) + self:append( field.string("ebsp", f_nalu_len.value-1) ) + remain = frame.size - (ba:position() - pos) + + table.insert(nalus, nalu_data) + end + + --decode nalu + if is_decode_h264 then + for _, nalu_data in ipairs(nalus) do + local nalu = string.pack("I3", 0x10000) .. nalu_data + self.ffh = bi.decode_avframe(AV_CODEC_ID.H264, nalu, #nalu) + end + if nil ~= self.ffh then + self.ih264 = #f_h264_frames + end + end + end, cb_desc_h264(frame)) + + f_frame_h264.cb_click = function(self) + bi.clear_bmp() + + if nil ~= self.ffh then + self.ffh:drawBmp() + end + end + + self:append( f_frame_h264 ) + + if f_frame_h264.ffh then + table.insert( f_h264_frames, f_frame_h264 ) + end + else + self:append( field.string("frame", frame.size, cb_desc_frame(frame)) ) + end + else + self:append( field.string("frame", frame.size, cb_desc_frame(frame)) ) + end + + bi.set_progress_value(tmp_index) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + + f_mdat = f + return f +end + +local field_mvhd = function() + + local f = field.list("mvhd MovieHeaderBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + local f_ver = self:append( field.uint8("version") ) + self:append( field.uint24("flags", swap_endian) ) + + if f_ver.value == 0 then + self:append( field.uint32("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint32("modification_time", swap_endian, cb_desc_time) ) + else + self:append( field.uint64("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint64("modification_time", swap_endian, cb_desc_time) ) + end + local f_time_scale = self:append( field.uint32("time_scale", swap_endian) ) + + local time_scale = f_time_scale.value + local cb_duration = function(self) + return cb_desc_duration(self, time_scale) + end + + local f_duration = nil + if f_ver.value == 0 then + f_duration = self:append( field.uint32("duration", swap_endian, cb_duration) ) + else + f_duration = self:append( field.uint64("duration", swap_endian, cb_duration) ) + end + + self:append( field.uint32("rate", swap_endian) ) + self:append( field.uint16("volume", swap_endian) ) + self:append( field.string("reserved", 10) ) + self:append( field.string("matrix", 36) ) + self:append( field.string("pre_defined", 24) ) + self:append( field.uint32("next_track_id", swap_endian) ) + + if mp4_summary then + mp4_summary.time_scale = time_scale + mp4_summary.duration = f_duration.value + end + end) + return f +end + +local field_tkhd = function() + local f = field.list("tkhd TrackHeaderBox", nil, function(self, ba) + local pos = ba:position() + + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + local f_ver = self:append( field.uint8("version") ) + + self:append( field.bit_array("flags", { + field.ubits("reversed", 20), + field.ubits("size_is_aspect_ratio", 1), + field.ubits("in_preivew", 1), + field.ubits("in_movie", 1), + field.ubits("enabled", 1), + })) + + if f_ver.value == 0 then + self:append( field.uint32("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint32("modification_time", swap_endian, cb_desc_time) ) + else + self:append( field.uint64("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint64("modification_time", swap_endian, cb_desc_time) ) + end + + self:append( field.uint32("track_id", swap_endian) ) + self:append( field.uint32("reserved", swap_endian) ) + + if f_ver.value == 0 then + self:append( field.uint32("duration", swap_endian, cb_desc_duration) ) + else + self:append( field.uint64("duration", swap_endian, cb_desc_duration) ) + end + + self:append( field.string("reversed", 8) ) + self:append( field.uint16("layer", swap_endian) ) + self:append( field.uint16("alternate_group", swap_endian)) + self:append( field.uint16("volume", swap_endian) ) + self:append( field.uint16("reversed", swap_endian) ) + self:append( field.string("matrix", 36) ) + + local f_width = self:append( field.uint32("width", swap_endian, cb_desc_resolution) ) + local f_height = self:append( field.uint32("height", swap_endian, cb_desc_resolution)) + + if mp4_summary then + local track = mp4_summary:last_track() + track.width = f_width.value >> 16 + track.height = f_height.value >> 16 + end + end) + return f +end + +local field_mdhd = function() + + local f = field.list("mdhd MediaHeaderBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local f_flags = self:append( field.uint8("flags") ) + local f_ver = self:append( field.uint24("version", swap_endian) ) + + if f_ver.value == 0 then + self:append( field.uint32("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint32("modification_time", swap_endian, cb_desc_time) ) + else + self:append( field.uint64("creation_time", swap_endian, cb_desc_time) ) + self:append( field.uint64("modification_time", swap_endian, cb_desc_time) ) + end + local f_time_scale = self:append( field.uint32("time_scale", swap_endian) ) + + local time_scale = f_time_scale.value + local cb_duration = function(self) + return cb_desc_duration(self, time_scale) + end + + local f_duration = nil + if f_ver.value == 0 then + f_duration = self:append( field.uint32("duration", swap_endian, cb_duration) ) + else + f_duration = self:append( field.uint64("duration", swap_endian, cb_duration) ) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("language", remain) ) + end + + if mp4_summary then + local track = mp4_summary:last_track() + track.time_scale = time_scale + track.duration = f_duration.value + end + + end) + return f +end + +local field_hdlr = function( save_handler ) + local f = field.list("hdlr HandlerReferenceBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + self:append( field.string("unknown", 4) ) + local f_handler_type = self:append( field.string("handler_type", 4, fh.str_desc) ) + self:append( field.string("unknwon", 12) ) + + local remain = f_len.value - (ba:position()-pos) + self:append( field.string("handler_name", remain, fh.str_desc) ) + + if save_handler and mp4_summary then + local track = mp4_summary:last_track() + track.type = f_handler_type.value + end + end) + return f +end + +local field_url = function() + + local f = field.list("url UrlBox", nil, function(self, ba) + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.bit_array("flags", { + field.ubits("unknown", 23), + field.ubits("media_data_location_is_defined_in_the_movie_box", 1), + })) + end) + return f + +end + +local field_dref = function() + local f = field.list("dref DataReferenceBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + self:append( field.uint32("entries_count", swap_endian) ) + self:append( field_url() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_dinf = function() + local f = field.list("dinf DataInformationBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field_dref() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_avc1 = function() + + local f = field.list("avc1", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.string("reversed", 6) ) + self:append( field.uint16("pps_count", swap_endian) ) + self:append( field.uint16("predefined1", swap_endian) ) + self:append( field.uint16("reserved", swap_endian) ) + self:append( field.string("predefined2", 12) ) + self:append( field.uint16("width", swap_endian) ) + self:append( field.uint16("height", swap_endian) ) + self:append( field.uint32("horiz_resolution", swap_endian) ) + self:append( field.uint32("vert_resolution", swap_endian) ) + self:append( field.uint32("reserved", swap_endian) ) + self:append( field.uint16("frame_count", swap_endian) ) + self:append( field.string("compressor_name", 32) ) + self:append( field.uint16("depth", swap_endian) ) + self:append( field.uint16("reserved", swap_endian) ) + + append_box(self, ba, pos, f_len.value) + end) + return f +end + +local field_avcC = function() + local f = field.list("avcC", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local track = nil + if mp4_summary then + track = mp4_summary:last_track() + track.codec = "avcC" + track.avcc = mp4_avcc_t.new() + end + + self:append( field.uint8("version") ) + self:append( field.uint8("profile") ) + self:append( field.uint8("profile_compatibility") ) + self:append( field.uint8("level") ) + local f_length_size = field.ubits("length_size_minus_one", 2, nil, fh.mkbrief()) + local f_sps_count = field.ubits("sps_count", 5, nil, fh.mkbrief()) + self:append( field.bit_list("flags", 2, function(self, ba) + self:append( field.ubits("reversed", 6) ) + self:append( f_length_size ) + self:append( field.ubits("reversed", 3) ) + self:append( f_sps_count ) + end)) + + if track then + track.avcc.length_size_minus_one = f_length_size.value + end + + local codec_h264 = helper.get_codec("h264") + for i=1, f_sps_count.value do + local f_sps_len = self:append(field.uint16("sps_len", swap_endian)) + local sps_data = ba:peek_bytes(f_sps_len.value) + self:append( codec_h264.field_sps( f_sps_len.value ) ) + + local sps_with_sig = string.pack("I3", 0x10000) .. sps_data + + table.insert(track.avcc.arr_sps, sps_with_sig) + + --bi.decode_frame_to_bmp(AV_CODEC_ID.H264, sps_with_sig, #sps_with_sig) + bi.decode_avframe(AV_CODEC_ID.H264, sps_with_sig, #sps_with_sig) + end + + local f_pps_count = self:append( field.uint8("pps_count") ) + for i=1, f_pps_count.value do + local f_pps_len = self:append(field.uint16("pps_len", swap_endian)) + local pps_data = ba:peek_bytes(f_pps_len.value) + self:append( codec_h264.field_pps( f_pps_len.value ) ) + + local pps_with_sig = string.pack("I3", 0x10000) .. pps_data + table.insert(track.avcc.arr_pps, pps_with_sig) + + --bi.decode_frame_to_bmp(AV_CODEC_ID.H264, pps_with_sig, #pps_with_sig) + bi.decode_avframe(AV_CODEC_ID.H264, pps_with_sig, #pps_with_sig) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_mp4a = function() + + local f = field.list("mp4a", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.string("reversed", 6) ) + self:append( field.uint16("data_reference_index", swap_endian) ) + local f_ver = self:append( field.uint16("version", swap_endian) ) + self:append( field.uint16("revision", swap_endian) ) + self:append( field.uint32("vendor", swap_endian) ) + local f_channel_count = self:append( field.uint16("channel_count", swap_endian) ) + local f_sample_size = self:append( field.uint16("sample_size", swap_endian) ) + self:append( field.uint16("compression_id", swap_endian) ) + self:append( field.uint16("packet_size", swap_endian) ) + local f_sample_rate = self:append( field.uint32("sample_rate", swap_endian, cb_desc_resolution) ) + + if f_ver.value == 1 then + self:append( field.uint32("v1_samples_per_packet", swap_endian) ) + self:append( field.uint32("v1_bytes_per_packet", swap_endian) ) + self:append( field.uint32("v1_bytes_per_frame", swap_endian) ) + self:append( field.uint32("v1_bytes_per_sample", swap_endian) ) + elseif f_ver.value == 2 then + self:append( field.uint32("v2_samples_per_packet", swap_endian) ) + self:append( field.uint64("v2_sample_rate64", swap_endian) ) + self:append( field.uint32("v2_channel_count", swap_endian) ) + self:append( field.uint32("v2_reseved", swap_endian) ) + self:append( field.uint32("v2_bits_per_channel", swap_endian) ) + self:append( field.uint32("v2_format_specific_flags", swap_endian) ) + self:append( field.uint32("v2_bytes_per_audio_packet", swap_endian) ) + self:append( field.uint32("v2_lpcm_frames_per_audio_packet", swap_endian) ) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + + if mp4_summary then + local track = mp4_summary:last_track() + track.channel_count = f_channel_count.value + track.sample_size = f_sample_size.value + end + + end) + return f +end + +local field_stsd = function() + local f = field.list("stsd SampleDescriptionBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + self:append( field.uint32("entries_count", swap_endian) ) + + append_box(self, ba, pos, f_len.value) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_stts = function() + local f = field.list("stts DecodingTimeToSampleBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + local f_entries = self:append( field.uint32("entries_count", swap_endian) ) + + for i=1, f_entries.value do + self:append( field.list(string.format("entry[%d]", i), nil, function(self, ba) + local f_sample_count = self:append( field.uint32("sample_count", swap_endian, nil, fh.mkbrief("sample_count") ) ) + local f_sample_delta = self:append( field.uint32("sample_delta", swap_endian, nil, fh.mkbrief("sample_delta") ) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.stts:add_entry( f_sample_count.value, f_sample_delta.value) + end + + end)) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + + + end) + return f +end + +local field_stsc = function() + local f = field.list("stsc SampleToChunkBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + local f_entries = self:append( field.uint32("entries_count", swap_endian) ) + + for i=1, f_entries.value do + self:append( field.list(string.format("entry[%d]", i), nil, function(self, ba) + local f_first_chunk = self:append( field.uint32("first_chunk", swap_endian, nil, fh.mkbrief() ) ) + local f_sample_per_chunk = self:append( field.uint32("sample_per_chunk", swap_endian, nil, fh.mkbrief() ) ) + local f_sample_di = self:append( field.uint32("sample_description_index", swap_endian, nil, fh.mkbrief() ) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.stsc:add_entry( f_first_chunk.value, f_sample_per_chunk.value, f_sample_di.value ) + end + end)) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_stco = function() + local f = field.list("stco ChunkOffsetBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + local f_offset_count = self:append( field.uint32("offset_count", swap_endian) ) + + for i=1, f_offset_count.value do + local f_offset = self:append( field.uint32(string.format("offset[%d/%d]", i, f_offset_count.value), swap_endian ) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.stco:add_offset( f_offset.value ) + end + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_stsz = function() + local f = field.list("stsz SampleSizeBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + self:append( field.uint32("sample_size") ) + local f_entries = self:append( field.uint32("sample_count", swap_endian) ) + + if f_entries.value > 1 then + for i=1, f_entries.value do + local f_sample_size = self:append( field.uint32(string.format("sample_size[%d/%d]", i, f_entries.value), swap_endian ) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.stsz:add_size( f_sample_size.value ) + end + end + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + + if mp4_summary then + local track = mp4_summary:last_track() + track.frame_count = f_entries.value + end + end) + return f +end + +local field_stss = function() + local f = field.list("stss SyncSampleTable", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local f_entries = self:append( field.uint32("entrie_count", swap_endian) ) + local f_iframe_count = self:append( field.uint32("iframe_count", swap_endian) ) + for i=1, f_iframe_count.value do + self:append( field.uint32(string.format("iframe[%d/%d]", i, f_iframe_count.value), swap_endian) ) + end + end) + return f +end + +local field_ctts = function() + local f = field.list("ctts CompositionTimeToSampleBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + local f_entries = self:append( field.uint32("entries_count", swap_endian) ) + + for i=1, f_entries.value do + self:append( field.list(string.format("entry[%d/%d]", i, f_entries.value), nil, function(self, ba) + local f_sample_count = self:append( field.uint32("sample_count", swap_endian, nil, fh.mkbrief() ) ) + local f_sample_offset = self:append( field.uint32("sample_offset", swap_endian, nil, fh.mkbrief() ) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.ctts:add_entry( f_sample_count.value, f_sample_offset.value ) + end + end)) + end + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local func_stbl = { + ["stsd"] = field_stsd, + ["stts"] = field_stts, + ["stsc"] = field_stsc, + ["stco"] = field_stco, + ["stsz"] = field_stsz, + ["stss"] = field_stss, + ["ctts"] = field_ctts, +} + +local field_stbl = function() + local f = field.list("stbl SampleToGroupBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + while ba:length() > 0 do + + local remain = f_len.value - (ba:position()-pos) + if remain <= 0 then + break + end + + local size, name = peek_box_header(ba) + + local fst = func_stbl[name] + if nil ~= fst then + self:append( fst() ) + else + self:append( field_unknown() ) + end + end +--[[ + self:append( field_stsd() ) + self:append( field_stts() ) + self:append( field_stsc() ) + self:append( field_stco() ) + self:append( field_stsz() ) + self:append( field_stss() ) --sound track has no stss +--]] + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_vmhd = function() + local f = field.list("vmhd VideoMediaHeaderBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_smhd = function() + local f = field.list("smhd SoundMediaHeaderBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + + +local field_minf = function() + local f = field.list("minf MediaInformationBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + append_box(self, ba, pos, f_len.value) + end) + return f +end + +local field_mdia = function() + local f = field.list("mdia Mediabox", nil, function(self, ba) + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field_mdhd() ) + self:append( field_hdlr(true) ) + self:append( field_minf() ) + end) + return f +end + +local field_udta = function() + local f = field.list("udta UserdataBox", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + end) + return f +end + +local field_elst = function() + local f = field.list("elst EditListAtoms", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field.uint8("version") ) + self:append( field.uint24("flags") ) + local f_entries = self:append( field.uint32("entries_count", swap_endian) ) + + for i=1, f_entries.value do + self:append( field.list(string.format("entry[%d]", i), nil, function(self, ba) + local f_seg_dur = self:append( field.uint32("segment_duration", swap_endian, nil, fh.mkbrief("duration")) ) + local f_media_time = self:append( field.uint32("media_time", swap_endian, nil, fh.mkbrief("time")) ) + local f_rate_i = self:append( field.uint16("media_rate_integer", swap_endian, nil, fh.mkbrief("integer"))) + local f_rate_f = self:append( field.uint16("media_rate_fraction", swap_endian, nil, fh.mkbrief("fraction")) ) + + if mp4_summary then + local track = mp4_summary:last_track() + track.elst:add_entry( f_seg_dur.value, f_media_time.value, f_rate_i.value, f_rate_f.value ) + end + end)) + end + + end) + return f +end + +local field_edts = function() + local f = field.list("edts Editatoms", nil, function(self, ba) + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + self:append( field_elst() ) + end) + return f +end + + +local field_trak = function() + + local f = field.list("trak TrackBox", nil, function(self, ba) + + if mp4_summary then + local track = mp4_summary_track_t.new() + table.insert(mp4_summary.tracks, track) + track.id = #mp4_summary.tracks + end + + local pos = ba:position() + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + append_box(self, ba, pos, f_len.value) + end) + return f +end + +local field_moov = function() + + local f = field.list("moov MovieBox", nil, function(self, ba) + local pos = ba:position() + pos_moov = pos + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + append_box(self, ba, pos, f_len.value) + + end) + return f +end + +local field_free = function() + + local f = field.list("free", nil, function(self, ba) + local pos = ba:position() + + local f_len = self:append( field_box_size() ) + local f_type = self:append( field_box_type() ) + + local remain = f_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("unknown", remain) ) + end + + end) + return f +end + +if nil == func_box then + func_box = { + ["ftyp"] = field_ftyp, + ["mdat"] = field_mdat, + ["moov"] = field_moov, + ["free"] = field_free, + + ["mvhd"] = field_mvhd, + ["tkhd"] = field_tkhd, + ["mdhd"] = field_mdhd, + ["hdlr"] = field_hdlr, + ["url"] = field_url, + ["dref"] = field_dref, + ["dinf"] = field_dinf, + ["stbl"] = field_stbl, + ["vmhd"] = field_vmhd, + ["smhd"] = field_smhd, + ["minf"] = field_minf, + ["mdia"] = field_mdia, + ["udta"] = field_udta, + ["elst"] = field_elst, + ["edts"] = field_edts, + ["trak"] = field_trak, + + ["avc1"] = field_avc1, + ["avcC"] = field_avcC, + ["mp4a"] = field_mp4a, + } +end + +local function decode_mp4( ba, len ) + f_h264_frames = {} + mp4_summary = mp4_summary_t.new() + + pos_mdat = 0 + pos_moov = 0 + f_mdat = nil + + local pos_start = ba:position() + local fmp4 = field.list("MP4", len, function(self, ba) + while ba:length() > 0 do + local pos = ba:position() + if pos - pos_start > len then + break + end + + local size, name = peek_box_header(ba) + append_box(self, ba, pos, size) + end + + if f_mdat and (pos_moov > pos_mdat) then + --parse mdat again + local pos = ba:position() + local back = pos - pos_mdat + ba:back_pos(back) + f_mdat:read(ba) + end + end) + + return fmp4 +end + +local function build_summary() + if nil == mp4_summary then return end + + local str_duration = helper.ms2time(mp4_summary.duration, mp4_summary.time_scale) + + bi.append_summary("duration", string.format("%d (%s)", mp4_summary.duration, str_duration)) + + local tk_video = nil + local tk_audio = nil + + local mtk = {} + for i, tk in ipairs(mp4_summary.tracks) do + if is_media_track(tk.type) then + table.insert(mtk, tk) + end + end + + for i, tk in ipairs(mtk) do + local pre = string.format("trk[%d]", i) + + if tk.type == "vide" then + tk_video = tk + elseif tk.type == "soun" then + tk_audio = tk + end + + bi.append_summary(string.format("%s.type", pre), tk.type) + + if tk.codec then + bi.append_summary(string.format("%s.codec", pre), get_summary_codec(tk.codec)) + end + + if tk.width ~= 0 then + bi.append_summary(string.format("%s.resolution", pre), string.format("%dx%d", tk.width, tk.height)) + end + + bi.append_summary(string.format("%s.time_scale", pre), tostring(tk.time_scale)) + str_duration = helper.ms2time(tk.duration, tk.time_scale) + bi.append_summary(string.format("%s.duration", pre), string.format("%d (%s)", tk.duration, str_duration)) + + tk.fps = tk.frame_count / (tk.duration/tk.time_scale) + bi.append_summary(string.format("%s.fps", pre), string.format("%.2f", tk.fps)) + bi.append_summary(string.format("%s.frame_count", pre), tostring(tk.frame_count)) + + local secs = tk.duration / tk.time_scale + tk.bitrate = tk.frame_total_size * 8 / secs + bi.append_summary(string.format("%s.bitrate", pre), string.format("%.2f Kbps", tk.bitrate/1000)) + + if tk.channel_count ~= 0 then + bi.append_summary(string.format("%s.channel_count", pre), tostring(tk.channel_count)) + bi.append_summary(string.format("%s.sample_size", pre), tostring(tk.sample_size)) + end + end + + --video/audio frames + local tw_video = bi.create_summary_table("VideoFrame") + local tw_audio = bi.create_summary_table("AudioFrame") + + local tw_header = { "index", "type", "dts", "ms_dts", "pts", "ms_pts", "duration", "range", "len", "chunk" } + local tw_ncol = #tw_header + tw_video:set_column_count(tw_ncol) + tw_audio:set_column_count(tw_ncol) + + for i=1, tw_ncol do + tw_video:set_header(i-1, tw_header[i] ) + tw_audio:set_header(i-1, tw_header[i] ) + end + + for i, tk in ipairs(mtk) do + local nframe = #tk.frames + local tw = tw_video + if tk.type == "soun" then + tw = tw_audio + end + + for j, frame in ipairs(tk.frames) do + local ms_pts = frame.pts / tk.time_scale * 1000 + local ms_dts = frame.dts / tk.time_scale * 1000 + local ms_duration = frame.duration / tk.time_scale * 1000 + + tw:append_empty_row() + + tw:set_last_row_column( 0, string.format("%d/%d", j, nframe) ) + tw:set_last_row_column( 1, frame.type ) + tw:set_last_row_column( 2, tostring(frame.dts) ) + tw:set_last_row_column( 3, helper.ms2time(ms_dts) ) + tw:set_last_row_column( 4, tostring(frame.pts) ) + tw:set_last_row_column( 5, helper.ms2time(ms_pts) ) + tw:set_last_row_column( 6, string.format("%d %.2f", frame.duration, ms_duration) ) + tw:set_last_row_column( 7, string.format("[%d,%d)", frame.offset, frame.offset + frame.size) ) + tw:set_last_row_column( 8, tostring(frame.size) ) + tw:set_last_row_column( 9, tostring(frame.ichunk) ) + end + end + + local tmp_frames = {} + for i, tk in ipairs(mp4_summary.tracks) do + for j, frame in ipairs(tk.frames) do + table.insert( tmp_frames, { + ms_pts = frame.pts / tk.time_scale * 1000, + index = frame.index, + type = frame.type, + pts = frame.pts, + dts = frame.dts, + size = frame.size, + }) + end + end + + table.sort(tmp_frames, function(a, b) return a.ms_pts < b.ms_pts end) + + --timeline + local tw_timeline = bi.create_summary_table("Timeline") + local tw_timeline_header = { "ms_pts", "type", "index", "pts", "dts", "size", "type", "index", "pts", "dts", "size" } + local tw_timeline_ncol = #tw_timeline_header + tw_timeline:set_column_count(tw_timeline_ncol) + for i=1, tw_timeline_ncol do + tw_timeline:set_header(i-1, tw_timeline_header[i] ) + end + + local last_ms_pts = -1 + for i, frame in ipairs(tmp_frames) do + local tk = tk_video + local ioffset = 0 + if frame.type == "soun" then + tk = tk_audio + ioffset = 5 + end + + if frame.ms_pts ~= last_ms_pts then + last_ms_pts = frame.ms_pts + tw_timeline:append_empty_row() + tw_timeline:set_last_row_column( 0, helper.ms2time(frame.ms_pts) ) + end + + tw_timeline:set_last_row_column( ioffset+1, frame.type ) + tw_timeline:set_last_row_column( ioffset+2, string.format("%d/%d", frame.index, tk.frame_count) ) + tw_timeline:set_last_row_column( ioffset+3, tostring(frame.pts) ) + tw_timeline:set_last_row_column( ioffset+4, tostring(frame.dts) ) + tw_timeline:set_last_row_column( ioffset+5, tostring(frame.size) ) + end +end + +local function clear() + for _, f in ipairs(f_h264_frames) do + if f.ffh then + bi.ffmpeg_helper_free(f.ffh) + f.ffh = nil + end + end + f_h264_frames = {} +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_ext = "mp4", + decode = decode_mp4, + build_summary = build_summary, + clear = clear, +} + +return codec diff --git a/src/scripts/codec/codec_nrtp.lua b/src/scripts/codec/codec_nrtp.lua new file mode 100644 index 0000000..d4cbcc3 --- /dev/null +++ b/src/scripts/codec/codec_nrtp.lua @@ -0,0 +1,72 @@ +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + +--[[ + file format: + size: 4 bytes + rtp_hdr: 12 bytes + data: size - 12 +--]] + +local max_len = 0 + +local function decode_nrtp( ba, len ) + + bi.log("decode_nrtp") + + local mk_desc = function(index) + local cb = function(self) + return string.format("[%d] len %d", index, self.value) + end + return cb + end + + local codec_rtp = helper.get_codec("rtp") + + local swap_endian = false + local f_pcap = field.list("nrtp", len, function(self, ba) + local pos_start = ba:position() + + local index = 0 + while true do + local remain = len - (ba:position() - pos_start) + if remain <= 0 then break end + + local f_frame = field.list(string.format("[%d] rtp", index), nil, function(self, ba) + local f_len = self:append(field.uint32("len", swap_endian, nil, fh.mkbrief("data_len"))) + if f_len.value > max_len then + max_len = f_len.value + end + + local f_rtp_hdr = self:append(codec_rtp.field_rtp_hdr()) + + self:append(field.string("data", f_len.value-f_rtp_hdr.len)) + end) + self:append( f_frame ) + + index = index + 1 + end + end) + + return f_pcap +end + +local function clear() +end + +local function build_summary() + + bi.append_summary("max_len", max_len) +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "nrtp", + file_ext = "nrtp", + decode = decode_nrtp, + clear = clear, + build_summary = build_summary, +} + +return codec diff --git a/src/scripts/codec/codec_pcap.lua b/src/scripts/codec/codec_pcap.lua new file mode 100644 index 0000000..bce9c21 --- /dev/null +++ b/src/scripts/codec/codec_pcap.lua @@ -0,0 +1,1060 @@ +require("class") +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") +local dict_parser = require("pcap_port_parser") + +--https://github.com/pcapng/pcapng/blob/master/draft-ietf-opsawg-pcap.md +--https://github.com/pcapng/pcapng/blob/master/draft-ietf-opsawg-pcapng.md +--https://www.winpcap.org/ntar/draft/PCAP-DumpFileFormat.html + +local MAGIC_PCAP_LE = 0xA1B2C3D4 +local MAGIC_PCAP_BE = 0xD4C3B2A1 + +local MAGIC_PCAPNG = 0x0A0D0D0A + +local PCAP_LINK_TYPE = { + DLT_NULL = 0, --BSD loopback encapsulation + DLT_EN10MB = 1, --Ethernet (10Mb) + DLT_EN3MB = 2, --Experimental Ethernet (3Mb) + DLT_AX25 = 3, --Amateur Radio AX.25 + DLT_PRONET = 4, --Proteon ProNET Token Ring + DLT_CHAOS = 5, --Chaos + DLT_IEEE802 = 6, --802.5 Token Ring + DLT_ARCNET = 7, --ARCNET, with BSD-style header + DLT_SLIP = 8, --Serial Line IP + DLT_PPP = 9, --Point-to-point Protocol + DLT_FDDI = 10, --FDDI + DLT_LINUX_SLL = 113, + DLT_LTALK = 114, +} + +local PCAP_LINK_TYPE_STR = { + [0] = "BSD loopback devices", + [1] = "Ethernet, and Linux loopback devices", + [2] = "Experimental Ethernet (3Mb)", + [3] = "Amateur Radio AX.25", + [4] = "Proteon ProNET Token Ring", + [5] = "Chaos", + [6] = "802.5 Token Ring", + [7] = "ARCnet", + [8] = "SLIP", + [9] = "PPP", + [10] = "FDDI", + [100] = "LLC/SNAP-encapsulated ATM", + [101] = "raw IP, with no link", + [102] = "BSD/OS SLIP", + [103] = "BSD/OS PPP", + [104] = "Cisco HDLC", + [105] = "802.11", + [108] = "later OpenBSD loopback devices (with the AF_value in network byte order)", + [113] = "linux cooked capture", + [114] = "LocalTalk", +} + +local PCAPNG_BLOCK_TYPE = { + RESERVED = 0x00000000, + IDB = 0x00000001, --interface description block + PB = 0x00000002, --packet block + SPB = 0x00000003, --simple packet block + NRB = 0x00000004, --name resolution block + ISB = 0x00000005, --Interface Statistics Block + EPB = 0x00000006, --Enhanced Packet Block + ITB = 0x00000007, --IRIG Timestamp Block + AFDX_EIB = 0x00000008, --Arinc 429 in AFDX Encapsulation Information Block + SHB = 0x0A0D0D0A, --Section Header Block +-- 0x0A0D0A00-0x0A0D0AFF Reserved. +-- 0x000A0D0A-0xFF0A0D0A Reserved. +-- 0x000A0D0D-0xFF0A0D0D Reserved. +-- 0x0D0D0A00-0x0D0D0AFF Reserved. +} + +local PCAPNG_BLOCK_TYPE_STR = { + [PCAPNG_BLOCK_TYPE.RESERVED] = "reserved", + [PCAPNG_BLOCK_TYPE.IDB] = "interface description block", + [PCAPNG_BLOCK_TYPE.PB] = "packet block", + [PCAPNG_BLOCK_TYPE.SPB] = "simple packet block", + [PCAPNG_BLOCK_TYPE.NRB] = "name resolution block", + [PCAPNG_BLOCK_TYPE.ISB] = "interface statistics block", + [PCAPNG_BLOCK_TYPE.EPB] = "enhanced packet block", + [PCAPNG_BLOCK_TYPE.ITB] = "IRIG timestamp block", + [PCAPNG_BLOCK_TYPE.AFDX_EIB] = "Arinc 429 in AFDX Encapsulation Information Block", + [PCAPNG_BLOCK_TYPE.SHB] = "section header block", +} + +local ETHER_TYPE = { + PUP = 0x0200, --Xerox PUP + SPRITE = 0x0500, --Sprite + IP = 0x0800, --IP + ARP = 0x0806, --Address resolution + REVARP = 0x8035, --Reverse ARP + AT = 0x809B, --AppleTalk protocol + AARP = 0x80F3, --AppleTalk ARP + VLAN = 0x8100, --IEEE 802.1Q VLAN tagging + IPX = 0x8137, --IPX + IPV6 = 0x86dd, --IP protocol version 6 + LOOPBACK= 0x9000, --used to test interfaces +} + +local ETHER_TYPE_STR = { + [ETHER_TYPE.PUP] = "PUP", + [ETHER_TYPE.SPRITE] = "SPRITE", + [ETHER_TYPE.IP] = "IP", + [ETHER_TYPE.ARP] = "ARP", + [ETHER_TYPE.REVARP] = "RARP", + [ETHER_TYPE.AT] = "AT", + [ETHER_TYPE.AARP] = "AARP", + [ETHER_TYPE.VLAN] = "VLAN", + [ETHER_TYPE.IPX] = "IPX", + [ETHER_TYPE.IPV6] = "IPV6", + [ETHER_TYPE.LOOPBACK] = "LOOPBACK", +} + +local IPV4_PROTOCOL = { + ICMP = 0x01, + TCP = 0x06, + UDP = 0x11 +} + +local IPV4_PROTOCOL_STR = { + [IPV4_PROTOCOL.ICMP] = "ICMP", + [IPV4_PROTOCOL.TCP] = "TCP", + [IPV4_PROTOCOL.UDP] = "UDP", +} + +local function cb_desc_mac(self) + local s = self.value + local mac = string.format("%02X:%02X:%02X:%02X:%02X:%02X", s:byte(1), s:byte(2), s:byte(3), s:byte(4), s:byte(5), s:byte(6)) + return string.format("%s: %s", self.name, mac) +end + +local function cb_desc_ip(self) + return string.format("%s %s: %u %s", self.type, self.name, self.value, helper.n2ip(self.value)) +end + +local function mkdesc_micro_secs(secs) + local cb = function(self) + local ms = math.floor(self.value / 1000) + ms = secs * 1000 + ms + return string.format("%s %s: %u %s", self.type, self.name, self.value, helper.ms2date(ms)) + end + return cb +end + +local function mkbrief_micro_secs(secs) + local cb = function(self) + local ms = math.floor(self.value / 1000) + ms = secs * 1000 + ms + return string.format("%s ", helper.ms2date(ms)) + end + return cb +end + +local pcap_pkthdr_t = class("pcap_pkthdr_t") +function pcap_pkthdr_t:ctor() + self.time_secs = 0 + self.time_micro_secs = 0 + self.caplen = 0 + self.len = 0 +end + +local dlt_en10mb_t = class("dlt_en10mb_t") +function dlt_en10mb_t:ctor() + self.src_mac = nil + self.dst_mac = nil + self.ether_type = nil +end + + +local dlt_linux_sll_t = class("dlt_linux_sll_t") +function dlt_linux_sll_t:ctor() + self.pack_type = 0 + self.addr_type = 0 + self.addr_len = 0 + self.src_mac = nil + self.ether_type = 0 +end + +local ipv4_hdr_t = class("ipv4_hdr_t") +function ipv4_hdr_t:ctor() + self.ihl = 0 + self.tos = 0 + self.tos_len = 0 + self.id = 0 + self.flag_off = 0 + self.ttl = 0 + self.protocol = 0 + self.check = 0 + self.saddr = 0 + self.daddr = 0 +end + +local tcp_hdr_t = class("tcp_hdr_t") +function tcp_hdr_t:ctor() + + self.source = 0 + self.dest = 0 + self.seq = 0 + self.ack_seq = 0 + + self.flags = { + doff = 0, + res1 = 0, + res2 = 0, + urg = 0, + ack = 0, + psh = 0, + rst = 0, + syn = 0, + fin = 0, + } + + self.window = 0 + self.check = 0 + self.urg_ptr = 0 +end + +local udp_hdr_t = class("udp_hdr_t") +function udp_hdr_t:ctor() + self.source = 0 + self.dest = 0 + self.len = 0 + self.check = 0 +end + +local interface_t = class("interface_t") +function interface_t:ctor() + self.link_type = 0 +end + +local pcap_t = class("pcap_t") +function pcap_t:ctor() + self.interfaces = {} + self.frame_count = 0 +end + +function pcap_t:add_interface( interface ) + table.insert( self.interfaces, interface ) +end + +function pcap_t:get_interface( i ) + local n = #self.interfaces + if i < 1 or i > n then return nil end + return self.interfaces[i] +end + +local stream_frame_t = class("stream_frame_t") +function stream_frame_t:ctor() + self.field_frames = {} + self.ntcp = 0 + self.nudp = 0 +end + +function stream_frame_t:append( field_frame ) + table.insert( self.field_frames, field_frame ) + + local protocol = field_frame.stream_info.protocol + + if protocol == IPV4_PROTOCOL.TCP then + self.ntcp = self.ntcp + 1 + elseif protocol == IPV4_PROTOCOL.UDP then + self.nudp = self.nudp + 1 + end +end + +local dict_streams_t = class("dict_streams_t") +function dict_streams_t:ctor() + self.streams = {} + self.count = 0 +end + +function dict_streams_t:has( key ) + return nil ~= self.streams[key] +end + +function dict_streams_t:get( key ) + local s = self.streams[key] + if s then + return s + end + + s = stream_frame_t.new() + self.streams[key] = s + self.count = self.count + 1 + return s +end + + +local pcap = nil + +local f_pcap_file_header = nil + +local f_pcapng_shb = nil +local f_pcapng_idb = {} + +local dict_stream_tcp = nil --src -> dest +local dict_stream_tcp2 = nil --src <-> dest +local dict_stream_udp = nil +local dict_stream_udp2 = nil + +local hex_desc = function(self) + return string.format("%s %s: %d (0x%X)", self.type, self.name, self.value, self.value) +end + +local hex_brief = function(self) + return string.format("%s:0x%X ", self.name, self.value) +end + +local function field_pcap_file_header(swap_endian) + local f = field.list("pcap_file_header", nil, function(self, ba) + self:append(field.uint32("magic", swap_endian, hex_desc, hex_brief)) + self:append(field.uint16("version_major", swap_endian, nil, fh.mkbrief_v("major"))) + self:append(field.uint16("version_minor", swap_endian, nil, fh.mkbrief_v("minor"))) + self:append(field.uint32("thiszone", swap_endian)) + self:append(field.uint32("sigfigs", swap_endian)) + self:append(field.uint32("snaplen", swap_endian)) + local f_link_type = self:append(field.uint32("linktype", swap_endian, fh.mkdesc(PCAP_LINK_TYPE_STR), fh.mkbrief("LINK", PCAP_LINK_TYPE_STR))) + + local interface = interface_t.new() + interface.link_type = f_link_type.value + pcap:add_interface( interface ) + end) + + f_pcap_file_header = f + return f +end + +local function field_pcap_pkthdr(swap_endian, pcap_pkthdr) + local f = field.list("pcap_pkthdr", nil, function(self, ba) + local f_time_secs = self:append(field.uint32("time_secs", swap_endian)) + local f_time_micro_secs = self:append(field.uint32("time_micro_secs", swap_endian + , mkdesc_micro_secs(f_time_secs.value), mkbrief_micro_secs(f_time_secs.value))) + local f_caplen = self:append(field.uint32("caplen", swap_endian)) + local f_len = self:append(field.uint32("len", swap_endian)) + + pcap_pkthdr.time_secs = f_time_secs.value + pcap_pkthdr.time_micro_secs = f_time_micro_secs.value + pcap_pkthdr.caplen = f_caplen.value + pcap_pkthdr.len = f_len.value + end, nil, fh.child_brief) + return f +end + +local function field_link_type_dlt_en10mb(swap_endian, dlt_en10mb) + local f = field.list("ethernet", nil, function(self, ba) + local f_src_mac = self:append( field.string("src_mac", 6, cb_desc_mac) ) + local f_dst_mac = self:append( field.string("dst_mac", 6, cb_desc_mac) ) + local f_ether_type = self:append( field.uint16("ether_type", swap_endian, fh.mkdesc_x(ETHER_TYPE_STR), fh.mkbrief("", ETHER_TYPE_STR)) ) + + dlt_en10mb.src_mac = f_src_mac.value + dlt_en10mb.dst_mac = f_dst_mac.value + dlt_en10mb.ether_type = f_ether_type.value + + end, nil, fh.child_brief) + return f +end + +local function field_link_type_dlt_linux_sll(swap_endian, dlt_linux_sll) + local f = field.list("linux_cooked_capture", nil, function(self, ba) + local f_pack_type = self:append( field.int16("pack_type", swap_endian) ) + local f_addr_type = self:append( field.int16("addr_type", swap_endian) ) + local f_addr_len = self:append( field.int16("addr_len", swap_endian) ) + local f_src_mac = self:append( field.string("src_mac", 6, cb_desc_mac) ) + self:append( field.int16("unused", swap_endian) ) + local f_ether_type = self:append( field.uint16("ether_type", swap_endian, fh.mkdesc_x(ETHER_TYPE_STR), fh.mkbrief("", ETHER_TYPE_STR)) ) + + dlt_linux_sll.pack_type = f_pack_type.value + dlt_linux_sll.addr_type = f_addr_type.value + dlt_linux_sll.addr_len = f_addr_len.value + dlt_linux_sll.src_mac = f_src_mac.value + dlt_linux_sll.ether_type = f_ether_type.value + + end, nil, fh.child_brief) + return f +end + +local function field_ipv4_hdr(swap_endian, ipv4_hdr) + + local f = field.list("ip_hdr", nil, function(self, ba) + local f_ver = self:append( field.ubits("version", 4) ) + local f_ihl = self:append( field.ubits("ihl", 4) ) + + local f_tos = self:append( field.uint8("tos") ) + local f_tos_len = self:append( field.uint16("tos_len", swap_endian) ) + local f_id = self:append( field.uint16("id", swap_endian) ) + local f_flag_off = self:append( field.uint16("flag_off", swap_endian) ) + local f_ttl = self:append( field.uint8("ttl") ) + --local f_protocol = self:append( field.uint8("protocol", fh.mkdesc(IPV4_PROTOCOL_STR), fh.mkbrief("", IPV4_PROTOCOL_STR)) ) + local f_protocol = self:append( field.uint8("protocol", fh.mkdesc_x(IPV4_PROTOCOL_STR)) ) + local f_check = self:append( field.uint16("check", swap_endian) ) + local f_saddr = self:append( field.uint32("saddr", swap_endian, cb_desc_ip) ) + local f_daddr = self:append( field.uint32("daddr", swap_endian, cb_desc_ip) ) + + ipv4_hdr.ihl = f_ihl.value + ipv4_hdr.tos = f_tos.value + ipv4_hdr.tos_len = f_tos_len.value + ipv4_hdr.id = f_id.value + ipv4_hdr.flag_off = f_flag_off.value + ipv4_hdr.ttl = f_ttl.value + ipv4_hdr.protocol = f_protocol.value + ipv4_hdr.check = f_check.value + ipv4_hdr.saddr = f_saddr.value + ipv4_hdr.daddr = f_daddr.value + end, nil, fh.child_brief) + return f +end + +local function field_tcp_hdr(swap_endian, tcp_hdr) + local f = field.list("tcp_hdr", nil, function(self, ba) + local pos = ba:position() + local f_source = self:append( field.uint16("source", swap_endian) ) + local f_dest = self:append( field.uint16("dest", swap_endian) ) + local f_seq = self:append( field.uint32("seq", swap_endian) ) + local f_ack_seq = self:append( field.uint32("ack_seq", swap_endian) ) + + local f_doff = field.ubits("doff", 4, nil, fh.mkbrief_v("DOFF")) + local f_res1 = field.ubits("res1", 4) + local f_res2 = field.ubits("res2", 2) + local f_urg = field.ubits("urg", 1, nil, fh.mkbrief_v("U")) + local f_ack = field.ubits("ack", 1, nil, fh.mkbrief_v("A")) + local f_psh = field.ubits("psh", 1, nil, fh.mkbrief_v("P")) + local f_rst = field.ubits("rst", 1, nil, fh.mkbrief_v("R")) + local f_syn = field.ubits("syn", 1, nil, fh.mkbrief_v("S")) + local f_fin = field.ubits("fin", 1, nil, fh.mkbrief_v("F")) + self:append( field.bit_list("flags", 2, function(self, ba) + self:append( f_doff ) + self:append( f_res1 ) + self:append( f_res2 ) + + self:append( f_urg ) + self:append( f_ack ) + self:append( f_psh ) + self:append( f_rst ) + self:append( f_syn ) + self:append( f_fin ) + end)) + + local f_window = self:append( field.uint16("window", swap_endian) ) + local f_check = self:append( field.uint16("check", swap_endian) ) + local f_urg_ptr = self:append( field.uint16("urg_ptr", swap_endian) ) + + local len_tcphdr = f_doff.value * 4 + local len_opt = len_tcphdr - (ba:position() - pos) + if len_opt > 0 then + self:append( field.string("opt_data", len_opt) ) + end + + tcp_hdr.source = f_source.value + tcp_hdr.dest = f_dest.value + tcp_hdr.seq = f_seq.value + tcp_hdr.ack_seq = f_ack_seq.value + + local flags = tcp_hdr.flags + flags.doff = f_doff.value + flags.res1 = f_res1.value + flags.res2 = f_res2.value + flags.urg = f_urg.value + flags.ack = f_ack.value + flags.psh = f_psh.value + flags.rst = f_rst.value + flags.syn = f_syn.value + flags.fin = f_fin.value + + tcp_hdr.window = f_window.value + tcp_hdr.check = f_check.value + tcp_hdr.urg_ptr = f_urg_ptr.value + end) + return f +end + +local function field_udp_hdr(swap_endian, udp_hdr) + local f = field.list("udp_hdr", nil, function(self, ba) + local f_source = self:append(field.uint16("source", swap_endian)) + local f_dest = self:append(field.uint16("dest", swap_endian)) + local f_len = self:append(field.uint16("len", swap_endian)) + local f_check = self:append(field.uint16("check", swap_endian)) + + udp_hdr.source = f_source.value + udp_hdr.dest = f_dest.value + udp_hdr.len = f_len.value + udp_hdr.check = f_check.value + end) + return f +end + +local cb_ipv4_brief = function(self) + local child_brief = self:get_child_brief() + local info = self.stream_info + local sproto = IPV4_PROTOCOL_STR[info.protocol] or "??" + + local dir = string.format("%s %s:%d > %s:%d", sproto, helper.n2ip(info.saddr), info.src_port + , helper.n2ip(info.daddr), info.dst_port) + return string.format("%s %s", dir, child_brief) +end + +local function field_ipv4() + + local len = nil + local f = field.list("ipv4", nil, function(self, ba) + local pos = ba:position() + + local ipv4_hdr = ipv4_hdr_t.new() + self:append( field_ipv4_hdr(true, ipv4_hdr) ) + + len = ipv4_hdr.tos_len + self.len = len + + local parser_args = {} + parser_args.ipv4_hdr = ipv4_hdr + + local ipv4_protocol = ipv4_hdr.protocol + + local src_port = 0 + local dst_port = 0 + if ipv4_protocol == IPV4_PROTOCOL.TCP then + + local tcp_hdr = tcp_hdr_t.new() + self:append( field_tcp_hdr(true, tcp_hdr) ) + + src_port = tcp_hdr.source + dst_port = tcp_hdr.dest + + parser_args.tcp_hdr = tcp_hdr + elseif ipv4_protocol == IPV4_PROTOCOL.UDP then + + local udp_hdr = udp_hdr_t.new() + self:append( field_udp_hdr(true, udp_hdr) ) + + src_port = udp_hdr.source + dst_port = udp_hdr.dest + + parser_args.udp_hdr = udp_hdr + else + --error + end + + self.stream_info = { + protocol = ipv4_protocol, + saddr = ipv4_hdr.saddr, + daddr = ipv4_hdr.daddr, + src_port = src_port, + dst_port = dst_port, + } + + local remain = len - (ba:position()-pos) + if remain > 0 then + + self.stream_info.payload = { + ba = ba, + offset = ba:position(), + len = remain, + } + + local parser = dict_parser[src_port] or dict_parser[dst_port] + if nil == parser then + --bi.log(string.format("nil == parser, %d => %d", src_port, dst_port)) + self:append( field.string("data", remain) ) + return + end + + local codec = helper.get_codec(parser) + self:append( codec.decode(ba, remain, parser_args) ) + + remain = len - (ba:position()-pos) + if remain > 0 then + self:append( field.string("data", remain) ) + end + end + end, nil, cb_ipv4_brief) + return f +end + +local function setup_menu_ipv4(f_ipv4, f_frame) + + local protocol = f_ipv4.stream_info.protocol + if protocol ~= IPV4_PROTOCOL.TCP and protocol ~= IPV4_PROTOCOL.UDP then + return + end + + local is_tcp = protocol == IPV4_PROTOCOL.TCP + + local info = f_ipv4.stream_info + f_frame.stream_info = info + + local sip = helper.n2ip(info.saddr) + local dip = helper.n2ip(info.daddr) + + local key = string.format("%s_%d_%s_%d", sip, info.src_port, dip, info.dst_port) + local key_revert = string.format("%s_%d_%s_%d", dip, info.dst_port, sip, info.src_port) + local stream = nil + local stream2 = nil + if is_tcp then + if nil == dict_stream_tcp then + dict_stream_tcp = dict_streams_t.new() + dict_stream_tcp2 = dict_streams_t.new() + end + stream = dict_stream_tcp:get(key) + + if true == dict_stream_tcp2:has(key_revert) then + stream2 = dict_stream_tcp2:get(key_revert) + else + stream2 = dict_stream_tcp2:get(key) + end + else + if nil == dict_stream_udp then + dict_stream_udp = dict_streams_t.new() + dict_stream_udp2 = dict_streams_t.new() + end + stream = dict_stream_udp:get(key) + + if true == dict_stream_udp2:has(key_revert) then + stream2 = dict_stream_udp2:get(key_revert) + else + stream2 = dict_stream_udp2:get(key) + end + end + stream:append( f_frame ) + stream2:append( f_frame ) + + local save_stream = function( streams, file ) + bi.log(string.format("save_stream %s", file)) + local fp = io.open(file, "wb") + if fp then + if nil ~= f_pcap_file_header then + fp:write( f_pcap_file_header:get_data() ) + else + fp:write( f_pcapng_shb:get_data() ) + for _, v in ipairs(f_pcapng_idb) do + fp:write( v:get_data() ) + end + end + + for _, frame in ipairs(streams) do + fp:write( frame:get_data() ) + end + fp:close() + end + end + + local save_payload = function( streams, file ) + bi.log(string.format("save_payload %s", file)) + local fp = io.open(file, "wb") + if fp then + for _, frame in ipairs(streams) do + local payload = frame.stream_info.payload + if nil ~= payload then + local ba = payload.ba + local data = ba:peek_bytes_from(payload.offset, payload.len) + fp:write( data ) + end + end + fp:close() + end + end + + local extract_stream = function(path, sproto, dict_stream) + if nil == dict_stream then return end + if dict_stream.count <= 0 then return end + + for k, s in pairs(dict_stream.streams) do + local file = string.format("%s/%s_%s.pcap", path, sproto, k) + save_stream( s.field_frames, file ) + end + end + + local extract_payload = function(path, sproto, dict_stream) + if dict_stream.count <= 0 then return end + + for k, s in pairs(dict_stream.streams) do + local file = string.format("%s/%s_payload_%s.bin", path, sproto, k) + save_payload( s.field_frames, file ) + end + end + + f_ipv4.cb_context_menu = function(self, menu) + local path = bi.get_tmp_dir() + + menu:add_action("extract all streams: src -> dest", function() + extract_stream(path, "tcp", dict_stream_tcp) + extract_stream(path, "udp", dict_stream_udp) + end) + menu:add_action("extract all streams: src <-> dest", function() + extract_stream(path, "tcp", dict_stream_tcp2) + extract_stream(path, "udp", dict_stream_udp2) + end) + + if dict_stream_tcp and dict_stream_tcp.count > 0 then + menu:add_action("extract tcp streams: src -> dest", function() + extract_stream(path, "tcp", dict_stream_tcp) + end) + end + + if dict_stream_tcp2 and dict_stream_tcp2.count > 0 then + menu:add_action("extract tcp streams: src <-> dest", function() + extract_stream(path, "tcp", dict_stream_tcp2) + end) + end + + if dict_stream_tcp and dict_stream_tcp.count > 0 then + menu:add_action("extract tcp stream payload: src -> dest", function() + extract_payload(path, "tcp", dict_stream_tcp) + end) + end + + if dict_stream_udp and dict_stream_udp.count > 0 then + menu:add_action("extract udp streams: src -> dest", function() + extract_stream(path, "udp", dict_stream_udp) + end) + end + + if dict_stream_udp2 and dict_stream_udp2.count > 0 then + menu:add_action("extract udp streams: src <-> dest", function() + extract_stream(path, "udp", dict_stream_udp2) + end) + end + + if dict_stream_udp and dict_stream_udp.count > 0 then + menu:add_action("extract udp payload: src -> dest", function() + extract_payload(path, "udp", dict_stream_udp) + end) + end + + local sproto = nil + if is_tcp then + sproto = "tcp" + else + sproto = "udp" + end + menu:add_action(string.format("extract this %s stream %s", sproto, key), function() + local file = string.format("%s/%s_%s.pcap", path, sproto, key) + save_stream( stream.field_frames, file ) + end) + + menu:add_action(string.format("extract this %s payload %s", sproto, key), function() + local file = string.format("%s/%s_payload_%s.bin", path, sproto, key) + save_payload( stream.field_frames, file ) + end) + + end +end + + +local function field_pcap_frame(index, swap_endian) + + local interface = pcap:get_interface(1) + local link_type = interface.link_type + local f = field.list(string.format("frame[%d]", index), nil, function(self, ba) + local pcap_pkthdr = pcap_pkthdr_t.new() + self:append( field_pcap_pkthdr(swap_endian, pcap_pkthdr) ) + + local pos = ba:position() + + local ether_type = nil + if link_type == PCAP_LINK_TYPE.DLT_LINUX_SLL then + + local dlt_linux_sll = dlt_linux_sll_t.new() + self:append( field_link_type_dlt_linux_sll(true, dlt_linux_sll) ) + + ether_type = dlt_linux_sll.ether_type + elseif link_type == PCAP_LINK_TYPE.DLT_EN10MB then + local dlt_en10mb = dlt_en10mb_t.new() + self:append( field_link_type_dlt_en10mb(true, dlt_en10mb) ) + + ether_type = dlt_en10mb.ether_type + end + + if ether_type == ETHER_TYPE.IP then + local f_ipv4 = self:append( field_ipv4() ) + + setup_menu_ipv4(f_ipv4, self) + end + + local remain = pcap_pkthdr.len - (ba:position() - pos) + if remain > 0 then + self:append(field.string("remain", remain)) + end + + local skip = pcap_pkthdr.caplen - pcap_pkthdr.len + if skip > 0 then + self:append(field.string("skip", skip)) + end + end) + return f +end + + + +local function field_pcap(ba, len, swap_endian) + + local f_pcap = field.list("PCAP", len, function(self, ba) + self:append( field_pcap_file_header(swap_endian) ) + + local index = 0 + while ba:length() > 0 do + index = index + 1 + + self:append(field_pcap_frame(index, swap_endian)) + + local pos = ba:position() + bi.set_progress_value(pos) + end + pcap.frame_count = index + end) + + return f_pcap +end + +local function field_pcapng_section_header_block(index, swap_endian) + local f = field.list("SHB", nil, function(self, ba) + local pos = ba:position() + self:append( field.uint32("magic", swap_endian) ) + local f_total_len = self:append( field.uint32("total_len", swap_endian) ) + self:append( field.uint32("byte_order", swap_endian) ) + self:append( field.uint16("major", swap_endian) ) + self:append( field.uint16("minor", swap_endian) ) + self:append( field.uint64("section_length", swap_endian) ) +--[[ + while true do + self:append( field.uint16("option_code", swap_endian) ) + local f_opt_len = self:append( field.uint16("option_length", swap_endian) ) + if f_opt_len.value == 0 then + break + end + + self:append( field.string("option_value", f_opt_len.value) ) + end +--]] + local remain = f_total_len.value - (ba:position()-pos) - 4 + if remain > 0 then + self:append( field.string("opt", remain) ) + end + + self:append( field.uint32("total_len", swap_endian) ) + end) + + f_pcapng_shb = f + return f +end + +local function field_pcapng_packet_block(index, swap_endian) + local f = field.list("PB", nil, function(self, ba) + local pos = ba:position() + + self:append(field.uint32("block_type", swap_endian)) + local f_total_len = self:append(field.uint32("total_len", swap_endian)) + + local remain = f_total_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("data", remain) ) + end + end) + return f +end + +local function field_pcapng_interface_description_block(index, swap_endian) + local f = field.list("IDB", nil, function(self, ba) + local pos = ba:position() + + self:append(field.uint32("block_type", swap_endian)) + local f_total_len = self:append(field.uint32("total_len", swap_endian)) + local f_link_type = self:append(field.uint16("link_type", swap_endian)) + self:append(field.uint16("reserved", swap_endian)) + self:append(field.uint32("snap_len", swap_endian)) + + local interface = interface_t.new() + interface.link_type = f_link_type.value + pcap:add_interface( interface ) + + local remain = f_total_len.value - (ba:position()-pos) - 4 + if remain > 0 then + self:append( field.string("options", remain) ) + end + + self:append( field.uint32("total_len", swap_endian) ) + end) + table.insert(f_pcapng_idb, f) + return f +end + +--[[ +https://github.com/boundary/wireshark/blob/master/wiretap/pcapng.c +ts = (((guint64)wblock->data.packet.ts_high) << 32) | ((guint64)wblock->data.packet.ts_low); +wblock->packet_header->ts.secs = (time_t)(ts / int_data.time_units_per_second); +wblock->packet_header->ts.nsecs = (int)(((ts % int_data.time_units_per_second) * 1000000000) / int_data.time_units_per_second); +--]] + +local function field_pcapng_enhanced_packet_block(index, swap_endian) + local f = field.list(string.format("EPB[%d]", index), nil, function(self, ba) + local pos = ba:position() + + self:append(field.uint32("block_type", swap_endian)) + local f_total_len = self:append(field.uint32("total_len", swap_endian)) + + local f_interface_id = self:append(field.uint32("interface_id", swap_endian)) + + local f_ts_high = self:append(field.uint32("ts_high", swap_endian)) + local f_ts_low = self:append(field.uint32("ts_low", swap_endian)) + + local ts = (f_ts_high.value << 32) | f_ts_low.value + --TODO IDB.if_tsresol + local secs = ts / 1000000 + local ms = (ts % 1000000) / 1000 + ms = math.floor(secs * 1000 + ms) + + f_ts_low.get_desc = function(self) + return string.format("%s %s: %u %s", self.type, self.name, self.value, helper.ms2date(ms)) + end + f_ts_low.get_brief = function(self) + return string.format("%s ", helper.ms2date(ms)) + end + + self:append(field.uint32("capture_len", swap_endian)) + local f_packet_len = self:append(field.uint32("packet_len", swap_endian)) + + local interface = pcap:get_interface( f_interface_id.value + 1 ) + + if nil ~= interface then + + local link_type = interface.link_type + if link_type == PCAP_LINK_TYPE.DLT_NULL then + + local f_family = self:append( field.uint32("family", swap_endian) ) + if f_family.value == 2 then --IP + local f_ipv4 = self:append( field_ipv4() ) + setup_menu_ipv4(f_ipv4, self) + end + + elseif link_type == PCAP_LINK_TYPE.DLT_EN10MB then + + local dlt_en10mb = dlt_en10mb_t.new() + self:append( field_link_type_dlt_en10mb(true, dlt_en10mb) ) + + local ether_type = dlt_en10mb.ether_type + if ether_type == ETHER_TYPE.IP then + local f_ipv4 = self:append( field_ipv4() ) + setup_menu_ipv4(f_ipv4, self) + end + + end + end + + local remain = f_total_len.value - (ba:position()-pos) - 4 + if remain > 0 then + self:append( field.string("options", remain) ) + elseif remain < 0 then + bi.log(string.format("pcap epb %s remain < 0: %d", self.name, remain)) + ba:back_pos(math.abs(remain)) + end + + self:append( field.uint32("total_len", swap_endian) ) + end) + return f +end + +local function field_pcapng_unknown_block(index, swap_endian) + local f = field.list("unknown_block", nil, function(self, ba) + local pos = ba:position() + + self:append(field.uint32("block_type", swap_endian)) + local f_total_len = self:append(field.uint32("total_len", swap_endian)) + + local remain = f_total_len.value - (ba:position()-pos) + if remain > 0 then + self:append( field.string("data", remain) ) + end + end) + return f +end + +local field_pcapng_blocks = { + [PCAPNG_BLOCK_TYPE.SHB] = field_pcapng_section_header_block, + [PCAPNG_BLOCK_TYPE.PB] = field_pcapng_packet_block, + [PCAPNG_BLOCK_TYPE.IDB] = field_pcapng_interface_description_block, + [PCAPNG_BLOCK_TYPE.EPB] = field_pcapng_enhanced_packet_block, +} + + +local function field_pcapng(ba, len, swap_endian) + local f_pcapng = field.list("PCAPNG", len, function(self, ba) + + local index = 0 + while ba:length() > 0 do + + local block_type = ba:peek_uint32() + if block_type == PCAPNG_BLOCK_TYPE.EPB then + index = index + 1 + end + + local cb_field = field_pcapng_blocks[block_type] or field_pcapng_unknown_block + self:append( cb_field(index, swap_endian) ) + + local pos = ba:position() + bi.set_progress_value(pos) + end + pcap.frame_count = index + end) + + return f_pcapng + +end + +local function decode_pcap( ba, len ) + pcap = pcap_t.new() + + local swap_endian = false + local magic = ba:peek_uint32(swap_endian) + + if magic == MAGIC_PCAP_LE or magic == MAGIC_PCAP_BE then + if magic == MAGIC_PCAP_BE then + swap_endian = true + end + + local f_pcap = field_pcap(ba, len, swap_endian) + return f_pcap + elseif magic == MAGIC_PCAPNG then + + ba:read_uint64() + local endian = ba:read_uint32(is_bigendian) + ba:back_pos(12) + + local pcapng_big_endian = 0x4D3C2B1A + if endian == pcapng_big_endian then + is_bigendian = true + end + + local f_pcapng = field_pcapng(ba, len, swap_endian) + return f_pcapng + else + bi.log(string.format("unknown pcap magic: 0x0X", magic)) + return + end +end + +local function clear() + pcap = nil + f_pcap_file_header = nil + + f_pcapng_shb = nil + f_pcapng_idb = {} + dict_stream_tcp = nil + dict_stream_tcp2 = nil + dict_stream_udp = nil + dict_stream_udp2 = nil +end + +local function build_summary() + if nil == pcap then return end + + bi.append_summary("frames", pcap.frame_count) +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "tcpdump", + file_ext = "pcap pcapng", + decode = decode_pcap, + clear = clear, + build_summary = build_summary, +} + +return codec diff --git a/src/scripts/codec/codec_rtp.lua b/src/scripts/codec/codec_rtp.lua new file mode 100644 index 0000000..306b18c --- /dev/null +++ b/src/scripts/codec/codec_rtp.lua @@ -0,0 +1,1109 @@ +require("class") +require("nalu_buf") +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") +local args = nil +local codec_h264 = nil + +--rtcp fir nack +--http://www.networksorcery.com/enp/rfc/rfc2032.txt + +--rtcp xr +--https://www.rfc-editor.org/rfc/rfc3611 + +--rtcp rtpfb psfb +--https://www.rfc-editor.org/rfc/rfc4585.html + +--RTCP message for Receiver Estimated Maximum Bitrate +--https://datatracker.ietf.org/doc/html/draft-alvestrand-rmcat-remb-03 + +local dict_stream_nalus = dict_stream_nalus_t.new() + +local UDP_NET_EVENT = { + NULL = 0, + CREATE = 1, + CREATERESPONSE = 2, + CLOSE = 3, + DATA = 4, + DATAMSG_0 = 5, + UNRELIABLEDATA = 6, + HEARTBEAT = 7, + HEARTBEATRESPONSE = 8, + MODIFYUDPSERVERVERSION = 9, + DATAMSG_1 = 10, + DETECTNETLINK = 11, + DETECTNETLINKRESPONSE = 12, + DETECTNTP = 13, + DETECTNTPRESPONSE = 14, +} + +local UDP_NET_EVENT_STR = { + [UDP_NET_EVENT.NULL] = "NULL", + [UDP_NET_EVENT.CREATE] = "CREATE", + [UDP_NET_EVENT.CREATERESPONSE] = "CREATERESPONSE", + [UDP_NET_EVENT.CLOSE] = "CLOSE", + [UDP_NET_EVENT.DATA] = "DATA", + [UDP_NET_EVENT.DATAMSG_0] = "DATAMSG_0", + [UDP_NET_EVENT.UNRELIABLEDATA] = "UNRELIABLEDATA", + [UDP_NET_EVENT.HEARTBEAT] = "HEARTBEAT", + [UDP_NET_EVENT.HEARTBEATRESPONSE] = "HEARTBEATRESPONSE", + [UDP_NET_EVENT.MODIFYUDPSERVERVERSION] = "MODIFYUDPSERVERVERSION", + [UDP_NET_EVENT.DATAMSG_1] = "DATAMSG_1", + [UDP_NET_EVENT.DETECTNETLINK] = "DETECTNETLINK", + [UDP_NET_EVENT.DETECTNETLINKRESPONSE] = "DETECTNETLINKRESPONSE", + [UDP_NET_EVENT.DETECTNTP] = "DETECTNTP", + [UDP_NET_EVENT.DETECTNTPRESPONSE] = "DETECTNTPRESPONSE", +} + +local XR_BLOCK_TYPE = { + LOSS_RLE = 1, + DUPLICATE_RLE = 2, + PACKET_RECEIPT_TIMES = 3, + RECEIVER_REFERENCE_TIME = 4, + DLRR = 5, + STATISTICS_SUMMARY = 6, + VOIP_METRICS = 7, +} + +local XR_BLOCK_TYPE_STR = { + [XR_BLOCK_TYPE.LOSS_RLE] = "LossRLE", + [XR_BLOCK_TYPE.DUPLICATE_RLE] = "DuplicateRLE", + [XR_BLOCK_TYPE.PACKET_RECEIPT_TIMES] = "PacketReceiptTimes", + [XR_BLOCK_TYPE.RECEIVER_REFERENCE_TIME] = "ReceiverReferenceTime", + [XR_BLOCK_TYPE.DLRR] = "DLRR", + [XR_BLOCK_TYPE.STATISTICS_SUMMARY] = "StatisticsSummary", + [XR_BLOCK_TYPE.VOIP_METRICS] = "VoIPMetrics", +} + +local rtp_hdr_t = class("rtp_hdr_t") +function rtp_hdr_t:ctor() + self.ver = 0 + self.padding = 0 + self.extension = 0 + self.cc = 0 + self.marker = 0 + self.pt = 0 + self.seq = 0 + self.timestamp = 0 + self.ssrc = 0 + self.csrc = nil +end + +local rtp_ext_t = class("rtp_ext_t") +function rtp_ext_t:ctor() + self.profile = 0 + self.length = 0 + self.ext = {} +end + +local rtcp_hdr_t = class("rtcp_hdr_t") +function rtcp_hdr_t:ctor() + self.ver = 0 + self.padding = 0 + self.rc = 0 + self.pt = 0 + self.length = 0 +end + +--[[ +The sender report header: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| RC | PT=SR=200 | length L | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC of sender | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The sender information block: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NTP timestamp, most significant word NTS | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| NTP timestamp, least significant word | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RTP timestamp RTS | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| sender's packet count SPC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| sender's octet count SOC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The receiver report blocks: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC_1 (SSRC of first source) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|fraction lost F| cumulative number of packets lost C | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| extended highest sequence number received EHSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| inter-arrival jitter J | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| last SR LSR | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| delay since last SR DLSR | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| SSRC_2 (SSRC of second source) | +: ... : ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +http://www.networksorcery.com/enp/rfc/rfc2032.txt +5.2.1. Full INTRA-frame Request (FIR) packet + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| MBZ | PT=RTCP_FIR | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +5.2.2. Negative ACKnowledgements (NACK) packet + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| MBZ | PT=RTCP_NACK | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| FSN | BLP | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +--]] + +local rtcp_fir_t = class("rtcp_fir_t", rtcp_hdr_t) +function rtcp_fir_t:ctor() + self.ssrc = 0 +end + +local rtcp_nack_t = class("rtcp_nack_t", rtcp_hdr_t) +function rtcp_nack_t:ctor() + self.ssrc = 0 + self.fsn = 0 + self.blp = {} +end + +local rtcp_sr_block_t = class("rtcp_sr_block_t") +function rtcp_sr_block_t:ctor() + self.ntp_msw = 0 + self.ntp_lsw = 0 + self.rts = 0 + self.send_packet = 0 + self.send_bytes = 0 +end + +local rtcp_rr_block_t = class("rtcp_rr_block_t") +function rtcp_rr_block_t:ctor() + self.ssrc = 0 + self.fraction_lost = 0 + self.cumulative_lost = 0 + + self.seq_cycles = 0 + self.highest_seq = 0 + + self.jitter = 0 + self.last_sr = 0 + self.delay_since_last_sr = 0 +end + +local rtcp_sr_t = class("rtcp_sr_t", rtcp_hdr_t) +function rtcp_sr_t:ctor() + self.ssrc = 0 + self.sr_block = nil +end + +local rtcp_rr_t = class("rtcp_rr_t", rtcp_hdr_t) +function rtcp_rr_t:ctor() + self.ssrc = 0 + self.rr_blocks = {} +end + +local rtcp_rtpfb_t = class("rtcp_rtpfb_t", rtcp_hdr_t) +function rtcp_rtpfb_t:ctor() + self.ssrc = 0 + self.media_ssrc = 0 +end + +local rtcp_psfb_t = class("rtcp_psfb_t", rtcp_hdr_t) +function rtcp_psfb_t:ctor() + self.ssrc = 0 + self.media_ssrc = 0 +end + +local rtcp_xr_t = class("rtcp_xr_t", rtcp_hdr_t) +function rtcp_xr_t:ctor() + self.ssrc = 0 +end + + +local function field_rtp_hdr(rtp_hdr) + rtp_hdr = rtp_hdr or rtp_hdr_t.new() + + local f = field.bit_list("rtp_hdr", nil, function(self, ba) + local f_ver = self:append( field.ubits("version", 2) ) + local f_padding = self:append( field.ubits("padding", 1, nil, fh.mkbrief_v("P")) ) + local f_extension = self:append( field.ubits("extension", 1, nil, fh.mkbrief_v("X")) ) + local f_cc = self:append( field.ubits("cc", 4, nil, fh.mkbrief_v("CC")) ) + + local f_marker = self:append( field.ubits("marker", 1, nil, fh.mkbrief_v("M")) ) + local f_pt = self:append( field.ubits("pt", 7, nil, fh.mkbrief_v("PT")) ) + local f_seq = self:append( field.ubits("seq", 16, nil, fh.mkbrief_v("SEQ")) ) + + local f_timestamp = self:append( field.ubits("timestamp", 32, nil, fh.mkbrief_v("TS")) ) + local f_ssrc = self:append( field.ubits("ssrc", 32, nil, fh.mkbrief_v("SSRC")) ) + + rtp_hdr.ver = f_ver.value + rtp_hdr.padding = f_padding.value + rtp_hdr.extension = f_extension.value + rtp_hdr.cc = f_cc.value + rtp_hdr.marker = f_marker.value + rtp_hdr.pt = f_pt.value + rtp_hdr.seq = f_seq.value + rtp_hdr.timestamp = f_timestamp.value + rtp_hdr.ssrc = f_ssrc.value + + if f_cc.value > 0 then + rtp_hdr.csrc = {} + end + + for i=1, f_cc.value do + local f_csrc = self:append( field.ubits("csrc", 32) ) + table.insert( rtp_hdr.csrc, f_csrc.value ) + end + end, nil, fh.child_brief) + return f +end + +local function field_rtp_extension(rtp_ext) + rtp_ext = rtp_ext or rtp_ext_t.new() + local f = field.list("rtp_ext", nil, function(self, ba) + local swap_endian = true + local u16 = ba:peek_uint16(true) + if u16 == 0xBEDE then + --one byte header + local f_profile = self:append( field.uint16("profile", swap_endian) ) + local f_length = self:append( field.uint16("length", swap_endian) ) + + rtp_ext.profile = f_profile.value + rtp_ext.length = f_length.value + + local index = 0 + local pos = ba:position() + local ext_len = f_length.value * 4 + --self:append( field.string("ext", ext_len) ) + + local remain = ext_len + while remain > 0 do + + self:append( field.list(string.format("ext[%d]", index), nil, function(self, ba) + local f_id = field.ubits("ID", 4, nil, fh.mkbrief_v("ID")) + local f_len = field.ubits("L", 4, nil, fh.mkbrief_v("L")) + + self:append( field.bit_list("hdr", 1, function(self, ba) + self:append(f_id) + self:append(f_len) + end, nil, fh.child_brief)) + + if f_id.value ~= 0 then + self:append( field.string("data", f_len.value + 1) ) + end + end)) + + remain = ext_len - (ba:position() - pos) + index = index + 1 + end + else + --TODO two byte header + + end + + end) + return f +end + +local function field_rtcp_hdr( rtcp_hdr ) + local f = field.bit_list("rtcp_hdr", nil, function(self, ba) + local f_ver = self:append( field.ubits("version", 2) ) + local f_padding = self:append( field.ubits("padding", 1, nil, fh.mkbrief_v("P")) ) + local f_rc= self:append( field.ubits("rc", 5, nil, fh.mkbrief_v("RC")) ) + local f_pt = self:append( field.ubits("pt", 8, nil, fh.mkbrief_v("T")) ) + local f_length = self:append( field.ubits("length", 16, nil, fh.mkbrief_v("L")) ) + + rtcp_hdr.ver = f_ver.value + rtcp_hdr.padding = f_padding.value + rtcp_hdr.rc = f_rc.value + rtcp_hdr.pt = f_pt.value + rtcp_hdr.length = f_length.value + end, nil, fh.child_brief) + return f +end + +local function field_nalu(rtp_hdr, key, len) + local NALU_TYPE = codec_h264.NALU_TYPE + local NALU_TYPE_STR = codec_h264.NALU_TYPE_STR + + local f_nalu_hdr = codec_h264.field_nalu_header() + + local cb_brief = function(self) + local stype = NALU_TYPE_STR[f_nalu_hdr.nalu_type] or "??" + return string.format("%s VLEN:%d ", stype, len) + end + + local f = field.list("NALU", len, function(self, ba) + local pos = ba:position() + + local i_nalu_hdr = ba:peek_uint8() + self:append( f_nalu_hdr ) + + local f_ebsp = self:append( field.string( "ebsp", len-1) ) + + --local stype = NALU_TYPE_STR[f_nalu_hdr.nalu_type] + --if nil ~= stype then + local nalu_buf = dict_stream_nalus:get(key) + nalu_buf:append( rtp_hdr.seq, f_ebsp, i_nalu_hdr, true) + --end + end, nil, cb_brief) + return f +end + +local function field_nalu_stap(rtp_hdr, key, len, is_stap_b) + local NALU_TYPE = codec_h264.NALU_TYPE + local NALU_TYPE_STR = codec_h264.NALU_TYPE_STR + + local cb_brief = function(self) + return string.format("%s VLEN:%d ", self.name, len) + end + + local name = nil + if true == is_stap_b then + name = NALU_TYPE_STR[NALU_TYPE.STAP_B] + else + name = NALU_TYPE_STR[NALU_TYPE.STAP_A] + end + + local f = field.list(name, len, function(self, ba) + local pos = ba:position() + + self:append( codec_h264.field_nalu_header() ) + + if true == is_stap_b then + self:append( field.uint16("DON", true) ) + end + + local index = 0 + local remain = len + while remain > 0 do + + local stap_len = ba:peek_uint16(true) + self:append( field.list(string.format("nalu[%d]", index), stap_len, function(self, ba) + + local f_size = self:append( field.uint16("size", true) ) + + local nalu_len = f_size.value + local remain2 = len - (ba:position() - pos) + if remain2 < f_size.value then + nalu_len = remain2 + bi.log(string.format("%s reset nalu_len %d => %d", self.name, f_size.value, remain2)) + end + + local i_nalu_hdr = ba:peek_uint8() + local f_nalu_hdr = self:append( codec_h264.field_nalu_header() ) + --local f_ebsp = self:append( field.string( "ebsp", f_size.value-1) ) + local f_ebsp = self:append( field.string( "ebsp", nalu_len-1) ) + + local nalu_buf = dict_stream_nalus:get(key) + nalu_buf:append( rtp_hdr.seq, f_ebsp, i_nalu_hdr, true) + end, nil, fh.child_brief)) + index = index + 1 + remain = len - (ba:position() - pos) + end + + end, nil, cb_brief) + return f +end + +local function field_nalu_stap_a(rtp_hdr, key, len) + return field_nalu_stap(rtp_hdr, key, len, false) +end + +local function field_nalu_stap_b(rtp_hdr, key, len) + return field_nalu_stap(rtp_hdr, key, len, true) +end + +local function field_nalu_fu_a(rtp_hdr, key, len) + local NALU_TYPE = codec_h264.NALU_TYPE + local NALU_TYPE_STR = codec_h264.NALU_TYPE_STR + + local f_nalu_hdr = codec_h264.field_nalu_header() + local f_fu_hdr = codec_h264.field_fu_header() + + local cb_brief = function(self) + local stype = "" + local fu = f_fu_hdr.fu + if fu.S == 1 then + stype = string.format("FUA:S %s", NALU_TYPE_STR[fu.T] or "??") + elseif fu.E == 1 then + stype = string.format("FUA:E %s", NALU_TYPE_STR[fu.T] or "??") + else + stype = string.format("FUA %s", NALU_TYPE_STR[fu.T] or "??") + end + return string.format("%s VLEN:%d ", stype, len) + end + + local f = field.list("FU-A", len, function(self, ba) + local pos = ba:position() + local i_nalu_hdr = ba:peek_uint8() + + self:append( f_nalu_hdr ) + self:append( f_fu_hdr ) + + local remain = len - (ba:position() - pos) + if remain <= 0 then return end + local f_ebsp = self:append(field.string("ebsp", remain)) + + local is_new_nalu = false + if nil == f_fu_hdr then + is_new_nalu = true + elseif 1 == f_fu_hdr.fu.S then + is_new_nalu = true + + i_nalu_hdr = i_nalu_hdr & 0xE0 | f_fu_hdr.fu.T + end + + local nalu_buf = dict_stream_nalus:get(key) + nalu_buf:append( rtp_hdr.seq, f_ebsp, i_nalu_hdr, is_new_nalu ) + + end, nil, cb_brief) + return f +end + +local function field_rtp_h264(rtp_hdr, len) + local f = field.select("h264", len, function(self, ba) + local NALU_TYPE = codec_h264.NALU_TYPE + local NALU_TYPE_STR = codec_h264.NALU_TYPE_STR + + local f_fu_hdr = nil + + local sip = 0 + local dip = 0 + local sport = 0 + local dport = 0 + local ssrc = rtp_hdr.ssrc + if args then + if args.ipv4_hdr then + sip = args.ipv4_hdr.saddr + dip = args.ipv4_hdr.daddr + end + + if args.udp_hdr then + sport = args.udp_hdr.source + dport = args.udp_hdr.dest + end + end + + --bi.log(string.format("video %s:%d:%s => %s:%d", helper.n2ip(sip), sport, ssrc, helper.n2ip(dip), dport)) + + local key_src = string.format("%s_%d_%d", helper.n2ip(sip), sport, ssrc) + local key_dst = string.format("%s_%d", helper.n2ip(dip), dport) + local key_stream = string.format("%s-%s", key_src, key_dst) + + local i_nalu_hdr = ba:peek_uint8() + local nalu_type = i_nalu_hdr & 0x1F + local f_video = nil + if nalu_type == NALU_TYPE.FU_A then + f_video = field_nalu_fu_a(rtp_hdr, key_stream, len) + elseif nalu_type == NALU_TYPE.STAP_A then + f_video = field_nalu_stap_a(rtp_hdr, key_stream, len) + elseif nalu_type == NALU_TYPE.STAP_B then + f_video = field_nalu_stap_b(rtp_hdr, key_stream, len) + else + f_video = field_nalu(rtp_hdr, key_stream, len) + end + + --setup context menu + f_video.cb_context_menu = function(self, menu) + if dict_stream_nalus.nstream > 0 then + menu:add_action("Extract all h264 streams", function() + for k, nalu_buf in pairs(dict_stream_nalus.stream_nalus) do + bi.log(string.format("save h264 stream %s", k)) + nalu_buf:save(string.format("%s/%s.h264", bi.get_tmp_dir(), k)) + end + --bi.message_box(string.format("save ok")) + end) + end + + if nil ~= key_stream then + menu:add_action(string.format("Extract h264 %s -> %s", key_src, key_dst), function() + local nalu_buf = dict_stream_nalus:get(key_stream) + if nil == nalu_buf then return end + local path = bi.save_as(string.format("%s/%s.h264", bi.get_tmp_dir(), key_stream)) + if nil == path or "" == path then return end + bi.log(string.format("save_as %s", path)) + nalu_buf:save(path) + + bi.message_box(string.format("save ok:%s", path)) + end) + end + end + return f_video + end) + return f +end + +local function field_rtp_video(len) + local f = field.list("payload_video", len, function(self, ba) + + local pos = ba:position() + local rtp_hdr = rtp_hdr_t.new() + self:append( field_rtp_hdr(rtp_hdr) ) + + if 1 == rtp_hdr.extension then + self:append( field_rtp_extension() ) + end + + local remain = len - (ba:position() - pos) + if remain <= 0 then return end + + self:append( field_rtp_h264(rtp_hdr, remain) ) + end, nil, fh.child_brief) + return f +end + +local function field_rtcp_sr(len) + local f = field.list("rtcp_sr", len, function(self, ba) + local swap_endian = true + local sr = rtcp_sr_t.new() + self:append( field_rtcp_hdr(sr) ) + + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + sr.ssrc = f_ssrc.value + + local f_ntp_msw = self:append( field.uint32("ntp_msw", swap_endian) ) + local f_ntp_lsw = self:append( field.uint32("ntp_lsw", swap_endian, function(self) + local ntp = (f_ntp_msw.value << 32) | self.value + local ms = bi.ntp2ms(ntp) + return string.format("%s %s: %u ms:%d %s ", self.type, self.name, self.value, ms, helper.ms2date(ms)) + end) ) + + local f_rts = self:append(field.uint32("rts", swap_endian)) + local f_send_packet = self:append(field.uint32("send_packet", swap_endian)) + local f_send_bytes = self:append(field.uint32("send_bytes", swap_endian)) + + local block = rtcp_sr_block_t.new() + block.ntp_msw = f_ntp_msw.value + block.ntp_lsw = f_ntp_lsw.value + block.rts = f_rts.value + block.send_packet = f_send_packet.value + block.send_bytes = f_send_bytes.value + + sr.sr_block = block + end) + return f +end + +local function field_rtcp_rr(len) + local f = field.list("rtcp_rr", len, function(self, ba) + local rr = rtcp_rr_t.new() + self:append( field_rtcp_hdr(rr) ) + + local swap_endian = true + self:append( field.uint32("ssrc", swap_endian) ) + + if rr.rc <= 0 then return end + + for i=1, rr.rc do + self:append( field.list(string.format("rr_block[%d]", i), nil, function(self, ba) + + local f_ssrc = self:append(field.uint32("ssrc", swap_endian)) + local f_fraction_lost = self:append(field.uint8("fraction_lost")) + local f_cumulative_lost = self:append(field.uint24("cumulative_lost", swap_endian)) + + local f_seq_cycles = self:append(field.uint16("seq_cycles", swap_endian)) + local f_highest_seq = self:append(field.uint16("highest_seq_received", swap_endian)) + + local f_jitter = self:append(field.uint32("jitter", swap_endian)) + local f_last_sr = self:append(field.uint32("last_sr", swap_endian)) + local f_delay_since_last_sr = self:append(field.uint32("delay_since_last_sr", swap_endian)) + + local block = rtcp_rr_block_t.new() + block.ssrc = f_ssrc.value + block.fraction_lost = f_fraction_lost.value + block.cumulative_lost = f_cumulative_lost.value + + block.seq_cycles = f_seq_cycles.value + block.highest_seq = f_highest_seq.value + + block.jitter = f_jitter.value + block.last_sr = f_last_sr.value + block.delay_since_last_sr = f_delay_since_last_sr.value + table.insert(rr.rr_blocks, block) + end)) + end + + end, nil, fh.child_brief) + return f +end + +local function field_rtcp_sdes(len) +end + +local function field_rtcp_bye(len) +end + +local function field_rtcp_app(len) +end + +--[[ +--Common Packet Format for Feedback Messages + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + +Transport Layer Feedback Message + 0: unassigned + 1: Generic NACK + 2-30: unassigned + 31: reserved for future expansion of the identifier number space + +-- FCI NACK: PT=RTPFB and FMT=1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-- Slice Loss Indication (SLI) PT=PSFB and FMT=2 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | First | Number | PictureID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-- Receiver Estimated Max Bitrate (REMB) + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=206 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unique identifier 'R' 'E' 'M' 'B' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Num SSRC | BR Exp | BR Mantissa | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC feedback | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... | + +Payload-Specific Feedback Messages + 0: unassigned + 1: Picture Loss Indication (PLI) //https://www.rfc-editor.org/rfc/rfc4585.html#section-6.3.1 + 2: Slice Loss Indication (SLI) //https://www.rfc-editor.org/rfc/rfc4585.html#section-6.3.2 + 3: Reference Picture Selection Indication (RPSI) //https://www.rfc-editor.org/rfc/rfc4585.html#section-6.3.3 + 4-14: unassigned + 15: Application layer FB (AFB) message // + 16-30: unassigned + 31: reserved for future expansion of the sequence number space + +--]] +local RTPFB_FMT = { + NACK = 1, + TCC = 15, --transport cc +} + +local RTPFB_FMT_STR = { + [RTPFB_FMT.NACK] = "NACK", + [RTPFB_FMT.TCC] = "TCC" +} + +local PSFB_FMT = { + PLI = 1, --picture loss indication + SLI = 2, --slice loss indication + RPSI = 3, --reference picture selection indication + AFB = 15 --application layer fb +} +local PSFB_FMT_STR = { + [PSFB_FMT.PLI] = "PLI", + [PSFB_FMT.SLI] = "SLI", + [PSFB_FMT.RPSI] = "RPLI", + [PSFB_FMT.AFB] = "AFB", +} + +local function field_psfb_fci_pli(len) + local f = field.list("PLI", len, function(self, ba) + --PLI no parameters + end) + return f +end + +local function field_psfb_fci_sli(len) + local f = field.list("SLI", len, function(self, ba) + for i=1, len do + self:append( field.bit_list(string.format("SLI[%d]", i), nil, function(self, ba) + self:append( field.ubits("first", 13) ) + self:append( field.ubits("number", 13) ) + self:append( field.ubits("picture_id", 6) ) + end)) + end + end) + return f +end + +local function field_psfb_fci_afb(len) + local f_num_ssrc = field.ubits("num_ssrc", 8) + local f_br_exp = field.ubits("br_exp", 6) + local f_br_mantissa = field.ubits("br_mantissa", 18) + + local function cb_brief_remb() + local remb = f_br_mantissa.value * (2 ^ f_br_exp.value) + return string.format("REMB:%d ", remb) + end + + local f = field.list("AFB", len, function(self, ba) + self:append( field.string("identifier", 4, fh.str_desc) ) + + self:append( field.bit_list("bits", nil, function(self, ba) + self:append( f_num_ssrc ) + self:append( f_br_exp ) + self:append( f_br_mantissa ) + end)) + + for i=1, f_num_ssrc.value do + self:append(field.uint32(string.format("ssrc_feedback[%d]", i), true)) + end + end, nil, cb_brief_remb) + return f +end + +local function field_psfb_fci_unknown(len) + local f = field.list("fci_unknown", len, function(self, ba) + self:append(field.string("unknown", len)) + end) + return f +end + +local function field_rtpfb_fci_nack(len) + local f = field.list("NACK", len, function(self, ba) + local swap_endian = true + local f_pid = self:append(field.uint16("PID", swap_endian)) + self:append(field.bit_list("BLP", 2, function(self, ba) + for i=0, 15 do + local v = ba:peek_ubits(1) + if 1 == v then + local loss_pid = f_pid.value+(16-i) + local function cb_brief_loss(self) + return string.format("%d", loss_pid) + end + self:append(field.ubits(string.format("BLP[%d] LOSS %d", i, loss_pid), 1, nil, cb_brief_loss)) + else + self:append(field.ubits(string.format("BLP[%d]", i))) + end + end + end, nil, fh.child_brief)) + end) + return f +end + +local dict_rtpfb_fci_field = { + [RTPFB_FMT.NACK] = field_rtpfb_fci_nack, + [RTPFB_FMT.TCC] = field_rtpfb_fci_tcc, +} + +local dict_psfb_fci_field = { + [PSFB_FMT.PLI] = field_psfb_fci_pli, + [PSFB_FMT.SLI] = field_psfb_fci_sli, + [PSFB_FMT.RPSI] = nil, + [PSFB_FMT.AFB] = field_psfb_fci_afb, +} + +local function field_rtcp_rtpfb(len) + local f = field.list("rtcp_rtpfb", len, function(self, ba) + local pos = ba:position() + local rtpfb = rtcp_rtpfb_t.new() + self:append( field_rtcp_hdr(rtpfb) ) + + local swap_endian = true + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + rtpfb.ssrc = f_ssrc.value + + local f_media_ssrc = self:append( field.uint32("media_ssrc", swap_endian)) + rtpfb.media_ssrc = f_media_ssrc.value + + local remain = len - (ba:position() - pos) + if remain <= 0 then return end + + local fmt = rtpfb.rc + local fcb = dict_rtpfb_fci_field[fmt] or field_psfb_fci_unknown + self:append( fcb(remain) ) + + remain = len - (ba:position() - pos) + if remain > 0 then + self:append( field.string("unparsed", remain) ) + end + end, nil, fh.child_brief) + return f +end + +local function field_rtcp_psfb(len) + local f = field.list("rtcp_psfb", len, function(self, ba) + local pos = ba:position() + local psfb = rtcp_psfb_t.new() + self:append( field_rtcp_hdr(psfb) ) + + local swap_endian = true + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + psfb.ssrc = f_ssrc.value + + local f_media_ssrc = self:append( field.uint32("media_ssrc", swap_endian)) + psfb.media_ssrc = f_media_ssrc.value + + local remain = len - (ba:position() - pos) + if remain <= 0 then return end + + local fmt = psfb.rc + local fcb = dict_psfb_fci_field[fmt] or field_psfb_fci_unknown + self:append( fcb(remain) ) + + remain = len - (ba:position() - pos) + if remain > 0 then + self:append( field.string("unparsed", remain) ) + end + end, nil, fh.child_brief) + return f +end + +local function field_rtcp_fir(len) + local f = field.list("rtcp_fir", len, function(self, ba) + local fir = rtcp_fir_t.new() + self:append( field_rtcp_hdr(fir) ) + + local swap_endian = true + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + fir.ssrc = f_ssrc.value + end) + return f +end + +local function field_rtcp_nack(len) + local f = field.list("rtcp_nack", len, function(self, ba) + local nack = rtcp_nack_t.new() + self:append( field_rtcp_hdr(nack) ) + + local swap_endian = true + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + self:append( field_rtpfb_fci_nack(16) ) + + nack.ssrc = f_ssrc.value + end) + return f +end + +local function field_rtpfb_fci_tcc(len) + --TODO +end + +--[[ + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=4 | reserved | block length = 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, most significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +--]] +local function field_rtcp_xr_receiver_reference_time(len) + local f = field.list("receiver_reference_time", len, function(self, ba) + local swap_endian = true + self:append(field.uint8("block_type", fh.mkdesc(XR_BLOCK_TYPE_STR), fh.mkbrief_v("BT", XR_BLOCK_TYPE_STR))) + self:append(field.uint8("reserved")) + self:append(field.uint16("block_length", swap_endian)) + + local f_ntp_msw = self:append( field.uint32("ntp_msw", swap_endian) ) + local f_ntp_lsw = self:append( field.uint32("ntp_lsw", swap_endian, function(self) + local ntp = (f_ntp_msw.value << 32) | self.value + local ms = bi.ntp2ms(ntp) + return string.format("%s %s: %u ms:%d %s ", self.type, self.name, self.value, ms, helper.ms2date(ms)) + end) ) + end) + return f +end + +--[[ + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=5 | reserved | block length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_1 (SSRC of first receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + | last RR (LRR) | 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last RR (DLRR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | SSRC_2 (SSRC of second receiver) | sub- + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block + : ... : 2 + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +--]] +local function field_rtcp_xr_dlrr(len) + local f = field.list("dlrr", len, function(self, ba) + local swap_endian = true + self:append(field.uint8("block_type", fh.mkdesc(XR_BLOCK_TYPE_STR), fh.mkbrief_v("BT", XR_BLOCK_TYPE_STR))) + self:append(field.uint8("reserved")) + local f_len = self:append(field.uint16("block_length", swap_endian)) + if f_len.value <= 0 then return end + local nblock = f_len.value / 3 + + for i=1, nblock do + self:append( field.list(string.format("receiver[%d]", i), nil, function(self, ba) + self:append( field.uint32("ssrc", swap_endian, nil, fh.mkbrief_v("SSRC")) ) + self:append( field.uint32("last_rr", swap_endian, nil, fh.mkbrief_v("LRR")) ) + self:append( field.uint32("delay_since_last_rr", swap_endian, nil, fh.mkbrief_v("DLRR")) ) + end)) + end + end) + return f +end + +local function field_rtcp_xr_block_unknown(len) + local f = field.list("unknown", len, function(self, ba) + local swap_endian = true + self:append(field.uint8("block_type", fh.mkdesc(XR_BLOCK_TYPE_STR), fh.mkbrief_v("BT", XR_BLOCK_TYPE_STR))) + + self:append( field.string("unknown", len-1) ) + end) + return f + +end + +local dict_rtcp_xr_field = { + [XR_BLOCK_TYPE.LOSS_RLE] = nil, + [XR_BLOCK_TYPE.DUPLICATE_RLE] = nil, + [XR_BLOCK_TYPE.PACKET_RECEIPT_TIMES] = nil, + [XR_BLOCK_TYPE.RECEIVER_REFERENCE_TIME] = field_rtcp_xr_receiver_reference_time, + [XR_BLOCK_TYPE.DLRR] = field_rtcp_xr_dlrr, + [XR_BLOCK_TYPE.STATISTICS_SUMMARY] = nil, + [XR_BLOCK_TYPE.VOIP_METRICS] = nil, +} + +local function field_rtcp_xr(len) + local f = field.list("rtcp_xr", len, function(self, ba) + local pos = ba:position() + local xr = rtcp_xr_t.new() + self:append( field_rtcp_hdr(xr) ) + + local swap_endian = true + local f_ssrc = self:append( field.uint32("ssrc", swap_endian) ) + xr.ssrc = f_ssrc.value + + local remain = len - (ba:position() - pos) + + local block_type = ba:peek_uint8() + local fcb = dict_rtcp_xr_field[block_type] or field_rtcp_xr_block_unknown + self:append( fcb(remain) ) + + remain = len - (ba:position() - pos) + if remain > 0 then + self:append( field.string("unparsed", remain) ) + end + end, nil, fh.child_brief) + return f +end + +local dict_pt = { + [96] = field_rtp_video, + [107] = field_rtp_video, + + [192] = field_rtcp_fir, + [193] = field_rtcp_nack, + + [200] = field_rtcp_sr, + [201] = field_rtcp_rr, + [202] = field_rtcp_sdes, + [203] = field_rtcp_bye, + [204] = field_rtcp_app, + [205] = field_rtcp_rtpfb, + [206] = field_rtcp_psfb, + [207] = field_rtcp_xr, +} + +--handler( len ) +local function set_payload_handler( pt, handler ) + dict_pt[pt] = handler +end + +local function decode_rtp( ba, len, rtp_args ) + args = rtp_args + + if nil == codec_h264 then + codec_h264 = helper.get_codec("h264") + end + + local f = field.list("rtp", len, function(self, ba) + local pos = ba:position() + + local u16 = ba:peek_uint16() + local pt = (u16 >> 8) & 0x7F + local pt_rtcp = (u16 >> 8) & 0xFF + local f = dict_pt[pt] or dict_pt[pt_rtcp] + + if nil ~= f then + self:append( f(len) ) + else + --bi.log( string.format("cant find u16:0x%x pt:%s pt_rtcp:%s", u16, tostring(pt), tostring(pt_rtcp)) ) + local rtcp_hdr = rtcp_hdr_t.new() + self:append( field_rtcp_hdr(rtcp_hdr) ) + end + + local remain = len - (ba:position() - pos) + if remain > 0 then + self:append(field.string("remain", remain)) + end + end, nil, fh.child_brief) + + return f +end + +local function build_summary() +end + +local function clear() + if dict_stream_nalus then + dict_stream_nalus:clear() + end + dict_seq = {} +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "Rtp_Rtcp", + file_ext = "rtp rtcp", + decode = decode_rtp, + clear = clear, + + build_summary = build_summary, + + set_payload_handler = set_payload_handler, + + rtp_hdr_t = rtp_hdr_t, + field_rtp_hdr = field_rtp_hdr, + field_rtp_extension = field_rtp_extension, + field_rtp_h264 = field_rtp_h264, +} + +return codec diff --git a/src/scripts/codec/codec_yuv.lua b/src/scripts/codec/codec_yuv.lua new file mode 100644 index 0000000..2cdce67 --- /dev/null +++ b/src/scripts/codec/codec_yuv.lua @@ -0,0 +1,140 @@ +local field = require("field") +local helper = require("helper") +local fh = require("field_helper") + +--yuv 420p : yyyy....u..v.. +--yuv 420sp: yyyy....uv..uv.. + +local yuv_summary_t = class("yuv_summary_t") +function yuv_summary_t:ctor() + self.width = 0 + self.height = 0 + self.fmt = 0 + self.frame_size = 0 + self.frames = 0 +end + +local yuv_summary = nil + +local function field_yuv420p(index, w, h, frame_size) + + local y_size = w*h + local u_size = w*h/4 + local v_size = u_size + local f = field.list(string.format("✓ [%d]frame", index), frame_size, function(self, ba) + local f_y = self:append( field.string("✓ y", y_size )) + local f_u = self:append( field.string("✓ u", u_size )) + local f_v = self:append( field.string("✓ v", v_size )) + + f_y.cb_click = function(self) + bi.clear_bmp() + local data = self.parent:get_data() + bi.draw_yuv( TYUV_FORMAT["420P"], data, w, h, 0x001 ) + end + f_u.cb_click = function(self) + bi.clear_bmp() + local data = self.parent:get_data() + bi.draw_yuv( TYUV_FORMAT["420P"], data, w, h, 0x010 ) + end + f_v.cb_click = function(self) + bi.clear_bmp() + local data = self.parent:get_data() + bi.draw_yuv( TYUV_FORMAT["420P"], data, w, h, 0x100 ) + end + end) + + f.cb_click = function(self) + bi.clear_bmp() + + local data = self:get_data() + bi.draw_yuv( TYUV_FORMAT["420P"], data, w, h, 0x111 ) + --self.ffh:saveBmp(string.format("%s/d_%d.bmp", bi.get_tmp_dir(), f_nalu.index)) + end + + return f +end + +--name_WxH_420p.yuv +local function decode_yuv( ba, len, args ) + +-- local width = 320 +-- local height = 240 + +-- local width = 1280 +-- local height = 720 + + local width = 0 + local height = 0 + + --TODO probe width,height + + if args then + --parse width height from file name + local base_name = helper.get_base_name(args.fname) + local strs = helper.split(base_name, "_") + for i=#strs, 2, -1 do + local str = strs[i] + if string.find(str, "x") then + local solution = helper.split(str, "x") + width = tonumber(solution[1]) + height = tonumber(solution[2]) + else + --fmt + end + end + end + + if width <= 0 or height <= 0 then + --unknown width height + bi.message_box("unknown yuv width, height\nfile name format: filename_WxH.yuv\neg. test_320x240.yuv") + local f_yuv = field.list("yuv", len, function(self, ba) end) + return f_yuv + end + + yuv_summary = yuv_summary_t.new() + --420p + local frame_size = width * height * 3 / 2 + local frame_count = len / frame_size + bi.log(string.format("decode_yuv %dx%d frame_size:%d frame_count:%d", width, height, frame_size, frame_count)) + local f_yuv = field.list("yuv", len, function(self, ba) + + local index = 0 + while ba:length() > 0 do + self:append( field_yuv420p(index, width, height, frame_size) ) + index = index + 1 + end + + yuv_summary.frames = index + end) + + yuv_summary.width = width + yuv_summary.height= height + yuv_summary.fmt = 0 + yuv_summary.frame_size = frame_size + + return f_yuv +end + +local function clear() +end + +local function build_summary() + if nil == yuv_summary then return end + + bi.append_summary("width", yuv_summary.width) + bi.append_summary("height", yuv_summary.height) + bi.append_summary("fmt", "420p") + bi.append_summary("frame_size", yuv_summary.frame_size) + bi.append_summary("frames", yuv_summary.frames) +end + +local codec = { + authors = { {name="fei", mail="bin_inspector@163.com"} }, + file_desc = "yuv", + file_ext = "yuv", + decode = decode_yuv, + clear = clear, + build_summary = build_summary, +} + +return codec diff --git a/src/scripts/field.lua b/src/scripts/field.lua new file mode 100644 index 0000000..15291f8 --- /dev/null +++ b/src/scripts/field.lua @@ -0,0 +1,811 @@ +require("class") +local helper = require("helper") + +local FieldType = { + kBits = "bits", + kUbits = "ubits", + kUEbits = "uebits", + + kUint8 = "uint8", + kUint16 = "uint16", + kUint24 = "uint24", + kUint32 = "uint32", + kUint64 = "uint64", + kInt8 = "int8", + kInt16 = "int16", + kInt24 = "int24", + kInt32 = "int32", + kInt64 = "int64", + kFloat = "float", + kDouble = "double", + kString = "string", + + kBitArray = "bit_array", --fixed bit fields + kBitList = "bit_list", --dynamic bit fields array + + kArray = "array", --fiexed byte fields + kList = "list", --dynamic byte fields array + + kCallback = "callback", --custom read function + kSelect = "select", --select a field if cond is true +} + +---------------------------------------------------------------- +local field_t = class("field_t") +function field_t:ctor(type, len, name, cb_desc, cb_brief) + self.type = type + self.name = name + + self.len = len or 0 + self.value = nil + self.children = nil + + self.offset = -1 + self.ba = nil + + self.cb_child_brief = nil + self.cb_click = nil + + self.cb_context_menu = nil --right click self or parent, will show children's context menu + self.cb_private_context_menu = nil --right click self item to show this context menu + + self.bg_color = nil --back ground color #FFFFFF + self.fg_color = nil --font color + + if cb_brief then self.get_brief = cb_brief end + if cb_desc then self.get_desc = cb_desc end +end + +--handler(self, data, data_len) +function field_t:set_click( handler ) + self.cb_click = handler +end + +--handler(self, menu) +function field_t:set_context_menu( handler ) + self.cb_context_menu = handler +end + +--handler(self, menu) +function field_t:set_private_context_menu( handler ) + self.cb_private_context_menu = handler +end + +function field_t:set_bg_color( c ) + self.bg_color = c +end + +function field_t:set_fg_color( c ) + self.fg_color = c +end + +--ba: byte_array +function field_t:read(ba) + self.offset = ba:position() + + if nil == self.ba then + self.ba = ba + end + + if self.is_bits then + local nbits = ba:bit_offset() + ba:bit_remain() + self.offset = self.offset - math.floor(nbits/8) + self.bit_offset = ba:bit_offset() + end +end + +function field_t:get_byte_offset() + local v = self.offset + if self.is_bits then + v = v + math.floor(self.bit_offset/8) + end + return v +end + +function field_t:get_byte_len() + if self.is_bits then + return math.ceil(self.len/8) + end + return self.len +end + +function field_t:set_raw_data(data) + local n = #data + local ba = byte_stream(n) + ba:set_data(data, n) + self.ba = ba +end + +function field_t:get_data() + local s = self:get_byte_offset() + local nbyte = self:get_byte_len() + local data = self.ba:peek_bytes_from(s, nbyte) + return data +end + +local function setup_menu( field, menu ) + if field.cb_private_context_menu then + field:cb_private_context_menu(menu) + end + if field.cb_context_menu then + field:cb_context_menu(menu) + end + + if field.selected_field then + setup_menu( field.selected_field, menu ) + end + + if nil == field.children then return end + for _, child in ipairs(field.children) do + setup_menu(child, menu) + end +end + +local function setup_menu_save_bin( field, menu ) + if field:get_byte_len() <= 0 then return end + + menu:add_action("save bin", function() + + local fp = helper.ask_save(string.format("%s/%s.bin", bi.get_tmp_dir(), field.name)) + if nil == fp then return end + + local data = field:get_data() + + fp:write( data ) + fp:close() + end) +end + +local g_dump_node = nil +function field_t:dump_range() + if self.len <= 0 then + return + end + + local s = self:get_byte_offset() + local e = s+self:get_byte_len() + + if g_dump_node ~= self.dump_node then + local nbyte = self:get_byte_len() + bi.dump_ba(self.ba) + g_dump_node = self.dump_node + end + + bi.dump_set_range(s, e) + bi.show_status( string.format("range:[%d,%d) len:%d", s, e, e-s) ) +end + +function field_t:setup_context_menu( node, cb_hander ) + + node:reg_handler_context_menu(function(menu) + --bi.log(string.format("on lua context_menu %s", self.name)) + if nil == self.parent then + --root ignore children menu + return + end + + if cb_hander then + cb_hander(self, menu) + end + + setup_menu_save_bin( self, menu ) + + --setup children menu + setup_menu(self, menu) + end) +end + +function field_t:build_tree() +-- bi.log(string.format("type:%s name:%s ", self.type, self.name)) +--[[ + if self.len > 0 then + bi.log(string.format("type:%s name:%s len:%d range:[%d,%d)", self.type, self.name, self.len, self.offset, self.offset+self.len)) + else + bi.log(string.format("type:%s name:%s range:[%d,??)", self.type, self.name, self.offset, self.offset)) + end +--]] + + if self.get_desc and type(self.get_desc) ~= "function" then + bi.message_box(string.format("field:%s %s self.get_desc need be function, get: %s", self.type, self.name, type(self.get_desc))) + return + end + + local desc = self:get_desc() + if nil == desc then + desc = string.format("ERROR: [%s] desc is nil", self.name) + bi.log(desc) + end + local node = bi.create_tree_item( desc ) + if self.children then + for _, v in ipairs(self.children) do + local c = v:build_tree() + node:addChild(c) + end + end + + if self.bg_color then + node:set_bg_color( self.bg_color ) + end + if self.fg_color then + node:set_fg_color( self.fg_color ) + end + + node:reg_handler_click(function() + if self.cb_click then + self:cb_click() + end + + self:dump_range() + end) + + self:setup_context_menu( node ) + + return node +end + + +---------------------------------------------------------------- +local field_bit_base_t = class("field_bit_base_t", field_t) +function field_bit_base_t:ctor(type, name, nbit, cb_desc, cb_brief) + nbit = nbit or 1 + field_t.ctor(self, type, nbit, name, cb_desc, cb_brief) + + self.is_bits = true + self.is_fixed_bits = true +end + +function field_bit_base_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_bits(self.len) + return self.value +end + +function field_bit_base_t:get_desc() + return string.format("[%2d bits] %s: %d", self.len, self.name, self.value) +end + +---------------------------------------------------------------- +local field_bits_t = class("field_bits_t", field_bit_base_t) +function field_bits_t:ctor(name, nbit, cb_desc, cb_brief) + field_bit_base_t.ctor(self, FieldType.kBits, name,nbit, cb_desc, cb_brief) +end + +---------------------------------------------------------------- +local field_ubits_t = class("field_ubits_t", field_bit_base_t) + +function field_ubits_t:ctor(name, nbit, cb_desc, cb_brief) + field_bit_base_t.ctor(self, FieldType.kUbits, name, nbit, cb_desc, cb_brief) +end + +function field_ubits_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_ubits(self.len) + return self.value +end + +---------------------------------------------------------------- +local field_uebits_t = class("field_uebits_t", field_bit_base_t) + +function field_uebits_t:ctor(name, cb_desc, cb_brief) + field_bit_base_t.ctor(self, FieldType.kUEbits, name, 0, cb_desc, cb_brief) + self.is_fixed_bits = false +end + +function field_uebits_t:read(ba) + field_t.read(self, ba) + self.value, self.len = ba:read_uebits() + return self.value +end + +---------------------------------------------------------------- +local field_sebits_t = class("field_sebits_t", field_bit_base_t) + +function field_sebits_t:ctor(name, cb_desc, cb_brief) + field_bit_base_t.ctor(self, FieldType.kSEbits, name, 0, cb_desc, cb_brief) + self.is_fixed_bits = false +end + +function field_sebits_t:read(ba) + field_t.read(self, ba) + self.value, self.len = ba:read_sebits() + return self.value +end + +---------------------------------------------------------------- +local field_string_t = class("field_string_t", field_t) +function field_string_t:ctor(name, len, cb_desc, cb_brief) + field_t.ctor(self, FieldType.kString, len, name, cb_desc, cb_brief) +end + +function field_string_t:read(ba, read_len) + field_t.read(self, ba) + if read_len == nil or read_len <= 0 then + read_len = self.len + end + if read_len == nil or read_len <= 0 then + read_len = ba:length() + end + + if read_len < 32 then + self.value = ba:read_bytes(read_len) + else + ba:skip_bytes(read_len) + self.value = self.get_data + end + self.len = read_len + return self.value +end + +function field_string_t:get_desc() + return string.format("%s: %d bytes", self.name, self.len) +end + +---------------------------------------------------------------- +local field_number_t = class("field_number_t", field_t) +function field_number_t:ctor(type, len, name, swap_endian, cb_desc, cb_brief) + field_t.ctor(self, type, len, name, cb_desc, cb_brief) + + self.swap_endian = (swap_endian == true) +end + +function field_number_t:get_desc() + return string.format("%s %s: %d", self.type, self.name, self.value) +end + +---------------------------------------------------------------- +local field_uint8_t = class("field_uint8_t", field_number_t) +function field_uint8_t:ctor(name, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kUint8, 1, name, nil, cb_desc, cb_brief) +end + +function field_uint8_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_uint8() + return self.value +end + +function field_uint8_t:get_desc() + return string.format("%s %s: %u", self.type, self.name, self.value) +end + +---------------------------------------------------------------- +local field_uint16_t = class("field_uint16_t", field_uint8_t) +function field_uint16_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kUint16, 2, name, swap_endian, cb_desc, cb_brief) +end + +function field_uint16_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_uint16(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_uint24_t = class("field_uint24_t", field_uint8_t) +function field_uint24_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kUint24, 3, name, swap_endian, cb_desc, cb_brief) +end + +function field_uint24_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_uint24(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_uint32_t = class("field_uint32_t", field_uint8_t) +function field_uint32_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kUint32, 4, name, swap_endian, cb_desc, cb_brief) +end + +function field_uint32_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_uint32(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_uint64_t = class("field_uint64_t", field_uint8_t) +function field_uint64_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kUint64, 8, name, swap_endian, cb_desc, cb_brief) +end + +function field_uint64_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_uint64(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_double_t = class("field_double_t", field_number_t) +function field_double_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kDouble, 8, name, swap_endian, cb_desc, cb_brief) +end + +function field_double_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_double(self.swap_endian) + return self.value +end + +function field_double_t:get_desc() + return string.format("%s %s: %.2f", self.type, self.name, self.value) +end + + +---------------------------------------------------------------- +local field_int8_t = class("field_int8_t", field_number_t) +function field_int8_t:ctor(name, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kInt8, 1, name, nil, cb_desc, cb_brief) +end + +function field_int8_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_int8() + return self.value +end + +---------------------------------------------------------------- +local field_int16_t = class("field_int16_t", field_number_t) +function field_int16_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kInt16, 2, name, swap_endian, cb_desc, cb_brief) +end + +function field_int16_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_int16(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_int24_t = class("field_int24_t", field_number_t) +function field_int24_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kInt24, 3, name, swap_endian, cb_desc, cb_brief) +end + +function field_int24_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_int24(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_int32_t = class("field_int32_t", field_number_t) +function field_int32_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kInt32, 4, name, swap_endian, cb_desc, cb_brief) +end + +function field_int32_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_int32(self.swap_endian) + return self.value +end + +---------------------------------------------------------------- +local field_int64_t = class("field_int64_t", field_number_t) +function field_int64_t:ctor(name, swap_endian, cb_desc, cb_brief) + field_number_t.ctor(self, FieldType.kInt64, 8, name, swap_endian, cb_desc, cb_brief) +end + +function field_int64_t:read(ba) + field_t.read(self, ba) + self.value = ba:read_int64(self.swap_endian) + return self.value +end + +function field_int64_t:get_desc() + return string.format("%s %s: %d", self.type, self.name, self.value) +end + +---------------------------------------------------------------- +local field_collection_t = class("field_collection_t", field_t) +function field_collection_t:ctor(type, name, len, children, cb_desc, cb_brief) + field_t.ctor(self, type, len, name, cb_desc, cb_brief) + self.children = children or {} +end + +function field_collection_t:get_len() + return self.len +end + +function field_collection_t:get_desc() + local child_brief = self:get_child_brief() + if child_brief then + return string.format("%s %slen:%d ", self.name, child_brief, self.len) + end + + return string.format("%s len:%d", self.name, self.len) +end + +function field_collection_t:get_child_brief() + if nil == self.children then return nil end + local brief = nil + for _, v in ipairs(self.children) do + if v.get_brief then + brief = (brief or "") .. (v:get_brief() or "") + end + end + + return brief +end + +---------------------------------------------------------------- +local field_list_t = class("field_list_t", field_collection_t) +function field_list_t:ctor(name, len, cb_read, cb_desc, cb_brief) + field_collection_t.ctor(self, FieldType.kList, name, len, children, cb_desc, cb_brief) + self.cb_read = cb_read +end + +function field_list_t:read(ba) + field_t.read(self, ba) + self.children = {} + +-- self.ba = ba + + if self.cb_read then + self:cb_read(ba) + end + + if self.len <= 0 then + for _, v in ipairs(self.children) do + self.len = self.len + v.len + end + end + +-- self.ba = nil +end + +function field_list_t:append(field_child, ba) + if not field_child then return nil end + field_child.parent = self + field_child:read(ba or self.ba) + table.insert(self.children, field_child) + return field_child +end + +function field_list_t:append_list(children, ba) + if not children then return nil end + + for _, v in ipairs(children) do + self:append(v, ba) + end +end + + +local field_array_t = class("field_array_t", field_collection_t) +function field_array_t:ctor(name, len, children, cb_desc, cb_brief) + field_collection_t.ctor(self, FieldType.kArray, name, len, children, cb_desc, cb_brief) +end + +function field_array_t:read(ba) + field_t.read(self, ba) + if nil == self.children then return end + + if self.len <= 0 then + self.len = ba:length() + end + + for _, v in ipairs(self.children) do + v.parent = self + v:read(ba) + end + + return self.children +end + +---------------------------------------------------------------- +local field_bit_list_t = class("field_bit_list_t", field_collection_t) +function field_bit_list_t:ctor(name, len, cb_read, cb_desc, cb_brief) + field_collection_t.ctor(self, FieldType.kBitList, name, len, children, cb_desc, cb_brief) + self.cb_read = cb_read +end + +function field_bit_list_t:read(ba) + field_t.read(self, ba) + if nil == self.children then return nil end + if self.len > 0 then + ba:to_bit_space(self.len) + end + + local pos_byte = ba:position() + local pos = ba:bit_offset() + + if self.cb_read then + self:cb_read(ba) + end + + local nbits = ba:bit_offset() - pos + local nbyte = bi.bits2byte(nbits) + if self.len == 0 then + self.len = nbyte + end + self.total_bit = nbits + + local remain_bits = self.len*8 - nbits + if remain_bits > 0 then + ba:skip_bits(remain_bits) + elseif remain_bits < 0 then + local nread = math.ceil(nbits/8) + local over = nread - self.len + ba:back_pos(math.abs(over)) + bi.log_error(string.format("%s %s read over %d, byte_len:%d", self.type, self.name, over, self.len)) + ba:clear_bit_status() + end + + return self.children +end + +function field_bit_list_t:append(bit_field) + if not bit_field then return end + + bit_field.parent = self + bit_field:read(self.ba) + table.insert(self.children, bit_field) + return bit_field +end + +function field_bit_list_t:append_list(bit_fields) + if nil == bit_fields then return end + + for _, v in ipairs(bit_fields) do + self:append(v) + end +end + +function field_bit_list_t:get_desc() + local child_brief = self:get_child_brief() + if child_brief then + return string.format("%s [%d bytes %d bits] %s", self.name, self.len, self.total_bit, child_brief) + end + + return string.format("%s [%d bytes %d bits]", self.name, self.len, self.total_bit) +end + +function field_bit_list_t:get_child_brief() + if nil == self.children then return nil end + if self.cb_child_brief then return self:cb_child_brief() end + + local brief = nil + for _, v in ipairs(self.children) do + if v.get_brief then + brief = (brief or "") .. v:get_brief() + end + end + + return brief +end + +---------------------------------------------------------------- +local field_bit_array_t = class("field_bit_array_t", field_bit_list_t) +function field_bit_array_t:ctor(name, children, cb_desc, cb_brief) + field_collection_t.ctor(self, FieldType.kBitArray, name, nil, children, cb_desc, cb_brief) + + for _, v in ipairs(self.children) do + v.parent = self + end +end + +function field_bit_array_t:read(ba) + field_t.read(self, ba) + + local pos = ba:bit_offset() + + for _, v in ipairs(self.children) do + v:read(ba) + end + + self.total_bit = ba:bit_offset() - pos + self.len = bi.bits2byte(self.total_bit) + + return self.children +end + +---------------------------------------------------------------- +local field_callback_t = class("field_callback_t", field_t) +function field_callback_t:ctor(name, cb_read, cb_desc, cb_brief) + field_t.ctor(self, FieldType.kCallback, 0, name, cb_desc, cb_brief) + self.cb_read = cb_read +end + +function field_callback_t:get_desc() + return string.format("%s: %s", self.name, tostring(self.value)) +end + +function field_callback_t:read(ba) + field_t.read(self, ba) + if self.cb_read then + self.value, self.len = self:cb_read(ba) + if self.len <= 0 then + error(string.format("field_callback_t [%s %s] cb_read should return [value, len]", self.type, self.name)) + end + end + return self.value +end + +---------------------------------------------------------------- +--[[ + cb_selector = function() if xxx == 1 then return f end end +--]] +local field_select_t = class("field_select_t", field_t) +function field_select_t:ctor(name, len, cb_selector, cb_desc, cb_brief) + field_t.ctor(self, FieldType.kSelect, len, name, cb_desc, cb_brief) + self.cb_selector = cb_selector + + self.selected_field = nil +end + +function field_select_t:get_desc() + if nil == self.selected_field then return string.format("%s:??", self.name) end + return self.selected_field:get_desc() +end + +function field_select_t:get_brief() + if nil == self.selected_field then return string.format("%s:??", self.name) end + if self.selected_field.get_brief then return self.selected_field:get_brief() end + return "" +end + +function field_select_t:read(ba) + local f = nil + if self.cb_selector then + f = self:cb_selector(ba) + if f then + self.selected_field = f + f.len = self.len + end + end + + --default + if nil == f then + f = field_string_t.new(self.name, self.len, self.cb_desc, self.cb_brief) + end + + f.parent = self.parent + self.value = f:read(ba) + self.offset = f.offset + if self.len == 0 then + self.len = f.len + end + + self.selected_field = f + return self.value +end + +function field_select_t:build_tree() + if self.selected_field then + return self.selected_field:build_tree() + end +end + +local field = { + bits = field_bits_t.new, + ubits = field_ubits_t.new, + uebits = field_uebits_t.new, + sebits = field_sebits_t.new, + + uint8 = field_uint8_t.new, + uint16 = field_uint16_t.new, + uint24 = field_uint24_t.new, + uint32 = field_uint32_t.new, + uint64 = field_uint64_t.new, + + int8 = field_int8_t.new, + int16 = field_int16_t.new, + int24 = field_int24_t.new, + int32 = field_int32_t.new, + int64 = field_int64_t.new, + + double = field_double_t.new, + string = field_string_t.new, + + list = field_list_t.new, + array = field_array_t.new, + + bit_list = field_bit_list_t.new, + bit_array = field_bit_array_t.new, + + callback = field_callback_t.new, + select = field_select_t.new, +} + +return field diff --git a/src/scripts/field_helper.lua b/src/scripts/field_helper.lua new file mode 100644 index 0000000..b5f7858 --- /dev/null +++ b/src/scripts/field_helper.lua @@ -0,0 +1,102 @@ + +--type name: 123 (dict_value) +local function mkdesc( dict ) + local f = function(self) + local str = dict[self.value] or "??" + + if self.is_bits then + return string.format("[%2d bits] %s: %d (%s)", self.len, self.name, self.value, tostring(str)) + end + return string.format("%s %s: %d (%s)", self.type, self.name, self.value, str) + end + return f +end + +--type name: 0xXXX (dict_value) +local function mkdesc_x( dict ) + local f = function(self) + local str = dict[self.value] or "??" + + if self.is_bits then + return string.format("[%2d bits] %s: 0x%X (%s)", self.len, self.name, self.value, tostring(str)) + end + return string.format("%s %s: 0x%X (%s)", self.type, self.name, self.value, str) + end + return f +end + + +local function str_desc(self) + return string.format("%s: %s", self.name, self.value) +end + +--name:dict_value +local function mkbrief(name, dict) + local f = function(self) + name = name or self.name + local sname = "" + if name ~= "" then + sname = string.format("%s:", name) + end + if dict then + local str = dict[self.value] or "??" + return string.format("%s%s ", sname, tostring(str)) + end + return string.format("%s%s ", sname, tostring(self.value)) + end + return f +end + +--name:123(dict_value) +local function mkbrief_v( name, dict ) + local f = function(self) + name = name or self.name + local sname = "" + if name ~= "" then + sname = string.format("%s:", name) + end + if dict then + local str = dict[self.value] or "??" + return string.format("%s%s(%s) ", sname, tostring(self.value), tostring(str)) + end + return string.format("%s%s ", sname, tostring(self.value)) + end + return f +end + +--name:0x123(dict_value) +local function mkbrief_x( name, dict ) + local f = function(self) + name = name or self.name + local sname = "" + if name ~= "" then + sname = string.format("%s:", name) + end + if dict then + local str = dict[self.value] or "??" + return string.format("%s0x%X(%s) ", sname, self.value, tostring(str)) + end + return string.format("%s0x%X ", sname, self.value) + end + return f +end + + + +local function child_brief(self) + return self:get_child_brief() +end + +local t = { + str_desc = str_desc, + + mkdesc = mkdesc, + mkdesc_x = mkdesc_x, + + mkbrief = mkbrief, + mkbrief_v = mkbrief_v, + mkbrief_x = mkbrief_x, + + child_brief = child_brief, +} +return t diff --git a/src/scripts/helper.lua b/src/scripts/helper.lua new file mode 100644 index 0000000..fa2d48f --- /dev/null +++ b/src/scripts/helper.lua @@ -0,0 +1,447 @@ +require("class") +local mri = require("MemoryReferenceInfo") +mri.m_cConfig.m_bAllMemoryRefFileAddTime = false + +local function dump_memory(path, name, obj) + assert( nil ~= path ) + + collectgarbage("collect") + if nil == name then + mri.m_cMethods.DumpMemorySnapshot(path, nil, -1) + return + end + assert( nil ~= obj ) + + mri.m_cMethods.DumpMemorySnapshot(path, nil, -1, name, obj) +end + +-- /a/b/c.txt => c.txt +local function get_file_name(file) + return file:match("^.+/(.+)$") +end + +-- /a/b/c/abc.txt => /a/b/c/abc +local function get_base_name(str) + return str:match("(.+)%..+") +end + +local function get_file_ext(str) + local ext = str:match("^.+(%..+)$") + return ext +end + +local function ms2time(v, timescale) + timescale = timescale or 1000 + local msv = math.floor(v / timescale * 1000) + local ms = msv % 1000 + local sec = math.floor(msv / 1000) + local s = math.floor(sec % 60) + local m = math.floor(sec / 60 % 60) + local h = math.floor(sec / 60 / 60 % 24) + return string.format("%02d:%02d:%02d.%03d", h, m, s, ms) +end + +local function ms2date(v) + local ms = v % 1000 + local sday = os.date("%Y-%m-%d %H:%M:%S", math.floor(v/1000)) + return string.format("%s.%03d", sday, ms) +end + + +local K = 1024 +local M = K * K +local G = K * M +local function size_format(v) + if v < K then + return string.format("%d", v) + end + if v < M then + return string.format("%.3fKB", v/K) + end + if v < G then + return string.format("%.3fMB", v/M) + end + return string.format("%.3fGB", v/G) +end + +local function n2ip( n ) + local a = (n >> 24) & 0xFF + local b = (n >> 16) & 0xFF + local c = (n >> 8) & 0xFF + local d = n & 0xFF + + return string.format("%d.%d.%d.%d", a, b, c, d) +end + +local function split(str, sep) + local sep, fields = sep or "\t", {} + local pattern = string.format("([^%s]+)", sep) + str:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +local function read_file(path) + local file = io.open(path, "rb") -- r read mode and b binary mode + if not file then return nil end + local content = file:read "*a" -- *a or *all reads the whole file + file:close() + return content +end + +local function ask_save(name) + local file = bi.save_as(name) + if nil == file or "" == file then return nil end + + local fp = io.open(file, "wb") + if nil == fp then + bi.message_box(string.format("cant save file to:%s", file)) + return nil + end + return fp +end + +local dict_codec = {} + +local function register_codec(script, codec) + assert( nil ~= codec ) + assert( nil ~= codec.file_ext ) --"ext0 ext1" + assert( nil ~= codec.decode ) + + --"ext0 ext1" => (*.ext0 *.ext1)" + local desc = codec.file_desc + local arr_ext = split(codec.file_ext, " ") + local ext = nil + for _, e in ipairs(arr_ext) do + if ext == nil then + ext = string.format(" *.%s", e) + desc = desc or string.format("%s file", string.upper(e)) + else + ext = ext .. string.format(" *.%s", e) + end + + if nil ~= dict_codec[e] then + --error(string.format("register_codec repeated: %s %s", codec.file_ext, e)) + bi.log(string.format("!!warning!! register_codec repeated, skip this one: %s.lua [%s] %s", script, codec.file_ext, e)) + return -1 + end + + dict_codec[e] = codec + end + + if ext ~= " *.*" then + bi.reg_supported_file_ext(string.format("%s (%s)", desc, ext)) + end + + bi.log(string.format("register codec %s.lua [%s]", script, codec.file_ext)) + return 0 +end + +local function get_codec(ext) + local def = dict_codec['*'] + if nil == ext then + return def + end + + local codec = dict_codec[ext] + if nil == codec then + return def + --error(string.format("cant find codec with ext: [%s]", ext)) + end + + return codec +end + +local function main_list_context_menu(field, menu) + + local mlist = bi.main_list() + local ncount = mlist:selected_count() + if ncount <= 1 then return end + + menu:add_action("save selected bin", function() + + local file = bi.save_as("%s/selected.bin", bi.get_tmp_dir()) + if nil == file or "" == file then return nil end + + mlist:save_selected(file) + end) + +end + +local function build_main_list(field) + local mlist = bi.main_list() + + local fields = nil + if nil ~= field.children and #field.children > 0 then + fields = field.children + else + fields = { field } + end + + for i, f in ipairs(fields) do + local node = mlist:append( f:get_desc() ) + + f.dump_node = f + + local s = f:get_byte_offset() + local n = f:get_byte_len() + node:set_byte_stream( f.ba, s, n ) + + if nil ~= f.fg_color then + node:set_fg_color( f.fg_color ) + end + if nil ~= f.bg_color then + node:set_bg_color( f.bg_color ) + end + + node:reg_handler_click(function() + + if f.cb_click then + f:cb_click() + end + + f:dump_range() + + local mt = bi.main_tree() + mt:clear() + + if nil ~= f.children and #f.children > 0 then + for j, ft in ipairs(f.children) do + local node_tree = ft:build_tree() + mt:addChild(node_tree) + end + --node:setExpanded(true) + end + end) + + f:setup_context_menu(node, main_list_context_menu) + end +end + +local function build_authors(codec) + local tw = bi.create_summary_table("Authors") + local tw_hdr = { "name", "mail" } + local tw_ncol = #tw_hdr + tw:set_column_count(tw_ncol) + + for i=1, tw_ncol do + tw:set_header(i-1, tw_hdr[i] ) + end + + if not codec.authors then return end + + for _, author in ipairs(codec.authors) do + local name = author.name or "" + local mail = author.mail or "" + + if name ~= "" or mail ~= "" then + tw:append_empty_row() + + tw:set_last_row_column( 0, name ) + tw:set_last_row_column( 1, mail ) + end + end +end + +local function do_decode_file(file) + local ext = get_file_ext(file) + if nil ~= ext then + local len = #ext + ext = string.sub( ext, -(len-1) ) + end + + bi.clear() + + for k, codec in pairs(dict_codec) do + if codec.clear then + codec.clear() + end + end + + local codec = get_codec(ext) + if nil == codec then return -1 end + local data = read_file(file) + + if nil == data then + error(string.format("cant read file [%s]", file)) + return -1 + end + + local tm_start = bi.now_ms() + + local fsize = #data + bi.append_summary("size", string.format("%d (%s)", fsize, size_format(fsize) )) + + if fsize <= 0 then + return + end + + local ba = byte_stream(fsize) + ba:set_data(data, fsize) + + bi.set_progress_max(fsize) + + local args = { + fname=file, + } + local field = codec.decode( ba, fsize, args ) + if nil == field then + error(string.format("decode_data field == nil, file:[%s]", file)) + return -1 + end + + field:read(ba) + + build_main_list(field) + + if fsize > 0 and codec.build_summary then + codec.build_summary() + end + + --build author + build_authors(codec) + + local tm_end = bi.now_ms() + local elapse = tm_end - tm_start + bi.log(string.format("decode cost %dms, %s", elapse, file)) + bi.show_status(string.format("decode cost %dms (%s) %s", elapse, ms2time(elapse), file)) + +--[[ + local node = field:build_tree() + local mt = bi.main_tree() + mt:addChild(node) + node:setExpanded(true) +--]] + + bi.adjust_table_header_width() + + return 0 +end + +local function decode_file(file) + do_decode_file(file) + collectgarbage("collect") + +-- dump_memory(string.format("%s", bi.get_tmp_dir())) + return 0 +end + +local function decode_data(ext, ba, len) + local codec = get_codec(ext) + if nil == codec then return -1 end + + local field = codec.decode( ba, len ) + if nil == field then + error(string.format("decode_data field == nil, ext:[%s]", ext)) + end + return field +end + +local function dump_find_all(str, len) + if len >= 4 then + bi.log(string.format("dump_find_all %d 0x%02X 0x%02X 0x%02X 0x%02X", len, str:byte(1), str:byte(2), str:byte(3), str:byte(4))) + else + bi.log(string.format("dump_find_all %d ", len)) + end + + local wlist = bi.find_result_widget() + wlist:clear() + bi.show_find_result() + + local pos = 0 + local index = 0 + while pos ~= -1 do + + pos = bi.find_next(pos, str, len) + if pos == -1 then break end + +-- bi.log(string.format("find in pos %d", pos)) + local node = wlist:append( string.format("[%d] %d", index, pos) ) + + local s = pos + local e = pos + len + + node:reg_handler_click(function() + bi.dump_set_range(s, e) + bi.show_status( string.format("range:[%d,%d) len:%d", s, e, e-s) ) + end) + + index = index + 1 + pos = pos + len + end +end + +local function load_codec_dir(path) + bi.log(string.format("load_codec from %s", path)) + + local files = list_dir(path) + if nil == files then + error(string.format("load_codec list_dir returns nil, path:%s", path)) + return -1 + end + + for _, file in ipairs(files) do + local ext = get_file_ext(file) + if ext == ".lua" then + local fname = get_base_name(file) + local codec = require(fname) + register_codec( fname, codec ) + end + end +end + +local function load_private_scripts_dir(path) + local files = list_dir(path) + if nil == files then + error(string.format("load_private_scripts_dir list_dir returns nil, path:%s", path)) + return -1 + end + + for _, file in ipairs(files) do + local ext = get_file_ext(file) + if ext == ".lua" then + local fname = get_base_name(file) + require(fname) + end + end +end + +local function init() + + bi.log(string.format("version: %s", bi.version())) + + bi.reg_handler_decode_data( decode_data ) + bi.reg_handler_decode_file( decode_file ) + + bi.reg_handler_dump_find_all( dump_find_all ) + + local res_path = bi.get_res_path() + + load_private_scripts_dir(string.format("%s", bi.get_private_scripts_path())) + + load_codec_dir(string.format("%s", bi.get_private_codec_path())) + load_codec_dir(string.format("%s/scripts/codec", res_path)) +end + +local t = { + init = init, + + ms2time = ms2time, + ms2date = ms2date, + + get_base_name = get_base_name, + get_file_ext = get_file_ext, + split = split, + + size_format = size_format, + n2ip = n2ip, + + read_file = read_file, + ask_save = ask_save, + + get_codec = get_codec, + + decode_file = decode_file, + decode_data = decode_data, +} + +return t diff --git a/src/scripts/main.lua b/src/scripts/main.lua new file mode 100644 index 0000000..f5715ab --- /dev/null +++ b/src/scripts/main.lua @@ -0,0 +1,8 @@ +local helper = require("helper") +local field = require("field") + +print(package.path) + +helper.init() + +--require("testcase") diff --git a/src/scripts/nalu_buf.lua b/src/scripts/nalu_buf.lua new file mode 100644 index 0000000..0b6238b --- /dev/null +++ b/src/scripts/nalu_buf.lua @@ -0,0 +1,92 @@ +require("class") + +nalu_buf_t = class("nalu_buf_t") +function nalu_buf_t:ctor() + self.field_ebsps = {} +end + +function nalu_buf_t:append( seq, field_ebsp, i_nalu_header, is_new_nal ) + local old = self.field_ebsps[seq] + if nil ~= old then + return + end + + local t = { + field_ebsp = field_ebsp, + i_nalu_header = i_nalu_header, + is_new_nal = is_new_nal + } + self.field_ebsps[seq] = t +end + + +function nalu_buf_t:clear() + self.field_ebsps = {} +end + +function nalu_buf_t:save(file) + + local fp = io.open(file, "wb") + if nil == fp then + bi.message_box(string.format("cant save file to:%s", file)) + return + end + + local seqs = {} + for k, _ in pairs(self.field_ebsps) do + table.insert( seqs, k ) + end + table.sort(seqs, function(a, b) return a < b end) + + for i, seq in ipairs(seqs) do + local t = self.field_ebsps[seq] + + local ebsp = t.field_ebsp:get_data() + local nalu = nil + if true == t.is_new_nal then + local sig = nil + if i == 1 then + sig = string.pack("I4", 0x1000000) + else + sig = string.pack("I3", 0x10000) + end + nalu = sig .. string.pack("I1", t.i_nalu_header) .. ebsp + else + nalu = ebsp + end + + fp:write(nalu) + end + + fp:close() +end + +dict_stream_nalus_t = class("dict_stream_nalus_t") +function dict_stream_nalus_t:ctor() + self.stream_nalus = {} + self.nstream = 0 +end + +--key: ip_port_ssrc +function dict_stream_nalus_t:get(key) + local stream = self.stream_nalus[key] + if stream then + return stream + end + + stream = nalu_buf_t.new() + self.stream_nalus[key] = stream + self.nstream = self.nstream + 1 + return stream +end + +function dict_stream_nalus_t:clear() + for k, v in pairs(self.stream_nalus) do + v:clear() + end + + self.stream_nalus = {} + self.nstream = 0 +end + + diff --git a/src/scripts/pcap_port_parser.lua b/src/scripts/pcap_port_parser.lua new file mode 100644 index 0000000..d95c158 --- /dev/null +++ b/src/scripts/pcap_port_parser.lua @@ -0,0 +1,14 @@ + +--port parse as codec +local dict_codec = { +-- [80] = "http", +-- [8080] = "http", + + [21234] = "rtp", + [4011] = "rtp", + + [40310] = "rtp", + [41635] = "rtp", +} + +return dict_codec \ No newline at end of file diff --git a/src/scripts/testcase/test_bit_stream.lua b/src/scripts/testcase/test_bit_stream.lua new file mode 100644 index 0000000..b3fd462 --- /dev/null +++ b/src/scripts/testcase/test_bit_stream.lua @@ -0,0 +1,48 @@ +local function test_bit_stream(bytes, nbits) + + local data = nil + + for _, v in ipairs(bytes) do + data = (data or "") .. string.pack(" nbits do + + local v = ba:read_ubits(nbits) + + bi.log(string.format("0x%X", v)) + len = ba:length() + end +end + +local function test_uebits() + +-- local data = string.pack(" %u, nbits:%d", v, nbits)) +end + + +local tmp_data = { + 0x12, 0x34, 0x56, 0x78, 0xAB, 0xCD, 0xEF +} + +local nbits = 4 +test_bit_stream(tmp_data, nbits) + +local nbits = 2 +--test_bit_stream(tmp_data, nbits) + +test_uebits() diff --git a/src/scripts/testcase/test_byte_stream.lua b/src/scripts/testcase/test_byte_stream.lua new file mode 100644 index 0000000..5ee4510 --- /dev/null +++ b/src/scripts/testcase/test_byte_stream.lua @@ -0,0 +1,41 @@ + +local function test_byte_stream() + local bb = byte_stream(0) + local str = "hello" + local len = #str + bb:set_data("hello", len) + + local a = bb:read_uint8() + bi.log("read_uint8:" .. string.char(a)) + + local c = bb:read_bytes(2) + local c0 = c:sub(1, 2) + bi.log("read_bytes c:" .. c .. " " .. c0) + bi.log( string.char(c:byte(1)) .. "=" .. c:byte(1) ) + bi.log( string.char(c:byte(2)) .. "=" .. c:byte(2) ) + + + bi.log( string.char(str:byte(1)) .. "=" .. str:byte(1) ) + bi.log( string.char(str:byte(2)) .. "=" .. str:byte(2) ) + + + local ba = byte_stream(0) + local data = string.pack(": sets big endian +=: sets native endian +![n]: sets maximum alignment to n (default is native alignment) +b: a signed byte (char) +B: an unsigned byte (char) +h: a signed short (native size) +H: an unsigned short (native size) +l: a signed long (native size) +L: an unsigned long (native size) +j: a lua_Integer +J: a lua_Unsigned +T: a size_t (native size) +i[n]: a signed int with n bytes (default is native size) +I[n]: an unsigned int with n bytes (default is native size) +f: a float (native size) +d: a double (native size) +n: a lua_Number +cn: a fixed-sized string with n bytes +z: a zero-terminated string +s[n]: a string preceded by its length coded as an unsigned integer with n bytes (default is a size_t) +x: one byte of padding +Xop: an empty item that aligns according to option op (which is otherwise ignored) +' ': (empty space) ignored +--]] +local function test_field() + local mt = bi.main_tree() + + local buf = byte_stream(512) + + local values = { + 0x12, 0x1234, 0x2BCDEF, 0x01234567, 0x0123456789ABCDEF, + 0x21, 0x3412, 0xFEDCBA, 0x76543210, 0xABCDEF0123456789, + "hello" + } + + local data = string.pack(" 0x%X", values[1], f_i8:read(buf) )) + bi.log(string.format("i16: 0x%X => 0x%X", values[2], f_i16:read(buf) )) + bi.log(string.format("i24: 0x%X => 0x%X", values[3], f_i24:read(buf) )) + bi.log(string.format("i32: 0x%X => 0x%X", values[4], f_i32:read(buf) )) + bi.log(string.format("i64: 0x%X => 0x%X", values[5], f_i64:read(buf) )) + + bi.log(string.format("u8: 0x%X => 0x%X", values[6], f_u8:read(buf) )) + bi.log(string.format("u16: 0x%X => 0x%X", values[7], f_u16:read(buf) )) + bi.log(string.format("u24: 0x%X => 0x%X", values[8], f_u24:read(buf) )) + bi.log(string.format("u32: 0x%X => 0x%X", values[9], f_u32:read(buf) )) + bi.log(string.format("u64: 0x%X => 0x%X", values[10], f_u64:read(buf) )) + + local str_len = f_str_len:read(buf) + bi.log("str len:" .. str_len) + bi.log("str:" .. f_str:read(buf, str_len) ) + end + + test_parse_data( buf, false ) + test_parse_data( buf, true ) +end + +local function test_field_bit_array() + local data = string.pack("B", 0x67) + + local bb = byte_stream(10) + bb:set_data(data, #data) + + local f_bit_array = field.bit_array("0x67", { + field.ubits("nal_unit_type", 5), + field.ubits("nal_reference_idc", 2), + field.ubits("forbidden_bit", 1), + }) + + f_bit_array:read(bb) + + bi.log(f_bit_array:get_desc()) +end + +test_field_bit_array() +test_field() diff --git a/src/scripts/testcase/test_h264.lua b/src/scripts/testcase/test_h264.lua new file mode 100644 index 0000000..4e3d978 --- /dev/null +++ b/src/scripts/testcase/test_h264.lua @@ -0,0 +1,87 @@ +local helper = require("helper") +local h264 = require("codec_h264") + +local function decode_h264(data, len) + + local ba = byte_stream(len) + ba:set_data(data, len) + + local root = h264.decode(ba) + + local mt = bi.main_tree() + mt:addChild(root) + + root:setExpanded(true) +-- mt:expandAll() +end + +local function test_find_nalu_from_file() +-- local data = helper.read_file("/Users/tpf/vm/software/learn_go/minirtp/res/Big_Buck_Bunny_360_10s_1MB.h264") +-- local data = helper.read_file("/Users/tpf/vm/software/learn_go/minirtp/res/2001.h264") +-- decode_h264(data, len) + + helper.decode_file("/Users/tpf/vm/software/learn_go/minirtp/res/Big_Buck_Bunny_360_10s_1MB.h264") +end + + +local function test_find_nalu(bytes) + local data = "" + for _, v in ipairs(bytes) do + --bi.log(string.format("push 0x%02X", v)) + data = data .. string.pack("B", v) + end + + decode_h264( data, #data ) +end + +local bytes_sps = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1e, + 0xda, 0x02, 0x80, 0xbf, 0xe5, 0xc0, 0x5a, 0x80, + 0x80, 0x80, 0xa0, 0x00, 0x00, 0x03, 0x20, 0x00, + 0x00, 0x06, 0x01, 0xe2, 0xc5, 0xd4 +} + +local bytes_sps2 = { + 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x1F, 0xAC, + 0x72, 0x84, 0x40, 0xA0, 0x2F, 0xF9, 0x70, 0x11, + 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, + 0x00, 0x3C, 0x0F, 0x18, 0x31, 0x84, 0x60, 0x00 + +--[[ + 0x64, 0x00, 0x1f, 0xac, + 0x72, 0x84, 0x40, 0xa0, 0x2f, 0xf9, 0x70, 0x11, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x3c, 0x0f, 0x18, 0x31, 0x84, 0x60, 0x00 +--]] +} + +local bytes_pps = { + 0x00, 0x00, 0x01, 0x68, 0xE8, 0x43, 0x94, 0xB2, + 0x2C +} + +local bytes1= { + 0x00, 0x00, 0x00, 0x01, 0x67, 0xCD, 0x00, 0x00, 0x01, 0x65, 0x34, 0x45, 0x78 + --0x00, 0x00, 0x00, 0x01, 0xAB +} + +local function test_nalu_unshell(data) + + local str = "" + for _, v in ipairs(data) do + str = str .. string.pack("B", v) + end + + local len, data2 = bi.nalu_unshell(str, #str) + bi.log(string.format("nalu_unshell len:%d", len)) + bi.dump( data2, len ) +end + + +--test_find_nalu(bytes1) +--test_nalu_unshell(bytes_sps2) +--test_find_nalu(bytes_sps) +--test_find_nalu(bytes_sps2) +--test_find_nalu(bytes_pps) +test_find_nalu_from_file() + diff --git a/src/scripts/testcase/test_tree.lua b/src/scripts/testcase/test_tree.lua new file mode 100644 index 0000000..0ca8adf --- /dev/null +++ b/src/scripts/testcase/test_tree.lua @@ -0,0 +1,58 @@ +local mt = bi.main_tree() + +local function test_tree() + local node = bi.create_tree_item("lua node") + mt:addChild(node) + + local node2 = bi.tree_add_child(mt, "lua node2") + + bi.log("hello lua2 ") +end + +local function is_array(t) + local i = 0 + for _ in pairs(t) do + i = i + 1 + if t[i] == nil then return false end + end + return true +end + +local function build_tree( tree_node, data ) + local tp = type(data) + if tp ~= "table" then + local node = bi.create_tree_item(data) + tree_node:addChild(node) + elseif is_array(data) then + for i, v in ipairs(data) do + build_tree( tree_node, v ) + end + else + for k, v in pairs(data) do + if type(v) == "table" then + local node = bi.create_tree_item(k) + tree_node:addChild(node) + + build_tree( node, v ) + else + local node = bi.create_tree_item( string.format("%s: %s", k, tostring(v)) ) + tree_node:addChild(node) + end + end + end +end + +local data = { + player = { + prop = { name="tpf", hp=100, gender="male" }, + skills = {"fire_ball", "ice_arraw"}, + }, + + monster = { + prop = { name="dragon", hp=100, mp=50, damage=10 }, + } +} + +--test_tree() +build_tree( mt, data ) +mt:expandAll() diff --git a/src/scripts/testcase/testcase.lua b/src/scripts/testcase/testcase.lua new file mode 100644 index 0000000..8f18823 --- /dev/null +++ b/src/scripts/testcase/testcase.lua @@ -0,0 +1,14 @@ +local t = { +--[[ + "test_field", + "test_tree", + "test_h264", + "test_byte_stream", +--]] + "test_bit_stream", +} + +for _, v in ipairs(t) do + bi.log(string.format("====== [testcase %s] ======", v)) + require(v) +end