Android--音视频+摄像头使用

本篇记录Android原生方法播放音视频和调用原生摄像头的使用方法。

  1. 使用MediaPlayer 来实现音视频的播放、控制。
  2. 使用CameraManager实现原生摄像头的调用。

音频播放

MediaPlayer 是 Android 提供的一个类,用于播放本地音频和视频文件,支持多种格式,包括 MP3、WAV、MP4 等。MediaPlayer 提供了丰富的控制接口,可以用来处理音频和视频的播放、暂停、停止、跳转、音量调节等操作。

1. 创建布局文件

布局文件中包括音频和视频两部分内容的控件,包括视频播放surfaceView、视频进度条SeekBar、开始/暂停/重播按钮、音量调节SeekBar。

<activity_main.xml>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <SeekBar
        android:id="@+id/video_seek_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start audio"/>

    <Button
        android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop audio"/>

    <Button
        android:id="@+id/replay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="replay audio"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置音量"/>

    <SeekBar
        android:id="@+id/audio_seek_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

2. 实现音频播放

(1)首先通过AudioManager实现音量调节功能,获取AudioManager后调用其中的getStreamVolume()和setStreamVolume获取和设置音频流的音量。

(2)播放音频首先要初始化MediaPlayer对象,本文使用的是本地音频资源文件,将.mp3文件添加到 res/raw文件夹中,然后在初始化MediaPlayer时传入。

(3)然后通过MediaPlayer的start()和stop()方法控制音频播放的开始和暂停,需要注意的时音频才播放前需要调用MediaPlayer的prepare()方法来加载媒体资源准备播放。

<MainActivity.java>

public class MainActivity extends AppCompatActivity {

    private SeekBar videoSeekBar;
    private SeekBar audioSeekBar;
    private Button start;
    private Button stop;

    private AudioManager audioManager;
    private MediaPlayer mediaPlayer;
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private Handler handler = new Handler();
    private Runnable updateSeekBar;


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

        // 播放音频测试:MediaPlayer:适用于播放本地文件或资源
        initAudio();
    }

    private void initAudio(){
        start = findViewById(R.id.start);
        stop = findViewById(R.id.stop);
        audioSeekBar = findViewById(R.id.audio_seek_bar);

        // 初始化音量控制
        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        audioSeekBar.setMax(maxVolume);
        audioSeekBar.setProgress(currentVolume);
        audioSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        // 初始化 MediaPlayer 对象
        try {
            mediaPlayer = MediaPlayer.create(this, R.raw.audio);     // mp3 音频
            Log.d("=====", "init mediaPlayer! =====");
        } catch (Exception e) {
            e.printStackTrace();
            // 处理初始化失败的情况
            Log.d("=====", "mediaPlayer error! =====" + e);
        }

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("=====", "start onClick! =====");
                // 播放音频
                if (!mediaPlayer.isPlaying()) {
                    mediaPlayer.start(); // 播放音频
                    Log.d("=====", "start mediaPlayer! =====");

                }
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("=====", "stop onClick! =====");
                // 播放音频
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.stop(); // 停止音频
                    try {
                        mediaPlayer.prepare(); // 准备播放
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        // mediaPlayer状态监听
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                // 播放完成后的操作
                mp.release(); // 释放资源
                Log.d("=====", "mediaPlayer onCompletion! =====");
            }
        });
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}

视频播放

播放视频同样是使用MediaPlayer实现,在MainActivity.java中添加添加initVideo()即可。

(1)视频资源同样保存在 res/raw 文件夹下。

(2)通过MediaPlayer的setDataSource()设置视频资源、通过setDisplay()设置视频输出显示的位置、可以用过setLooping()控制视频是否循环播放、getCurrentPosition()可以获取视频播放进度,用于更新SeekBar。

(3)调用MediaPlayer的start()方法继续播放视频、调用pause()暂停播放、调用seekTo(int)跳转视频进度。

<MainActivity.java>

    private void initVideo(){
        Button start = findViewById(R.id.start);
        Button stop = findViewById(R.id.stop);
        Button replay = findViewById(R.id.replay);

        // 初始化 SurfaceView
        surfaceView = findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();

        // 继续播放
        start.setOnClickListener(v -> {
            if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
                mediaPlayer.start(); // 继续播放
            }
        });

        // 暂停播放
        stop.setOnClickListener(v -> {
            if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                mediaPlayer.pause(); // 暂停播放
            }
        });

        // 重新播放
        replay.setOnClickListener(v -> {
            if (mediaPlayer != null) {
                mediaPlayer.seekTo(0); // 将播放位置重置到视频的开头
                mediaPlayer.start(); // 重新开始播放
            }
        });

        // 初始化 SeekBar
        videoSeekBar = findViewById(R.id.video_seek_bar);
        videoSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) {
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        //
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                Log.d("=====", "surfaceCreated! =====");
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                Log.d("=====", "surfaceChanged! =====");

                // 初始化 MediaPlayer
                mediaPlayer = new MediaPlayer();
                try {
                    // 设置视频源(可以是本地文件或网络资源)
                    Uri videoUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.video);
                    mediaPlayer.setDataSource(MainActivity.this, videoUri);
                    mediaPlayer.setDisplay(surfaceHolder);
                    mediaPlayer.prepare();
                    mediaPlayer.setLooping(true);
                    mediaPlayer.start();

                    // 每秒更新 SeekBar
                    videoSeekBar.setMax(mediaPlayer.getDuration());
                    updateSeekBar = new Runnable() {
                        @Override
                        public void run() {
                            if (mediaPlayer != null) {
                                videoSeekBar.setProgress(mediaPlayer.getCurrentPosition());
                                handler.postDelayed(this, 1000);
                            }
                        }
                    };
                    handler.post(updateSeekBar);

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

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
                Log.d("=====", "surfaceDestroyed! =====");

                if (mediaPlayer != null) {
                    mediaPlayer.release();
                    mediaPlayer = null;
                }
            }
        });
    }

调用原生摄像头

1. 创建布局

布局中添加了两个按钮,分别用于打开原生摄像头和关闭原生摄像头,还有一个TextureView用于展示摄像头画面。

2. 功能实现

(1)获取CameraManager实例和摄像头ID。

(2)判断Activity能够获取相机权限的话,调用CameraManager.openCamera()方法传入摄像头ID开启摄像头,该方法包含三个回调,当开启摄像头成功会走到onOpened()回调中,继续实现预览摄像头画面的逻辑。

(3)预览摄像头画面需要船舰捕获请求和预览会话,然后通过CameraCaptureSession的setRepeatingRequest()方法启动预览并展示在TextureView上。

<MainActivity.java>

    private void initView() {

        // 打开原生摄像头
        findViewById(R.id.start_camera).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    startCamera();
                } catch (CameraAccessException e) {
                    Log.d("=====startCamera=====", String.valueOf(e));
                    e.printStackTrace();
                }
            }
        });

        // 关闭原生摄像头
        findViewById(R.id.stop_camera).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                releaseCamera();
            }
        });

        mTextureView = findViewById(R.id.preview);
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
                Log.d("=====mTextureView=====", "onSurfaceTextureAvailable");
            }

            @Override
            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
                // Surface大小变化时可以重新配置预览
                Log.d("=====mTextureView=====", "onSurfaceTextureSizeChanged");
            }

            @Override
            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
                // Surface销毁时,关闭摄像头
                Log.d("=====mTextureView=====", "onSurfaceTextureDestroyed");
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
                // Surface更新时可以做处理
                Log.d("=====mTextureView=====", "onSurfaceTextureUpdated");
            }
        });
    }


    // 打开原生摄像头
    private void startCamera() throws CameraAccessException {
        Log.d("=====startCamera=====", "");
        mTextureView.setVisibility(View.VISIBLE);

        CameraManager mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        String mCameraId = mCameraManager.getCameraIdList()[0]; // 获取默认的摄像头ID
        Log.d("===cameraIdList===", "cameraIdList = " + Arrays.toString(mCameraManager.getCameraIdList()));
//        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        // 相机权限检查
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            Log.d("=====start_camera=====", "无相机权限!");
            return;
        }
        try {
            mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    // 摄像头已打开,开始预览
                    mCameraDevice = camera;
                    createCameraPreviewSession();
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    camera.close();
                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    camera.close();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
            Log.d("=====startCamera=====", "CameraManager.openCamera error : " + String.valueOf(e));

        }
    }

    private void createCameraPreviewSession() {
        try {
            // 创建一个Surface,用于显示预览画面
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
//            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            Surface surface = new Surface(texture);

            // 创建捕获请求
            CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            previewRequestBuilder.addTarget(surface);

            // 创建预览会话
            mCameraDevice.createCaptureSession(Collections.singletonList(surface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    if (mCameraDevice == null) {
                        Log.d("=====onConfigured=====", "mCameraDevice is null!");
                        return;
                    }

                    // 启动预览
                    try {
                        session.setRepeatingRequest(previewRequestBuilder.build(), null, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        Log.d("=====setRequest=====", String.valueOf(e));
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    // 配置失败处理
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
            Log.d("=====createSession=====", String.valueOf(e));

        }
    }

    private void releaseCamera(){
        mTextureView.setVisibility(View.GONE);
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容