接上一篇文章,[Android多媒体一]调用系统相机拍照并存储到指定位置,适配安卓 7.0
本文讲述如何调用系统录音机,完成录音后,对录音进行指定位置的保存。
一、开始编写
首先,还是理清一下思路,在着手编写代码。
1、启动系统录音机并保存到指定位置依然设计读写权限,此时需要向用户请求权限,并根据用户操作进行相应的动作。录音使用到的权限有:
<!-- 读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 录音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2、将录音文件保存到SD的指定位置,需要创建一定的目录层级,像上一篇文章讲述的一样,这次,把录音文件保存为SD根目录下的TestDir/voice/xxx.amr。
3、根据用户录音的结果,进行存储操作。
4、不同于启动相机拍照,这次,不把uri加入到启动录音机的额外数据,因为不管加还是不加,录音成功后,获取到的uri都是系统存放刚刚的录音文件的uri。
废话不多说,直接上代码:
主界面布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照并保存" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录音并保存" />
</LinearLayout>
MainActivity代码:
package com.my.example.multimediatest;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final String SD_APP_DIR_NAME = "TestDir"; //存储程序在外部SD卡上的根目录的名字
public static final String PHOTO_DIR_NAME = "photo"; //存储照片在根目录下的文件夹名字
public static final String VOICE_DIR_NAME = "voice"; //存储音频在根目录下的文件夹名字
public static final String VIDEO_DIR_NAME = "video"; //存储视频在根目录下的文件夹名字
public static final int PHOTO_RESULT_CODE = 100; //标志符,图片的结果码,判断是哪一个Intent
public static final int VOICE_RESULT_CODE = 101; //标志符,音频的结果码,判断是哪一个Intent
public static final int VIDEO_RESULT_CODE = 102; //标志符,视频的结果码,判断是哪一个Intent
private String mImagePath; //用于存储图片的最终目录,即根目录 / 图片的文件夹 / 图片
private Uri mImageUri; //存储相机返回的uri
private String mImageName; //保存的图片的名字
private File mImageFile; //图片文件
private String mVoicePath; //用于存储录音的最终目录,即根目录 / 录音的文件夹 / 录音
private String mVoiceName; //保存的录音的名字
private File mVoiceFile; //录音文件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "开始...");
// android 7.0系统解决拍照的问题
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
//拍照按钮的点击事件
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 200);
}
});
//录音按钮的点击事件
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 201);
}
});
}
/**
* 返回用户是否允许权限的结果,并处理
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResult) {
if (requestCode == 200) {
//用户允许权限
if (grantResult[0] == PackageManager.PERMISSION_GRANTED && grantResult[1] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "用户已允许权限,准备启动相机。");
//启动照相机
startCamera();
} else { //用户拒绝
Log.d(TAG, "用户已拒绝权限,程序终止。");
Toast.makeText(this, "程序需要足够权限才能运行", Toast.LENGTH_SHORT).show();
}
}
if (requestCode == 201) {
//用户允许权限
if (grantResult[0] == PackageManager.PERMISSION_GRANTED && grantResult[1] == PackageManager.PERMISSION_GRANTED) {
//启动录音机
startRecord();
} else {
Log.d(TAG, "用户已拒绝权限,程序终止。");
Toast.makeText(this, "程序需要足够权限才能运行", Toast.LENGTH_SHORT).show();
}
}
}
/**
* 启动录音机,创建文件
*/
private void startRecord() {
Intent intent = new Intent();
intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
createVoiceFile();
Log.d(TAG, "创建录音文件");
//添加权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.d(TAG, "启动系统录音机,开始录音...");
startActivityForResult(intent, VOICE_RESULT_CODE);
}
/**
* 创建音频目录
*/
private void createVoiceFile() {
mVoiceName = getMyTime() + ".amr";
Log.d(TAG, "录音文件名称:" + mVoiceName);
mVoiceFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/" + SD_APP_DIR_NAME + "/" + VOICE_DIR_NAME + "/", mVoiceName);
mVoicePath = mVoiceFile.getAbsolutePath();
mVoiceFile.getParentFile().mkdirs();
Log.d(TAG, "按设置的目录层级创建音频文件,路径:" + mVoicePath);
mVoiceFile.setWritable(true);
}
/**
* 启动相机,创建文件,并要求返回uri
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void startCamera() {
Intent intent = new Intent();
//指定动作,启动相机
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Log.d(TAG, "指定启动相机动作,完成。");
//创建文件
createImageFile();
Log.d(TAG, "创建图片文件结束。");
//添加权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.d(TAG, "添加权限。");
//获取uri
mImageUri = FileProvider.getUriForFile(this, "com.my.example.multimediatest.provider", mImageFile);
Log.d(TAG, "根据图片文件路径获取uri。");
//将uri加入到额外数据
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
Log.d(TAG, "将uri加入启动相机的额外数据。");
Log.d(TAG, "启动相机...");
//启动相机并要求返回结果
startActivityForResult(intent, PHOTO_RESULT_CODE);
Log.d(TAG, "拍摄中...");
}
/**
* 创建图片文件
*/
private void createImageFile() {
Log.d(TAG, "开始创建图片文件...");
//设置图片文件名(含后缀),以当前时间的毫秒值为名称
mImageName = getMyTime() + ".jpg";
Log.d(TAG, "设置图片文件的名称为:" + mImageName);
//创建图片文件
mImageFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/" + SD_APP_DIR_NAME + "/" + PHOTO_DIR_NAME + "/", mImageName);
//将图片的绝对路径设置给mImagePath,后面会用到
mImagePath = mImageFile.getAbsolutePath();
//按设置好的目录层级创建
mImageFile.getParentFile().mkdirs();
Log.d(TAG, "按设置的目录层级创建图片文件,路径:" + mImagePath);
//不加这句会报Read-only警告。且无法写入SD
mImageFile.setWritable(true);
Log.d(TAG, "将图片文件设置可写。");
}
/**
* 处理返回结果。
* 1、图片
* 2、音频
* 3、视频
*
* @param requestCode 请求码
* @param resultCode 结果码 成功 -1 失败 0
* @param data 返回的数据
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Log.d(TAG, "拍摄结束。");
Log.d(TAG, "录音结束。");
if (resultCode == Activity.RESULT_OK) {
Log.d(TAG, "返回成功。");
Log.d(TAG, "请求码:" + requestCode + " 结果码:" + resultCode + " data:" + data);
switch (requestCode) {
case PHOTO_RESULT_CODE: {
Bitmap bitmap = null;
try {
//根据uri设置bitmap
bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), mImageUri);
Log.d(TAG, "根据uri设置bitmap。");
} catch (IOException e) {
e.printStackTrace();
}
//将图片保存到SD的指定位置
savePhotoToSD(bitmap);
//更新系统图库
updateSystemGallery();
Log.d(TAG, "结束。");
break;
}
case VOICE_RESULT_CODE: {
try {
Uri uri = data.getData();
String filePath = getAudioFilePathFromUri(uri);
Log.d(TAG, "根据uri获取文件路径:" + filePath);
Log.d(TAG, "开始保存录音文件");
saveVoiceToSD(filePath);
} catch (Exception e) {
throw new RuntimeException(e);
}
break;
}
case VIDEO_RESULT_CODE: {
// saveVideoTOSD();
break;
}
}
}
}
/**
* 保存照片到SD卡的指定位置
*/
private void savePhotoToSD(Bitmap bitmap) {
Log.d(TAG, "将图片保存到指定位置。");
//创建输出流缓冲区
BufferedOutputStream os = null;
try {
//设置输出流
os = new BufferedOutputStream(new FileOutputStream(mImageFile));
Log.d(TAG, "设置输出流。");
//压缩图片,100表示不压缩
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
Log.d(TAG, "保存照片完成。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
//不管是否出现异常,都要关闭流
os.flush();
os.close();
Log.d(TAG, "刷新、关闭流");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 更新系统图库
*/
private void updateSystemGallery() {
//把文件插入到系统图库
try {
MediaStore.Images.Media.insertImage(this.getContentResolver(),
mImageFile.getAbsolutePath(), mImageName, null);
Log.d(TAG, "将图片文件插入系统图库。");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 最后通知图库更新
this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + mImagePath)));
Log.d(TAG, "通知系统图库更新。");
}
/**
* 获取日期并格式化
* 如:2017_10_20 周三 上午 11:20:35
*
* @return 格式化好的日期字符串
*/
private String getMyTime() {
//存储格式化后的时间
String time;
//存储上午下午
String ampTime = "";
//判断上午下午,am上午,值为 0 ; pm下午,值为 1
int apm = Calendar.getInstance().get(Calendar.AM_PM);
if (apm == 0) {
ampTime = "上午";
} else {
ampTime = "下午";
}
//设置格式化格式
SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd E " + ampTime + " kk:mm:ss");
time = format.format(new Date());
return time;
}
/**
* 保存音频到SD卡的指定位置
*
* @param path 录音文件的路径
*/
private void saveVoiceToSD(String path) {
//创建输入输出
InputStream isFrom = null;
OutputStream osTo = null;
try {
//设置输入输出流
isFrom = new FileInputStream(path);
osTo = new FileOutputStream(mVoicePath);
byte bt[] = new byte[1024];
int len;
while ((len = isFrom.read(bt)) != -1) {
Log.d(TAG, "len = " + len);
osTo.write(bt, 0, len);
}
Log.d(TAG, "保存录音完成。");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (osTo != null) {
try {
//不管是否出现异常,都要关闭流
osTo.close();
Log.d(TAG, "关闭输出流");
} catch (IOException e) {
e.printStackTrace();
}
}
if (isFrom != null) {
try {
isFrom.close();
Log.d(TAG, "关闭输入流");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 保存视频到SD卡的指定位置
*/
private void saveVideoTOSD() {
}
/**
* 通过Uri,获取录音文件的路径(绝对路径)
*
* @param uri 录音文件的uri
* @return 录音文件的路径(String)
*/
private String getAudioFilePathFromUri(Uri uri) {
Cursor cursor = getContentResolver()
.query(uri, null, null, null, null);
cursor.moveToFirst();
int index = cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA);
String temp = cursor.getString(index);
cursor.close();
return temp;
}
}
实现思路和拍摄图片并保存的思路一致,都是在启动系统程序前检查有无读写权限,有权限则创建对应的目录层级和对应的文件,操作完成后通过返回的结果进行保存操作。
二、运行结果
还是完整的执行流程。
接下来去系统的文件夹下找到刚刚录音的文件。
可以看到保存的位置、文件名称都符合预期。并且可以播放,文件大小由于是amr格式,所以非常小。
实现启动录音机录音,并保存到指定位置还是非常简单的,代码量相对于相机来说少了很多。逻辑也更清晰了。
相关文章:[Android多媒体一]调用系统相机拍照并存储到指定位置,适配安卓 7.0
原创文章,转载请注明出处:https://blog.csdn.net/Lone1yCode/article/details/79951477