美文网首页我的面试准备Android音视频系列
视频开发(1)- 基础知识点整理

视频开发(1)- 基础知识点整理

作者: 前行的乌龟 | 来源:发表于2019-01-04 23:28 被阅读225次

    话说音视频播放现在真是不搞不行啦,不光我们要从市面上 N 多的播放器中找出好用的,熟悉其 API ,甚至还需要我们能利用开源解码器来编写我们自己的视频播放器,模块,这点在越来越不一样,差异化的播放器中可以看得见,很多 app 不光播放器 UI 不一样,甚至很多逻辑都不同

    所以大家努力吧,先不求能搞定 FFMG 编解码等底层技术,至少我们现在得能利用 ijkPlayer 编写自己的视频播放器出来,才能彻底搞得定 app 端的播放需求,要是打算自己上直播推流 sdk ,那么学的就更多了,都是得按年来计算了,音视频水太神了,至少要求我们得搞定 UI 部分才行,在视频这块用别人开源的播放器是走不远的,因为需求差异性太大

    录制音视频 AudioRecord/MediaRecord

    视频的基础知识点推荐看下面:


    视频流媒体协议有哪些

    • HTTP
      -> 最传统的视频流媒体,不支持实时流媒体的播放
    • RTMP
      -> Adobe 公司用于 flash 播放的
    • RTSP
      -> android 原生支持此种流媒体协议
    • FLV
    • HLS
      -> apple 公司开发,把视频分成 3-10 秒的小段,下发 m3u8 文件来标记文件顺序,使用 HTTP、HTTPS 传输
    • MMS
      -> 微软公司开发的

    官方原生播放器 MediaPlayer

    MediaPlayer 是 Androd 多媒体框架中的一个重要组件,通过该类,我们可以解码和播放音视频,但是 MediaPlayer 本身只支持 音频 播放,需要传入专门的视频承载 view 才能播放视频

    MediaPlayer 可以支持三种不同的媒体来源:

    • 本地资源
    • 内部URI,比如你可以通过ContentResolver来获取
    • 外部URL(流)

    MediaPlayer支持两种流媒体协议,HTTP 和 RTSP,这两种协议最大的不同是,RTSP 协议支持实时流媒体的播放,而 HTTP 协议不支持

    原生 MediaPalyer 支持的协议和封装格式实在太有限了,如果我们想播放那些它不支持的视频,这时候就需要第三方播放器了,很多第三方播放器的底层实现都是基于 FFmpeg

    MediaPlayer 对于视频格式支持的也是非常少,值能支持: MP4,AVI,3DP 这3个早期的手机品是格式

    开源的音视频解码器在 API 上都参考了 MediaPlayer ,所以学习 MediaPlayer API 就是我们的第一步

    1. 创建 MediaPlayer 对象,可以直接和本地资源绑定
    MediaPlayer mp = new MediaPlayer();
    MediaPlayer mp = MediaPlayer.create(this, R.raw.test); //无需再调用setDataSource
    create(Context context, Uri uri, SurfaceHolder holder)
    
    1. 设置播放资源
    MediaPlayer.create(this, R.raw.test); // raw下的资源
    mp.setDataSource("/sdcard/test.mp3"); // 本地文件路径
    mp.setDataSource("http://www.xxx.com/music/test.mp3");// 网络URL文件
    
    1. 播放资源
    // uri 资源
    Uri myUri = ....;   /**initialize Uri here*/
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(getApplicationContext(), myUri);
    mediaPlayer.prepare();
    mediaPlayer.start();
    
    // 网络文件
    String url = "http://........"; // your URL here 
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.prepareAsync()
    mediaPlayer.setOnPreparedListener({
        // 加载完毕再开始播放
        mediaPlayer.start()
    })
    
    1. MediaPlayer 主要 API,需要熟知
    getCurrentPosition( ):得到当前的播放位置
    getDuration() :得到文件的时间
    getVideoHeight() :得到视频高度
    getVideoWidth() :得到视频宽度
    isLooping():是否循环播放
    isPlaying():是否正在播放
    pause():暂停
    prepare():准备(同步)
    prepareAsync():准备(异步)
    release():释放MediaPlayer对象
    reset():重置MediaPlayer对象
    seekTo(int msec):指定播放的位置(以毫秒为单位的时间)
    setAudioStreamType(int streamtype):指定流媒体的类型
    setDisplay(SurfaceHolder sh):设置用SurfaceHolder来显示多媒体
    setLooping(boolean looping):设置是否循环播放
    setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener):网络流媒体的缓冲监听
    setOnCompletionListener(MediaPlayer.OnCompletionListener listener):网络流媒体播放结束监听
    setOnErrorListener(MediaPlayer.OnErrorListener listener):设置错误信息监听
    setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener):视频尺寸监听
    setScreenOnWhilePlaying(boolean screenOn):设置是否使用SurfaceHolder显示
    setVolume(float leftVolume, float rightVolume):设置音量
    start():开始播放
    stop():停止播放
    

    我们需要熟知下面这张 MediaPalyer 解码器生命周期图,所有的开源项目都是按这个思路来做的


    播放器生命周期图
    • 状态1:Idel(空闲)状态
      当 mediaplayer创建或者执行reset()方法后处于这个状态。

    • 状态2:Initialized(已初始化)状态
      当调用mediaplayer的setDataResource()方法给mediaplayer设置播放的数据源后,mediaplayer会处于该状态。

    • 状态3:Prepared(准备就续)状态
      设置完数据源后,调用mediaplayer的prepare()方法,让mediaplayer准备播放。值得一提的是,这里除了prepare()方法,还有prepareAsnyc()方法,此方法是异步方法,一般用于网络视频的缓冲。当缓冲完毕后,就会触发准备完毕的事件。我们要做的就是监听该事件(OnPreparedListener),当缓冲完成时,执行相应的操作。在此状态上,我们可以调用seekTo()方法定位视频,此方法不改变mediaplayer的状态;亦可调用stop()放弃视频播放,使mediaplayer处于Stopped状态。一般我们会在此状态上调用start()方法开始播放视频。

    • 状态4:Started(开始)状态
      当处于Prepared状态、Paused状态和PlayebackCompeleted状态时,调用Started()方法即可进入该状态。在该状态中,mediaplayer开始播放视频,可以通过seekTo()方法和start()方法改变视频播放的进度,当Looping为真且播放完毕后,它会重新开始播放(即循环播放);否则播放完毕后,会触发事件并调用OnCompletionaListener.OnCompletion()方法,进行特定操作,并进入PlaybackCompleted状态。在此状态中,亦可调用pause()方法或者stop()方法让视频暂停或停止,此时mediaplayer分别处于Stopped和Paused状态。

    • 状态5:Stopped(停止)状态
      当 mediaplayer处于Prepared、Started、Paused、PlaybackCompleted状态时,调用stop()方法即可进入本状态。应特别注意的是,在本状态中,若想重新开始播放,不能直接调用start()方法,必须调用prepare()方法或prepareAsync()方法重新让mediaplayer处于Prepared状态方可调用start()方法播放视频。

    • 状态6:Paused(暂停)状态
      当mediaplayer处于Started状态是,调用pause()方法即可进入本状态。在本状态里,可直接调用start()方法使,mediaplayer回到Started状态,亦可调用stop()方法停止视频播放,让播放器处于停止态。

    • 状态7:PlaybackCompleted(播放完成)状态
      当mediaplayer播放完成且Looping为假时即可进入本状态。在本状态可调用start()方法使mediaplayer回到Started状态(注意此时是从头开始播放);亦可调用stop()方法使mediaplayer处于停止态,结束播放。

    • 状态8:Error(错误)状态
      当mediaplayer出现错误时处于此状态。


    SurfaceView , TextureView

    SurfaceView , TextureView 都是 android 中用于承载视频帧显示的 view ,熟悉这2个 view 的大家都知道,这 2个 view 都是异步的,都是再非 UI 线程中计算的,拥有独立 surface 显存的,这是因为视频播放任务太重,UI 线程 hold 不住

    • SurfaceView
      大概原理就是在现有View的位置上创建一个新的 Window,内容的显示和渲染都在新的 Window 中。这使得 SurfaceView 的绘制和刷新可以在单独的线程中进行,从而大大提高效率。但是呢,由于 SurfaceView 的内容没有显示在View中而是显示在新建的 Window中, 使得 SurfaceView 的显示不受 View 的属性控制,不能进行平移,缩放等变换,也不能放在其它 RecyclerView 或 ScrollView中,一些 View 中的特性也无法使用。

    • TextureView
      TextureView 是在4.0(API level 14)引入的,与 SurfaceView 相比,它不会创建新的窗口来显示内容。它是将内容流直接投放到View中,并且可以和其它普通View一样进行移动,旋转,缩放,动画等变化。TextureView必须在硬件加速的窗口中使用。TextureView被创建后不能直接使用,必须要在它被它添加到ViewGroup后,待SurfaceTexture准备就绪才能起作用(看TextureView的源码,TextureView是在绘制的时候创建的内部SurfaceTexture。SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。当TextureView内部创建好SurfaceTexture后,在监听器的onSurfaceTextureAvailable方法中,用SurfaceTexture来关联MediaPlayer,作为播放视频的图像数据来源。SurfaceTexture作为数据通道,把从数据源(MediaPlayer)中获取到的图像帧数据转为GL外部纹理,交给TextureVeiw作为View heirachy中的一个硬件加速层来显示,从而实现视频播放功能。


    视频播放方式一:VideoView + MediaController

    这是官方原生的实现,VideoView 继承 SurfaceView ,内部封装了 SurfaceView 的所有操作,MediaController 这样来显示视频控制相关的 view 部分

    我个人是非常喜欢官方的 API 设计的,代码功能,层次分离,起名都非常规范,让人一看就懂,用起来也是很爽,奈何就是支持的流媒体格式太少,我们只能去使用开源组件

    VideoView + MediaController 的简单使用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.play);
        videoview = (VideoView) findViewById(R.id.video);
    
        mMediaController = new MediaController(this);
        videoview.setMediaController(mMediaController);
        mMediaController.setAnchorView(videoview)
    
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                loadView(url.getText().toString());
            }
        });
    }
    
    public void loadView(String path) {
        Uri uri = Uri.parse(path);
        videoview.setVideoURI(uri);
        videoview.start;
        videoview.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
     //         mp.setLooping(true);
                mp.start();// 播放
                Toast.makeText(MainActivity.this, "开始播放!", Toast.LENGTH_LONG).show();
            }
        });
        videoview.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(MainActivity.this, "播放完毕", Toast.LENGTH_SHORT).show();
            }
        });
    }
    
    

    我们简单看下 MediaController 是如何管理 视频控制 view 的

    MediaController 继承 FrameLayout

    public class MediaController extends FrameLayout
    

    在 makeControllerView 中创建 控制布局 view ,并初始化 view

        protected View makeControllerView() {
            LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);
    
            initControllerView(mRoot);
    
            return mRoot;
        }
    

    media_controller 样式


    media_controller

    在 setAnchorView 关联视频播放器 VideoView 时把 控制 view 添加到自己身上

        public void setAnchorView(View view) {
            if (mAnchor != null) {
                mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);
            }
            mAnchor = view;
            if (mAnchor != null) {
                mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);
            }
    
            FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
            );
    
            removeAllViews();
            View v = makeControllerView();
            addView(v, frameParams);
        }
    

    视频播放方式二:开源解码器 + TextureView

    我们注定是不会使用原生 VideoView 去远程视频的,没办法主流的流媒体格式 VideoView 都不支持,我们只能去使用 开源的视频解码器 + TextureView 去自己实现了

    这就是我们后面需要自己的搞定的


    参考:

    相关文章

      网友评论

        本文标题:视频开发(1)- 基础知识点整理

        本文链接:https://www.haomeiwen.com/subject/vcllrqtx.html