项目中遇到的录音、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