android仿微信UI长按控件录音,转化为MP3,支持上传,播放等功能

主要实现功能点:

1.实现了长按录音及播放音频功能,并把录音资源格式转化为mp3(基于开源库LAME)
2.实现了把mp3文件上传到服务器(基于Retrofit2),支持再次下载播放,缓存功能
3.对于权限的管理,本Demo用的第三方库:https://github.com/yanzhenjie/AndPermission
4.本Demo内部封装了长按录音控件,解决了与列表滚动View的冲突
用法参考MainActivity。
核心类
    录音类:RecorderManager
    播音类:MediaManager
    录音控件:LongPressRecordView

前期调研:

android原生SDK里提供了两种语音方案:

1、AudioRecord

主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩

示例:

使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码)

2、MediaRecorder

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp

优点:大部分以及集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

示例:

使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码)

以上引用自:https://www.cnblogs.com/MMLoveMeMM/articles/3444718.html传送门里有具体的代码实现。

考虑到跨平台的需求,aac,amr做的并不好,我们可以将录音文件转为为mp3格式的文件(基于LAME开源库),文件体积小,而且还可以平台。

技术实现:

大体思路是:利用AudioRecord类录制音频文件-录制的同时借助LAME库进行转化-存储mp3音频文件

之所以用AudioRecord是为了获取录制的实时资源;

之所以边录边转是为了如果录制时间太久,录完再转,用户等待的时间太过于长久。

对于将android原生的音频格式转为mp3,可以参考GavinCT提供的开源库:https://github.com/GavinCT/AndroidMP3Recorder

我是在此库的基础上添加了播放,上传,下载等功能,同时由于此库作者已不再维护,期间也踩过坑,后面会细说。

项目结构及核心代码分析:

1.录音手势长按实现类LongPressRecordView,通过重写setOnTouchListener将控件的手势操作传递给GestureDetector对应的对象来处理。

注意有个小坑:如果此控件的父控件是列表类的,比如Scrollview,如果Scrollview足够长(超过全屏)可以滚动的话,会产生bug,长按录音控件,滚动到此控件外,会发现父控件也跟着滚动了。解决方案也很简单,我们需要处理下事件分发机制,重写dispatchTouchEvent方法(因为此方法发生在onTouchEvent之前),调用下

getParent().requestDisallowInterceptTouchEvent(true);

方法,告诉父控件我的事件自己处理,不要给我拦截了。

LongPressRecordView具体代码:
/**
 * Created by wangmaobo on 2018/9/14.
 */
public class LongPressRecordView extends android.support.v7.widget.AppCompatTextView {
    private OnGestureListener mListener;

    public LongPressRecordView(Context context) {
        this(context, null, -1);
    }

    public LongPressRecordView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public LongPressRecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void setListener(OnGestureListener listener) {
        mListener = listener;
    }

    private void init() {
        GestureDetector gesture = new GestureDetector(new RecordLongClickGesture().setLongPressListener(new OnLongPressListener() {
            @Override
            public void startRecord() {
                if (null != mListener) {
                    mListener.longClick();
                }
            }
        }));
        setOnTouchListener((view, motionEvent) -> {
            if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                if (null != mListener) {
                    mListener.actionUp();
                }
            }
            return gesture.onTouchEvent(motionEvent);
        });
        setFocusable(true);
        setClickable(true);
        setLongClickable(true);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(event);
    }

    public interface OnGestureListener {
        void longClick();

        void actionUp();
    }
}

2.mp3转化类MP3Recorder,这个类有个bug,就是对于低版本即android6.0(api23)以下的手机由于无法真正判断录音权限是否开启,导致权限被禁止后无法正常录音,app崩溃。

我们可以重写一下此类的开始录音,即start方法,通过

mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING

间接获取录音权限(关于权限的坑,可以在传送门里细看)。

initAudioRecorder();
        mAudioRecord.startRecording();
        if (!checkPermission()) {
            mListener.noRecordRight();
            return;
        } else {
            mListener.hasRecordRight();
        }
public boolean checkPermission() {
        if (null != mAudioRecord) {
            return mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
        }
        return false;
    }

3.播音控制器MediaManager

如果播放时需要倒计时,可以在构造函数里传入handler,提供了两种播放方式,播放本地资源和播放网络url资源:

 /**
     * 加载本地资源
     *
     * @param path
     * @param onCompletionListener
     */
    public void playSound(String path, OnCompletionListener onCompletionListener) {
        if (mPlayer == null) {
            mPlayer = new MediaPlayer();
            mPlayer.setOnErrorListener((mp, what, extra) -> {
                mPlayer.reset();
                return false;
            });
        } else {
            mPlayer.reset();
        }

        try {
            mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mPlayer.setOnCompletionListener(onCompletionListener);
            mPlayer.setDataSource(path);
            mPlayer.prepare();
            mPlayer.start();
            if (null != mHandler) {
                mHandler.sendMessage(Message.obtain(mHandler, 2, 1));
            }
            Log.e("tag", "time=" + mPlayer.getDuration());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载网络资源
     */
    public void play(String url) {
        try {
            this.uri = url;
            mPlayer = new MediaPlayer();
            mPlayer.setLooping(false);
            mPlayer.setOnCompletionListener(this);
            mPlayer.setOnErrorListener(this);
            mPlayer.setOnPreparedListener(this);
            mPlayer.setDataSource(mContext, Uri.parse(url));
            mHud.show();
            mPlayer.prepareAsync();

        } catch (IOException e) {
            Log.v("AudioHttpPlayer", e.getMessage());
        }
    }

最后附上Demo地址:https://github.com/MarsToken/RecordDemo传送门

参考资料:

https://www.cnblogs.com/MMLoveMeMM/articles/3444718.html

https://github.com/GavinCT/AndroidMP3Recorder

https://github.com/yanzhenjie/AndPermission

猜你喜欢

转载自blog.csdn.net/qq_20089667/article/details/82854298
今日推荐