Android音视频调研

基础知识

一、音频

将声音保存成音频的过程,其实就是将模拟音频数字化的过程
模拟信号 -> 采样 -> 量化 -> 编码 -> 数字信号

采样

根据奈奎斯特定律:为了不失真地恢复模拟信号,采样频率应该不小于模拟信号频谱中最高频率的2倍。人耳能听到的最高频率为 20khz,所以,为了满足人耳的听觉要求,采样率至少为40khz,通常就是为 44.1khz,更高则是 48 khz。一般我们都采用 44.1khz 即可达到无损音质。

1.2 采样位数
模拟信号是连续的样本值,而数字信号一般是不连续的,所以模拟信号量化,只能取一个近似的整数值,为了记录这些振幅值,采样器会采用一个固定的位数来记录这些振幅值,通常有 8 位,16位,32位。

位数 最小值 最大值
8 0 255
16 -32468 32767
32 -2147483648 2147483648

位数越大,记录的值越准确,还原度越高。

量化

量化是什么?就是要将上面分割的信息用具体的数据来进行表示,用形象的话来说,就是给每个x时间轴,对应其y轴的变化数值。那么我们要表示一段声音的具体值,肯定就需要先分配一个可变化区间(这个区间就叫做量化格式),二进制中的1个单位就是一个比特,通常有8比特,16比特,32比特。


image.png

这里还需引入分贝(单位db)的概念:是描述声音大小的单位,人耳承受的dB范围是:0~90dB,0dB是人耳能听到的最微弱的的声音,在90dB环境中听力会受到严重影响。1个比特可以大约记录6分贝的声音,那么我可以得出下图的数据

单位 范围 dB
8比特 0~2^8-1 <=> 0~255 0~48
16比特 0~2^16-1 <=> 0~65535 0~96
32比特 0~2^32 -1 <=> 0~4294967295 0~192

由上可知,一般我们使用16比特,因为其有0~65535中变化,而且刚好符合人对声音大小的范围,而8比特处理音频,只有0~255中变化,变化不能够被很精细的记录下来,那为什么不使用32比特呢?这是因为32位太消耗存储了,当然如果想使音频更加精细,也是可以使用32比特的。 由于量化是建立在采样的基础上的,所有一个量化就对应一个采样,那么我们怎么存储呢?这就会涉及到下面的编码了。

编码

数字信号由0,1组成的,因此,需要将振幅转换成二进制编码,一系列 0和1进行存储最后得到的数据就是数字信号:一串0和1组成的数据。

整个流程

image.png
音频编码

一个存储的格式有很多种,通常音频裸数据即是我们所说的PCM,也叫脉冲编码调制。 通常描述一段PCM数据需要以下几个概念:

  • 量化格式 - SampleFormat,默认16比特
  • 采样率 - SampleRate,默认44.1KHz
  • 声道数 - Channel ,默认2个

一般我们使用码率来描述音频,即是1秒内吞吐量,单位bps :

码率 = 采样率 * 量化格式 * 声道数
码率 = 44100 * 16 * 2 = 1411200 bps = 1411.2 kbps 还可以得出1分钟数据大小:1411.2 * 60 / 8 / 1024 = 10.34M(体积太大大)

压缩编码就是对数据进行压缩,压缩编码的原理实际上是压缩掉冗余信号,冗余信号是指不能被人耳感知到的信号

  • 有损压缩,数据经过压缩后,信息不受损失,还能完全恢复到压缩前的原样
  • 无损压缩,经过此方法压缩、解压的数据会与原始数据不同但是非常接近

在音乐应用中,我们经常看到“无损音乐”,如果你发现它是mp3格式的,那么它一定是有损压缩,也就是我们所说的假无损。常见的有损压缩如下:

  • MP2
  • MP3(常用)
  • AAC(常用)
  • WMA(常用)
  • ADPCM
  • ATRAC
  • Dolby AC-3
  • Musepack
  • Ogg Vorbis

二、 视频

视频是由一帧一帧联系图像构成的,所以再介绍视频之前,先得了解一下什么是图像?

图像

我们从小学习物理知识就知道,通过三菱镜就可以将光色散成不同的颜色,经过进一步研究发现,只有红(R)、绿(G)、蓝(B)不能被分解,所以它们被称为光的三原色。

一般我们买手机的时候会参考它的分辨率,当然是分辨率越大越好,因为越大越清晰,更接近事物的原始样貌,那这是为什么呢?

其实为了让人能够在手机上感知图像,也采用了这样的RGB模式。以“1080x1920”为例,那就是每一个横向就有1080个像素点,纵向有1920个像素点,那总共就有1080x1920=2073600个像素。每一个像素又包含了红(R)、绿(G)、蓝(B)三个子像素,这样每个像素就可以有自己的全部颜色呈现。


image.png

图像表示

红(R)、绿(G)、蓝(B)都可以使用00~FF或者0~255表示,由上面我们知道8比特刚好可以表示0~255,一个像素又包含了红(R)、绿(G)、蓝(B)三个子像素,那一个像素至少需要24位,我们一般还要给颜色加上一个不透明度(A),所以一个像素其实是32位,这种表示方式就是我们常用的RGBA_8888,那如果一张图片要在上面分辨率的手机上全屏展示出来需要多大的空间呢?

图像占用内存的公式是:numBytes = width * height * bitsPerPixel / 8
1080*1920*32/8 = 8294400b = 8100kb = 7.91Mb

这也是位图(bitmap)在内存中所占用的大小,每一张图像的裸数据都是很大的,进行一个压缩,常见的压缩格式有:

  • BMP - 无损压缩
  • PNG - 无损压缩
  • JPEG - 有损压缩

这是就是为什么在制作小图(比如icon)的时候使用png格式,而大图采用jpeg的原因,小图经过无损压缩放大之后不让其太过模糊,大图保证其能够清晰即可。

视频表示

视频的裸数据我们一般使用YUV来进行表示,YUV也是一种颜色编码方式,为什么不使用RGB呢?与RGB视频信号传输相比,它最大的优点在于只需要占用极少的频宽(RGB要求三个独立的视频信号同时传输)

“Y”表示明亮度(Luminance、Luma),也称灰阶值;“U”和“V”则是色度,它们的作用是描述影像的色彩及饱和度,用于指定像素的颜色。如果忽略掉UV,那就是只剩下灰(Y),那就跟以前黑白电视机信号一样了,所以YUV的发明了是为了从黑白电视过渡彩色电视而发明的。

我们再对1080x1920手机上展示视频一帧的数据量大小如下:

YUV格式 大小 (1080x1920分辨率)
420 1080* 1920* (1+0.5+0) = 3110400 b = 3037.5 kb = 2.97Mb

视频编码

  • 帧率(fps) - 测量单位时间(s)显示的帧数,一般视频中使用24fps就可以了。对于游戏来讲,如果帧率少于30fps就会出现不连贯,也就是我们平常说的卡顿。
  • 比特率 - 测量单位时间(s)数据量大小。

我们就可以计算一下在1080* 1920的手机上播放YUV420视频数据的比特率:

bitRate = 1080* 1920* (1+0.5+0)* 24 = 71.2 Mbps

还可以得出90分钟电影数据量:

total = bitRate * 60 * 90 = 375.42 GB

压缩编码包括时间上的冗余信息和空间上的冗余信息。

  • 时间上的冗余信息(temporal redundancy)- 在视频数据中,相邻的帧(frame)与帧之间通常有很强的关连性,这样的关连性即为时间上的冗余信息。
  • 空间上的冗余信息(spatial redundancy)- 在同一张帧之中,相邻的像素之间通常有很强的关连性,这样的关连性即为空间上的冗余信息。

现在常见的视频编码有两个系列:

  • MPEG系列 - 包括 Mpeg1(用于VCD)、Mpeg2(用于DVD)、Mpeg4 AVC
  • H.26x系列 - 包括 H.261、H.262、H.263、H.264(主流)

H.264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称,在编码方面,它的理论依据是:一段时间内图像的统计结果表明,在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内。所以对于一段变化不大的图像画面,我们可以先编码出一个完整的图像帧A,随后的B帧不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小!B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。这段图像我们称为一个序列,当某个图像与之前的图像变化很大,无法参考前面的帧来生成,那我们就结束上一个序列,开始下一段序列。 在H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。 H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。

  • I帧 - 帧内编码帧,就是通过压缩算法压成一张单独的完整视频画面,也是一组的第一帧,所以I帧去掉的是视频帧在空间维度上的冗余信息。
  • P帧 - 前向预测编码帧,需要参考其前面的一个I帧或者P帧来解码成一张完整的视频画面 。
  • B帧 - 双向预测内插编码帧,需要参考其前一个I帧或者P帧及其后面的一个P帧来生成一张完整的视频画面,所以P帧与B帧去掉的是视频帧在时间维度上的冗余信息。

前面我们说到,视频是由一系列图像组成,每个图像就是一帧。图像是一组一组处理的,一组帧常用的结构由15个帧组成,具有IBBPBBPBBPBBPBB形式,一组帧又叫GOP,可以参考下图

image.png

编解码种类(硬件编码,软件编码):

硬编码:(Android原生编码)
用设备GPU去实现编解码,这样可以减轻CPU的压力。
软编码:(FFmpeg)
让CPU来进行编解码,在c层代码来进行编解码,因为c/c++有很多好的编解码库。

软硬编码对比:

硬编的好处主要在于速度快,而且系统自带不需要引入外部的库,但是特性支持有限,而且硬编的压缩率一般偏低,而对于软编码来说,虽然速度较慢,但是压缩率比较高,而且支持的H264特性也会比硬编码多很多,相对来说比较可控。硬编码会受硬件设备支持的影响。

image.png

Android音视频

image.png

Android音频

1. 音频采集

Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger(主要承担音频混合输出) 进行交互的。直播中实时采集音频要用AudioRecord。

2. 音频播放
AudioTrack 类可以完成Android平台上音频数据的输出任务。AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对应的是数据加载模式和音频流类型, 对应着两种完全不同的使用场景。

MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。
MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

AudioTrack播放

对于 MODE_STATIC 模式,必须要调用 write(...) 相关方法将数据写入到对应的缓冲区中,然后才可以调用 paly(...) 方法进行播放操作。

//先将所有的数据写入到缓冲区
write(...)
//然后在播放
play(..)

对于 MODE_STREAM 模式

paly(...)

new Thread() {
    public void run() {
        //一系列的 write 操作
        `write(...)`
    }
    
}.start();

2.3、 AudioTrack 与 MediaPlayer

最大的区别是MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。
MediaPlayer会在framework层创建对应的音频解码器。而AudioTrack只能播放已经解码的PCM流,如果对比支持的文件格式的话则是AudioTrack只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。
MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以是MediaPlayer包含了AudioTrack。

3. 视频采集
Camera1(傻瓜式,同步操作,功能少)
Camera2(调用步骤繁琐,兼容性差)
CameraX(丰富的API和拓展效果。Jetpack支持库,感知生命周期)
使用 Camera API 采集视频数据并保存到文件,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到 NV21 的数据回调。

3.1SurfaceView、TextureView区别

与其它View不同的是,两者都能在独立的线程中绘制和渲染,在专用的GPU线程中大大提高渲染的性能。

一、SurfaceView专门提供了嵌入视图层级的绘制界面,开发者可以控制该界面像Size等的形式,能保证界面在屏幕上的正确位置。但也有局限:
由于是独立的一层View,更像是独立的一个Window,不能加上动画、平移、缩放;
两个SurfaceView不能相互覆盖。
二、TextureView更像是一般的View,像TextView那样能被缩放、平移,也能加上动画。
TextureView只能在开启了硬件加速的Window中使用,并且消费的内存要比SurfaceView多,并伴随着1-3帧的延迟。
三、TextureView和SurfaceView都是继承自View类的,但是TextureView在Andriod4.0之后的API中才能使用。
SurfaceView可以通过SurfaceHolder.addCallback方法在子线程中更新UI,TextureView则可以通过TextureView.setSurfaceTextureListener在子线程中更新UI,能够在子线程中更新UI是上述两种View相比于View的最大优势。

但是,两者更新画面的方式也有些不同,由于SurfaceView的双缓冲功能,可以是画面更加流畅的运行,但是由于其holder的存在导致画面更新会存在间隔(不太好表达,直接上图)
image.png

并且,由于holder的存在,SurfaceView也不能进行像View一样的setAlpha和setRotation方法,但是对于一些类似于坦克大战等需要不断告诉更新画布的游戏来说,SurfaceView绝对是极好的选择。但是比如视频播放器或相机应用的开发,TextureView则更加适合。

SurfaceView优缺点:

优点
可以在一个独立的线程中进行绘制,不会影响主线程使用双缓冲机制,播放视频时画面更流畅
缺点
由于是独立的一层View,更像是独立的一个Window,不能加上动画、平移、缩放;

TextureView优缺点:

优点:
动画支持良好,可以获取视频截图.
视图不可见时可以保留当前帧不黑屏
缺点
必须开启硬件加速,否则无画面,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

3.2音视频编码(MediaCodec 介绍)

MediaCodec是Android提供的用于对音视频进行编解码的类,它通过访问底层的codec来实现编解码的功能。是Android media基础框架的一部分,通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface和AudioTrack 一起使用。

3.2.1 编解码原理

MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据:

  • 1.首先,客户端向获取到的编解码器输入缓存区写入要编解码的数据并将其提交给编解码器。
  • 2.待编解码器处理完毕后将其转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;
  • 3.然后,客户端从获取到编解码输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。
  • 4.不断重复整个过程,直至编码器停止工作或者异常退出。

输出流程如下:

  • 1.MediaCodec采用异步方式处理数据,并且使用了一组输入输出buffer(ByteBuffer)。使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理。
  • 2.MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。
  • 3.使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码器。
image
3.2.2 MediaCodec API 说明

MediaCodec可以处理具体的视频流,主要有这几个方法:

  • getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组
  • queueInputBuffer:输入流入队列
  • dequeueInputBuffer:从输入流队列中取数据进行编码操作
  • getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组
  • dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
  • releaseOutputBuffer:处理完成,释放ByteBuffer数据
3.2.3 MediaCodec 使用示例
//-1 创建并配置MediaCodec对象。
//- 2 给MediaCodec对象设置回调MediaCodec.Callback
//- 3 在onInputBufferAvailable回调中:
//    - 读取一段输入,将其填充到输入buffer中
//- 4 在onOutputBufferAvailable回调中:
//    - 从输出buffer中获取数据进行处理。
//- 5 处理完毕后,release MediaCodec 对象。
MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

3.3 音视频分离

Android提供了一个MediaExtractor类,可以用来分离容器中的视频track和音频track。

流程:
在一个多媒体视频中获取轨道
从MediaFormat里提取视频数据信息

主要API介绍:

  • setDataSource(String path):即可以设置本地文件又可以设置网络文件
  • getTrackCount():得到源文件通道数
  • getTrackFormat(int index):获取指定(index)的通道格式
  • getSampleTime():返回当前的时间戳
  • readSampleData(ByteBuffer byteBuf, int offset):把指定通道中的数据按偏移量读取到ByteBuffer中;
  • advance():读取下一帧数据
  • release(): 读取结束后释放资源
MediaExtractor extractor = new MediaExtractor();
 extractor.setDataSource(...);
 int numTracks = extractor.getTrackCount();
 for (int i = 0; i < numTracks; ++i) {
   MediaFormat format = extractor.getTrackFormat(i);
   String mime = format.getString(MediaFormat.KEY_MIME);
   if (weAreInterestedInThisTrack) {
     extractor.selectTrack(i);
   }
 }
 ByteBuffer inputBuffer = ByteBuffer.allocate(...)
 while (extractor.readSampleData(inputBuffer, ...) >= 0) {
   int trackIndex = extractor.getSampleTrackIndex();
   long presentationTimeUs = extractor.getSampleTime();
   ...
   extractor.advance();
 }

 extractor.release();
 extractor = null;

3.4 音视频混合

MediaMuxer的作用是生成音频或视频文件;还可以把音频与视频混合成一个音视频文件。

相关API介绍:

  • MediaMuxer(String path, int format):path:输出文件的名称 format:输出文件的格式;当前只支持MP4格式;
  • addTrack(MediaFormat format):添加通道;我们更多的是使用MediaCodec.getOutpurForma()或- Extractor.getTrackFormat(int index)来获取MediaFormat;也可以自己创建;
  • start():开始合成文件
  • writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer中的数据写入到在构造器设置的文件中;
  • stop():停止合成文件
  • release():释放资源
  • 使用示例
MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
 // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()
 // or MediaExtractor.getTrackFormat().
 MediaFormat audioFormat = new MediaFormat(...);
 MediaFormat videoFormat = new MediaFormat(...);
 int audioTrackIndex = muxer.addTrack(audioFormat);
 int videoTrackIndex = muxer.addTrack(videoFormat);
 ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);
 boolean finished = false;
 BufferInfo bufferInfo = new BufferInfo();

 muxer.start();
 while(!finished) {
   // getInputBuffer() will fill the inputBuffer with one frame of encoded
   // sample from either MediaCodec or MediaExtractor, set isAudioSample to
   // true when the sample is audio data, set up all the fields of bufferInfo,
   // and return true if there are no more samples.
   finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo);
   if (!finished) {
     int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex;
     muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
   }
 };
 muxer.stop();
 muxer.release();

以合成 MP4 视频为例:

  1. 整体来看,合成的 MP4 文件,视频部分为 H.264 编码格式的数据,音频部分为 AAC 编码格式的数据。

  2. 通过 MediaMuxer 提供的接口-writeSampleData(),将 H.264 和 AAC 数据分别同时写入到 MP4 文件。


    image.png

[参考项目代码]

3.5 视频播放

目前播放器比较火热的有Android系统自带的MediaPalyer,还有google的ExoPlayer

  • MediaPalyer:在Android系统中原生的实现MediaPlayer, 以及将MediaPlayer,SurfaceView封装在一起的VideoView, 两者都只是使用硬解播放,基本上只支持本地和HTTP协议的视频播放,扩展性都很差,只适合最简单的视频播放需求。
     mediaPlayer = new MediaPlayer();
            mediaPlayer.setDataSource(path);
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            
            // 通过异步的方式装载媒体资源
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(new OnPreparedListener() {                    
                @Override
                public void onPrepared(MediaPlayer mp) {
                    // 装载完毕回调
                    mediaPlayer.start();
                }
            });
  • ExoPlayer提供了更好的扩展性和定制能力,并加入了对DASH和HLS等直播协议的支持,但也只支持硬码,如果项目中只需要支持对H264格式的视频播放,以及流媒体协议比较常规(比如HTTP,HLS),基于ExoPlayer定制也是不错的选择。
    image.png

    常用API
  • play并pause 开始和暂停播放。
  • seekTo 允许在media内搜寻。
  • hasPrevious,hasNext,previous并next允许通过播放列表进行浏览。
  • setRepeatMode 控制media 是否循环以及如何循环。
  • setShuffleModeEnabled 控制播放列表移动。
  • setPlaybackParameters 调整播放速度和音频音高。

与Android内置的MediaPlayer相比,ExoPlayer具有许多优点:
*支持通过HTTP(DASH)和SmoothStreaming进行动态自适应流,这两种都不受MediaPlayer的支持。还支持许多其他格式
*能够自定义和扩展播放器,以适应各种不同需求。 ExoPlayer专门设计了这一点,大部分组件都可以自己替换
*官网说了很多,其实说到底最主要的就是各个组件可以自定义,还可以接入ffmpeg组件,基本能满足99.9%的需求

private void initializePlayer() {
        if (player==null){
            player = ExoPlayerFactory.newSimpleInstance(
                    new DefaultRenderersFactory(this),
                    new DefaultTrackSelector(), new DefaultLoadControl());

            playerView.setPlayer(player);

            player.setPlayWhenReady(playWhenReady);
            player.seekTo(currentWindow, playbackPosition);
        }

        Uri uri = Uri.parse(getString(R.string.media_url_mp4));
        MediaSource mediaSource = buildMediaSource(uri);
        player.prepare(mediaSource, false, true);
    }
  • ijkplayer是Bilibili公司开源的播放器实现,整合了FFMpeg, ExoPlayer, MediaPlayer等多种实现,提供了类似于MediaPlayer的API,可以实现软硬解码自由切换,自定义TextureView实现,同时得益于FFMpeg的能力,也能支持多种流媒体协议(RTSP,RTMP,HLS等),多种视频编码格式(h264, mpeg4, mjpeg),具有很高的灵活性,可以定制实现自己特色的播放器(比如支持视频缩放,视频翻转等)。 start 29K

4. 多媒体数据传输

例如直播流程
采集视频,音频→视频处理(美颜,滤镜等)→音视频数据压缩→推流→流媒体服务器数据处理→拉流→音视频解码→播放→聊天互动→弹幕

4.1、流媒体

*流媒体技术就是边下载,边播放。主要特点是以“流(Streaming)”的形式在基于IP协议的互联网中进行多媒体数据的实时、连续传播。

  • HTTP方式: 先通过服务器将FLV下载到本地缓存,然后再通过NetConnection的本地连接来播放这个FLV,这种方法是播放本地的视频,并不是播放服务器的视频。

  • RTSP方式: RTSP 1.0标准的制订者没有充分预测到互联网带宽的快速增长,以及由于IPv4地址短缺导致的NAT技术的广泛使用,还有代理服务器的大量存在,它在传输可靠性和易用性上都存在一定的缺陷。

  • 用RTMP方式: 通过NetConnection连接到FMS(Flash Media Server)或Red5服务器,并实时播放服务器的FLV文件,这种方式可以任意选择视频播放点,消耗服务器资源,连接始终是实时的。

协议名称 优点 缺点
HTTP 基础设施完善;代理服务器强(数量多,CDN缓存优化);实现简单,部署快;自适应码率切换;有路由器防火墙穿透 实时性不高;播放控制精度不高;播放器要做协议授权,开放度比rtsp低;无倍速播放
RTSP 带宽好时效率高;倍速播放;控制精准,任意选择播放点 服务端实现复杂;代理服务器弱(数量少,优化少);无路由器防火墙穿透;管流分离(需要1-3个通道)
RTMP 实时性高;控制精准,任意选择播放点;应用广泛(flash) Flv缓存在内存,会消耗服务器资源;协议复杂,未全部公开;似乎CDN缓存不好弄

4.2 音视频推流

通过摄像头和麦克获取视频和音频数据,把视频和音频数据压缩成H264视频格式和ACC音频格式,封装并发送给服务器,服务器再根据相关的协议分发。

推流,可以推H264裸流,也可以封装成FLV格式再推送,
为什么不直接推H264裸流,而是要封装成FLV格式再推,多此一举?其实是为了兼容多种编码格式的流。
如果直接推H264裸流,服务端就对应一套H264裸流的逻辑。
假如后面要推H265的流或者其它封装格式的流,那么无论是推流端还是服务端,都要改逻辑。
而封装成FLV格式再推流,后面如果要推H265流,只需要将H265流封装成FLV格式即可,服务端不需要任何更改,拉流端格式也没变。
流程如下:

推流流程

  • 1.连接流媒体服务器,不断从队列读取封装好的数据,推流。
  • 2.视频流来源:通过采集摄像头数据-编码成H264格式(avc),然后调用通过RTMPDump开源工具,将每一帧数据封装成FLV格式,放到队列中去。
  • 3.音频流来源:通过AudioTrack采集音频PCM数据-编码成aac格式,然后通过通过RTMPDump,封装成FLV格式放到队列去。
    腾讯CDN旁路直播

4.3 音视频混流

混流是把多路音视频流混合成单流的技术。

主播端和观众端均可主动触发混流。SDK 既支持音视频混流,也支持纯音频混流。

SDK 对设置混流的时机没有硬性要求,建议开发者在拉流 / 推流后,或根据需求,在其他合适时机进行混流。

混流的主要流程是:
设置混流配置。
获取混流配置更新通知。
[参考腾讯云端混流转码](https://cloud.tencent.com/document/product/647/16827

4.4音视频拉流

从服务器获取相关的二进制信息,再通过解复用得到封装前的H264视频和ACC音频,再解压,把解压后的视频和音频同步到耳机和屏幕上。

image.png

直播拉流观看

FFmpeg介绍(后续研究)

FFmpeg

音视频深入学习:

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容