Android多媒体开发与应用(二):Android音视频处理

目录

第一章:音频管理

第二章:视频播放

一、使用系统中已安装的播放器app(使用Intent方式播放)。

二、使用VideoView配合MediaController播放。

三、使用SurfaceView配合MediaPlayer播放

第三章 拍照功能实现及应用

一、调用系统相机

二、使用 Camera API

第四章 音频录制与播放

一、音频录制

二、音频播放

三、动态权限申请

权限的分类

权限检查与申请


第一章:音频管理

MediaPlayer的生命周期

Android多媒体开发与应用(二):Android音视频处理_第1张图片

MediaPlayer官方文档:https://developer.android.com/reference/android/media/MediaPlayer

MediaPlayer详细介绍:https://blog.csdn.net/u014606081/article/details/79927057

使用Service播放音乐的例子:https://blog.csdn.net/qq_33200967/article/details/77263068

第二章:视频播放

视频播放的实现方式一般有三种:

1、使用系统中已安装的播放器app。通过Intent播放视频,这种方式有一些坑,比如在Android6.0以上的版本中,SDCard中视频播放需要权限申请,在Android7.0以上还要考虑FileProvider带来的一些适配的问题。

2、使用VideoView配合MediaController实现。这种方式实现非常简单,缺点是MediaController操作栏固定。

3、使用SurfaceView配合MediaPlayer实现。可以自己写控制视频的控件,具有非常高的可定制性。

一、使用系统中已安装的播放器app(使用Intent方式播放)。

    private static final int REQUEST_CODE_EXTERNAL_STORAGE = 0x110;
    private void checkPermissionAndPlayVideo() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},REQUEST_CODE_EXTERNAL_STORAGE);
        }else{
            playVideoUseIntent();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_EXTERNAL_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    playVideoUseIntent();
                } else {
                    Toast.makeText(this, "该功能需要SDCard权限", Toast.LENGTH_SHORT).show();
                }
                return;
            }

        }
    }

    public void playVideoUseIntent() {
        File file = new File(Environment.getExternalStorageDirectory().getPath(), "lalala.mp4");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if(Build.VERSION.SDK_INT>=24){
            Uri contentUri = FileProvider.getUriForFile(this,
                    "com.administrator.videodemo.fileprovider", file);
            intent.setDataAndType(contentUri, "video/*");
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }else {
            intent.setDataAndType(Uri.fromFile(file), "video/*");
        }
        startActivity(intent);
    }

manifests.xml文件配置

        
            
        

file_paths.xml文件



    

参考Android官方文档:

权限申请:https://developer.android.com/training/permissions/requesting

FileProvider:https://developer.android.com/reference/android/support/v4/content/FileProvider?hl=en

二、使用VideoView配合MediaController播放。

activity_video_view.xml:



    

java源码

    private VideoView mVideoView;
    private MediaController mController;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view);

        mVideoView = findViewById(R.id.id_videoview);

        File file = new File(Environment.getExternalStorageDirectory().getPath(), "lalala.mp4");
        mVideoView.setVideoPath(file.getAbsolutePath());
        mController=new MediaController(this);
        mVideoView.setMediaController(mController);
    }

前后台切换、横竖屏切换保持:

    private int mCurrentPos;
    private boolean mIsPause;
    private static final String KEY_CUR_POS="key_cur_pos";
    private static final String KEY_IS_PAUSE="key_is_pause";
    @Override
    protected void onResume() {
        mVideoView.seekTo(mCurrentPos);
        if(!mIsPause) {
            mVideoView.start();
        }
        super.onResume();

    }

    @Override
    protected void onPause() {
        mCurrentPos=mVideoView.getCurrentPosition();
        mIsPause=!mVideoView.isPlaying();
        super.onPause();
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt(KEY_CUR_POS,mCurrentPos);
        outState.putBoolean(KEY_IS_PAUSE,mIsPause);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        mCurrentPos = savedInstanceState.getInt(KEY_CUR_POS);
        mIsPause=savedInstanceState.getBoolean(KEY_IS_PAUSE);
        super.onRestoreInstanceState(savedInstanceState);
    }

三、使用SurfaceView配合MediaPlayer播放

activity_media_player.xml




    
        

        
        

java源码

public class MediaPlayerActivity extends AppCompatActivity {

    private RelativeLayout mRlContainer;
    private SurfaceView mSurfaceView;
    private SeekBar mSeekBar;
    private Button mBtnPlay;

    private MediaPlayer mMediaPlayer;
    private boolean mIsPause;

    private Handler mHandler=new Handler();
    private Runnable mRunnable=new Runnable() {
        @Override
        public void run() {
            if(mMediaPlayer==null){
                return;
            }
            int duration=mMediaPlayer.getDuration();
            int currentPos=mMediaPlayer.getCurrentPosition();
            if(mSeekBar!=null && duration>0) {
                int progress = (int) (currentPos * 1.0f / duration * 1000);
                mSeekBar.setProgress(progress);
                if(mMediaPlayer.isPlaying()) {
                    mHandler.postDelayed(mRunnable, 1000);
                }
            }
        }
    };

    private float mRatioHW;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_player);

        initViews();

        initMediaPlayer();

        initEvents();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(!mIsPause) {
            mMediaPlayer.start();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(!mIsPause) {
            mMediaPlayer.pause();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ViewTreeObserver observer = mRlContainer.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mRlContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                ViewGroup.LayoutParams lp = mRlContainer.getLayoutParams();
                lp.height = (int) (mRatioHW * mRlContainer.getWidth());
                mRlContainer.setLayoutParams(lp);
            }
        });

    }

    private void initEvents() {
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mMediaPlayer.setDisplay(holder);
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {

            }
        });

        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                mHandler.removeCallbacks(mRunnable);
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int targetPos = (int) (seekBar.getProgress() * 1.0f / 1000 * mMediaPlayer.getDuration());
                mMediaPlayer.seekTo(targetPos);

                if(mMediaPlayer.isPlaying()){
                    mHandler.post(mRunnable);
                }
            }
        });

        mBtnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mIsPause){
                    mMediaPlayer.start();
                    mIsPause=false;
                    mHandler.post(mRunnable);
                    mBtnPlay.setText("暂停");
                }else{
                    mMediaPlayer.pause();
                    mIsPause=true;
                    mHandler.removeCallbacks(mRunnable);
                    mBtnPlay.setText("播放");
                }
            }
        });

        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                ViewGroup.LayoutParams layoutParams = mRlContainer.getLayoutParams();
                mRatioHW = height * 1.0f / width;
                layoutParams.height= (int) (mRlContainer.getWidth()*1.0f/width*height);
                mRlContainer.setLayoutParams(layoutParams);
            }
        });
    }

    private void initMediaPlayer() {
        mMediaPlayer=new MediaPlayer();
        File file = new File(Environment.getExternalStorageDirectory().getPath(), "lalala.mp4");
        try {
            mMediaPlayer.setDataSource(file.getAbsolutePath());
            mMediaPlayer.prepareAsync();
            mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mMediaPlayer.start();
                    mIsPause=false;
                    mBtnPlay.setText("暂停");
                    mHandler.post(mRunnable);
                }
            });

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initViews() {
        mRlContainer = findViewById(R.id.id_rl_container);
        mSurfaceView = findViewById(R.id.id_surface_view);
        mSeekBar=findViewById(R.id.id_seekbar);
        mBtnPlay=findViewById(R.id.id_btn_play);
    }

    public static void start(Context context){
        Intent intent=new Intent(context,MediaPlayerActivity.class);
        context.startActivity(intent);
    }
}

manifests文件

        

补充:内存不足时activity的恢复

参考1:https://www.jianshu.com/p/04a8d85807ca

参考2:https://blog.csdn.net/u013777094/article/details/78617475

 

第三章 拍照功能实现及应用

照相机的使用一般有两种方式:调用系统相机和使用 Camera API

一、调用系统相机

1、设置权限

    
    

2、请求系统相机

//使用Intent请求相机
        Intent intent=new Intent();

        //设置请求条件
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addCategory(Intent.CATEGORY_DEFAULT);

        //图片保存位置
        File file=new File(FILE_APTH);
        if(file.exists()){
            file.delete();
        }
        //图片转成Uri
        Uri uri=Uri.fromFile(file);
        intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);

        startActivityForResult(intent, REQUEST_CODE);

3、获取图片

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(REQUEST_CODE==requestCode){
            File file=new File(FILE_APTH);
            Uri uri=Uri.fromFile(file);
            mImageView.setImageURI(uri);
        }
    }

二、使用 Camera API

1、设置权限

1.5、检查手机是否有相机

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

2、打开相机

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

3、创建自定义类继承自SurfaceView

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

4、向布局中添加自定义类

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }
}

5、获取图片

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};
// Add a listener to the Capture button
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an image from the camera
            mCamera.takePicture(null, null, picture);
        }
    }
);

参考:https://developer.android.com/guide/topics/media/camera#considerations

第四章 音频录制与播放

一、音频录制

录制音频的方法很多,这里只介绍一种MediaRecorder。MediaRecorder可以用来录制音频和视频。

                    //录制
                    MediaRecorder mRecorder=new MediaRecorder();
                    mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                    mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
                    mVoiceFile = File.createTempFile("myvoice",".mp3");
                    mRecorder.setOutputFile(mVoiceFile.getPath());
                    mRecorder.prepare();
                    mRecorder.start();
                    
                    //停止
                    mRecorder.stop();
                    mRecorder.release();

二、音频播放

                        MediaPlayer mPlayer=new MediaPlayer();
                        mPlayer.setDataSource(FILE_PATH);
                        mPlayer.prepare();
                        mPlayer.start();
                        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                            @Override
                            public void onCompletion(MediaPlayer mp) {
                                mPlayer.stop();
                                mPlayer.release();
                            }
                        });

 补充:

1、ListView更新后自动滚动

mListView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);

2、Button手势相应--抬起

        mBtnRecord.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction()==MotionEvent.ACTION_UP){
                    ...

三、动态权限申请

动态权限指从Android6.0(API23)开始,当app运行时用户所授予的权限,而不是在安装程序的时候。

权限的分类

分为normal和dangerous两类。

1、普通权限,对于用户隐私没有危险的,只需要在AndroidManifest.xml文件中申请就可以直接授权。

Android多媒体开发与应用(二):Android音视频处理_第2张图片

2、危险权限,需要访问用户的隐私信息等权限,即使在AndroidManifest.xml文件中注册,也需要在运行时通过用户授权。

Android多媒体开发与应用(二):Android音视频处理_第3张图片

权限检查与申请

1、检查程序是否拥有某项权限

int permission = ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTECTS)

返回值:

  • PackageManager.PERMISSION_GRANTED       //权限被授予 0
  • PackageManager.PERMISSION_DENIED    //权限被拒绝 -1

        如果是6.0以下的手机,该方法的返回值会始终为PERMISSION_GRANTED,因此你并不需要动态申请权限,直接做你想做的;如果是6.0以上的手机,情况就分为两种:如果是normal权限,该方法的返回值会始终为PERMISSION_GRANTED,如果是dangerous权限,动态请求。

2、请求权限

ActivityCompat.requestPermissions(thisActivity,new String[]{Manifest.permission.READ_CONTECTS},REQUEST_CODE);

参数1:activity对象
参数2:需要被授权的权限
参数3:请求码

调用此方法会弹出对话框让用户选择,第一次出现:允许、拒绝两个按钮,第二次出现允许、拒绝两个按钮和拒绝后不再询问选择框,选择拒绝后不再询问选择框并点击拒绝按钮后,再次运行程序将不再出现对话框。

3、提示权限

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)

参数1:在requestPermissions()传递的请求码。

参数2:在requestPermissions()传递的需要请求的权限。

参数3:申请权限后返回的结果,与requestPermissions()权限数组一一对应。返回值为:PackageManager.PERMISSION_GRANTED、PackageManager.PERMISSION_DENIED(或者其他值?)

请求权限后的回调方法。可以用来处理用户拒绝权限后的操作,例如定义一个全局变量保存用户拒绝相机访问的权限,在需要访问相机的地方提醒用户没有权限。

4、解释权限

 boolean should = ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,Manifest.permission.READ_CONTECTS)

参数1:activity对象(this)

参数2:需要解释的权限

返回值:布尔类型。返回值为false的情况:第一次被用户拒绝权限时;再次被用户拒绝并且选择“拒绝后不再询问”复选框。返回值为true的情况:第二次起被用户拒绝权限时(拒绝后不再询问”复选框不能被选中)。

是否要向用户解释请求权限的行为,告诉用户如果权限被拒绝可能导致的结果(在程序第一次运行并且被用户拒绝权限后,再次运行程序才需要向用户解释为什么需要权限,如果用户都没有拒绝还解释那么多干什么)。

代码

    public static int storageWritePermission = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //检查权限
        initPermission();
        //初始化控件
        initViews();
        //添加监听器
        addListeners();
    }

    private void initPermission() {
        int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if(permission!= PackageManager.PERMISSION_GRANTED){
            boolean should = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if(should){
                explainDialog();
                return;
            }
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1001);
        }
    }

    private void explainDialog() {
        AlertDialog.Builder builder=new AlertDialog.Builder(this)
                .setMessage("XX功能需要读写磁盘权限!是否授权?")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1001);
                    }
                })
                .setNegativeButton("取消",null);
        builder.show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode==1001){
            if(grantResults[0]==PackageManager.PERMISSION_DENIED){
                storageWritePermission =0;
            }else{
                storageWritePermission =1;
            }

        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

 

你可能感兴趣的:(Android)