一、前言

  在 Android 音视频开发学习思路 中,我们不断的学习和了解音视频相关的知识,随着知识点不断的学习,我们现在应该做的事情,就是将知识点不断的串联起来。这样才能得到更深层次的领悟。通过整理 Android 音视频开发(一) : 通过三种方式绘制图片 我们知道可以使用ImageView和SurfaceView甚至是View来展示图片,通过整理 Android 音视频开发(三):使用 AudioTrack 播放PCM音频 我们知道如何播放音频原始数据了。那么可不可以定义为,我们找到了如何播放音视频的最基本的方式呢?答,当然是的!在 JavaCV 学习(一):JavaCV 初体验 里,我们接触了一次JavaCV,发现里面提供的API相当丰富,尤其是图形图像处理方面,那么下面我们就基于JavaCV加上它提供的ffmpegAPi工具,来完成一个基本的拉流播放器的制作,鉴于起名很难,我们先把名字起好:NBPlayer。

二、设计方案

我们要做的是一个简单的拉流播放器,需要具备以下功能:

  1. 将直播流拉取到设备上并展现出来;
  2. 保证播放当前直播流的音视频是同步的;
  3. 播放视频时可以切换全屏幕与非全屏;

三、定义播放器的生命周期

在定义播放器的生命周期们需要做到以下两步:1. 先定义一下播放器的事件   2. 定义播放器展示的控件

1. 定义播放器事件

因为我们要做的就是一个播放器,所以就需要定义出来相应的播放器的事件,最基本的播放器的操作就是:播放、暂停、停止。示例代码如下:

/**
* 播放器抽象类
*/
public abstract class Player { protected boolean play = false; public void play() {
this.play = true;
} public void pause() {
this.play = false;
} public void stop() {
this.play = false;
}

2. 定义播放器展示的控件 - SurfaceView

为什么定义完播放器的操作事件之后,就去定义播放器展示的控件呢?

答:主要是因为我们做的播放器在展示控件方面的思路上和Android原生的MediaPlayer及Ijkplayer是一样的,都是监听Surface的状态来控制播放器什么时候创建,什么时候暂停,什么时候停止并release。

这里我们使用的控件是SurfaceView,创建一个VideoSurfaceView继承SurfaceView,并实现SurfaceHolder.Callback接口:

@Override
public void surfaceCreated(SurfaceHolder holder) {
initLayout(mPlayer.getWidth(), mPlayer.getHeight());
play();
if (onPreparedListener != null) onPreparedListener.onPrepared();
} @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.v(TAG, "surfaceChanged...");
} @Override
public void surfaceDestroyed(SurfaceHolder holder) {
mPlayer.pause();
} public void releasePlayer() {
mPlayer.stop();
}

上述代码可以看到我们把基本的播放器的生命周期的控制部分完成了,后续的工作就是完成基本的音视频数据的获取和播放了。

四、使用JavaCV + FFmpeg的API播放拉取音视频流

我们使用的是 JavaCV + FFmpeg的API,关于JavaCV的基本的介绍在上一篇文章 JavaCV 学习(一):JavaCV 初体验 里面已经做了,下面一边介绍使用到的核心类一边说明音视频播放的流程:

1. FFmpegFrameGrabber

所在package包为:org.bytedeco.javacv,完整类名为:org.bytedeco.javacv.FFmpegFrameGrabber

FFmpegFrameGrabber可以理解为解码器,也可以理解为帧收集器,主要作用就是将视频流以帧的形式拉去到手机设备上。

mFrameGrabber = FFmpegFrameGrabber.createDefault(path);

上面的代码就是创建FFmpegFrameGrabber的方式,path就是要拉取流的地址。

mFrameGrabber.setPixelFormat(AV_PIX_FMT_RGBA);

设置帧收集时的像素格式,这块设置AV_PIX_FMT_RGBA的原因主要是,我们展示画面的时候是转换为Bitmap格式的。

mFrameGrabber.setOption("fflags", "nobuffer");

上面的代码表示我们可以像ijkplayer一样,设置一些参数,这些参数格式我们可以参考ijkplayer也可以去ffmpeg命令行的一些设置参数文档里面去查找,这里就不多赘述了。

mFrameGrabber.start();

上面的代码就是让帧收集器启动,这样就开始拉流了。

2. Frame

所在package包为:org.bytedeco.javacv,完整类名为:org.bytedeco.javacv.Frame

Frame 是一个用于管理音频和视频帧数据的类。 在CanvasFrame、FrameGrabber、FrameRecorder及他们的子类里面都有用到。

Frame grabframe = mFrameGrabber.grab(); 

上面的代码表示从帧收集器里面抓去最新的帧:

播放音频:grabframe.samples里面获取到的就是原始的pcm音频数据,交给AudioTrack处理就ok了。

播放视频:首先需要将Frame图像转换为Bitmap,AndroidFrameConverter.convert(frame)就可以转换,但是在这之前需要使用OpenCVFrameConverter.ToIplImage将抓出来的Frame转换一下。

Canvas canvas = mHolder.lockCanvas();
canvas.drawBitmap(bmp, null, new Rect(0, 0, canvas.getWidth(), frame.imageHeight * canvas.getWidth() / frame.imageWidth), null);
mHolder.unlockCanvasAndPost(canvas);

上面的代码表示将获取到的位图绘制到SurfaceHolder里面去,这里建议启动线程去绘制,这样效率会高很多。And 别问为啥子能在线程里面绘制画面,自己学习SurfaceView去。

五、说明

1. 针对此播放器实现的功能的说明:

  • 只实现了拉取直播RTMP流并播放的功能,只能播放不带B帧的直播流,因为B帧解析出来全是带方向的箭头(双向预测帧),所以这个播放器也就顺势起名叫做NBPlayer。
  • 有关于I帧、B帧、P帧这方面的内容的可以参考本人之前写的 视频直播技术——帧概念 了解一下,当然也可以自行百度,有很多大神的文章。

2. 针对此播放器的Demo示例:

3. 针对此播放器实现时本人的一些感悟:

  • 做技术嘛,感觉更多的是对一些知识的理解和整合,其实能做出来这个播放器,成就感也是不小的。
  • 如果没有之前的一些知识储备和技术铺垫,也是没办法实现的,做出来了,对音视频的一些理解,也变得更加清晰了。

4. 针对此播放器的一些功能拓展的想法:

  • 展示的内容为RGB的,如果需要是可以转换为YUV格式的,这个在实际项目中可能会使用到。
  • 我们能拿到直播的画面和声音数据,当然可以实时的保存这些数据了,这也就为录制成文件做好了铺垫了。

JavaCV 学习(二):使用 JavaCV + FFmpeg 制作拉流播放器的更多相关文章

  1. 用C#做一个 拉流播放器

    做拉流播放器第一个想到就是,.,..FFmpeg没错 我也是用强大的他它来做的.但是我用的不是  cmd 调用 而是用的强大的FFmpeg.AutoGen FFmpeg.AutoGen 这个是C# 一 ...

  2. ffmpeg学习(三)——ffmpeg+SDL2 实现简单播放器

    本篇实现基于ffmpeg动态库用测试程序播放本地文件和RTSP视频流. 参考文章:http://blog.csdn.net/leixiaohua1020/article/details/8652605 ...

  3. 吴裕雄--天生自然python学习笔记:python 用pygame模块制作一个音效播放器

    用 Sound 对象制作一个音效播放器. 应用程序总览 程序在执行后默认会把 WAV 音频文件加载到清单中,单击“播放”按钮可开始 播放,同时显示 “正在播放 xxx 音效”的信息 . 播放过程中,可 ...

  4. FFmpeg入门,简单播放器

    一个偶然的机缘,好像要做直播相关的项目 为了筹备,前期做一些只是储备,于是开始学习ffmpeg 这是学习的第一课 做一个简单的播放器,播放视频画面帧 思路是,将视频文件解码,得到帧,然后使用定时器,1 ...

  5. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  6. H.264:FFMpeg 实现简单的播放器

    H.264:FFMpeg 实现简单的播放器   FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我 ...

  7. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  8. c# 基于RTMP推流 PC+移动端 拉流播放

    网上关于直播相关的文章很多,但是讲解还是不够系统,对于刚刚接触直播开发的朋友实施起来会浪费不少时间.下面结合我自己的经验, 介绍一下直播方面的实战经验. 分成两个部分 第一部分是标题中介绍的基于RTM ...

  9. JavaCV 学习(一):JavaCV 初体验

    最近工作中因为项目的原因边学边用,接触并使用JavaCV做了一些相关的产品,目前觉得,JavaCV在图形图像(人家本身就是一个视觉库)的功能真的挺强大,而且使用起来避免了复杂的平台处理.下面整理一下对 ...

随机推荐

  1. Day2数据结构和算法

    2019-02-28,10:23:52 算法效率的度量方法 事后统计方法:为每一个程序编制测试程序 ,比较时间.(很麻烦,没有普遍适用性) 事前分析估算方法:在计算机程序编写前,依据统计方法对算法进行 ...

  2. java 实现udp通讯

    需求:应用A(通常有多个)和应用B(1个)进行 socket通讯,应用A必须知道应用B的ip地址(在应用A的配置文件中写死的),这个时候就必须把应用B的ip设成固定ip(但是某些时候如更换路由后要重新 ...

  3. VNF网络性能提升解决方案及实践

    VNF网络性能提升解决方案及实践 2016年7月 作者:    王智民 贡献者:     创建时间:    2016-7-20 稳定程度:    初稿 修改历史 版本 日期 修订人 说明 1.0 20 ...

  4. 洛谷1027 Car的旅行路线

    原题链接 将每个城市拆成四个点,即四个机场来看,那么这题就是求最短路. 不过建图有些麻烦,先要找出第四个机场的坐标. 设另外三个机场的坐标为\((x_1, y_1), (x_2, y_2), (x_3 ...

  5. python 解方程 和 python 距离公式实现

    解方程参考:https://zhuanlan.zhihu.com/p/24893371 缺点太慢,最后还是自己算了 距离公式参考:https://www.cnblogs.com/denny402/p/ ...

  6. 初学C的感想

    既然是随笔,那就让我谈谈或者说聊聊自己的感想吧.刚刚进入大学的时候,对物联网工程这个专业挺迷茫的,至少我不知道自己将要学什么,只知道高数和英语是一定要学的,后来听学长说要学C语言,对这个概念很陌生,有 ...

  7. java8 学习记录

    一.  lambda表达式 参考 https://www.cnblogs.com/franson-2016/p/5593080.html package com.mytest.java8; impor ...

  8. 【第一次作业】&&软件工程大一班---甘昀

    这个作业属于哪个课程: <课程的链接点这里>  这个作业要求在哪里: <作业要求的链接点这里> 我在这个课程的目标是:  学会软件开发的流程和思想 这个作业在哪个具体方面帮助我 ...

  9. cerr与cout区别

    语言:C++ 一.简介 平常常会用的主要有三个:cout.cerr.clog,首先简单介绍下三者. 这三者在C++中都是标准IO库中提供的输出工具(至于有关的重载问题在此不讨论): cout:写到标准 ...

  10. 父组件传值给子组件的v-model属性

    父组件如何修改子组件中绑定的v-model属性 因为v-model属性是双向数据绑定,而vue的通信方式又是单向通信,所以,当子组件想要改变父组件传过来的值的属性时,就会报错,典型的就是父组件传值给子 ...