项目目标
做一个能在VRGlass上观看直播的APP,输入一个rtmp直播链接,即可观看该链接的直播。
预备知识
-
直播协议总结
各种直播协议调研总结
演进中视频流媒体容器格式与传输协议 -
RTMP协议
RTMP(Real-Time Messaging Protocol实时消息传送协议)的缩写,它是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的协议。这是一个标准的,未加密的实时消息传递协议。
RTMP协议应用优势
使用RTMP技术的流媒体系统有一个非常明显的特点:使用 Flash Player 作为播放器客户端,而Flash Player 现在已经安装在了全世界将近99%的PC上,因此一般情况下收看RTMP流媒体系统的视音频是不需要安装插件的。用户只需要打开网页,就可以直接收看流媒体,十分方便。
RTMP协议有效的保证了媒体传输质量,使用户可以观看到高质量的多媒体。RTMP采用TCP协议作为其在传输层的协议,避免了多媒体数据在广域网传输过程中的丢包对质量造成的损失。此外RTMP协议传输的FLV封装格式支持的H.264视频编码方式可以在很低的码率下显示质量还不错的画面,非常适合网络带宽不足的情况下收看流媒体。
RTMP协议应用劣势
当然RTMP协议也有一些局限,RTMP基于TCP协议,而TCP协议实时性不如UDP,也非常占用带宽。目前基于UDP的RTMFP协议能很好的解决这些问题,如Adobe的AMS和800li media server。
RTMP协议的播放依赖于Flash Player,优势是直接将直播内容很容易就嵌入网页进行流媒体内容直播。那么它的一个局限也自然是这个协议的播放依赖于Flash Player。 如果没有这个播放媒介,这个协议就没有用武之地了,如苹果的MacOS电脑,苹果iOS手机和移动设备都是屏蔽Flash Player的。 目前谷歌公司也宣布安卓Android系统也不再继续支持Flash Player。 -
FFmpeg
FFmpeg is a free and open-source software project consisting of a large suite of libraries and programs for handling video, audio, and other multimedia files and streams. At its core is the FFmpeg program itself, designed for command-line-based processing of video and audio files, and widely used for format transcoding, basic editing (trimming and concatenation), video scaling, video post-production effects, and standards compliance (SMPTE, ITU).[From Wiki]
我们接下来会使用FFmpeg拉流,视音频分离,音频解码。因此也参考了雷神以下文章:
[总结]FFMPEG视音频编解码零基础学习方法
最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)
最简单的基于FFMPEG的推流器附件:收流器 -
JNI
JNI,即 Java Native Interface ,是 Java 提供用来与其他语言通信的 api ,“其他语言”意味不止局限于 C 或 C++ ,也可以调用除 C 和 C++ 之外的语言,只是大多数情况下调用 C 或 C++ ; “通信”意味着 Java 和 其他语言之间可以相互调用,不止局限于 Java 调用其他语言,其他语言也可以主动调用 Java .
Java 虚拟机实现了跨平台特性, 无法很好的实现与操作系统相关的本地操作,而 C 或 C++ 可以,同时代表着 C 或 C++ 不具备 Java 的跨平台能力,那么当我们在程序中使用 JNI 功能时,就必须关注程序的平台可移植性, JNI 标准要求本地代码至少能工作在任何Java 虚拟机环境。
简而言之,跨平台的 Java 调用了不跨平台的 C/C++,使程序丧失了跨平台性,这就是 JNI 的副作用,所以可以不使用 JNI 时就尽量避免。而大多数不可避免的情况是:已存在用 C/C++ 写的程序/库或者 Java 语言不支持程序所要实现的特性,比如 ffmpeg 是由 C 编写的,则必须要通过 JNI 实现调用。
我们会使用JNI来完成C++层和Java层的交互,将C++层从网络收到的nal送入Java层的MediaCodec。
JNI/NDK 开发指南
JNI语法小结 -
多线程编程
在Java、C++的多线程编程,收藏里有
实现思路
- 首先使用FFMpeg从网络拉流,再从直播流中分离音频视频(Demuxer)。音频(.aac)通过FFmpeg解码为pcm数据,之后走OpenSLES;视频(.h264)分为Nal送入NalList
- 通过JNI在java层从NalList获取到nal,并把nal送入MediaCodec解码,解码输出至Surface
- Unity使用HVR SDK搭建场景,使用一个Quad的纹理来呈现视频帧
- C#脚本中使用OVR API将Quad的纹理和AndroidSurface绑定,并把Surface传递给Java层,脚本Update()中循环调用渲染函数,若Surface缓存中数据有刷新(即解码有新输出),则刷新纹理。
由于需要在Android调用FFmpeg,而官网提供的FFmpeg库只有在Windows平台适用的.dll/.exe,因此我们需要下载FFmpeg源码并编译为Android(Linux)平台能用的.so库.
另外,由于HVR SDK是在Unity平台的,因此完成主要逻辑功能的Android程序需要打包为.jar作为Unity的插件使用。
准备工作
-
FFmpeg在Windows平台下交叉编译为so库
MSYS下载很慢,我有点忘记当时我怎么下载配置的了,还有build_android.sh这个脚本怎么写需要注意一下。
参考链接:
android开发-Windows环境下编译FFMPEG源码
Android 集成 FFmpeg (一) 基础知识及简单调用(这篇博客对交叉编译所需知识讲解理解的比较深刻)
最简单的基于FFmpeg的移动端例子:Android HelloWorld(雷神这个例子时间稍微有点早,但是对Android应用程序使用FFmpeg类库的流程描述的非常清晰,build_android.sh这个脚本内容也可以参考雷神的)
Android cmake编译FFmpeg(包含cmakelist.txt怎么写?) -
Android Studio工程打包生成jar包
参考了一些博客,操作比较简单,修改Android Studio的工程Module的build.gradle文件即可
总共分为三步,具体请看下面代码
//apply plugin: 'com.android.application' //1.注释并添加下一行,表明该Module是library
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
defaultConfig {
//applicationId "com.xxx.xxx.xxxxxx" //2.注释这句
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
externalNativeBuild {
cmake {
cppFlags ""
}
ndk{
abiFilters "armeabi-v7a" // 指定abiFilters
//因为AndroidStudio默认会编译所有架构的动态库
}
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
}
lintOptions {
abortOnError false
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
//3. 打包成jar
task makeJar(type: Copy) {
//删除存在的
delete 'build/libs/LivePlayer.jar'
from('build/intermediates/bundles/release/')
into('release/')
//include ,exclude参数来设置过滤
//将classes.jar放入build/libs/目录下(我们只关心classes.jar这个文件)
include('classes.jar')
//重命名
rename('classes.jar', 'LivePlayer.jar')
}
makeJar.dependsOn(build)
//3.结束
}
afterEvaluate {
generateReleaseBuildConfig.enabled = false
generateDebugBuildConfig.enabled = false
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-annotations:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation files('libs/classes.jar')
implementation files('libs/hvrbridge.jar')
}
Sync Gradle之后在Android Studio左侧点开Gradle,点击你的Module,里面找到makeJar双击即可生成.jar。[app-Tasks-other-makeJar]
- Unity脚本调用.jar内接口方法
首先将我们在Android Studio生成的LivePlayer.jar以及build\intermediates\cmake\release\obj\armeabi-v7a\libnative-lib.so拷贝至Assets\Plugins\Android路径下。因为libnative-lib.so用到ffmpeg的库,所以也要把FFmpeg的那些.so库拷贝过来。
在C#脚本里可以通过以下代码调用接口方法
javaPlayer = new AndroidJavaObject("com/xxx/xxxxxx/xxxxxxx/MainActivity");//jar包的MainActivity.class
if (javaPlayer == null)
{
Debug.Log("javaPlayer is null!!");
}
IntPtr methodId = AndroidJNI.GetMethodID(javaPlayer.GetRawClass(), "setSurface", "(Landroid/view/Surface;)V");
jvalue[] parms = new jvalue[1];
parms[0] = new jvalue();
parms[0].l = androidSurface;
AndroidJNI.CallVoidMethod(javaPlayer.GetRawObject(), methodId, parms);
参考链接
HUAWEI VR SDK for Unity开发指南
Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频