gst-shark 是由 RidgeRun 公司开发的一款开源性能分析与调试工具集,它通过 GStreamer 内置的追踪(Tracing)子系统,在不修改管道代码的前提下,实时提取关键性能数据,帮助开发者快速定位多媒体处理管道中的性能瓶颈。
gst-shark基本介绍
九大追踪器(Tracers)
gst-shark 的核心是一系列可插拔的追踪器,每个都针对特定的性能分析场景。你可以通过 GST_TRACERS 环境变量按需启用。
| 追踪器名称 | 核心功能 | 典型用途 |
|---|---|---|
proctime |
测量每个元素处理单个缓冲区所花费的时间 | 定位处理最慢的元素,是分析性能瓶颈的首选。 |
interlatency |
测量缓冲区在管道不同点之间的累积延迟 | 分析延迟在哪个环节累积最多。 |
framerate |
测量每秒通过源衬垫(src pad)的帧数 | 验证实际输出帧率是否达标,检测丢帧。 |
scheduletime |
测量接收衬垫(sink pad)上两个连续缓冲区到达的时间间隔 | 判断数据供给是否均匀,检测上游是否"卡顿"。 |
cpuusage |
测量每个 CPU 核心的使用率 | 发现 CPU 瓶颈,判断负载是否均衡。 |
graphic |
记录当前管道的图形化表示 | 快速理解管道拓扑,验证 Caps 协商。 |
bitrate |
测量当前流的比特率(bits/s) | 监控编码输出码率是否稳定。 |
queuelevel |
测量每个 queue 元素中排队的缓冲区数量 |
诊断管道"卡住"或抖动,判断是上游太慢还是下游太慢。 |
buffer |
打印每个经过接收衬垫的缓冲区的详细信息(PTS, DTS, size等) | 逐帧分析时间戳连续性,排查数据异常。 |
工作原理与使用流程
gst-shark 是一个 GStreamer 插件,它利用 GStreamer 的追踪钩子(tracing hooks)在关键代码路径上插入"探针",将采集到的数据整理后,以 CTF(Common Trace Format) 格式输出。
使用流程高度依赖环境变量,通常在启动 GStreamer 应用前设置:
-
GST_TRACERS:指定要启用的追踪器,多个用分号;隔开。例如:export GST_TRACERS="proctime;interlatency;framerate"。 -
GST_DEBUG:启用追踪系统的调试日志,通常设为GST_TRACER:7。 -
GST_SHARK_LOCATION:指定追踪数据(datastream文件)的保存目录。
运行管道后,数据会以 CTF 格式保存到 GST_SHARK_LOCATION 指定的目录中。
数据分析与可视化
gst-shark 采集的 datastream 文件是二进制的,需要配合 metadata 文件(描述数据结构的"说明书")才能被解析。针对不同需求,有多种分析工具:
-
gstshark-plot(官方可视化) :一套 Octave 脚本 ,可将 CTF 数据渲染成直观的图表(PNG 或 PDF 格式)。需要安装octave和babeltrace等依赖。 -
babeltrace(命令行解析) :一个强大的命令行工具,能将 CTF 数据转换为人类可读的文本格式 ,方便用grep等工具进行快速过滤和排查。 -
Trace Compass(图形化深度分析) :一款功能强大的 Eclipse 插件,原生支持 CTF 格式,可提供时间轴、事件表、统计图表等多种视图,适合进行复杂的交互式分析。
gst-shark编译使用
编译环境:
Ubuntu 20.04.6 LTS
gstshark地址:https://github.com/RidgeRun/gst-shark
gstreamer地址:https://github.com/GStreamer/cerbero
gstreamer-common地址:https://github.com/GStreamer/common
本案例使用gstreamer的cerbero项目源码来编译gstreamer的库(应该也可以采用官方提供的gsteamer的版本包)。
gstshark源码编译
(1)github下载源码,搭建编译环境。其中gst-shark/common可能下载失败,删除目录单独下载
gstreamer-common地址:https://github.com/GStreamer/common。
(2)配置编译环境,使用NDK搭建交叉编译环境,主要是配置NDK工具链以及gstreamer的源码目录。
生成产物:libgstsharktracers.so和libgstshark.so
bash
#!/bin/bash
set -e
# ############################################################################
#
# gst-shark Android 交叉编译脚本
#
# ---------------------------------------------------------------------------
# 产物说明
# ---------------------------------------------------------------------------
# gst-shark 编译出来不是一个 .so, 而是两个:
#
# 1. libgst<插件名>.so ← GStreamer 插件本体 (默认 libgstsharktracers.so)
# 里面含 gst_plugin_desc, GStreamer 会扫描加载它
#
# 2. libgst<依赖库名>.so ← tracer 的公共实现库, 被插件本体依赖
# 默认 libgstshark.so, 不是插件
#
# ★ 部署到 APK 时, 两个 .so 都必须拷贝, 只拷插件会 dlopen 失败。
#
# ---------------------------------------------------------------------------
# 插件改名的关键规则 (GStreamer 插件加载机制)
# ---------------------------------------------------------------------------
# 文件名 内部注册名 导出符号
# libgst<X>.so <X> gst_plugin_<X>_get_desc
#
# GStreamer 从文件名 libgst<X>.so 推导符号 gst_plugin_<X>_get_desc,
# 所以文件名和内部注册名必须一致, 否则插件加载失败:
# "Could not find symbol 'gst_plugin_<X>_get_desc'"
#
# 本脚本会自动同步:
# PLUGIN_NAME=libgst<X> -> 文件名 libgst<X>.so, 内部名 <X>
#
# ---------------------------------------------------------------------------
# 编译产物安装位置
# ---------------------------------------------------------------------------
# <脚本目录>/out/<abi>/lib/gstreamer-1.0/
# ├── libgst<插件名>.so ← 插件本体 (可通过参数改)
# └── libgst<依赖库名>.so ← 依赖库 (可通过参数改, 默认不变)
#
# ---------------------------------------------------------------------------
# 用法
# ---------------------------------------------------------------------------
# ./build.sh armv7 编译 armv7 (默认名)
# ./build.sh arm64 编译 arm64 (默认名)
# ./build.sh arm64 libgstmyplugin 插件改名 libgstmyplugin.so
# ./build.sh arm64 libgstmyplugin libmycore 插件 + 依赖库同时改名
# ./build.sh clean 清理源码中间文件, 保留 out/
#
# ############################################################################
# ============================================================================
# 配置区
# ============================================================================
# ----------------------------------------------------------------------------
# 1. 插件名 (文件名 + 内部注册名会同步修改)
# ----------------------------------------------------------------------------
# 必须以 libgst 开头, 否则 GStreamer 不会扫描加载。
# 内部注册名会自动从 libgst<X> 推导为 <X>, 保证导出符号 gst_plugin_<X>_get_desc
# 与文件名匹配。
#
# 请避免使用和官方 GStreamer 插件重名的名字,否则可能覆盖系统插件或引起注册冲突。
PLUGIN_NAME_DEFAULT="libgstsharktracers"
# ----------------------------------------------------------------------------
# 2. Android NDK
# ----------------------------------------------------------------------------
NDK_PATH_DEFAULT="/home/user/cerbero/build/android-ndk-21"
# ----------------------------------------------------------------------------
# 3. GStreamer 依赖库路径 (Cerbero 输出)
# ----------------------------------------------------------------------------
CERBERO_DIST_DEFAULT="/home/user/cerbero/build/dist"
DEP_ARCH_ARMV7_DEFAULT="android_armv7"
DEP_ARCH_ARM64_DEFAULT="android_arm64"
# ----------------------------------------------------------------------------
# 4. Android API level
# ----------------------------------------------------------------------------
API_DEFAULT=21
# ----------------------------------------------------------------------------
# 5. 交叉编译工具链参数
# ----------------------------------------------------------------------------
ARCH_ARMV7_TARGET="armv7a-linux-androideabi"
ARCH_ARMV7_HOST="arm-linux-androideabi"
ARCH_ARMV7_ABI="armeabi-v7a"
ARCH_ARM64_TARGET="aarch64-linux-android"
ARCH_ARM64_HOST="aarch64-linux-android"
ARCH_ARM64_ABI="arm64-v8a"
# ----------------------------------------------------------------------------
# 6. 依赖库文件名 (默认 libgstshark)
# ----------------------------------------------------------------------------
SHARK_LIB_NAME_DEFAULT="libgstshark"
# ============================================================================
# 以下是脚本逻辑, 一般不需要修改
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# ---------- 合并配置 ----------
PLUGIN_NAME="${PLUGIN_NAME:-$PLUGIN_NAME_DEFAULT}"
SHARK_LIB_NAME="${SHARK_LIB_NAME:-$SHARK_LIB_NAME_DEFAULT}"
NDK_PATH="${NDK_PATH:-$NDK_PATH_DEFAULT}"
CERBERO_DIST="${CERBERO_DIST:-$CERBERO_DIST_DEFAULT}"
API="${API:-$API_DEFAULT}"
DEP_ARCH_ARMV7="${DEP_ARCH_ARMV7:-$DEP_ARCH_ARMV7_DEFAULT}"
DEP_ARCH_ARM64="${DEP_ARCH_ARM64:-$DEP_ARCH_ARM64_DEFAULT}"
TOOLCHAIN="$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64"
MAKEFILE_AM="plugins/tracers/Makefile.am"
PLUGIN_C="plugins/tracers/gstplugin.c"
BUILD_FILES_FIND=(
find . -path "./out" -prune -o -type f
\( -name "Makefile.am" -o -name "*.mak" -o -name "configure.ac" \) -print
)
# ============================================================================
# 名字合法性校验
# ============================================================================
validate_name() {
local kind="$1"
local name="$2"
if [ -z "$name" ]; then
echo "ERROR: $kind 名字为空"
exit 1
fi
if ! echo "$name" | grep -qE '^lib[A-Za-z][A-Za-z0-9_]*$'; then
echo "ERROR: $kind 名字非法: '$name'"
echo " 规则: 以 lib 开头, 后面第一个字符是字母,"
echo " 之后只允许字母/数字/下划线"
echo " 合法示例: libgstsharktracers / libgstmyplugin / libmycore"
echo " 非法示例: libgst.so / 1libfoo / my-lib"
exit 1
fi
}
# ============================================================================
# 清理函数: 只清源码目录, 不动 out/
# ============================================================================
clean_build() {
echo "==> 清理源码目录中间文件 (保留 out/) ..."
if [ -f Makefile ]; then
make distclean >/dev/null 2>&1 || true
fi
rm -rf config.status config.log autom4te.cache
rm -f config.h stamp-h1 libtool
find . -path "./out" -prune -o -type f -name "Makefile" -print \
-exec rm -f {} + 2>/dev/null || true
find . -path "./out" -prune -o -type d \( -name ".libs" -o -name ".deps" \) \
-exec rm -rf {} + 2>/dev/null || true
find . -path "./out" -prune -o -type f \
\( -name "*.o" -o -name "*.lo" -o -name "*.la" -o -name "*.lai" \
-o -name "*.exp" -o -name "*.ver" \) \
-print -exec rm -f {} + 2>/dev/null || true
find . -path "./out" -prune -o -type f -name "*.so*" \
-print -exec rm -f {} + 2>/dev/null || true
echo "==> 清理完成 (out/ 未受影响)"
}
# ============================================================================
# 改名: 插件本体
# 同步修改两处:
# 1) Makefile.am 里的 libgst<X> (文件名)
# 2) gstplugin.c 里 GST_PLUGIN_DEFINE 第三个参数 (内部注册名)
#
# 规则: PLUGIN_NAME=libgst<X> -> 内部名 = <X>
# 保证 libgst<X>.so 里导出的符号是 gst_plugin_<X>_get_desc
#
# 幂等: 从当前状态改到目标状态, 反复跑安全
# ============================================================================
rename_plugin() {
echo "==> 插件改名: $PLUGIN_NAME (文件名 + 内部注册名同步修改)"
[ -f "$MAKEFILE_AM" ] || { echo "ERROR: 找不到 $MAKEFILE_AM"; exit 1; }
[ -f "$PLUGIN_C" ] || { echo "ERROR: 找不到 $PLUGIN_C"; exit 1; }
# ---------- 推导目标内部名 ----------
# libgst<X> -> <X>
local TARGET_INTERNAL="${PLUGIN_NAME#libgst}"
if [ -z "$TARGET_INTERNAL" ] || [ "$TARGET_INTERNAL" = "$PLUGIN_NAME" ]; then
echo "ERROR: 无法从 '$PLUGIN_NAME' 推导内部名 (必须以 libgst 开头)"
exit 1
fi
echo " 目标文件名: $PLUGIN_NAME.so"
echo " 目标内部名: $TARGET_INTERNAL"
# ---------- 1. Makefile.am: 替换文件名 ----------
local CURRENT_NAME
CURRENT_NAME=$(grep -oE '^plugin_LTLIBRARIES *= *lib[A-Za-z][A-Za-z0-9_]*' "$MAKEFILE_AM" \
| head -1 | sed 's/.*= *//')
if [ -z "$CURRENT_NAME" ]; then
echo "ERROR: 无法从 $MAKEFILE_AM 解析出插件名"
echo " 当前 plugin_LTLIBRARIES 行:"
grep -n "plugin_LTLIBRARIES" "$MAKEFILE_AM" || true
echo " 修复: git checkout plugins/tracers/Makefile.am"
exit 1
fi
echo " 当前文件名: $CURRENT_NAME.so"
if [ "$CURRENT_NAME" != "$PLUGIN_NAME" ]; then
while IFS= read -r f; do
if grep -q "$CURRENT_NAME" "$f" 2>/dev/null; then
sed -i "s/${CURRENT_NAME}/${PLUGIN_NAME}/g" "$f"
echo " 更新文件名: $f"
fi
done < <("${BUILD_FILES_FIND[@]}")
else
echo " 文件名已是目标名, 跳过"
fi
# ---------- 2. gstplugin.c: 跨行匹配 GST_PLUGIN_DEFINE 第三个参数 ----------
# 宏结构: GST_PLUGIN_DEFINE(major, minor, name, description, init, ...)
# 第三个参数 name 就是内部注册名
# 用 perl -0777 把整个文件当一行处理, 正则用 \s 跨行匹配
local CURRENT_INTERNAL
CURRENT_INTERNAL=$(perl -0777 -ne '
if (/GST_PLUGIN_DEFINE\s*\(\s*[^,]+,\s*[^,]+,\s*([a-zA-Z_][a-zA-Z0-9_]*)/s) {
print $1;
}
' "$PLUGIN_C")
if [ -z "$CURRENT_INTERNAL" ]; then
echo "ERROR: 无法从 $PLUGIN_C 解析出 GST_PLUGIN_DEFINE 的内部名"
echo " 当前 GST_PLUGIN_DEFINE 附近内容:"
grep -A 3 "GST_PLUGIN_DEFINE" "$PLUGIN_C" | head -8 || true
exit 1
fi
echo " 当前内部名: $CURRENT_INTERNAL"
if [ "$CURRENT_INTERNAL" != "$TARGET_INTERNAL" ]; then
# 只替换第三个参数, 其余内容 (描述字符串、init、version 等) 完全不动
perl -0777 -i -pe '
s/(GST_PLUGIN_DEFINE\s*\(\s*[^,]+,\s*[^,]+,\s*)[a-zA-Z_][a-zA-Z0-9_]*/${1}'"${TARGET_INTERNAL}"'/s
' "$PLUGIN_C"
echo " 更新内部名: $CURRENT_INTERNAL -> $TARGET_INTERNAL"
else
echo " 内部名已是目标名, 跳过"
fi
# ---------- 3. 验证 ----------
echo " 验证 Makefile.am:"
grep -n "plugin_LTLIBRARIES" "$MAKEFILE_AM" || true
echo " 验证 gstplugin.c:"
grep -A 3 "GST_PLUGIN_DEFINE" "$PLUGIN_C" | head -5
}
# ============================================================================
# 改名: 依赖库 (libgstshark)
# 必须在 rename_plugin 之后调用
# ============================================================================
rename_shark_lib() {
echo "==> 依赖库改名: $SHARK_LIB_NAME (内部库不是插件)"
[ -f "$MAKEFILE_AM" ] || { echo "ERROR: 找不到 $MAKEFILE_AM"; exit 1; }
local CURRENT_LIB
CURRENT_LIB=$(grep -oE '^noinst_LTLIBRARIES *= *lib[A-Za-z][A-Za-z0-9_]*' "$MAKEFILE_AM" \
| head -1 | sed 's/.*= *//')
if [ -z "$CURRENT_LIB" ]; then
CURRENT_LIB=$(grep -oE '^lib_LTLIBRARIES *= *lib[A-Za-z][A-Za-z0-9_]*' "$MAKEFILE_AM" \
| head -1 | sed 's/.*= *//')
fi
if [ -z "$CURRENT_LIB" ]; then
local PLUGIN_LA
PLUGIN_LA=$(grep -oE '^plugin_LTLIBRARIES *= *lib[A-Za-z][A-Za-z0-9_]*' "$MAKEFILE_AM" \
| head -1 | sed 's/.*= *//')
CURRENT_LIB=$(grep -oE '^lib[A-Za-z][A-Za-z0-9_]*_la_SOURCES' "$MAKEFILE_AM" \
| sed 's/_la_SOURCES//' \
| grep -v "^${PLUGIN_LA}$" \
| head -1)
fi
if [ -z "$CURRENT_LIB" ]; then
echo "ERROR: 无法从 $MAKEFILE_AM 解析出依赖库名"
grep -n "LTLIBRARIES\|_la_SOURCES" "$MAKEFILE_AM" | head || true
exit 1
fi
echo " 当前: $CURRENT_LIB -> 目标: $SHARK_LIB_NAME"
if [ "$CURRENT_LIB" = "$SHARK_LIB_NAME" ]; then
echo " 已是目标名, 跳过"
return
fi
local updated=0
while IFS= read -r f; do
if grep -q "$CURRENT_LIB" "$f" 2>/dev/null; then
sed -i -E "s/${CURRENT_LIB}([^a-zA-Z0-9]|$)/${SHARK_LIB_NAME}\1/g" "$f"
echo " 更新: $f"
updated=$((updated + 1))
fi
done < <("${BUILD_FILES_FIND[@]}")
echo " 共更新 $updated 个文件"
}
# ============================================================================
# clean 子命令
# ============================================================================
if [ "$1" = "clean" ]; then
clean_build
echo
echo "out/ 目录内容:"
ls -R "$SCRIPT_DIR/out" 2>/dev/null || echo " (空)"
exit 0
fi
# ============================================================================
# 参数解析
# ============================================================================
ARCH=${1:-armv7}
if [ -n "$2" ]; then
PLUGIN_NAME="$2"
fi
if [ -n "$3" ]; then
SHARK_LIB_NAME="$3"
fi
case "$ARCH" in
armv7)
TARGET="$ARCH_ARMV7_TARGET"
HOST="$ARCH_ARMV7_HOST"
ANDROID_ABI="$ARCH_ARMV7_ABI"
DEP_ARCH="$DEP_ARCH_ARMV7"
;;
arm64)
TARGET="$ARCH_ARM64_TARGET"
HOST="$ARCH_ARM64_HOST"
ANDROID_ABI="$ARCH_ARM64_ABI"
DEP_ARCH="$DEP_ARCH_ARM64"
;;
*)
echo "用法: $0 [armv7|arm64|clean] [插件文件名] [依赖库文件名]"
echo "示例: $0 arm64 libgstmyplugin libmycore"
exit 1
;;
esac
validate_name "插件" "$PLUGIN_NAME"
validate_name "依赖库" "$SHARK_LIB_NAME"
# 插件名必须以 libgst 开头 (内部名推导需要)
if ! echo "$PLUGIN_NAME" | grep -qE '^libgst[A-Za-z][A-Za-z0-9_]*$'; then
echo "ERROR: 插件名必须以 libgst 开头, 当前: '$PLUGIN_NAME'"
echo " 合法示例: libgstsharktracers / libgstmyplugin"
exit 1
fi
# ============================================================================
# 安装目录
# ============================================================================
OUT_DIR="$SCRIPT_DIR/out/$ANDROID_ABI"
PLUGIN_INSTALL_DIR="$OUT_DIR/lib/gstreamer-1.0"
rm -rf "$PLUGIN_INSTALL_DIR"
mkdir -p "$PLUGIN_INSTALL_DIR"
mkdir -p "$OUT_DIR/include"
# ============================================================================
# 工具链
# ============================================================================
export CC="$TOOLCHAIN/bin/${TARGET}${API}-clang"
export CXX="$TOOLCHAIN/bin/${TARGET}${API}-clang++"
export AR="$TOOLCHAIN/bin/llvm-ar"
export RANLIB="$TOOLCHAIN/bin/llvm-ranlib"
export STRIP="$TOOLCHAIN/bin/llvm-strip"
export PATH="$TOOLCHAIN/bin:$PATH"
# ============================================================================
# GStreamer 依赖
# ============================================================================
GSTREAMER_ROOT_ANDROID="$CERBERO_DIST/$DEP_ARCH"
export PKG_CONFIG_LIBDIR="$GSTREAMER_ROOT_ANDROID/lib/pkgconfig"
export PKG_CONFIG_PATH="$GSTREAMER_ROOT_ANDROID/lib/pkgconfig"
export LDFLAGS="${LDFLAGS} -L$GSTREAMER_ROOT_ANDROID/lib"
export LDFLAGS="${LDFLAGS} -Wl,-rpath-link,$GSTREAMER_ROOT_ANDROID/lib"
export LDFLAGS="${LDFLAGS} -lm"
# ============================================================================
# 前置检查
# ============================================================================
check_prereq() {
local fail=0
[ -x "$TOOLCHAIN/bin/${TARGET}${API}-clang" ] || {
echo "ERROR: 找不到编译器 $TOOLCHAIN/bin/${TARGET}${API}-clang"
fail=1
}
[ -f "$GSTREAMER_ROOT_ANDROID/lib/pkgconfig/gstreamer-1.0.pc" ] || {
echo "ERROR: 找不到 $GSTREAMER_ROOT_ANDROID/lib/pkgconfig/gstreamer-1.0.pc"
fail=1
}
[ -d "$GSTREAMER_ROOT_ANDROID/lib/gstreamer-1.0" ] || {
echo "ERROR: 找不到 $GSTREAMER_ROOT_ANDROID/lib/gstreamer-1.0"
fail=1
}
[ -f "$MAKEFILE_AM" ] || { echo "ERROR: 找不到 $MAKEFILE_AM"; fail=1; }
[ -f "$PLUGIN_C" ] || { echo "ERROR: 找不到 $PLUGIN_C"; fail=1; }
[ "$fail" = "0" ] || exit 1
}
# ============================================================================
# 打印配置
# ============================================================================
print_config() {
local TARGET_INTERNAL="${PLUGIN_NAME#libgst}"
echo "============================================================"
echo " 架构 : $ARCH ($ANDROID_ABI)"
echo " 插件文件名 : $PLUGIN_NAME.so"
echo " 插件内部名 : $TARGET_INTERNAL"
echo " 导出符号 : gst_plugin_${TARGET_INTERNAL}_get_desc"
echo " 依赖库文件名 : $SHARK_LIB_NAME.so"
echo " TARGET : $TARGET"
echo " CC : $CC"
echo " NDK : $NDK_PATH"
echo " 依赖 GSt : $GSTREAMER_ROOT_ANDROID"
echo " 安装目录 : $PLUGIN_INSTALL_DIR"
echo "============================================================"
}
# ============================================================================
# 主流程
# ============================================================================
check_prereq
print_config
echo "==> pkg-config 测试:"
pkg-config --cflags --libs gstreamer-1.0 || {
echo "ERROR: pkg-config 找不到 gstreamer-1.0"
exit 1
}
clean_build
rename_plugin # 同步改 Makefile.am 和 gstplugin.c
rename_shark_lib # 改依赖库名
./autogen.sh --host="$HOST" \
--prefix="$OUT_DIR" \
--libdir="$OUT_DIR/lib" \
--disable-gtk-doc \
--disable-graphviz \
--disable-fatal-warnings \
--disable-tests \
--disable-examples
make -j"$(nproc)"
# ============================================================================
# 校验编译产物
# ============================================================================
SO="plugins/tracers/.libs/${PLUGIN_NAME}.so"
if [ ! -f "$SO" ]; then
echo "ERROR: 插件编译产物不存在: $SO"
ls -l plugins/tracers/.libs/ 2>/dev/null || true
exit 1
fi
echo "==> 插件本体: $SO"
file "$SO"
# 校验导出符号是否匹配文件名
TARGET_INTERNAL="${PLUGIN_NAME#libgst}"
echo "==> 导出符号校验 (期望 gst_plugin_${TARGET_INTERNAL}_get_desc):"
"$TOOLCHAIN/bin/llvm-readelf" --dyn-syms "$SO" 2>/dev/null \
| grep -E "gst_plugin_.*_get_desc" || echo " (未找到)"
# ============================================================================
# 安装
# ============================================================================
make install
find "$OUT_DIR/lib" -maxdepth 1 -name "${SHARK_LIB_NAME}*.so*" \
-exec mv {} "$PLUGIN_INSTALL_DIR/" \; 2>/dev/null || true
INSTALLED_SO="$PLUGIN_INSTALL_DIR/${PLUGIN_NAME}.so"
if [ ! -f "$INSTALLED_SO" ]; then
echo "==> 预期位置不存在, 全盘搜索实际落点 ..."
FOUND=$(find "$OUT_DIR" -name "${PLUGIN_NAME}.so" -type f 2>/dev/null | head -1)
if [ -n "$FOUND" ]; then
echo "==> 实际位置: $FOUND"
mv "$FOUND" "$INSTALLED_SO"
else
echo "ERROR: $OUT_DIR 下找不到 ${PLUGIN_NAME}.so"
find "$OUT_DIR" -maxdepth 5 -print 2>/dev/null || true
exit 1
fi
fi
# ============================================================================
# 探测依赖
# ============================================================================
echo
echo "==> 插件本体的动态依赖 (与 $SHARK_LIB_NAME 相关):"
SHARK_DEPS=$( "$TOOLCHAIN/bin/llvm-readelf" -d "$INSTALLED_SO" 2>/dev/null \
| grep NEEDED | grep -i "$(basename "$SHARK_LIB_NAME")" \
| sed 's/.*\[\(.*\)\].*/\1/' ) || true
if [ -n "$SHARK_DEPS" ]; then
echo "$SHARK_DEPS" | while read -r dep; do
echo " $dep"
done
else
echo " (未检测到)"
fi
# ============================================================================
# 完成
# ============================================================================
echo
echo "============================================================"
echo " 安装完成: $INSTALLED_SO"
echo "============================================================"
file "$INSTALLED_SO"
if [ -n "$SHARK_DEPS" ]; then
echo
echo " 依赖库:"
echo "$SHARK_DEPS" | while read -r dep; do
LIB_PATH="$PLUGIN_INSTALL_DIR/$dep"
if [ -f "$LIB_PATH" ]; then
file "$LIB_PATH"
fi
done
fi
echo
echo "============================================================"
echo " APK 集成: 以下文件全部拷到"
echo " app/src/main/jniLibs/$ANDROID_ABI/"
echo "============================================================"
find "$PLUGIN_INSTALL_DIR" -maxdepth 1 \( -name "*.so" -o -name "*.so.*" \) \
-exec basename {} \; 2>/dev/null | sort | while read -r f; do
echo " $f"
done
gstshark集成使用
(1)将产物也就是插件库libgstsharktracers.so以及依赖libgstshark.so集成到我们的项目
(2)在gst_init前设置env环境值:
g_setenv("GST_TRACERS", "proctime;interlatency;framerate;cpuusage;bitrate;queuelevel;graphic", TRUE);
g_setenv("GST_SHARK_LOCATION", "/data/app", TRUE);
g_setenv("GST_DEBUG", "GST_TRACER:7", TRUE);
(3)运行程序抓取数据,GST_SHARK_LOCATION目录下生成datastream和metadata文件。
:datastream是追踪数据的输出文件。
:metadata是CTF 格式的schema/说明书,它描述的是 datastream 二进制文件的结构,用于解析datastream。
:graphic/pipeline0_2026-10-20_14:21:59.dot是设置**graphic抓取的管道dot。**
但是由于在安卓上兼容性的问题,所以无法抓取metadata的。可以采用在ubuntu上生成metadata
(4)ubuntu上生成metadata
(a)ubuntu安装同样版本的gstreamer环境。
bash
# 1. 添加 PPA
sudo add-apt-repository ppa:tidewise/gstreamer-1.20
# 2. 更新软件源
sudo apt update
# 3. 升级 GStreamer
sudo apt upgrade
# 4. 验证版本(应显示 1.20.x)
gst-inspect-1.0 --version
(b)ubuntu编译安装gst-shark插件库(编译gst-shark源码,目标架构是ubuntu)。
bash
#!/bin/bash
set -e
# ############################################################################
#
# gst-shark Ubuntu 主机编译与 metadata 生成脚本
#
# ---------------------------------------------------------------------------
# 用途
# ---------------------------------------------------------------------------
# 在 Ubuntu 主机上编译安装 gst-shark, 用于:
# 1. 生成 metadata 模板 (Android 上无法自动生成)
# 2. 与 Android 采集的 datastream 配对, 用 gstshark-plot / Trace Compass 分析
#
# ---------------------------------------------------------------------------
# 产物
# ---------------------------------------------------------------------------
# 安装到系统:
# /usr/lib/<arch>/gstreamer-1.0/libgstsharktracers.so ← 插件
# /usr/lib/<arch>/libgstshark-0.9.so.0 ← 依赖库
#
# metadata 模板:
# <脚本目录>/gstshark_meta/metadata
#
# 与 Android datastream 配对目录:
# <脚本目录>/tracing_data/
# ├── datastream (从 Android 拷贝)
# └── metadata (本脚本生成, UUID 已对齐)
#
# ---------------------------------------------------------------------------
# 用法
# ---------------------------------------------------------------------------
# ./build-ubuntu.sh 编译安装并生成 metadata
# ./build-ubuntu.sh --skip-deps 跳过依赖安装
# ./build-ubuntu.sh --android-datastream <path>
# 编译后直接与 Android datastream 配对
# ./build-ubuntu.sh --plot 生成 metadata 后自动运行 gstshark-plot
# ./build-ubuntu.sh clean 清理编译中间文件
#
# ############################################################################
# ============================================================================
# 配置区
# ============================================================================
# tracer 列表, 必须和 Android 端完全一致
TRACERS_DEFAULT="proctime;interlatency;framerate;cpuusage;bitrate;queuelevel"
# 安装路径
PREFIX_DEFAULT="/usr"
# GStreamer API 版本
GST_API_VERSION="1.0"
# Android datastream 路径 (可选, 通过 --android-datastream 覆盖)
ANDROID_DATASTREAM=""
# ============================================================================
# 脚本逻辑
# ============================================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# ---------- 参数解析 ----------
SKIP_DEPS=0
RUN_PLOT=0
while [ $# -gt 0 ]; do
case "$1" in
--skip-deps)
SKIP_DEPS=1
shift
;;
--android-datastream)
ANDROID_DATASTREAM="$2"
shift 2
;;
--plot)
RUN_PLOT=1
shift
;;
clean)
echo "==> 清理源码目录中间文件 ..."
make distclean 2>/dev/null || true
rm -rf config.status config.log autom4te.cache
rm -f config.h stamp-h1 libtool
find . -path ./out -prune -o -type f -name "Makefile" -print -exec rm -f {} + 2>/dev/null || true
find . -path ./out -prune -o -type d \( -name ".libs" -o -name ".deps" \) -exec rm -rf {} + 2>/dev/null || true
find . -path ./out -prune -o -type f \( -name "*.o" -o -name "*.lo" -o -name "*.la" -o -name "*.lai" \) -delete 2>/dev/null || true
echo "==> 清理完成 (out/ 保留)"
exit 0
;;
-h|--help)
sed -n '3,40p' "$0"
exit 0
;;
*)
echo "未知参数: $1"
echo "用法: $0 [--skip-deps] [--android-datastream <path>] [--plot] [clean]"
exit 1
;;
esac
done
TRACERS="${TRACERS:-$TRACERS_DEFAULT}"
PREFIX="${PREFIX:-$PREFIX_DEFAULT}"
# ============================================================================
# 1. 检查源码
# ============================================================================
echo "==> [1/8] 检查源码 ..."
MAKEFILE_AM="plugins/tracers/Makefile.am"
PLUGIN_C="plugins/tracers/gstplugin.c"
[ -f "$MAKEFILE_AM" ] || { echo "ERROR: 找不到 $MAKEFILE_AM"; exit 1; }
[ -f "$PLUGIN_C" ] || { echo "ERROR: 找不到 $PLUGIN_C"; exit 1; }
# 如果有 git, 恢复到干净状态, 避免 Android 编译时的改名残留
if [ -d .git ]; then
if ! git diff --quiet 2>/dev/null; then
echo " 检测到源码有未提交改动, 尝试 git checkout 恢复 ..."
git checkout . 2>/dev/null || true
fi
fi
# 二次确认插件名和内部名
PLUGIN_NAME=$(grep -oE '^plugin_LTLIBRARIES *= *lib[A-Za-z][A-Za-z0-9_]*' "$MAKEFILE_AM" \
| head -1 | sed 's/.*= *//')
PLUGIN_INTERNAL=$(perl -0777 -ne '
if (/GST_PLUGIN_DEFINE\s*\(\s*[^,]+,\s*[^,]+,\s*([a-zA-Z_][a-zA-Z0-9_]*)/s) {
print $1;
}
' "$PLUGIN_C")
echo " 插件文件名: $PLUGIN_NAME.so"
echo " 插件内部名: $PLUGIN_INTERNAL"
if [ "$PLUGIN_INTERNAL" != "${PLUGIN_NAME#libgst}" ]; then
echo "ERROR: 文件名和内部名不匹配"
echo " 文件名推导内部名: ${PLUGIN_NAME#libgst}"
echo " gstplugin.c 实际: $PLUGIN_INTERNAL"
echo " 修复: git checkout ."
exit 1
fi
# ============================================================================
# 2. 安装依赖
# ============================================================================
echo
echo "==> [2/8] 检查依赖 ..."
if [ "$SKIP_DEPS" = "0" ]; then
MISSING=""
command -v gcc >/dev/null 2>&1 || MISSING="$MISSING gcc"
command -v make >/dev/null 2>&1 || MISSING="$MISSING make"
command -v autoconf >/dev/null 2>&1 || MISSING="$MISSING autoconf"
command -v automake >/dev/null 2>&1 || MISSING="$MISSING automake"
command -v libtoolize >/dev/null 2>&1 || MISSING="$MISSING libtool"
command -v pkg-config >/dev/null 2>&1 || MISSING="$MISSING pkg-config"
command -v gst-inspect-1.0 >/dev/null 2>&1 || MISSING="$MISSING gstreamer1.0-tools"
if [ -n "$MISSING" ]; then
echo " 缺少: $MISSING"
echo " 安装中 (需要 sudo) ..."
sudo apt update
sudo apt install -y \
git build-essential \
autoconf automake libtool pkg-config \
gstreamer1.0-tools \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-bad1.0-dev \
graphviz libgraphviz-dev \
gtk-doc-tools \
babeltrace \
octave epstool
else
echo " 基本工具已安装"
# 检查开发库
if ! pkg-config --exists gstreamer-1.0; then
echo " 缺少 libgstreamer1.0-dev, 安装中 ..."
sudo apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
fi
if ! command -v babeltrace >/dev/null 2>&1; then
echo " 缺少 babeltrace, 安装中 ..."
sudo apt install -y babeltrace
fi
fi
else
echo " --skip-deps, 跳过依赖安装"
fi
# 确认 GStreamer 版本
GST_VERSION=$(pkg-config --modversion gstreamer-1.0 2>/dev/null || echo "unknown")
echo " GStreamer 版本: $GST_VERSION"
# 简单版本检查
GST_MAJOR=$(echo "$GST_VERSION" | cut -d. -f1)
GST_MINOR=$(echo "$GST_VERSION" | cut -d. -f2)
if [ "$GST_MAJOR" = "1" ] && [ "$GST_MINOR" -lt 7 ]; then
echo "ERROR: GStreamer 版本过低 (需要 >= 1.7.1)"
exit 1
fi
# ============================================================================
# 3. 清理旧配置
# ============================================================================
echo
echo "==> [3/8] 清理旧配置 ..."
make distclean >/dev/null 2>&1 || true
rm -rf config.status config.log autom4te.cache
rm -f config.h stamp-h1 libtool
find . -path ./out -prune -o -type f -name "Makefile" -print -exec rm -f {} + 2>/dev/null || true
echo " 完成 (out/ 保留)"
# ============================================================================
# 4. 配置
# ============================================================================
echo
echo "==> [4/8] 配置 ..."
LIBDIR=$(pkg-config --variable=libdir gstreamer-1.0)
echo " 安装 prefix : $PREFIX"
echo " 安装 libdir : $LIBDIR"
./autogen.sh \
--host="$(gcc -dumpmachine)" \
--prefix="$PREFIX" \
--libdir="$LIBDIR" \
--disable-gtk-doc \
--disable-fatal-warnings \
--disable-tests \
--disable-examples
echo " 配置完成"
# ============================================================================
# 5. 编译安装
# ============================================================================
echo
echo "==> [5/8] 编译 ..."
make -j"$(nproc)" ERROR_CFLAGS= || {
echo "ERROR: 编译失败"
exit 1
}
echo " 编译产物:"
ls -l plugins/tracers/.libs/*.so* 2>/dev/null | sed 's/^/ /'
echo
echo "==> [6/8] 安装 (需要 sudo) ..."
sudo make install
sudo ldconfig
echo " 安装完成"
echo " 插件路径: $LIBDIR/gstreamer-$GST_API_VERSION/libgstsharktracers.so"
# ============================================================================
# 6. 验证安装
# ============================================================================
echo
echo "==> [7/8] 验证安装 ..."
# 清 GStreamer 插件缓存
rm -rf "$HOME/.cache/gstreamer-1.0/" 2>/dev/null || true
if gst-inspect-1.0 sharktracers >/dev/null 2>&1; then
echo " gst-inspect-1.0 sharktracers 验证成功"
echo " 可用 tracer:"
gst-inspect-1.0 sharktracers 2>/dev/null | grep -E "^ \+" | sed 's/^/ /'
else
echo "ERROR: gst-inspect-1.0 sharktracers 找不到插件"
echo " 检查:"
echo " ls -l $LIBDIR/gstreamer-$GST_API_VERSION/libgstsharktracers.so"
echo " ldd $LIBDIR/gstreamer-$GST_API_VERSION/libgstsharktracers.so"
exit 1
fi
# ============================================================================
# 7. 生成 metadata
# ============================================================================
echo
echo "==> [8/8] 生成 metadata ..."
META_DIR="$SCRIPT_DIR/gstshark_meta"
rm -rf "$META_DIR"
mkdir -p "$META_DIR"
# 找一个可用的 src 元素
SRC_ELEM=""
for elem in fakesrc audiotestsrc videotestsrc; do
if gst-inspect-1.0 "$elem" >/dev/null 2>&1; then
SRC_ELEM="$elem"
break
fi
done
if [ -z "$SRC_ELEM" ]; then
echo "ERROR: 找不到可用的 src 元素 (fakesrc/audiotestsrc/videotestsrc 都不可用)"
exit 1
fi
echo " 使用 src 元素: $SRC_ELEM"
echo " tracer 列表: $TRACERS"
GST_SHARK_LOCATION="$META_DIR/" \
GST_DEBUG="GST_TRACER:7" \
GST_TRACERS="$TRACERS" \
gst-launch-1.0 -q "$SRC_ELEM" num-buffers=1 ! fakesink 2>/dev/null
if [ ! -s "$META_DIR/metadata" ]; then
echo "ERROR: metadata 生成失败 (文件不存在或为空)"
echo " 尝试查看完整日志:"
echo " GST_SHARK_LOCATION=$META_DIR/ GST_DEBUG='GST_TRACER:9' GST_TRACERS='$TRACERS' \\"
echo " gst-launch-1.0 -q $SRC_ELEM num-buffers=1 ! fakesink"
exit 1
fi
echo " metadata 已生成: $META_DIR/metadata"
ls -lh "$META_DIR/" | sed 's/^/ /'
# ============================================================================
# 8. 与 Android datastream 配对 (可选)
# ============================================================================
TRACING_DIR="$SCRIPT_DIR/tracing_data"
if [ -n "$ANDROID_DATASTREAM" ]; then
echo
echo "==> 与 Android datastream 配对 ..."
if [ ! -f "$ANDROID_DATASTREAM" ]; then
echo "ERROR: 找不到 Android datastream: $ANDROID_DATASTREAM"
exit 1
fi
rm -rf "$TRACING_DIR"
mkdir -p "$TRACING_DIR"
cp "$META_DIR/metadata" "$TRACING_DIR/metadata"
cp "$ANDROID_DATASTREAM" "$TRACING_DIR/datastream"
# 提取 Android datastream 里的真实 UUID
REAL_UUID=$(xxd -p -l 16 -s 4 "$TRACING_DIR/datastream" 2>/dev/null | tr -d '\n' | \
awk '{printf "%s-%s-%s-%s-%s", substr($0,1,8), substr($0,9,4), substr($0,13,4), substr($0,17,4), substr($0,21,12)}')
if [ -n "$REAL_UUID" ] && [ ${#REAL_UUID} -eq 36 ]; then
echo " Android UUID: $REAL_UUID"
sed -i "s/uuid = \"[^\"]*\"/uuid = \"$REAL_UUID\"/" "$TRACING_DIR/metadata"
echo " 已更新 metadata 的 UUID"
else
echo " WARN: 无法从 datastream 提取 UUID, 保持 metadata 原值"
fi
# 验证
echo " 用 babeltrace 验证:"
if babeltrace "$TRACING_DIR/" 2>/dev/null | head -5 | sed 's/^/ /'; then
echo " 验证成功"
else
echo " WARN: babeltrace 验证失败, 手动检查"
echo " babeltrace $TRACING_DIR/"
fi
echo
echo " 配对目录: $TRACING_DIR"
ls -lh "$TRACING_DIR/" | sed 's/^/ /'
fi
# ============================================================================
# 9. 可选: 画图
# ============================================================================
if [ "$RUN_PLOT" = "1" ] && [ -d "$TRACING_DIR" ]; then
echo
echo "==> 运行 gstshark-plot ..."
if ! command -v octave >/dev/null 2>&1; then
echo "ERROR: 未安装 octave, 无法运行 gstshark-plot"
echo " sudo apt install -y octave epstool"
exit 1
fi
cd "$SCRIPT_DIR/scripts/graphics"
./gstshark-plot "$TRACING_DIR/" -s png
echo " 图表已生成在: $TRACING_DIR/"
ls -lh "$TRACING_DIR"/*.png 2>/dev/null | sed 's/^/ /'
fi
# ============================================================================
# 完成
# ============================================================================
echo
echo "============================================================"
echo " 全部完成"
echo "============================================================"
echo " 插件安装 : $LIBDIR/gstreamer-$GST_API_VERSION/libgstsharktracers.so"
echo " metadata : $META_DIR/metadata"
if [ -n "$ANDROID_DATASTREAM" ]; then
echo " 配对目录 : $TRACING_DIR"
fi
echo
echo " 以后复用 metadata:"
echo " cp $META_DIR/metadata /path/to/tracing_data/"
echo " cp /path/to/android/datastream /path/to/tracing_data/"
echo " # UUID 对齐"
echo " cd /path/to/tracing_data"
echo " REAL_UUID=\$(xxd -p -l 16 -s 4 datastream | tr -d '\\n' | \\"
echo " awk '{printf \"%s-%s-%s-%s-%s\", substr(\$0,1,8), substr(\$0,9,4), substr(\$0,13,4), substr(\$0,17,4), substr(\$0,21,12)}')"
echo " sed -i \"s/uuid = \\\"[^\\\"]*\\\"/uuid = \\\"\$REAL_UUID\\\"/\" metadata"
echo " # 画图"
echo " cd $SCRIPT_DIR/scripts/graphics"
echo " ./gstshark-plot /path/to/tracing_data/ -s png"
echo "============================================================"
(c)运行gst-launch-1.0插件抓取数据
bash
# 在 Ubuntu 上跑一个 10 秒的视频测试
GST_SHARK_LOCATION=/home/root/gst-shark/tracing_data/ \
GST_DEBUG="GST_TRACER:7" \
GST_TRACERS="proctime;interlatency;framerate;cpuusage;bitrate;queuelevel" \
gst-launch-1.0 -q videotestsrc num-buffers=300 ! x264enc ! fakesink
gst-shark/tracing_data下生成datastream和metadata(只需要metadata)
gst-shark数据分析
数据分析有几种方式:gstshark-plot/babeltrace/Trace Compass
Trace Compass下载地址: https://eclipse.dev/tracecompass/download.html
(1)将datastream和metadata置于同一个目录下
(2)open trace file打开datastream文件
metadata:
/* CTF 1.8 */
typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
trace {
major = 1;
minor = 3;
uuid = "d18e6374-35a1-cd42-8e70-a9cffa712793";
byte_order = le;
packet.header := struct {
uint32_t magic;
uint8_t uuid[16];
uint32_t stream_id;
};
};
clock {
name = monotonic;
uuid = "84db105b-b3f4-4821-b662-efc51455106a";
description = "Monotonic Clock";
freq = 1000000; /* Frequency, in Hz */
/* clock value offset from Epoch is: offset * (1/freq) */
offset_s = 21600;
};
typealias integer {
size = 32; align = 8; signed = false;
map = clock.monotonic.value;
} := uint32_clock_monotonic_t;
typealias integer {
size = 64; align = 8; signed = false;
map = clock.monotonic.value;
} := uint64_clock_monotonic_t;
struct packet_context {
uint64_clock_monotonic_t timestamp_begin;
uint64_clock_monotonic_t timestamp_end;
};
struct event_header {
enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;
variant <id> {
struct {
uint32_t timestamp;
} compact;
struct {
uint32_t id;
uint64_t timestamp;
} extended;
} v;
} align(8);
stream {
id = 0;
event.header := struct event_header;
packet.context := struct packet_context;
};
event {
name = init;
id = 0;
stream_id = 0;
};
event {
name = proctime;
id = 2;
stream_id = 0;
fields := struct {
string element;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _time;
};
};
event {
name = interlatency;
id = 3;
stream_id = 0;
fields := struct {
string from_pad;
string to_pad;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _time;
};
};
event {
name = queuelevel;
id = 6;
stream_id = 0;
fields := struct {
string queue;
integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } size_bytes;
integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } max_size_bytes;
integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } size_buffers;
integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } max_size_buffers;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } size_time;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } max_size_time;
};
};
event {
name = bitrate;
id = 7;
stream_id = 0;
fields := struct {
string pad;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _bps;
};
};
event {
name = cpuusage;
id = 1;
stream_id = 0;
fields := struct {
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu0;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu1;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu2;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu3;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu4;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu5;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu6;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu7;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu8;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu9;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu10;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu11;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu12;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu13;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu14;
floating_point { exp_dig = 8; mant_dig = 24; byte_order = le; align = 8; } _cpu15;
};
};
event {
name = framerate;
id = 4;
stream_id = 0;
fields := struct {
string pad;
integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _fps;
};
};