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之後還需要動態申請許可權:動態獲取許可權的封裝