基于FFmpeg的安卓直播播放器技术总结

基于FFmpeg的安卓直播播放器技术总结

项目目标

做一个能在VRGlass上观看直播的APP,输入一个rtmp直播链接,即可观看该链接的直播。

预备知识

  1. 直播协议总结
    各种直播协议调研总结
    演进中视频流媒体容器格式与传输协议

  2. 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。

  3. 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的推流器附件:收流器

  4. 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语法小结

  5. 多线程编程
      在Java、C++的多线程编程,收藏里有

实现思路

  1. 首先使用FFMpeg从网络拉流,再从直播流中分离音频视频(Demuxer)。音频(.aac)通过FFmpeg解码为pcm数据,之后走OpenSLES;视频(.h264)分为Nal送入NalList
  2. 通过JNI在java层从NalList获取到nal,并把nal送入MediaCodec解码,解码输出至Surface
  3. Unity使用HVR SDK搭建场景,使用一个Quad的纹理来呈现视频帧
  4. 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的插件使用。

准备工作

  1. 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怎么写?)

  2. 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]

  1. 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音频

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值