GStreamer for Android Demo
GStreamer for Android
本文主要是梳理GStreamer 官方入门demo写的随手笔记
文章目录
- GStreamer for Android
- 一、编译过程
- 二、初始化GStreamer
- 2.1 GStreamer.java 基础类
- 2.2 Gstreamer_android-1.0.c.in 安卓平台初始化专用 jni
- 2.3 gst_android_init 初始化
- 2.4 init context初始化操作
- 2.5 gst_android_register_static_plugins
- 2.6 gst_android_load_gio_modules 获取gio modeule
- 三、API示例
- 3.1 tutorial-1 获取版本信息
- 3.2 tutorial-2 播放音频
- 3.2.1 示例代码
- 3.2.2 JNI method对应表
- 3.2.3 CustomData 自定义APP信息
- 3.2.3 gst_native_class_init 将java层的class与GStreamer关联
- 3.2.4 gst_native_init 初始化thread
- 3.2.5 app_function thread循环
- 3.2.6 gst_native_play 开始播放
- 3.2.7 gst_native_pause 暂停
- 3.2.8 gst_native_finalize 释放资源
- 3.3 tutorial-3 简单视频播放
- 3.3.1 实例代码
- 3.3.2 JNI method对应表
- 3.3.3 app_function 与tutorial-2差别
- 3.3.4 gst_native_surface_init surface显示初始化
- 3.3.5 check_initialization_complete 回调初始化完成
- 3.3.6 gst_native_surface_finalize 释放
- 3.4 tutorial-4 视频播放
- 3.4.1实例代码
- 3.4.2 JNI method列表
- 3.4.3 app_function
- 3.4.4 state_changed_cb 状态改变回调
- 3.4.5 check_media_size 检查媒体的size
- 3.4.6 clock_lost_cb 当计时器丢失时回调
- 3.4.7 buffering_cb 播放流媒体 0%-》100%
- 3.4.8 duration_cb 时长进度回调
- 3.4.9 eos_cb 播放结束
- 3.4.10 error_cb 错误回调
- 3.4.11 delayed_seek_cb 延时seek回调
- 3.4.12 关键 execute_seek 执行seek操作
- 3.4.13 gst_native_set_position设置 pos
- 3.4.14 set_current_ui_position 通知UI时长和pos
- 3.5 turorial-5 完整播放器
一、编译过程
1.1 环境搭建
下载GStreamer SDK https://gstreamer.freedesktop.org/data/pkg/android/
下载Demo https://gitlab.freedesktop.org/gstreamer/gst-docs/
下载NDK
本文使用为 NDK 21.3.65 + gstream 17.1
在gradle.properties中增加配置文件
如
gstAndroidRoot=D:\\GitHubSample\\gstreamer\\gstreamer-1.0-android-universal-1.17.1.tar\\gstreamer-1.0-android-universal-1.17.1
如出现GradleException 找不到的问题 将 GradleException改成Exception
如出现?android:attr/colorError 错误
更改到合适版本号
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
1.2 JNI mk文件配置
APP会加载 两个NDK lib 一个是gstreamer_android包含初始化等,一个是私有的native库提供gstreamer的接口封装
tutorial-5也是依赖gstreamer_android的
static {
System.loadLibrary("gstreamer_android");
System.loadLibrary("tutorial-5");
nativeClassInit();
}
1.2.1 gradle配置
gstAndroidRoot为GStreamer SDK的路径
externalNativeBuild {
ndkBuild {
def gstRoot
if (project.hasProperty('gstAndroidRoot'))
gstRoot = project.gstAndroidRoot
else
gstRoot = System.env.GSTREAMER_ROOT_ANDROID
if (gstRoot == null)
throw new Exception('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
//路径赋值
arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets"
targets "tutorial-1"
// All archs except MIPS and MIPS64 are supported
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
1.2.2 Application.mk
APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_STL = c++_shared
1.2.3 Android.mk
编译生成tutorial-5
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := tutorial-5
LOCAL_SRC_FILES := tutorial-5.c dummy.cpp
LOCAL_SHARED_LIBRARIES := gstreamer_android
LOCAL_LDLIBS := -llog -landroid
include $(BUILD_SHARED_LIBRARY)
ifndef GSTREAMER_ROOT_ANDROID
$(error GSTREAMER_ROOT_ANDROID is not defined!)
endif
ifeq ($(TARGET_ARCH_ABI),armeabi)
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm
else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7
else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64
else ifeq ($(TARGET_ARCH_ABI),x86)
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86
else ifeq ($(TARGET_ARCH_ABI),x86_64)
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64
else
$(error Target arch ABI not supported: $(TARGET_ARCH_ABI))
endif
#NDK buildpath
GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
#插件 mk
include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS)
G_IO_MODULES := openssl
GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0
#gstreamer 初始化的 mk
include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
1.2.3 plugins.mk
在Android.mk里
#插件 mk
include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS)
G_IO_MODULES := openssl
GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0
plugins.mk 里预设了好多插件
如GSTREAMER_PLUGINS_PLAYBACK 对应了GSTREAMER_PLUGINS_PLAYBACK := playback
GSTREAMER_PLUGINS_CORE := coreelements coretracers adder app audioconvert audiomixer audiorate audioresample audiotestsrc compositor gio overlaycomposition pango rawparse typefindfunctions videoconvert videorate videoscale videotestsrc volume autodetect videofilter
GSTREAMER_PLUGINS_CODECS := subparse ogg theora vorbis opus ivorbisdec alaw apetag audioparsers auparse avi dv flac flv flxdec icydemux id3demux isomp4 jpeg lame matroska mpg123 mulaw multipart png speex taglib vpx wavenc wavpack wavparse y4menc adpcmdec adpcmenc bz2 dash dvbsuboverlay dvdspu hls id3tag kate midi mxf openh264 opusparse pcapparse pnm rfbsrc siren smoothstreaming subenc videoparsersbad y4mdec jpegformat gdp rsvg openjpeg spandsp sbc zbar androidmedia
GSTREAMER_PLUGINS_ENCODING := encoding
GSTREAMER_PLUGINS_NET := tcp rtsp rtp rtpmanager soup udp dtls netsim rtmp2 sctp sdpelem srtp srt webrtc nice rtspclientsink
GSTREAMER_PLUGINS_PLAYBACK := playback
GSTREAMER_PLUGINS_SYS := opengl ipcpipeline opensles
GSTREAMER_PLUGINS_VIS := libvisual goom goom2k1 audiovisualizers
GSTREAMER_PLUGINS_EFFECTS := alpha alphacolor audiofx cairo cutter debug deinterlace dtmf effectv equalizer gdkpixbuf imagefreeze interleave level multifile replaygain shapewipe smpte spectrum videobox videocrop videomixer accurip aiff audiobuffersplit audiofxbad audiolatency audiomixmatrix autoconvert bayer coloreffects closedcaption debugutilsbad fieldanalysis freeverb frei0r gaudieffects geometrictransform inter interlace ivtc legacyrawparse proxy removesilence segmentclip smooth speed soundtouch timecode videofiltersbad videoframe_audiolevel webrtcdsp ladspa
GSTREAMER_PLUGINS_CAPTURE := camerabin
GSTREAMER_PLUGINS_CODECS_GPL := assrender
GSTREAMER_PLUGINS_CODECS_RESTRICTED := asfmux dtsdec mpegpsdemux mpegpsmux mpegtsdemux mpegtsmux voaacenc a52dec amrnb amrwbdec asf dvdsub dvdlpcmdec xingmux realmedia x264 libav
GSTREAMER_PLUGINS_NET_RESTRICTED := mms rtmp
GSTREAMER_PLUGINS_VULKAN := vulkan
GSTREAMER_PLUGINS_GES := nle ges
在初始化阶段会将这些插件regist进去
genstatic_$(TARGET_ARCH_ABI):
$(hide)$(HOST_ECHO) "GStreamer : [GEN] => $(PRIV_C)"
$(hide)$(call host-mkdir,$(PRIV_B_DIR))
$(hide)$(SED_LOCAL) "s/@PLUGINS_DECLARATION@/$(PRIV_P_D)/g" $(PRIV_C_IN) | $(SED_LOCAL) "s/@PLUGINS_REGISTRATION@/$(PRIV_P_R)/g" | $(SED_LOCAL) "s/@G_IO_MODULES_LOAD@/$(PRIV_G_L)/g" | $(SED_LOCAL) "s/@G_IO_MODULES_DECLARE@/$(PRIV_G_R)/g" > $(PRIV_C)
@PLUGINS_REGISTRATION@ 引用这个
/* Call this function to register static plugins */
void
gst_android_register_static_plugins (void)
{
@PLUGINS_REGISTRATION@
}
最后编译过程中就是 GST_PLUGIN_STATIC_REGISTER(playback); 是其中一个
/* Call this function to register static plugins */
void
gst_android_register_static_plugins (void)
{
GST_PLUGIN_STATIC_REGISTER(coreelements); GST_PLUGIN_STATIC_REGISTER(coretracers); GST_PLUGIN_STATIC_REGISTER(adder); GST_PLUGIN_STATIC_REGISTER(app); GST_PLUGIN_STATIC_REGISTER(audioconvert); GST_PLUGIN_STATIC_REGISTER(audiomixer); GST_PLUGIN_STATIC_REGISTER(audiorate); GST_PLUGIN_STATIC_REGISTER(audioresample); GST_PLUGIN_STATIC_REGISTER(audiotestsrc); GST_PLUGIN_STATIC_REGISTER(compositor); GST_PLUGIN_STATIC_REGISTER(gio); GST_PLUGIN_STATIC_REGISTER(overlaycomposition); GST_PLUGIN_STATIC_REGISTER(pango); GST_PLUGIN_STATIC_REGISTER(rawparse); GST_PLUGIN_STATIC_REGISTER(typefindfunctions); GST_PLUGIN_STATIC_REGISTER(videoconvert); GST_PLUGIN_STATIC_REGISTER(videorate); GST_PLUGIN_STATIC_REGISTER(videoscale); GST_PLUGIN_STATIC_REGISTER(videotestsrc); GST_PLUGIN_STATIC_REGISTER(volume); GST_PLUGIN_STATIC_REGISTER(autodetect); GST_PLUGIN_STATIC_REGISTER(videofilter); GST_PLUGIN_STATIC_REGISTER(playback); GST_PLUGIN_STATIC_REGISTER(subparse); GST_PLUGIN_STATIC_REGISTER(ogg); GST_PLUGIN_STATIC_REGISTER(theora); GST_PLUGIN_STATIC_REGISTER(vorbis); GST_PLUGIN_STATIC_REGISTER(opus); GST_PLUGIN_STATIC_REGISTER(ivorbisdec); GST_PLUGIN_STATIC_REGISTER(alaw); GST_PLUGIN_STATIC_REGISTER(apetag); GST_PLUGIN_STATIC_REGISTER(audioparsers); GST_PLUGIN_STATIC_REGISTER(auparse); GST_PLUGIN_STATIC_REGISTER(avi); GST_PLUGIN_STATIC_REGISTER(dv); GST_PLUGIN_STATIC_REGISTER(flac); GST_PLUGIN_STATIC_REGISTER(flv); GST_PLUGIN_STATIC_REGISTER(flxdec); GST_PLUGIN_STATIC_REGISTER(icydemux); GST_PLUGIN_STATIC_REGISTER(id3demux); GST_PLUGIN_STATIC_REGISTER(isomp4); GST_PLUGIN_STATIC_REGISTER(jpeg); GST_PLUGIN_STATIC_REGISTER(lame); GST_PLUGIN_STATIC_REGISTER(matroska); GST_PLUGIN_STATIC_REGISTER(mpg123); GST_PLUGIN_STATIC_REGISTER(mulaw); GST_PLUGIN_STATIC_REGISTER(multipart); GST_PLUGIN_STATIC_REGISTER(png); GST_PLUGIN_STATIC_REGISTER(speex); GST_PLUGIN_STATIC_REGISTER(taglib); GST_PLUGIN_STATIC_REGISTER(vpx); GST_PLUGIN_STATIC_REGISTER(wavenc); GST_PLUGIN_STATIC_REGISTER(wavpack); GST_PLUGIN_STATIC_REGISTER(wavparse); GST_PLUGIN_STATIC_REGISTER(y4menc); GST_PLUGIN_STATIC_REGISTER(adpcmdec); GST_PLUGIN_STATIC_REGISTER(adpcmenc); GST_PLUGIN_STATIC_REGISTER(bz2); GST_PLUGIN_STATIC_REGISTER(dash); GST_PLUGIN_STATIC_REGISTER(dvbsuboverlay); GST_PLUGIN_STATIC_REGISTER(dvdspu); GST_PLUGIN_STATIC_REGISTER(hls); GST_PLUGIN_STATIC_REGISTER(id3tag); GST_PLUGIN_STATIC_REGISTER(kate); GST_PLUGIN_STATIC_REGISTER(midi); GST_PLUGIN_STATIC_REGISTER(mxf); GST_PLUGIN_STATIC_REGISTER(openh264); GST_PLUGIN_STATIC_REGISTER(opusparse); GST_PLUGIN_STATIC_REGISTER(pcapparse); GST_PLUGIN_STATIC_REGISTER(pnm); GST_PLUGIN_STATIC_REGISTER(rfbsrc); GST_PLUGIN_STATIC_REGISTER(siren); GST_PLUGIN_STATIC_REGISTER(smoothstreaming); GST_PLUGIN_STATIC_REGISTER(subenc); GST_PLUGIN_STATIC_REGISTER(videoparsersbad); GST_PLUGIN_STATIC_REGISTER(y4mdec); GST_PLUGIN_STATIC_REGISTER(jpegformat); GST_PLUGIN_STATIC_REGISTER(gdp); GST_PLUGIN_STATIC_REGISTER(rsvg); GST_PLUGIN_STATIC_REGISTER(openjpeg); GST_PLUGIN_STATIC_REGISTER(spandsp); GST_PLUGIN_STATIC_REGISTER(sbc); GST_PLUGIN_STATIC_REGISTER(zbar); GST_PLUGIN_STATIC_REGISTER(androidmedia); GST_PLUGIN_STATIC_REGISTER(tcp); GST_PLUGIN_STATIC_REGISTER(rtsp); GST_PLUGIN_STATIC_REGISTER(rtp); GST_PLUGIN_STATIC_REGISTER(rtpmanager); GST_PLUGIN_STATIC_REGISTER(soup); GST_PLUGIN_STATIC_REGISTER(udp); GST_PLUGIN_STATIC_REGISTER(dtls); GST_PLUGIN_STATIC_REGISTER(netsim); GST_PLUGIN_STATIC_REGISTER(rtmp2); GST_PLUGIN_STATIC_REGISTER(sctp); GST_PLUGIN_STATIC_REGISTER(sdpelem); GST_PLUGIN_STATIC_REGISTER(srtp); GST_PLUGIN_STATIC_REGISTER(srt); GST_PLUGIN_STATIC_REGISTER(webrtc); GST_PLUGIN_STATIC_REGISTER(nice); GST_PLUGIN_STATIC_REGISTER(rtspclientsink); GST_PLUGIN_STATIC_REGISTER(opengl); GST_PLUGIN_STATIC_REGISTER(ipcpipeline); GST_PLUGIN_STATIC_REGISTER(opensles);
}
@G_IO_MODULES_DECLARE@
void
gst_android_load_gio_modules (void)
{
GTlsBackend *backend;
const gchar *ca_certs;
GST_G_IO_MODULE_LOAD(openssl);
1.3 SDK里的NDK build
SDK里有以下几个mk
gstreamer-1.0.mk
gstreamer_prebilt.mk
plugins.mk //
tools.mk
1.3.1 gstreamer-1.0.mk
初始化资源路径路径变量相关 GSTREAMER_ROOT为SDK根目录
$(call assert-defined, GSTREAMER_ROOT)
$(if $(wildcard $(GSTREAMER_ROOT)),,\
$(error "The directory GSTREAMER_ROOT=$(GSTREAMER_ROOT) does not exists")\
)
#####################
# Setup variables #
#####################
ifndef GSTREAMER_PLUGINS
$(info "The list of GSTREAMER_PLUGINS is empty")
endif
# Expand home directory (~/)
GSTREAMER_ROOT := $(wildcard $(GSTREAMER_ROOT))
# Path for GStreamer static plugins
GSTREAMER_STATIC_PLUGINS_PATH := $(GSTREAMER_ROOT)/lib/gstreamer-1.0
# Path for the NDK integration makefiles
GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build
ifndef GSTREAMER_INCLUDE_FONTS
GSTREAMER_INCLUDE_FONTS := yes
endif
ifndef GSTREAMER_INCLUDE_CA_CERTIFICATES
GSTREAMER_INCLUDE_CA_CERTIFICATES := yes
endif
ifndef GSTREAMER_JAVA_SRC_DIR
GSTREAMER_JAVA_SRC_DIR := src
endif
加载tools.mk 和工具相关变量
# Include tools
include $(GSTREAMER_NDK_BUILD_PATH)/tools.mk
# Path for the static GIO modules
G_IO_MODULES_PATH := $(GSTREAMER_ROOT)/lib/gio/modules
# Path for libc++_shared
CXX_SHARED_ROOT := $(NDK_ROOT)/sources/cxx-stl/llvm-libc++/libs/$(TARGET_ARCH_ABI)
# Host tools
ifeq ($(HOST_OS),windows)
SED := $(GSTREAMER_NDK_BUILD_PATH)/tools/windows/sed
SED_LOCAL := "$(GSTREAMER_NDK_BUILD_PATH)/tools/windows/sed"
EXE_SUFFIX := .exe
else
SED := sed
SED_LOCAL := sed
EXE_SUFFIX :=
endif
编译变量和编译module信息 生成gstreamer_android 的库
ifndef GSTREAMER_ANDROID_MODULE_NAME
GSTREAMER_ANDROID_MODULE_NAME := gstreamer_android
endif
GSTREAMER_BUILD_DIR := gst-build-$(TARGET_ARCH_ABI)
GSTREAMER_ANDROID_O := $(GSTREAMER_BUILD_DIR)/$(GSTREAMER_ANDROID_MODULE_NAME).o
GSTREAMER_ANDROID_SO := $(GSTREAMER_BUILD_DIR)/lib$(GSTREAMER_ANDROID_MODULE_NAME).so
GSTREAMER_ANDROID_C := $(GSTREAMER_BUILD_DIR)/$(GSTREAMER_ANDROID_MODULE_NAME).c
GSTREAMER_ANDROID_C_IN := $(GSTREAMER_NDK_BUILD_PATH)/gstreamer_android-1.0.c.in
GSTREAMER_DEPS := $(GSTREAMER_EXTRA_DEPS) gstreamer-1.0
GSTREAMER_LD := -fuse-ld=gold$(EXE_SUFFIX) -Wl,-soname,lib$(GSTREAMER_ANDROID_MODULE_NAME).so
# for setting the default GTlsDatabase
ifeq ($(GSTREAMER_INCLUDE_CA_CERTIFICATES),yes)
GSTREAMER_DEPS += gio-2.0
endif
################################
# NDK Build Prebuilt library #
################################
# Declare a prebuilt library module, a shared library including
# gstreamer, its dependencies and all its plugins.
# Since the shared library is not really prebuilt, but will be built
# using the defined rules in this file, we can't use the
# PREBUILT_SHARED_LIBRARY makefiles like explained in the docs,
# as it checks for the existance of the shared library. We therefore
# use a custom gstreamer_prebuilt.mk, which skips this step
include $(CLEAR_VARS)
LOCAL_MODULE := $(GSTREAMER_ANDROID_MODULE_NAME)
LOCAL_SRC_FILES := $(GSTREAMER_ANDROID_SO)
LOCAL_BUILD_SCRIPT := PREBUILT_SHARED_LIBRARY
LOCAL_MODULE_CLASS := PREBUILT_SHARED_LIBRARY
LOCAL_MAKEFILE := $(local-makefile)
LOCAL_PREBUILT_PREFIX := lib
LOCAL_PREBUILT_SUFFIX := .so
LOCAL_EXPORT_C_INCLUDES := $(subst -I$1, $1, $(call pkg-config-get-includes,$(GSTREAMER_DEPS)))
LOCAL_EXPORT_C_INCLUDES += $(GSTREAMER_ROOT)/include
开始加载plugin 其中会加载到 prebuilt.mk
##################################################################
# Our custom rules to create a shared libray with gstreamer #
# and the requested plugins in GSTREAMER_PLUGINS starts here #
##################################################################
include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer_prebuilt.mk
fix-deps = \
$(subst $1,$1 $2,$(GSTREAMER_ANDROID_LIBS))
# Generate list of plugin links (eg: -lcoreelements -lvideoscale)
GSTREAMER_PLUGINS_LIBS := $(foreach plugin, $(GSTREAMER_PLUGINS), -lgst$(plugin) )
GSTREAMER_PLUGINS_CLASSES := $(strip \
$(subst $(GSTREAMER_NDK_BUILD_PATH),, \
$(foreach plugin,$(GSTREAMER_PLUGINS), \
$(wildcard $(GSTREAMER_NDK_BUILD_PATH)/$(plugin)/*.java))))
GSTREAMER_PLUGINS_WITH_CLASSES := $(strip \
$(subst $(GSTREAMER_NDK_BUILD_PATH),, \
$(foreach plugin, $(GSTREAMER_PLUGINS), \
$(wildcard $(GSTREAMER_NDK_BUILD_PATH)/$(plugin)))))
# Generate the plugins' declaration strings
GSTREAMER_PLUGINS_DECLARE := $(foreach plugin, $(GSTREAMER_PLUGINS), \
GST_PLUGIN_STATIC_DECLARE($(plugin));)
# Generate the plugins' registration strings
GSTREAMER_PLUGINS_REGISTER := $(foreach plugin, $(GSTREAMER_PLUGINS), \
GST_PLUGIN_STATIC_REGISTER($(plugin));)
IO模块
# Generate list of gio modules
G_IO_MODULES_LIBS := $(foreach module, $(G_IO_MODULES), $(G_IO_MODULES_PATH)/libgio$(module).a)
G_IO_MODULES_DECLARE := $(foreach module, $(G_IO_MODULES), \
GST_G_IO_MODULE_DECLARE($(module));)
G_IO_MODULES_LOAD := $(foreach module, $(G_IO_MODULES), \
GST_G_IO_MODULE_LOAD($(module));)
# Get the full list of libraries
# link at least to gstreamer-1.0 in case the plugins list is empty
GSTREAMER_ANDROID_LIBS := $(call pkg-config-get-libs,$(GSTREAMER_DEPS))
GSTREAMER_ANDROID_LIBS += $(GSTREAMER_PLUGINS_LIBS) $(G_IO_MODULES_LIBS) $(GSTREAMER_EXTRA_LIBS) -llog -lz
GSTREAMER_ANDROID_WHOLE_AR := $(call pkg-config-get-libs-no-deps,$(GSTREAMER_DEPS)) $(GSTREAMER_EXTRA_LIBS)
GSTREAMER_ANDROID_CFLAGS := $(call pkg-config-get-includes,$(GSTREAMER_DEPS)) -I$(GSTREAMER_ROOT)/include
NDK相关 和 Android cmd
# In newer NDK, SYSROOT is replaced by SYSROOT_INC and SYSROOT_LINK, which
# now points to the root directory. But this will probably change in the future from:
# https://android.googlesource.com/platform/ndk/+/fa8c1b4338c1bef2813ecee0ee298e9498a1aaa7
ifdef SYSROOT
SYSROOT_GST_INC := $(SYSROOT)
SYSROOT_GST_LINK_ARG := --sysroot=$(SYSROOT)
else
ifdef SYSROOT_LINK
ifdef SYSROOT_LINK
SYSROOT_GST_INC := $(SYSROOT_INC)
# SYSROOT_GST_LINK_ARG := --sysroot=$(SYSROOT_LINK)
SYSROOT_GST_LINK_ARG := --sysroot=$(SYSROOT_INC)
endif
else
ifdef SYSROOT_LIB_DIR
# https://android.googlesource.com/platform/ndk/+/8afb627a222005272e61d4b222b50c69e760d77d
# introduced SYSROOT_LIB_DIR
SYSROOT_GST_INC := $(SYSROOT_INC)
SYSROOT_GST_LINK_ARG := -L$(SYSROOT_API_LIB_DIR) -L$(SYSROOT_LIB_DIR)
else
SYSROOT_GST_INC := $(NDK_PLATFORMS_ROOT)/$(TARGET_PLATFORM)/arch-$(TARGET_ARCH)
SYSROOT_GST_LINK_ARG := -L$(SYSROOT_GST_INC)
endif
endif
endif
# Create the link command
GSTREAMER_ANDROID_CMD := $(call libtool-link,$(TARGET_CXX) $(GLOBAL_LDFLAGS) $(TARGET_LDFLAGS) -nostdlib++ -shared $(SYSROOT_GST_LINK_ARG) \
-o $(GSTREAMER_ANDROID_SO) $(GSTREAMER_ANDROID_O) \
-L$(GSTREAMER_ROOT)/lib -L$(GSTREAMER_STATIC_PLUGINS_PATH) -L$(CXX_SHARED_ROOT) \
$(GSTREAMER_ANDROID_LIBS), $(GSTREAMER_LD)) -Wl,-no-undefined $(GSTREAMER_LD)
GSTREAMER_ANDROID_CMD := $(call libtool-whole-archive,$(GSTREAMER_ANDROID_CMD),$(GSTREAMER_ANDROID_WHOLE_AR))
重点 用户自定义 包含插件 字体 签名
# This triggers the build of our library using our custom rules
$(GSTREAMER_ANDROID_SO): buildsharedlibrary_$(TARGET_ARCH_ABI)
$(GSTREAMER_ANDROID_SO): copyjavasource_$(TARGET_ARCH_ABI)
ifeq ($(GSTREAMER_INCLUDE_FONTS),yes)
$(GSTREAMER_ANDROID_SO): copyfontsres_$(TARGET_ARCH_ABI)
endif
ifeq ($(GSTREAMER_INCLUDE_CA_CERTIFICATES),yes)
$(GSTREAMER_ANDROID_SO): copycacertificatesres_$(TARGET_ARCH_ABI)
endif
delsharedlib_$(TARGET_ARCH_ABI): PRIV_B_DIR := $(GSTREAMER_BUILD_DIR)
delsharedlib_$(TARGET_ARCH_ABI):
$(hide)$(call host-rm,$(prebuilt))
$(hide)$(foreach path,$(wildcard $(PRIV_B_DIR)/sed*), $(call host-rm,$(path)) && ) echo Done rm
$(LOCAL_INSTALLED): delsharedlib_$(TARGET_ARCH_ABI)
# Generates a source file that declares and registers all the required plugins
# about the sed command, android-studio doesn't seem to like line continuation characters when executing shell commands
genstatic_$(TARGET_ARCH_ABI): PRIV_C := $(GSTREAMER_ANDROID_C)
genstatic_$(TARGET_ARCH_ABI): PRIV_B_DIR := $(GSTREAMER_BUILD_DIR)
genstatic_$(TARGET_ARCH_ABI): PRIV_C_IN := $(GSTREAMER_ANDROID_C_IN)
genstatic_$(TARGET_ARCH_ABI): PRIV_P_D := $(GSTREAMER_PLUGINS_DECLARE)
genstatic_$(TARGET_ARCH_ABI): PRIV_P_R := $(GSTREAMER_PLUGINS_REGISTER)
genstatic_$(TARGET_ARCH_ABI): PRIV_G_L := $(G_IO_MODULES_LOAD)
genstatic_$(TARGET_ARCH_ABI): PRIV_G_R := $(G_IO_MODULES_DECLARE)
genstatic_$(TARGET_ARCH_ABI):
$(hide)$(HOST_ECHO) "GStreamer : [GEN] => $(PRIV_C)"
$(hide)$(call host-mkdir,$(PRIV_B_DIR))
$(hide)$(SED_LOCAL) "s/@PLUGINS_DECLARATION@/$(PRIV_P_D)/g" $(PRIV_C_IN) | $(SED_LOCAL) "s/@PLUGINS_REGISTRATION@/$(PRIV_P_R)/g" | $(SED_LOCAL) "s/@G_IO_MODULES_LOAD@/$(PRIV_G_L)/g" | $(SED_LOCAL) "s/@G_IO_MODULES_DECLARE@/$(PRIV_G_R)/g" > $(PRIV_C)
# Compile the source file
$(GSTREAMER_ANDROID_O): PRIV_C := $(GSTREAMER_ANDROID_C)
$(GSTREAMER_ANDROID_O): PRIV_CC_CMD := $(TARGET_CC) --sysroot=$(SYSROOT_GST_INC) $(SYSROOT_ARCH_INC_ARG) $(GLOBAL_CFLAGS) $(TARGET_CFLAGS) \
-c $(GSTREAMER_ANDROID_C) -Wall -Werror -o $(GSTREAMER_ANDROID_O) $(GSTREAMER_ANDROID_CFLAGS)
$(GSTREAMER_ANDROID_O): PRIV_GST_CFLAGS := $(GSTREAMER_ANDROID_CFLAGS) $(TARGET_CFLAGS)
$(GSTREAMER_ANDROID_O): genstatic_$(TARGET_ARCH_ABI)
$(hide)$(HOST_ECHO) "GStreamer : [COMPILE] => $(PRIV_C)"
$(hide)$(PRIV_CC_CMD)
# Creates a shared library including gstreamer, its plugins and all the dependencies
buildsharedlibrary_$(TARGET_ARCH_ABI): PRIV_CMD := $(GSTREAMER_ANDROID_CMD)
buildsharedlibrary_$(TARGET_ARCH_ABI): PRIV_SO := $(GSTREAMER_ANDROID_SO)
buildsharedlibrary_$(TARGET_ARCH_ABI): $(GSTREAMER_ANDROID_O)
$(hide)$(HOST_ECHO) "GStreamer : [LINK] => $(PRIV_SO)"
$(hide)$(PRIV_CMD)
ifeq ($(GSTREAMER_INCLUDE_FONTS),yes)
GSTREAMER_INCLUDE_FONTS_SUBST :=
else
GSTREAMER_INCLUDE_FONTS_SUBST := //
endif
ifeq ($(GSTREAMER_INCLUDE_CA_CERTIFICATES),yes)
GSTREAMER_INCLUDE_CA_CERTIFICATES_SUBST :=
else
GSTREAMER_INCLUDE_CA_CERTIFICATES_SUBST := //
endif
ifneq (,$(findstring yes,$(GSTREAMER_INCLUDE_FONTS)$(GSTREAMER_INCLUDE_CA_CERTIFICATES)))
GSTREAMER_COPY_FILE_SUBST :=
else
GSTREAMER_COPY_FILE_SUBST := //
endif
# about the sed command, android-studio doesn't seem to like line continuation characters when executing shell commands
copyjavasource_$(TARGET_ARCH_ABI):
$(hide)$(call host-mkdir,$(GSTREAMER_JAVA_SRC_DIR)/org/freedesktop/gstreamer)
$(hide)$(foreach plugin,$(GSTREAMER_PLUGINS_WITH_CLASSES), \
$(call host-mkdir,$(GSTREAMER_JAVA_SRC_DIR)/org/freedesktop/gstreamer/$(plugin)) && ) echo Done mkdir
$(hide)$(foreach file,$(GSTREAMER_PLUGINS_CLASSES), \
$(call host-cp,$(GSTREAMER_NDK_BUILD_PATH)$(file),$(GSTREAMER_JAVA_SRC_DIR)/org/freedesktop/gstreamer/$(file)) && ) echo Done cp
$(hide)$(SED_LOCAL) "s;@INCLUDE_FONTS@;$(GSTREAMER_INCLUDE_FONTS_SUBST);g" $(GSTREAMER_NDK_BUILD_PATH)/GStreamer.java | $(SED_LOCAL) "s;@INCLUDE_CA_CERTIFICATES@;$(GSTREAMER_INCLUDE_CA_CERTIFICATES_SUBST);g" | $(SED_LOCAL) "s;@INCLUDE_COPY_FILE@;$(GSTREAMER_COPY_FILE_SUBST);g" > $(GSTREAMER_JAVA_SRC_DIR)/org/freedesktop/gstreamer/GStreamer.java
ifndef GSTREAMER_ASSETS_DIR
GSTREAMER_ASSETS_DIR := src/main/assets
endif
copyfontsres_$(TARGET_ARCH_ABI):
$(hide)$(call host-mkdir,$(GSTREAMER_ASSETS_DIR)/fontconfig)
$(hide)$(call host-mkdir,$(GSTREAMER_ASSETS_DIR)/fontconfig/fonts/truetype/)
$(hide)$(call host-cp,$(GSTREAMER_NDK_BUILD_PATH)/fontconfig/fonts.conf,$(GSTREAMER_ASSETS_DIR)/fontconfig)
$(hide)$(call host-cp,$(GSTREAMER_NDK_BUILD_PATH)/fontconfig/fonts/Ubuntu-R.ttf,$(GSTREAMER_ASSETS_DIR)/fontconfig/fonts/truetype)
copycacertificatesres_$(TARGET_ARCH_ABI):
$(hide)$(call host-mkdir,$(GSTREAMER_ASSETS_DIR)/ssl/certs)
$(hide)$(call host-cp,$(GSTREAMER_ROOT)/etc/ssl/certs/ca-certificates.crt,$(GSTREAMER_ASSETS_DIR)/ssl/certs)
1.3.2 Tools.mk
加载一堆lib库
1.3.3 Gstreamer_prebuilt.mk
最终加载到build-module.mk
include $(BUILD_SYSTEM)/build-module.mk
二、初始化GStreamer
try {
GStreamer.init(this);
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
finish();
return;
}
2.1 GStreamer.java 基础类
public class GStreamer {
private static native void nativeInit(Context context) throws Exception;
public static void init(Context context) throws Exception {
//将assets中签名证书copy到应用的files目录
copyCaCertificates(context);
//将assets字体格式设置copy到应用饿files目录
copyFonts(context);
//调用 native初始化
nativeInit(context);
}
2.2 Gstreamer_android-1.0.c.in 安卓平台初始化专用 jni
jint
JNI_OnLoad (JavaVM * vm, void * reserved)
{
JNIEnv *env = NULL;
GModule *module;
if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not retrieve JNIEnv");
return 0;
}
// 找到 GStreamer里的java类 适配需要修改这个路径
jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/GStreamer");
if (!klass) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not retrieve class org.freedesktop.gstreamer.GStreamer");
return 0;
}
//注册JNI函数
if ((*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods))) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Could not register native methods for org.freedesktop.gstreamer.GStreamer");
return 0;
}
/* Remember Java VM */
_java_vm = vm;
// 打开模块 set java vm虚拟机
/* Tell the androidmedia plugin about the Java VM if we can */
module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
if (module) {
void (*set_java_vm) (JavaVM *) = NULL;
if (g_module_symbol (module, "gst_amc_jni_set_java_vm",
(gpointer *) & set_java_vm) && set_java_vm) {
set_java_vm (vm);
}
g_module_close (module);
}
return JNI_VERSION_1_4;
}
Gmodule.h
/**
* GModuleFlags:
* @G_MODULE_BIND_LAZY: specifies that symbols are only resolved when
* needed. The default action is to bind all symbols when the module
* is loaded.
* @G_MODULE_BIND_LOCAL: specifies that symbols in the module should
* not be added to the global name space. The default action on most
* platforms is to place symbols in the module in the global name space,
* which may cause conflicts with existing symbols.
* @G_MODULE_BIND_MASK: mask for all flags.
*
* Flags passed to g_module_open().
* Note that these flags are not supported on all platforms.
*/
typedef enum
{
G_MODULE_BIND_LAZY = 1 << 0, //懒加载
G_MODULE_BIND_LOCAL = 1 << 1, //命名空间 local
G_MODULE_BIND_MASK = 0x03 //以上两个集合
} GModuleFlags;
/* open a module 'file_name' and return handle, which is NULL on error */
GLIB_AVAILABLE_IN_ALL
GModule* g_module_open (const gchar *file_name,
GModuleFlags flags);
2.3 gst_android_init 初始化
void
gst_android_init (JNIEnv * env, jobject context)
{
//1. init android context
if (!init (env, context)) {
__android_log_print (ANDROID_LOG_INFO, "GStreamer",
"GStreamer failed to initialize");
}
// 初始化already判定
if (gst_is_initialized ()) {
__android_log_print (ANDROID_LOG_INFO, "GStreamer",
"GStreamer already initialized");
return;
}
//2. 获取存有证书签名和字体config的文件路径 获取APP的 cache目录和 files目录
if (!get_application_dirs (env, context, &cache_dir, &files_dir)) {
__android_log_print (ANDROID_LOG_ERROR, "GStreamer",
"Failed to get application dirs");
}
// 设置环境变量 缓存路径和 home路径
if (cache_dir) {
g_setenv ("TMP", cache_dir, TRUE);
registry = g_build_filename (cache_dir, "registry.bin", NULL); //注册
g_setenv ("GST_REGISTRY", registry, TRUE);
g_free (registry);
····
}
if (files_dir) {
gchar *fontconfig, *certs;
g_setenv ("HOME", files_dir, TRUE);
···
fontconfig = g_build_filename (files_dir, "fontconfig", NULL); //字体
g_setenv ("FONTCONFIG_PATH", fontconfig, TRUE);
g_free (fontconfig);
certs = g_build_filename (files_dir, "ssl", "certs", "ca-certificates.crt", NULL); //证书
g_setenv ("CA_CERTIFICATES", certs, TRUE);
g_free (certs);
}
//设置输出打印 handler
/* Set GLib print handlers */
g_set_print_handler (glib_print_handler);
g_set_printerr_handler (glib_printerr_handler);
g_log_set_default_handler (glib_log_handler, NULL);
//未知
/* Call this function to register static plugins */
gst_android_register_static_plugins ();
/* Call this function to load GIO modules */
gst_android_load_gio_modules ();
}
2.4 init context初始化操作
static gboolean
init (JNIEnv *env, jobject context)
{
jclass context_cls = NULL;
jmethodID get_class_loader_id = 0;
jobject class_loader = NULL;
// 获得java层的 context对象
context_cls = (*env)->GetObjectClass (env, context);
get_class_loader_id = (*env)->GetMethodID (env, context_cls,
"getClassLoader", "()Ljava/lang/ClassLoader;");
if ((*env)->ExceptionCheck (env)) {
(*env)->ExceptionDescribe (env);
(*env)->ExceptionClear (env);
return FALSE;
}
//获取 class loader对象
class_loader = (*env)->CallObjectMethod (env, context, get_class_loader_id);
//context 和 classloader 赋值给全局变量
if (_context) {
(*env)->DeleteGlobalRef (env, _context);
}
_context = (*env)->NewGlobalRef (env, context);
if (_class_loader) {
(*env)->DeleteGlobalRef (env, _class_loader);
}
_class_loader = (*env)->NewGlobalRef (env, class_loader);
return TRUE;
}
2.5 gst_android_register_static_plugins
void
gst_android_register_static_plugins (void)
{
//编译替代
@PLUGINS_REGISTRATION@
}
/* Call this function to register static plugins */
void
gst_android_register_static_plugins (void)
{
GST_PLUGIN_STATIC_REGISTER(coreelements); GST_PLUGIN_STATIC_REGISTER(coretracers); GST_PLUGIN_STATIC_REGISTER(adder); GST_PLUGIN_STATIC_REGISTER(app); GST_PLUGIN_STATIC_REGISTER(audioconvert); GST_PLUGIN_STATIC_REGISTER(audiomixer); GST_PLUGIN_STATIC_REGISTER(audiorate); GST_PLUGIN_STATIC_REGISTER(audioresample); GST_PLUGIN_STATIC_REGISTER(audiotestsrc); GST_PLUGIN_STATIC_REGISTER(compositor); GST_PLUGIN_STATIC_REGISTER(gio); GST_PLUGIN_STATIC_REGISTER(overlaycomposition); GST_PLUGIN_STATIC_REGISTER(pango); GST_PLUGIN_STATIC_REGISTER(rawparse); GST_PLUGIN_STATIC_REGISTER(typefindfunctions); GST_PLUGIN_STATIC_REGISTER(videoconvert); GST_PLUGIN_STATIC_REGISTER(videorate); GST_PLUGIN_STATIC_REGISTER(videoscale); GST_PLUGIN_STATIC_REGISTER(videotestsrc); GST_PLUGIN_STATIC_REGISTER(volume); GST_PLUGIN_STATIC_REGISTER(autodetect); GST_PLUGIN_STATIC_REGISTER(videofilter); GST_PLUGIN_STATIC_REGISTER(opengl); GST_PLUGIN_STATIC_REGISTER(ipcpipeline); GST_PLUGIN_STATIC_REGISTER(opensles);
}
2.6 gst_android_load_gio_modules 获取gio modeule
void
gst_android_load_gio_modules (void)
{
GTlsBackend *backend;
const gchar *ca_certs;
@G_IO_MODULES_LOAD@
ca_certs = g_getenv ("CA_CERTIFICATES");
//获取后端
backend = g_tls_backend_get_default ();
if (backend && ca_certs) {
GTlsDatabase *db;
GError *error = NULL;
//创建db
db = g_tls_file_database_new (ca_certs, &error);
if (db) {
//关联后端和DB
g_tls_backend_set_default_database (backend, db);
g_object_unref (db);
} else {
g_warning ("Failed to create a database from file: %s",
error ? error->message : "Unknown");
}
}
}
三、API示例
3.1 tutorial-1 获取版本信息
3.1.1 示例代码
static {
System.loadLibrary("gstreamer_android");
System.loadLibrary("tutorial-1");
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
try {
GStreamer.init(this);
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
finish();
return;
}
setContentView(R.layout.main);
TextView tv = (TextView)findViewById(R.id.textview_info);
tv.setText("Welcome to " + nativeGetGStreamerInfo() + " !");
}
private native String nativeGetGStreamerInfo();
3.1.2 gst_native_get_gstreamer_info
static jstring
gst_native_get_gstreamer_info (JNIEnv * env, jobject thiz)
{
char *version_utf8 = gst_version_string ();
jstring *version_jstring = (*env)->NewStringUTF (env, version_utf8);
g_free (version_utf8);
return version_jstring;
}
3.2 tutorial-2 播放音频
3.2.1 示例代码
3.2.1.1 播放逻辑代码
static {
System.loadLibrary("gstreamer_android");
System.loadLibrary("tutorial-2");
nativeClassInit();
}
private native void nativeInit(); // Initialize native code, build pipeline, etc
private native void nativeFinalize(); // Destroy pipeline and shutdown native code
private native void nativePlay(); // Set pipeline to PLAYING
private native void nativePause(); // Set pipeline to PAUSED
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
//native 可操作属性变量
private long native_custom_data; // Native code will use this to keep private data
void onCreate(){
ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
play.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = true;
//播放
nativePlay();
}
});
ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
pause.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = false;
//暂停
nativePause();
}
});
//初始化播放
nativeInit();
}
//用于native的回调 使用 nativeClasssInit回调
private void onGStreamerInitialized () {
Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired);
// Restore previous playing state
if (is_playing_desired) {
nativePlay();
} else {
nativePause();
}
// Re-enable buttons, now that GStreamer is initialized
final Activity activity = this;
runOnUiThread(new Runnable() {
public void run() {
activity.findViewById(R.id.button_play).setEnabled(true);
activity.findViewById(R.id.button_stop).setEnabled(true);
}
});
}
protected void onDestroy() {
//完成播放
nativeFinalize();
super.onDestroy();
}
3.2.1.2 setMessage native回调
// Called from native code. This sets the content of the TextView from the UI thread.
private void setMessage(final String message) {
final TextView tv = (TextView) this.findViewById(R.id.textview_message);
runOnUiThread (new Runnable() {
public void run() {
tv.setText(message);
}
});
}
3.2.1.3 onGStreamerInitialized native回调初始化完成
//用于native的回调 使用 nativeClasssInit回调
private void onGStreamerInitialized () {
Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired);
// Restore previous playing state
if (is_playing_desired) {
nativePlay();
} else {
nativePause();
}
// Re-enable buttons, now that GStreamer is initialized
final Activity activity = this;
runOnUiThread(new Runnable() {
public void run() {
activity.findViewById(R.id.button_play).setEnabled(true);
activity.findViewById(R.id.button_stop).setEnabled(true);
}
});
}
3.2.2 JNI method对应表
static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
};
3.2.3 CustomData 自定义APP信息
typedef struct _CustomData
{
jobject app; /* Application instance, used to call its methods. A global reference is kept. */
GstElement *pipeline; /* The running pipeline */
GMainContext *context; /* GLib context used to run the main loop */
GMainLoop *main_loop; /* GLib main loop */
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
} CustomData;
3.2.3 gst_native_class_init 将java层的class与GStreamer关联
/* Static class initializer: retrieve method and field IDs */
static jboolean
gst_native_class_init (JNIEnv * env, jclass klass)
{
//设置三个回调函数
custom_data_field_id =
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
3.2.4 gst_native_init 初始化thread
static void
gst_native_init (JNIEnv * env, jobject thiz)
{
//初始化CustomData
CustomData *data = g_new0 (CustomData, 1);
//回调设置 属性变量 native_custom_data
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
//设置debug log
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0,
"Android tutorial 2");
gst_debug_set_threshold_for_name ("tutorial-2", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data);
//将 java层的 引用对象放到 data->app
data->app = (*env)->NewGlobalRef (env, thiz);
GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
// 创建gst_app_thread 便传入 thread创建好了 的__start_routine function
pthread_create (&gst_app_thread, NULL, &app_function, data);
}
3.2.5 app_function thread循环
/* Main method for the native code. This is executed on its own thread. */
static void *
app_function (void *userdata)
{
JavaVMAttachArgs args;
GstBus *bus;
CustomData *data = (CustomData *) userdata;
GSource *bus_source;
GError *error = NULL;
GST_DEBUG ("Creating pipeline in CustomData at %p", data);
//1.context 赋值
/* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new ();
g_main_context_push_thread_default (data->context);
//2. 创建pipeline pipeline_description包括 src convert sample sink
/* Build pipeline */
data->pipeline =
gst_parse_launch
("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);
if (error) {
gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error);
set_ui_message (message, data);
g_free (message);
return NULL;
}
//3. 获取 元件bus
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context);
g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
data);
g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, data);
gst_object_unref (bus);
// 4. 主线程死循环
/* Create a GLib Main Loop and set it to run */
GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
data->main_loop = g_main_loop_new (data->context, FALSE);
check_initialization_complete (data);
g_main_loop_run (data->main_loop);
GST_DEBUG ("Exited main loop");
//循环结束
g_main_loop_unref (data->main_loop);
data->main_loop = NULL;
// 5. 资源释放
/* Free resources */
g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context);
gst_element_set_state (data->pipeline, GST_STATE_NULL);
gst_object_unref (data->pipeline);
return NULL;
}
3.2.6 gst_native_play 开始播放
/* Set pipeline to PLAYING state */
static void
gst_native_play (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PLAYING");
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
}
3.2.7 gst_native_pause 暂停
/* Set pipeline to PAUSED state */
static void
gst_native_pause (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PAUSED");
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
}
3.2.8 gst_native_finalize 释放资源
/* Quit the main loop, remove the native thread and free resources */
static void
gst_native_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Quitting main loop...");
//退出主线程
g_main_loop_quit (data->main_loop);
GST_DEBUG ("Waiting for thread to finish...");
pthread_join (gst_app_thread, NULL);
GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
(*env)->DeleteGlobalRef (env, data->app);
GST_DEBUG ("Freeing CustomData at %p", data);
g_free (data);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
GST_DEBUG ("Done finalizing");
}
3.3 tutorial-3 简单视频播放
/* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) data->native_window);
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) NULL);
3.3.1 实例代码
3.3.1.1 SurfaceView
public class GStreamerSurfaceView extends SurfaceView {
public int media_width = 320;
public int media_height = 240;
//根据 media_width 和 media_height进行 measure
// Called by the layout manager to find out our size and give us some rules.
// We will try to maximize our size, and preserve the media's aspect ratio if
// we are given the freedom to do so.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
}
3.3.1.2 播放逻辑代码
static {
System.loadLibrary("gstreamer_android");
System.loadLibrary("tutorial-3");
nativeClassInit();
}
public void onCreate(Bundle savedInstanceState){
GStreamer.init(this);
SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video);
SurfaceHolder sh = sv.getHolder();
sh.addCallback(this);
ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
play.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = true;
nativePlay();
}
});
ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
pause.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = false;
nativePause();
}
});
nativeInit();
}
3.3.1.3 SurfaceHolder.Callback
//implements SurfaceHolder.Callback
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d("GStreamer", "Surface changed to format " + format + " width "
+ width + " height " + height);
nativeSurfaceInit (holder.getSurface());
}
public void surfaceCreated(SurfaceHolder holder) {
Log.d("GStreamer", "Surface created: " + holder.getSurface());
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("GStreamer", "Surface destroyed");
nativeSurfaceFinalize ();
}
3.3.1.4 native回调函数
与audio示例相同
custom_data_field_id =
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
3.3.2 JNI method对应表
/* List of implemented native methods */
static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause},
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
(void *) gst_native_surface_init},
{"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
};
3.3.3 app_function 与tutorial-2差别
/* Main method for the native code. This is executed on its own thread. */
static void *
app_function (void *userdata)
{
JavaVMAttachArgs args;
GstBus *bus;
CustomData *data = (CustomData *) userdata;
GSource *bus_source;
GError *error = NULL;
GST_DEBUG ("Creating pipeline in CustomData at %p", data);
/* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new ();
g_main_context_push_thread_default (data->context);
/* Build pipeline */
//pipe 描述 增加video相关描述
data->pipeline =
gst_parse_launch ("videotestsrc ! warptv ! videoconvert ! autovideosink",
&error);
if (error) {
gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error);
set_ui_message (message, data);
g_free (message);
return NULL;
}
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */
gst_element_set_state (data->pipeline, GST_STATE_READY);
//video_sink 赋值
data->video_sink =
gst_bin_get_by_interface (GST_BIN (data->pipeline),
GST_TYPE_VIDEO_OVERLAY);
if (!data->video_sink) {
GST_ERROR ("Could not retrieve video sink");
return NULL;
}
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context);
g_source_unref (bus_source);
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
data);
g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, data);
gst_object_unref (bus);
/* Create a GLib Main Loop and set it to run */
GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
data->main_loop = g_main_loop_new (data->context, FALSE);
check_initialization_complete (data);
g_main_loop_run (data->main_loop);
GST_DEBUG ("Exited main loop");
g_main_loop_unref (data->main_loop);
data->main_loop = NULL;
/* Free resources */
g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context);
gst_element_set_state (data->pipeline, GST_STATE_NULL);
//释放 video_sink
gst_object_unref (data->video_sink);
gst_object_unref (data->pipeline);
return NULL;
}
3.3.4 gst_native_surface_init surface显示初始化
static void
gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
// jni方法 转化 surfaceview
ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
if (data->native_window) {
//release之前surface绘制
ANativeWindow_release (data->native_window);
if (data->native_window == new_native_window) {
// 相同 重写覆盖 video_sink
GST_DEBUG ("New native window is the same as the previous one %p",
data->native_window);
if (data->video_sink) {
gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
}
return;
} else {
GST_DEBUG ("Released previous native window %p", data->native_window);
data->initialized = FALSE;
}
}
data->native_window = new_native_window;
check_initialization_complete (data);
}
3.3.5 check_initialization_complete 回调初始化完成
static void
check_initialization_complete (CustomData * data)
{
JNIEnv *env = get_jni_env ();
if (!data->initialized && data->native_window && data->main_loop) {
GST_DEBUG
("Initialization complete, notifying application. native_window:%p main_loop:%p",
data->native_window, data->main_loop);
/* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) data->native_window);
// 回调 java层 onGStreamerInitialized对应的方法
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env);
}
data->initialized = TRUE;
}
}
3.3.6 gst_native_surface_finalize 释放
static void
gst_native_surface_finalize (JNIEnv * env, jobject thiz)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window);
if (data->video_sink) {
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
(guintptr) NULL);
gst_element_set_state (data->pipeline, GST_STATE_READY);
}
ANativeWindow_release (data->native_window);
data->native_window = NULL;
data->initialized = FALSE;
}
3.4 tutorial-4 视频播放
播放的流媒体,与tutorial 3相比多了,进度调整, 画面调整,设置了流媒体uri
native方法
private native void nativeInit(); // Initialize native code, build pipeline, etc
private native void nativeFinalize(); // Destroy pipeline and shutdown native code
private native void nativeSetUri(String uri); // Set the URI of the media to play
private native void nativePlay(); // Set pipeline to PLAYING
private native void nativeSetPosition(int milliseconds); // Seek to the indicated position, in milliseconds
private native void nativePause(); // Set pipeline to PAUSED
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
private native void nativeSurfaceInit(Object surface); // A new surface is available
private native void nativeSurfaceFinalize(); // Surface about to be destroyed
private long native_custom_data; // Native code will use this to keep private data
3.4.1实例代码
3.3.1.1 界面控间初始化
@Override
public void onCreate(Bundle savedInstanceState)
{
GStreamer.init(this);
ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
play.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = true;
nativePlay();
}
});
ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
pause.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = false;
nativePause();
}
});
SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video);
SurfaceHolder sh = sv.getHolder();
sh.addCallback(this);
SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar);
sb.setOnSeekBarChangeListener(this);
// Start with disabled buttons, until native code is initialized
this.findViewById(R.id.button_play).setEnabled(false);
this.findViewById(R.id.button_stop).setEnabled(false);
nativeInit();
}
3.3.1.2 SurfaceHolder.Callback
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d("GStreamer", "Surface changed to format " + format + " width "
+ width + " height " + height);
nativeSurfaceInit (holder.getSurface());
}
public void surfaceCreated(SurfaceHolder holder) {
Log.d("GStreamer", "Surface created: " + holder.getSurface());
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("GStreamer", "Surface destroyed");
nativeSurfaceFinalize ();
}
3.3.1.3 onGStreamerInitialized GStreamer初始化完成
private void onGStreamerInitialized () {
Log.i ("GStreamer", "GStreamer initialized:");
Log.i ("GStreamer", " playing:" + is_playing_desired + " position:" + position + " uri: " + mediaUri);
//设置uri
// Restore previous playing state
setMediaUri ();
nativeSetPosition (position);
if (is_playing_desired) {
nativePlay();
} else {
nativePause();
}
// Re-enable buttons, now that GStreamer is initialized
final Activity activity = this;
runOnUiThread(new Runnable() {
public void run() {
activity.findViewById(R.id.button_play).setEnabled(true);
activity.findViewById(R.id.button_stop).setEnabled(true);
}
});
}
3.3.1.4 setMediaUri 设置uri
private void setMediaUri() {
nativeSetUri (mediaUri);
is_local_media = mediaUri.startsWith("file://");
}
3.3.1.5 onMediaSizeChanged 尺寸更新
// Called from native code when the size of the media changes or is first detected.
// Inform the video surface about the new size and recalculate the layout.
private void onMediaSizeChanged (int width, int height) {
Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video);
gsv.media_width = width;
gsv.media_height = height;
runOnUiThread(new Runnable() {
public void run() {
gsv.requestLayout();
}
});
}
3.4.2 JNI method列表
/* List of implemented native methods */
static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause},
{"nativeSetPosition", "(I)V", (void *) gst_native_set_position},
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
(void *) gst_native_surface_init},
{"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
};
回调方法
static jboolean
gst_native_class_init (JNIEnv * env, jclass klass)
{
custom_data_field_id =
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
set_message_method_id =
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
set_current_position_method_id =
(*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V");
on_gstreamer_initialized_method_id =
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
on_media_size_changed_method_id =
(*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V");
3.4.3 app_function
开始变得复杂了,新增了很多回调,包括播放状态、时长、EOS回调,一秒刷新4次UI refresh_ui
/* Main method for the native code. This is executed on its own thread. */
static void *
app_function (void *userdata)
{
JavaVMAttachArgs args;
GstBus *bus;
CustomData *data = (CustomData *) userdata;
GSource *timeout_source;
GSource *bus_source;
GError *error = NULL;
guint flags;
GST_DEBUG ("Creating pipeline in CustomData at %p", data);
/* Create our own GLib Main Context and make it the default one */
data->context = g_main_context_new ();
g_main_context_push_thread_default (data->context);
/* Build pipeline */
data->pipeline = gst_parse_launch ("playbin", &error);
if (error) {
gchar *message =
g_strdup_printf ("Unable to build pipeline: %s", error->message);
g_clear_error (&error);
set_ui_message (message, data);
g_free (message);
return NULL;
}
//添加字幕
/* Disable subtitles */
g_object_get (data->pipeline, "flags", &flags, NULL);
flags &= ~GST_PLAY_FLAG_TEXT;
g_object_set (data->pipeline, "flags", flags, NULL);
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */
data->target_state = GST_STATE_READY;
gst_element_set_state (data->pipeline, GST_STATE_READY);
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
bus = gst_element_get_bus (data->pipeline);
bus_source = gst_bus_create_watch (bus);
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
NULL, NULL);
g_source_attach (bus_source, data->context);
g_source_unref (bus_source);
//添加了各种回调
//错误回调
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
data);
//eos 播放完毕回调
g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data);
//状态改变回调
g_signal_connect (G_OBJECT (bus), "message::state-changed",
(GCallback) state_changed_cb, data);
//时长回调
g_signal_connect (G_OBJECT (bus), "message::duration",
(GCallback) duration_cb, data);
//得到buffer回调
g_signal_connect (G_OBJECT (bus), "message::buffering",
(GCallback) buffering_cb, data);
//时钟丢失回调
g_signal_connect (G_OBJECT (bus), "message::clock-lost",
(GCallback) clock_lost_cb, data);
gst_object_unref (bus);
//一秒刷新4次UI refresh_ui
/* Register a function that GLib will call 4 times per second */
timeout_source = g_timeout_source_new (250);
//refresh_ui 返回true 循环回调
g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL);
g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source);
/* Create a GLib Main Loop and set it to run */
GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
data->main_loop = g_main_loop_new (data->context, FALSE);
check_initialization_complete (data);
g_main_loop_run (data->main_loop);
GST_DEBUG ("Exited main loop");
g_main_loop_unref (data->main_loop);
data->main_loop = NULL;
/* Free resources */
g_main_context_pop_thread_default (data->context);
g_main_context_unref (data->context);
data->target_state = GST_STATE_NULL;
gst_element_set_state (data->pipeline, GST_STATE_NULL);
gst_object_unref (data->pipeline);
return NULL;
}
3.4.4 state_changed_cb 状态改变回调
/* Notify UI about pipeline state changes */
static void
state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
data->state = new_state;
gchar *message = g_strdup_printf ("State changed to %s",
gst_element_state_get_name (new_state));
//回调java层的 setUiMessage
set_ui_message (message, data);
g_free (message);
if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY)
data->is_live = FALSE;
/* The Ready to Paused state change is particularly interesting: */
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
/* By now the sink already knows the media size */
//检查 media的size 回调重设上层surfaceview大小
check_media_size (data);
//如果seek的时候在暂停状态 在这个时候来执行seek
/* If there was a scheduled seek, perform it now that we have moved to the Paused state */
if (GST_CLOCK_TIME_IS_VALID (data->desired_position))
execute_seek (data->desired_position, data);
}
}
}
3.4.5 check_media_size 检查媒体的size
/* Retrieve the video sink's Caps and tell the application about the media size */
static void
check_media_size (CustomData * data)
{
JNIEnv *env = get_jni_env ();
GstElement *video_sink;
GstPad *video_sink_pad;
GstCaps *caps;
GstVideoInfo info;
/* Retrieve the Caps at the entrance of the video sink */
//获取video sink =》pad =》caps
g_object_get (data->pipeline, "video-sink", &video_sink, NULL);
video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
caps = gst_pad_get_current_caps (video_sink_pad);
//从caps获取videoinfo
if (gst_video_info_from_caps (&info, caps)) {
info.width = info.width * info.par_n / info.par_d;
GST_DEBUG ("Media size is %dx%d, notifying application", info.width,
info.height);
//回调java层 onMediaSizeChange
(*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id,
(jint) info.width, (jint) info.height);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env);
}
}
gst_caps_unref (caps);
gst_object_unref (video_sink_pad);
gst_object_unref (video_sink);
}
3.4.6 clock_lost_cb 当计时器丢失时回调
pause-》playing
/* Called when the clock is lost */
static void
clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
if (data->target_state >= GST_STATE_PLAYING) {
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
}
}
3.4.7 buffering_cb 播放流媒体 0%-》100%
/* Called when buffering messages are received. We inform the UI about the current buffering level and
* keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */
static void
buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
gint percent;
if (data->is_live)
return;
gst_message_parse_buffering (msg, &percent);
if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
//小于100% 更新UI
gchar *message_string = g_strdup_printf ("Buffering %d%%", percent);
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
set_ui_message (message_string, data);
g_free (message_string);
} else if (data->target_state >= GST_STATE_PLAYING) {
//根据 target 进行状态改变
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
} else if (data->target_state >= GST_STATE_PAUSED) {
set_ui_message ("Buffering complete", data);
}
}
3.4.8 duration_cb 时长进度回调
设置成none 下次UI刷新 进行获取
/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */
static void
duration_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->duration = GST_CLOCK_TIME_NONE;
}
3.4.9 eos_cb 播放结束
target_state 设置成pause , seek to 0号位
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
static void
eos_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
data->target_state = GST_STATE_PAUSED;
data->is_live |=
(gst_element_set_state (data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
execute_seek (0, data);
}
3.4.10 error_cb 错误回调
/* Retrieve errors from the bus and show them on the UI */
static void
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
{
GError *err;
gchar *debug_info;
gchar *message_string;
gst_message_parse_error (msg, &err, &debug_info);
message_string =
g_strdup_printf ("Error received from element %s: %s",
GST_OBJECT_NAME (msg->src), err->message);
g_clear_error (&err);
g_free (debug_info);
//更新UI
set_ui_message (message_string, data);
g_free (message_string);
//target_state = null
data->target_state = GST_STATE_NULL;
gst_element_set_state (data->pipeline, GST_STATE_NULL);
}
3.4.11 delayed_seek_cb 延时seek回调
/* Delayed seek callback. This gets called by the timer setup in the above function. */
static gboolean
delayed_seek_cb (CustomData * data)
{
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT,
GST_TIME_ARGS (data->desired_position));
execute_seek (data->desired_position, data);
return FALSE;
}
3.4.12 关键 execute_seek 执行seek操作
/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
* some time in the future. */
static void
execute_seek (gint64 desired_position, CustomData * data)
{
gint64 diff;
//没有期望 pos 返回
if (desired_position == GST_CLOCK_TIME_NONE)
return;
//获取上次seek的时间
diff = gst_util_get_timestamp () - data->last_seek_time;
//和上次的seek时间太近了 延时这一次
if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) {
/* The previous seek was too close, delay this one */
GSource *timeout_source;
if (data->desired_position == GST_CLOCK_TIME_NONE) {
/* There was no previous seek scheduled. Setup a timer for some time in the future */
//添加时间回调 时间到了回调delayed_seek_cb
timeout_source =
g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
// delayed_seek_cb 返回false 执行一次
g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb,
data, NULL);
g_source_attach (timeout_source, data->context);
g_source_unref (timeout_source);
}
/* Update the desired seek position. If multiple petitions are received before it is time
* to perform a seek, only the last one is remembered. */
//上次的seek也没有执行 跟新期望pos 延时seek到这次的pos 连续拖动case
data->desired_position = desired_position;
GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %"
GST_TIME_FORMAT, GST_TIME_ARGS (desired_position),
GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
} else {
//直接seek
/* Perform the seek now */
GST_DEBUG ("Seeking to %" GST_TIME_FORMAT,
GST_TIME_ARGS (desired_position));
//记录lastseektime
data->last_seek_time = gst_util_get_timestamp ();
//进行seek
gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position);
data->desired_position = GST_CLOCK_TIME_NONE;
}
}
3.4.13 关键 refresh_ui 刷新UI
/* If we have pipeline and it is running, query the current position and clip duration and inform
* the application */
static gboolean
refresh_ui (CustomData * data)
{
gint64 current = -1;
gint64 position;
/* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */
if (!data || !data->pipeline || data->state < GST_STATE_PAUSED)
return TRUE;
//播放状态 刷新duration
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
&data->duration)) {
GST_WARNING
("Could not query current duration (normal for still pictures)");
data->duration = 0;
}
}
if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
GST_WARNING
("Could not query current position (normal for still pictures)");
position = 0;
}
//回调到 UI
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND,
data);
return TRUE;
}
3.4.13 关键gst_native_set_uri 设置播放uri
/* Set playbin's URI */
void
gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline)
return;
const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL);
GST_DEBUG ("Setting URI to %s", char_uri);
//更新状态到ready
if (data->target_state >= GST_STATE_READY)
gst_element_set_state (data->pipeline, GST_STATE_READY);
//设置uri
g_object_set (data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars (env, uri, char_uri);
data->duration = GST_CLOCK_TIME_NONE;
data->is_live =
(gst_element_set_state (data->pipeline,
data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
}
3.4.13 gst_native_set_position设置 pos
/* Instruct the pipeline to seek to a different position */
void
gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds)
{
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
//校验 data
if (!data)
return;
//设置期望pos
gint64 desired_position = (gint64) (milliseconds * GST_MSECOND);
if (data->state >= GST_STATE_PAUSED) {
execute_seek (desired_position, data);
} else {
//ready null void pending状态
GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later",
GST_TIME_ARGS (desired_position));
data->desired_position = desired_position;
}
}
3.4.14 set_current_ui_position 通知UI时长和pos
/* Tell the application what is the current position and clip duration */
static void
set_current_ui_position (gint position, gint duration, CustomData * data)
{
JNIEnv *env = get_jni_env ();
(*env)->CallVoidMethod (env, data->app, set_current_position_method_id,
position, duration);
if ((*env)->ExceptionCheck (env)) {
GST_ERROR ("Failed to call Java method");
(*env)->ExceptionClear (env);
}
}
3.5 turorial-5 完整播放器
增加了本地文件的支持,jni代码基本一样,有小部分更新
//使用data->is_live |= 替代 data->is_live =
data->is_live |=
在state_change_cb
if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY)
data->is_live = FALSE;
在refresh_ui中添加了 获取当前pos失败的处理
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
&data->duration)) {
GST_WARNING
("Could not query current duration (normal for still pictures)");
data->duration = 0;
}
}
if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
GST_WARNING
("Could not query current position (normal for still pictures)");
//设置成0
position = 0;
}
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND,
data);
return TRUE;
}
3.5.1 实例代码
3.5.1 powerManager 管理 保持唤醒,不灭屏
保持唤醒,不灭屏
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GStreamer tutorial 5");
wake_lock.setReferenceCounted(false);
ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
play.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = true;
//保持唤醒
wake_lock.acquire();
nativePlay();
}
});
ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
pause.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
is_playing_desired = false;
//release 唤醒锁
wake_lock.release();
}
});
3.5.2 打开文件管理,从中选择播放
//打开file dialog
ImageButton select = (ImageButton) this.findViewById(R.id.button_select);
select.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent i = new Intent(getBaseContext(), FileDialog.class);
i.putExtra(FileDialog.START_PATH, last_folder);
startActivityForResult(i, PICK_FILE_CODE);
}
});
3.5.3 本地文件夹播放 多种文件支持
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (resultCode == RESULT_OK && requestCode == PICK_FILE_CODE) {
mediaUri = "file://" + data.getStringExtra(FileDialog.RESULT_PATH);
position = 0;
last_folder = new File (data.getStringExtra(FileDialog.RESULT_PATH)).getParent();
Log.i("GStreamer", "Setting last_folder to " + last_folder);
setMediaUri();
}
}
intent支持类型
<application
android:icon="@drawable/gstreamer_logo_5"
android:label="@string/app_name" >
<activity
android:name=".Tutorial5"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Local files whose MIME type is known to Android -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="audio/*" />
<data android:mimeType="video/*" />
<data android:mimeType="image/*" />
</intent-filter>
<!-- Local files with unknown MIME type.
The list of extensions and supported protocols can certainly be extended. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.avi" />
<data android:pathPattern=".*\\.AVI" />
<data android:pathPattern=".*\\.mkv" />
<data android:pathPattern=".*\\.MKV" />
<data android:pathPattern=".*\\.webm" />
<data android:pathPattern=".*\\.WEBM" />
<data android:pathPattern=".*\\.ogv" />
<data android:pathPattern=".*\\.OGV" />
<data android:pathPattern=".*\\.mp4" />
<data android:pathPattern=".*\\.MP4" />
<data android:pathPattern=".*\\.mov" />
<data android:pathPattern=".*\\.MOV" />
</intent-filter>
<!-- Remote files. These typically have unknown MIME type.
The list of extensions and supported protocols can certainly be extended. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:pathPattern=".*\\.avi" />
<data android:pathPattern=".*\\.AVI" />
<data android:pathPattern=".*\\.mkv" />
<data android:pathPattern=".*\\.MKV" />
<data android:pathPattern=".*\\.webm" />
<data android:pathPattern=".*\\.WEBM" />
<data android:pathPattern=".*\\.ogv" />
<data android:pathPattern=".*\\.OGV" />
<data android:pathPattern=".*\\.mp4" />
<data android:pathPattern=".*\\.MP4" />
<data android:pathPattern=".*\\.mov" />
<data android:pathPattern=".*\\.MOV" />
</intent-filter>
</activity>
<activity
android:name="com.lamerman.FileDialog"
android:label="@string/filechooser_name" >
</activity>
</application>
3.5.4 状态保存savedInstanceState
// Retrieve our previous state, or initialize it to default values
if (savedInstanceState != null) {
is_playing_desired = savedInstanceState.getBoolean("playing");
position = savedInstanceState.getInt("position");
duration = savedInstanceState.getInt("duration");
mediaUri = savedInstanceState.getString("mediaUri");
last_folder = savedInstanceState.getString("last_folder");
Log.i ("GStreamer", "Activity created with saved state:");
} else {
is_playing_desired = false;
position = duration = 0;
last_folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath();
Intent intent = getIntent();
android.net.Uri uri = intent.getData();
if (uri == null)
mediaUri = defaultMediaUri;
else {
Log.i ("GStreamer", "Received URI: " + uri);
if (uri.getScheme().equals("content")) {
android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA));
cursor.close();
} else
mediaUri = uri.toString();
}
Log.i ("GStreamer", "Activity created with no saved state:");
}
本文地址:https://blog.csdn.net/sinat_18179367/article/details/107284180
上一篇: DOS批处理不支持将UNC 路径作为当前目录的解决方法
下一篇: 深入理解浏览器的各种刷新规则
推荐阅读
-
Python实现使用request模块下载图片demo示例
-
Django框架实现的分页demo示例
-
jQuery实现分页功能(含ajax请求、后台数据、附完整demo)
-
Android分包MultiDex策略详解
-
Windows编译OpenCV4Android解决undefined reference to std错误
-
常程:上手Android 10最大感受是iOS开始学习Android了
-
Android--解决图片保存到相册显示1970年1月1日 8:00的问题
-
Android 中使用 dlib+opencv 实现动态人脸检测
-
Android Studio 在项目中引用第三方jar包
-
用户反映谷歌Pixel更新Android 10卡在Google界面:时间最长6小时