1. 程式人生 > >android進階4step2:Android音視訊處理——音視訊錄製與播放

android進階4step2:Android音視訊處理——音視訊錄製與播放

錄音

MediaRecoder

Android有一個內建的麥克風,通過它可以捕獲音訊和儲存,或在手機進行播放。

有很多方法可以做到這一點,但最常見的方法是通 過MediaRecorder類。

MediaRecoder常用方法

方法名

描述

setAudioSource()

指定聲音源

setOutputFormat()

該方法規定了音訊格式中的音訊將被儲存

setAudioEncoder()

該方法指定要使用的音訊編碼器 

setOutputFile()

該方法配置檔案路徑到其中記錄的音訊將被儲存 

stop()

該方法停止記錄處理

release()

錄音結束後,釋放

大致流程:

  • 長按事件:進行錄音工作  初始化錄音、設定引數、建立臨時資料夾保存錄音檔案,將mic(麥克風錄製)好的音訊放到之前建立的臨時檔案中
  • touch擡手事件:錄音自動播放(之前錄製好的音訊) 播放完畢停止、釋放、刪除資源
  • 單獨點選 不起作用 新增識別符號來控制

效果:

佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/id_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="點選錄音"
        android:layout_alignParentBottom="true" />

</RelativeLayout>

 清單檔案: 新增許可權

    <uses-permission android:name="android.permission.RECORD_AUDIO" />

java程式碼 重要的兩個類 錄製MediaRecoder和播放類MediaPlayer  注意:android 6.0 要動態申請許可權 

瞭解更多許可權請看:動態獲取許可權的封裝

//長按下按鈕,開始錄製(文字會變成"請大聲說話")
//鬆開按鈕,停止錄製(文字復原),播放剛剛錄製的聲音
public class MainActivity extends AppCompatActivity {

    private Button mRecordBtn;
    private MediaRecorder mRecord;//錄音
    private MediaPlayer mPlayer;//播放聲音
    private File voiceFile;
    private int REQ_CODE = 1001;
    private volatile boolean flag = false; //許可權識別符號

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecordBtn = findViewById(R.id.id_btn);
        initEvents();
    }

    private void initEvents() {
        //長按按鍵的監聽事件
        mRecordBtn.setOnLongClickListener(new View.OnLongClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            @Override
            public boolean onLongClick(View v) {
                if (Build.VERSION.SDK_INT >= 23)
                    requestPermission();
                else {
                    flag = true;
                }
                //有許可權才能操作
                if (flag) {
                    try {
                        //建立錄音物件
                        mRecord = new MediaRecorder();
                        //設定聲音來源  MIC 系統麥克風
                        mRecord.setAudioSource(MediaRecorder.AudioSource.MIC);
                        //設定聲音格式  MPEG_4 音訊、視訊的標準格式
                        mRecord.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                        //設定聲音的編碼格式
                        mRecord.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
                        //準備臨時檔案用於承載錄製的聲音
                        voiceFile = File.createTempFile("myvoice", "MP3");
                        //將錄製好的聲音輸出到建立好的臨時檔案中
                        mRecord.setOutputFile(voiceFile.getAbsoluteFile());

                        //準備錄音
                        mRecord.prepare();
                        //開始錄製
                        mRecord.start();
                        //更改文字
                        mRecordBtn.setText("錄音中...");

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

        });

        //手向上擡起的按鍵
        mRecordBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(final View v, MotionEvent event) {
                if (flag) {
                    if (event.getAction() == MotionEvent.ACTION_UP) {
                        //停止錄製
                        mRecord.stop();
                        //釋放資源
                        mRecord.release();
                        //更改文字
                        mRecordBtn.setText("點選錄音");

                        try {
                            //播放錄音
                            mPlayer = new MediaPlayer();
                            //設定播放源
                            mPlayer.setDataSource(voiceFile.getAbsolutePath());
                            //準備
                            mPlayer.prepare();
                            //開始
                            mPlayer.start();
                            //播放結束後的監聽
                            mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                                @Override
                                public void onCompletion(MediaPlayer mp) {
                                    mp.stop();
                                    mp.release();
                                    //刪掉錄音的檔案
                                    voiceFile.delete();
                                    //重置
                                    flag = false;
                                }

                            });


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

                return false;
            }
        });
    }

    /**
     * android 6.0之後動態申請許可權
     */
    private void requestPermission() {
        //如果許可權沒有被申請
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, REQ_CODE);
        } else {
            //如果已經申請了
            flag = true;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQ_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //許可權申請成功
                flag = true;
            } else {
                //許可權申請失敗
                Toast.makeText(this, "需要開啟錄音許可權", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

簡單的語音聊天案例:

  • 長按傳送語音
  • 鬆開將語音儲存起來
  • 點選對話方塊播放語音

佈局檔案省略了

注意要在清單檔案中新增錄音許可權

    <uses-permission android:name="android.permission.RECORD_AUDIO" />

Msg.java 訊息封裝類

package com.demo.voicerecorddemo;


import java.io.File;

public class Msg {
    //內容、涉及到 語音、傳送/接受的標誌
    private String txt;
    private File f;
    private int flag;   //1:本人   -1:對方

    public Msg(String txt, File f, int flag) {
        this.txt = txt;
        this.f = f;
        this.flag = flag;
    }

    public String getTxt() {
        return txt;
    }

    public void setTxt(String txt) {
        this.txt = txt;
    }

    public File getF() {
        return f;
    }

    public void setF(File f) {
        this.f = f;
    }

    public int getFlag() {
        return flag;
    }

    public void setFlag(int flag) {
        this.flag = flag;
    }
}

介面卡類 ChattingAdapter.java

根據不同的標識來顯示不同的佈局

//介面卡:處理聊天記錄樣式
public class ChattingAdapter extends BaseAdapter {
    private List<Msg> data;
    private Context ctx;

    public ChattingAdapter(List<Msg> data,Context ctx) {
        this.data = data;
        this.ctx = ctx;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        Msg g = data.get(i);
//        if(view == null){ }
            if(g.getFlag() == 1) {
                view = LayoutInflater.from(ctx).inflate(R.layout.to_msg,null);
            }else{
                view = LayoutInflater.from(ctx).inflate(R.layout.from_msg,null);
            }

        TextView msg = (TextView) view.findViewById(R.id.msg);
        msg.setText(g.getTxt());
        return view;
    }
}

注意聊天類: ChattingActivity.java

聲音的錄製 與播放 儲存邏輯 和許可權申請

public class ChattingActivity extends AppCompatActivity {

    private Button voiceBtn;
    private ListView msgList;
    private ChattingAdapter adapter;
    private List<Msg> datas = new ArrayList<>();
    private MediaRecorder recorder;
    private List<File> voices = new ArrayList<>();
    private int REQ_CODE = 1001;
    private volatile boolean flag = false; //許可權識別符號

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

    private void initView() {
        voiceBtn = findViewById(R.id.vice_btn);
        msgList = findViewById(R.id.msg_list);
        adapter = new ChattingAdapter(datas, this);
        msgList.setAdapter(adapter);

        //長按按鈕,開始錄音,鬆開按鈕,錄音結束
        //傳送語音的效果
        //未來在點選了聊天氣泡後才播放對應的語音
        voiceBtn.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                //android 6.0 之後才需要動態申請許可權
                if (Build.VERSION.SDK_INT >= 23)
                    requestPermission();
                else {
                    flag = true;
                }
                if (flag) {
                    voiceBtn.setText("請開始說話...");
                    //錄音
                    //一系列特性的設定
                    try {
                        recorder = new MediaRecorder();
                        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                        recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
                        //指定語音檔案
                        File tempFile = File.createTempFile("temp", "mp3");
                        recorder.setOutputFile(tempFile.getAbsolutePath());

                        //儲存語音檔案
                        voices.add(tempFile);

                        //開始錄音
                        recorder.prepare();
                        recorder.start();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return false;
            }
        });
        voiceBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (flag) {//如果沒授予許可權
                    if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                        voiceBtn.setText("按住說話");
                        //停止錄音
                        recorder.stop();
                        recorder.release();
                        flag = false;
                        //更新聊天介面
                        datas.add(new Msg("           ", voices.get(voices.size() - 1), 1));
                        datas.add(new Msg("哈哈哈哈", null, -1));        //偽造一條跟隨著的收到的資訊
                        adapter.notifyDataSetChanged();
                        //讓listView一直滾動到底部
                        msgList.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
                    }
                } else {
                    if (ContextCompat.checkSelfPermission(ChattingActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED)
                        Toast.makeText(ChattingActivity.this, "需要開啟錄音許可權", Toast.LENGTH_SHORT).show();
                    else {
                        Toast.makeText(ChattingActivity.this, "長按說話", Toast.LENGTH_SHORT).show();
                    }
                }
                return false;
            }
        });

        msgList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Msg m = datas.get(i);
                try {
                    if (m.getFlag() == 1) {
                        final MediaPlayer player = new MediaPlayer();
                        player.setDataSource(m.getF().getAbsolutePath());
                        player.prepare();
                        player.start();

                        //錄音播放完畢
                        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                            @Override
                            public void onCompletion(MediaPlayer mediaPlayer) {
                                player.stop();
                                player.release();
                            }
                        });
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 銷燬後刪除之前保留的錄音
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        for (int i = 0; i < voices.size(); i++) {
            voices.get(i).delete();
        }
    }

    /**
     * android 6.0之後動態申請許可權
     */
    private void requestPermission() {
        //如果許可權沒有被申請
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, REQ_CODE);
        } else {
            //如果已經申請了
            flag = true;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQ_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //許可權申請成功
                flag = true;
            } else {
                //許可權申請失敗
                Toast.makeText(this, "需要開啟錄音許可權", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

視訊錄製的簡單實現

參考案例:

Android使用MediaRecorder和Camera實現視訊錄製及播放功能整理

Android多媒體錄製--MediaRecorder視訊錄製

Android (系統+自定義)短視訊錄製(含暫停繼續錄製功能) 總結

1. 頁面展示兩個按鈕,分別是開始和停止錄製,SurfaceView 位置來展示視訊,video_layout.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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="9">

        <SurfaceView
            android:id="@+id/id_surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_start"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="開始錄製" />

        <Button
            android:id="@+id/btn_stop"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="停止錄製" />
    </LinearLayout>

</LinearLayout>

2. VideoActivity.java 中為按鈕繫結監聽器,為 SurfaceView 準備回撥介面等初始化工作


/**
 * 為按鈕繫結監聽器
 * 為SurfaceView 準備回撥等初始化操作
 */
public class VideoActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private Button mBtnStart;//開始錄製按鈕
    private Button mBtnStop;//停止錄製按鈕
    private SurfaceView mSurfaceView;//顯示視訊控制元件
    private SurfaceHolder mHolder;
    private SurfaceHolder Holder;

    private MediaRecorder mRecorder;//錄製視訊的類

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //去掉標題欄
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //設定全屏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //設定橫屏顯示
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        setContentView(R.layout.video_layout);
        initViews();
        initEvents();
    }

    /**
     * 初始化檢視
     */
    private void initViews() {
        mBtnStart = findViewById(R.id.btn_start);
        mBtnStop = findViewById(R.id.btn_stop);
        mSurfaceView = findViewById(R.id.id_surfaceView);
    }

    /**
     * 初始化事件
     */
    private void initEvents() {
        mBtnStart.setOnClickListener(new VideoListener());
        mBtnStop.setOnClickListener(new VideoListener());
        mHolder = mSurfaceView.getHolder();//取得holder
        Log.e("TAG", "此時的mHolder是否為空" + mHolder);
        mHolder.addCallback(this);//新增surfaceView的回撥
        // 設定Surface不需要維護自己的緩衝區
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    /**
     * 按鈕點選事件
     */
    private class VideoListener implements View.OnClickListener {
        @RequiresApi(api = Build.VERSION_CODES.O)
        @Override
        public void onClick(View v) {
            if (v == mBtnStart) {
                startVideo();
            } else {
                stopVideo();
            }
        }
    }
}

3. VideoActivity 中 MediaRecorder 錄製與停止的工作

/**
     * 開始錄製視訊
     */
    private void startVideo() {
        //建立MediaRecorder物件
        mRecorder = new MediaRecorder();
        Camera camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
        camera.unlock();
        mRecorder.setCamera(camera);
        //設定視訊錄製源為相機
        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        //聲音來源
        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        //設定錄製完成後視訊的封裝格式為THREE_GPP 3gp,MPEG_4 為mp4
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //設定錄製視訊編碼為h263 h264
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //設定視訊錄製的解析度。必須放在設定 編碼和格式的後面
        mRecorder.setVideoSize(1920, 1440);
        //設定錄製視訊的幀率,必須放在設定編碼和格式的後面
        mRecorder.setVideoFrameRate(50);
        //設定編位元速率
        mRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
        mRecorder.setOrientationHint(90);
        //設定記錄會話的最大持續時間(毫秒)
        mRecorder.setMaxDuration(30 * 1000);
        File file = new File(Environment.getExternalStorageDirectory().getPath() + "/video/test.mp4");
        if (file.exists()) {
            file.delete();
        }
        //設定視訊檔案的輸出路徑
        mRecorder.setOutputFile(file.getAbsolutePath());
        //讓錄製的視訊放在surfaceView上預覽
        mRecorder.setPreviewDisplay(Holder.getSurface());

        try {
            //準備錄製
            mRecorder.prepare();
            //開始錄製
            mRecorder.start();

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

    }

    /**
     * 停止錄製視訊
     */
    private void stopVideo() {
        if (mRecorder != null) {
            try {
                mRecorder.stop();
                mRecorder.release();//釋放資源
                mRecorder = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

4. VideoActivity 中為視訊展示相關的 SurfaceHolder.Callback


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //將holder ,這個holder為開始在initView裡面取得的holder,將它賦給Holder
        Holder = holder;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //將holder ,這個holder為開始在initView裡面取得的holder,將它賦給Holder
        Holder = holder;

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mSurfaceView = null;
        mHolder = null;
        mRecorder = null;
    }

5. 最後別忘記申請許可權

   <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

android 6.0之後還需要動態申請許可權:動態獲取許可權的封裝