项目中遇到的录音、6.0权限、隐式服务转显式的问题

文章中用不到的代码都用斜杠注销了,可以忽略

录音步骤:先写个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);
        }

    }





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值