项目中遇到的录音、6.0权限、隐式服务转显式的问题
来源:互联网 发布:默默软件下载 编辑:程序博客网 时间:2024/05/16 17:42
文章中用不到的代码都用斜杠注销了,可以忽略
录音步骤:先写个AudioRecorder
public class AudioRecorder { String TAG = "AudioRecorder"; private static final int SAMPLE_RATE = 44100; //采样率(CD音质) private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; //音频通道(单声道) private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; //音频格式 private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; //音频源(麦克风) private static boolean is_recording = false; public static File recordFile; private AudioEncoder audioEncoder; private static AudioRecorder instance; private RecorderTask recorderTask = new RecorderTask(); private AudioRecorder(File file) { recordFile = file; } public static AudioRecorder getInstance(File file) { return new AudioRecorder(file); } public void setAudioEncoder(AudioEncoder audioEncoder) { this.audioEncoder = audioEncoder; } /* 开始录音 */ public void startAudioRecording() { new Thread(recorderTask).start(); } /* 停止录音 */ public void stopAudioRecording() { is_recording = false; } class RecorderTask implements Runnable { int bufferReadResult = 0; public int samples_per_frame = 2048; @Override public void run() { long audioPresentationTimeNs; //音频时间戳 pts //获取最小缓冲区大小 int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); AudioRecord audioRecord = new AudioRecord( AUDIO_SOURCE, //音频源 SAMPLE_RATE, //采样率 CHANNEL_CONFIG, //音频通道 AUDIO_FORMAT, //音频格式 bufferSizeInBytes //缓冲区 ); audioRecord.startRecording(); is_recording = true; Log.v(TAG, "recordFile.getAbsolutepath---" + recordFile.getAbsolutePath()); while (is_recording) { byte[] buffer = new byte[samples_per_frame]; audioPresentationTimeNs = System.nanoTime(); //从缓冲区中读取数据,存入到buffer字节数组数组中 bufferReadResult = audioRecord.read(buffer, 0, samples_per_frame); //判断是否读取成功 if (bufferReadResult == AudioRecord.ERROR_BAD_VALUE || bufferReadResult == AudioRecord.ERROR_INVALID_OPERATION) Log.e(TAG, "Read error"); if (audioRecord != null) { audioEncoder.offerAudioEncoder(buffer, audioPresentationTimeNs); } } if (audioRecord != null) { audioRecord.setRecordPositionUpdateListener(null); audioRecord.stop(); audioRecord.release(); audioRecord = null; } } }}然后是编码,写个AudioEncoder:
public class AudioEncoder { String TAG = "AudioEncoder"; //编码 private MediaCodec mAudioCodec; //音频编解码器 private MediaFormat mAudioFormat; private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm"; //音频类型 private static final int SAMPLE_RATE = 44100; //采样率(CD音质) private TrackIndex mAudioTrackIndex = new TrackIndex(); private MediaMuxer mMediaMuxer; //混合器 private boolean mMuxerStart = false; //混合器启动的标志 private MediaCodec.BufferInfo mAudioBufferInfo; private static long audioBytesReceived = 0; //接收到的音频数据 用来设置录音起始时间的 private long audioStartTime; private String recordFile; private boolean eosReceived = false; //终止录音的标志 private ExecutorService encodingService = Executors.newSingleThreadExecutor(); //序列化线程任务 //枚举值 一个用来标志编码 一个标志编码完成 enum EncoderTaskType { ENCODE_FRAME, FINALIZE_ENCODER } ; public AudioEncoder() { recordFile = AudioRecorder.recordFile.getAbsolutePath(); prepareEncoder(); } class TrackIndex { int index = 0; } public void prepareEncoder() { eosReceived = false; audioBytesReceived = 0; mAudioBufferInfo = new MediaCodec.BufferInfo(); mAudioFormat = new MediaFormat(); mAudioFormat.setString(MediaFormat.KEY_MIME, AUDIO_MIME_TYPE); mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); mAudioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000); mAudioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); try { mAudioCodec = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE); mAudioCodec.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mAudioCodec.start(); mMediaMuxer = new MediaMuxer(recordFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { e.printStackTrace(); } } //此方法 由AudioRecorder任务调用 开启编码任务 public void offerAudioEncoder(byte[] input, long presentationTimeStampNs) { if (!encodingService.isShutdown()) {// Log.d(TAG,"encodingService--submit"); encodingService.submit(new AudioEncodeTask(this, input, presentationTimeStampNs)); } } //发送音频数据和时间进行编码 public void _offerAudioEncoder(byte[] input, long pts) { if (audioBytesReceived == 0) { audioStartTime = pts; } audioBytesReceived += input.length; drainEncoder(mAudioCodec, mAudioBufferInfo, mAudioTrackIndex, false); try { ByteBuffer[] inputBuffers = mAudioCodec.getInputBuffers(); int inputBufferIndex = mAudioCodec.dequeueInputBuffer(-1);// Log.d(TAG,"inputBufferIndex--"+inputBufferIndex); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(input); //录音时长 long presentationTimeUs = (pts - audioStartTime) / 1000; Log.d("hsk", "presentationTimeUs--" + presentationTimeUs); if (eosReceived) { mAudioCodec.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); closeEncoder(mAudioCodec, mAudioBufferInfo, mAudioTrackIndex); closeMuxer(); encodingService.shutdown(); } else { mAudioCodec.queueInputBuffer(inputBufferIndex, 0, input.length, presentationTimeUs, 0); } } } catch (Throwable t) { Log.e(TAG, "_offerAudioEncoder exception"); } } public void drainEncoder(MediaCodec encoder, MediaCodec.BufferInfo bufferInfo, TrackIndex trackIndex, boolean endOfStream) { final int TIMEOUT_USEC = 100; ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); while (true) { int encoderIndex = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); Log.d("hsk", "encoderIndex---" + encoderIndex); if (encoderIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { //没有可进行混合的输出流数据 但还没有结束录音 此时退出循环 Log.d(TAG, "info_try_again_later"); if (!endOfStream) break; else Log.d(TAG, "no output available, spinning to await EOS"); } else if (encoderIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { //只会在第一次接收数据前 调用一次 if (mMuxerStart) throw new RuntimeException("format 在muxer启动后发生了改变"); MediaFormat newFormat = encoder.getOutputFormat(); trackIndex.index = mMediaMuxer.addTrack(newFormat); mMediaMuxer.start(); mMuxerStart = true; } else if (encoderIndex < 0) { Log.w(TAG, "encoderIndex 非法" + encoderIndex); } else { ByteBuffer encodeData = encoderOutputBuffers[encoderIndex]; if (encodeData == null) { throw new RuntimeException("编码数据为空"); } if (bufferInfo.size != 0) { if (!mMuxerStart) { throw new RuntimeException("混合器未开启"); } encodeData.position(bufferInfo.offset); encodeData.limit(bufferInfo.offset + bufferInfo.size); mMediaMuxer.writeSampleData(trackIndex.index, encodeData, bufferInfo); } encoder.releaseOutputBuffer(encoderIndex, false); //退出循环 if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } } /** * 关闭编码 * * @param encoder * @param bufferInfo */ public void closeEncoder(MediaCodec encoder, MediaCodec.BufferInfo bufferInfo, TrackIndex trackIndex) { drainEncoder(encoder, bufferInfo, trackIndex, true); encoder.stop(); encoder.release(); encoder = null; } /** * 关闭混合器 */ public void closeMuxer() { mMediaMuxer.stop(); mMediaMuxer.release(); mMediaMuxer = null; mMuxerStart = false; } //发送终止编码信息 public void stop() { if (!encodingService.isShutdown()) { encodingService.submit(new AudioEncodeTask(this, EncoderTaskType.FINALIZE_ENCODER)); } } //终止编码 public void _stop() { eosReceived = true; Log.d(TAG, "停止编码"); } /** * 音频编码任务 */ class AudioEncodeTask implements Runnable { private static final String TAG = "AudioEncoderTask"; private boolean is_initialized = false; private AudioEncoder encoder; private byte[] audio_data; long pts; private EncoderTaskType type; //进行编码任务时 调用此构造方法 public AudioEncodeTask(AudioEncoder encoder, byte[] audio_data, long pts) { this.encoder = encoder; this.audio_data = audio_data; this.pts = pts; is_initialized = true; this.type = EncoderTaskType.ENCODE_FRAME; //这里是有数据的// Log.d(TAG,"AudioData--"+audio_data);// Log.d(TAG,"pts--"+pts); } //当要停止编码任务时 调用此构造方法 public AudioEncodeTask(AudioEncoder encoder, EncoderTaskType type) { this.type = type; if (type == EncoderTaskType.FINALIZE_ENCODER) { this.encoder = encoder; is_initialized = true; } Log.d(TAG, "完成..."); } ////编码 private void encodeFrame() { Log.d(TAG, "audio_data---encoder--" + audio_data + " " + encoder); if (audio_data != null && encoder != null) { encoder._offerAudioEncoder(audio_data, pts); audio_data = null; } } //终止编码 private void finalizeEncoder() { encoder._stop(); } @Override public void run() { Log.d(TAG, "is_initialized--" + is_initialized); if (is_initialized) { switch (type) { case ENCODE_FRAME: //进行编码 encodeFrame(); break; case FINALIZE_ENCODER: //完成编码 finalizeEncoder(); break; } is_initialized = false; } else { //打印错误日志 Log.e(TAG, "AudioEncoderTask is not initiallized"); } } }}
播放录音用的是绑定服务,而且是隐式调用,但是在6.0手机上一直闪退,低版本手机没有问题,查了下才知道android5.0以上服务必须显式调用。然后写个ServieChangetoExplicit类可以把隐式变为显式:
public class ServieChangetoExplicit { /** * 把隐式Service变为显式 * @param context * @param implicitIntent * @return */ public static Intent getExplicitIntent(Context context, Intent implicitIntent) { // Retrieve all services that can match the given intent PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); // Make sure only one match was found if (resolveInfo == null || resolveInfo.size() != 1) { return null; } // Get component info and create ComponentName ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); // Create a new intent. Use the old one for extras and such reuse Intent explicitIntent = new Intent(implicitIntent); // Set the component to be explicit explicitIntent.setComponent(component); return explicitIntent; }}然后是BindService代码如下:
public class BindService extends Service { private MyBinder binder = new MyBinder(); private MediaPlayer mediaPlayer = null; public BindService() { } public MediaPlayer getMediaPlayer(){ return mediaPlayer; } /** * 实现 Service的抽象方法onBind,并返回一个实现 IBinder接口的对象 */ @Override public IBinder onBind(Intent intent) { return binder; } /** *1、定义内部类MyBinder继承Binder实现IBinder接口,并提供方法返回Service实例。 */ public class MyBinder extends Binder { /** * 获取 Service实例 * @return */ public BindService getService(){ return BindService.this; } } public void play(String strfile){ if(mediaPlayer==null){ mediaPlayer=new MediaPlayer(); try { mediaPlayer.setDataSource(strfile); mediaPlayer.prepare(); mediaPlayer.start(); } catch (IOException e) { e.printStackTrace(); }// mediaPlayer=MediaPlayer.create(this,R.raw.pyghlkn); } if(mediaPlayer!=null&&!mediaPlayer.isPlaying()){ mediaPlayer.start(); } } public void stop(){ if(mediaPlayer!=null&&mediaPlayer.isPlaying()){ mediaPlayer.stop(); try {//在调用stop后如果需要再次通过start进行播放,需要之前调用prepare函数 mediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } } } public void pause(){ if(mediaPlayer!=null&&mediaPlayer.isPlaying()){ mediaPlayer.pause(); } } @Override public void onDestroy() { super.onDestroy(); if(mediaPlayer!=null){ mediaPlayer.stop(); mediaPlayer.release(); } }}
然后在要使用到录音和播放功能的Activity中的代码:
private AudioRecorder audioRecorder; private AudioEncoder audioEncoder; private File file=null; private BindService b_service; private long recordstarttime=0; private long recordendtime=0; private long recordtime=0; private static final int MY_PERMISSIONS_REQUEST_RECORD_AUDIO = 1; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_hidden_danger_upload);File path = new File(Environment.getExternalStorageDirectory(), "AudioRecord"); path.mkdirs(); System.out.print("path" + path); initView(); initListener(); connBind(); }public void onClick(View view) { switch (view.getId()) { case R.id.btn_upload: break; case R.id.iv_recordplayback: if(file!=null&&b_service!=null){//文章中用不到的代码注释掉了 //iv_playAnimation.setBackgroundResource(R.drawable.playrecord_animation); //ad_play=(AnimationDrawable)iv_playAnimation.getBackground(); //iv_playAnimation.setVisibility(View.VISIBLE); //iv_recordplayback.setVisibility(View.INVISIBLE); //ad_play.start();//播放录音动画开始 //System.out.println("--->>>"+file.toString()); b_service.play(file.toString());//播放录音 b_service.getMediaPlayer().setOnCompletionListener(new MediaPlayer.OnCompletionListener() {//录音播放结束 @Override public void onCompletion(MediaPlayer mediaPlayer) { // if (ad_play.isRunning()) { // ad_play.stop(); unbindService(conn); connBind(); //iv_recordplayback.setVisibility(View.VISIBLE); //iv_playAnimation.setVisibility(View.INVISIBLE); //} } }); } break; } }之前一直是录音后再录音播放的一直是第一次录音文件,但是file的路径都变了 啊,就各种试,然后发现播放结束后先解绑再绑定就可以了
上面是播放录音的时候的代码,但是6.0获取权限和之前也不一样,然后就在录音按钮的按下状态下判断:
/** * 录音按钮点击事件 */ btn_record.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN://按下 if (ContextCompat.checkSelfPermission(HiddenDangerUploadActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(HiddenDangerUploadActivity.this, new String[]{Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_REQUEST_RECORD_AUDIO); }else{ StartRecord();//开始录音方法 } break; case MotionEvent.ACTION_UP://抬起 StopRecord();//停止录音方法 break; } return false; } }); /** * 开始录音方法 */ private void StartRecord(){ //iv_animation.setBackgroundResource(R.drawable.record_animation); //ad_record = (AnimationDrawable) iv_animation.getBackground(); //iv_animation.setVisibility(View.VISIBLE); //ad_record.start();//开始录音动画 btn_record.setText("松开保存"); file = new File(Environment.getExternalStorageDirectory() + "/AudioRecord/", System.currentTimeMillis() + ".m4a"); audioRecorder = AudioRecorder.getInstance(file); audioEncoder = new AudioEncoder(); audioRecorder.setAudioEncoder(audioEncoder); recordstarttime=System.currentTimeMillis(); audioRecorder.startAudioRecording();//开始录音 } /** * 结束录音方法 */ private void StopRecord(){ //if(ad_record!=null&&ad_record.isRunning()){ //ad_record.stop();//结束录音动画 audioRecorder.stopAudioRecording();//停止录音 recordendtime=System.currentTimeMillis(); audioEncoder.stop();//停止编码 recordtime=recordendtime-recordstarttime; if(recordtime>0){ tv_recordtime.setText(recordtime/1000+"'"); tv_recordtime.setVisibility(View.VISIBLE); iv_recordplayback.setVisibility(View.VISIBLE); iv_recordplayback.setOnClickListener(HiddenDangerUploadActivity.this); } // } //iv_animation.setVisibility(View.INVISIBLE); //btn_record.setText("按住录音"); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST_RECORD_AUDIO) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // StartRecord();//注意!!!网上的6.0权限判断方法中写的简单例子基本都是在onCreate中写的,但是现在是在按钮的触摸事件中,所以这里不要再调用要执行的方法 } else { // Permission Denied Toast.makeText(HiddenDangerUploadActivity.this, "没有录音权限,请在手机设置中添加权限", Toast.LENGTH_SHORT).show(); } return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
下面的代码是bindService方面的:
public void connBind(){ //隐式调用 Intent intent=new Intent(); intent.setAction("com.xxxxxxxxxxxxxxxxxxxxxx.BindService"); Intent eintent = new Intent(ServieChangetoExplicit.getExplicitIntent(HiddenDangerUploadActivity.this,intent)); /** * 注:标志位Context.BIND_AUTO_CREATE, * 说明:表示收到绑定请求的时候,如果服务尚未创建,则即刻创建, * 在系统内存不足需要先摧毁优先级组件来释放内存, * 且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁 */ bindService(eintent,conn, Context.BIND_AUTO_CREATE); } /** * Activity里定义ServiceConnection接口对象并实现ServiceConnection接口, * 重写onServiceConnected方法进行Service连接 * 重写onServiceDisconnected方法进行Service销毁 */ private ServiceConnection conn=new ServiceConnection() { //连接建立成功 @Override public void onServiceConnected(ComponentName name, IBinder service) { b_service=((BindService.MyBinder)((Binder)service)).getService(); } //连接断开 @Override public void onServiceDisconnected(ComponentName name) { b_service=null; } }; @Override protected void onDestroy() { super.onDestroy(); if(conn!=null){ unbindService(conn); } }
0 0
- 项目中遇到的录音、6.0权限、隐式服务转显式的问题
- 【Android】5.0录音权限处理时遇到的问题
- 【笔记】android录音权限遇到的问题和兼容8.0权限请求的方案
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- 项目中遇到的问题
- java 中如何执行插入操作后返回自增id
- iOS 9 以后使用3D touch()
- unityIOS截图并在相册中更新
- Android Glide 优化用户体验
- 怎么从excel中读取数据_python
- 项目中遇到的录音、6.0权限、隐式服务转显式的问题
- VC++的Unicode编程(宏UNICODE与_UNICODE)
- Android事件处理方法总结-基于回调的事件处理
- 阿里云服务器ECS上CentOS安装mysql
- R note for Bioinfo :cel下载时找不到下载对象
- IntelliJ IDEA 2016.3.2(64)使用阿里云maven仓库
- iOS - 毛玻璃效果
- spring mvc 异常统一处理方式
- 使用js滾動內容