android 蓝牙语音转换成pcm文件,进行播放暂停停止操作

发布于:2024-10-09 ⋅ 阅读:(21) ⋅ 点赞:(0)

需求描述

最近在做蓝牙与android之间互联通信,有个需求,是通过指令,控制蓝牙开启录音,结束录音,录音过程中,将蓝牙传回的数据,转换成pcm文件,然后再做个文件列表,点击播放pcm,包括暂停,重新播放之类的操作。

蓝牙数据转pcm

蓝牙传输的数据,先转换成Byte[],然后再转换成pcm文件,保存到本地。
我们蓝牙协议的真正数据是从字节第4位开始

public class ByteKit {

    private BitOperator bitOperator;
    private BCD8421Operater bcd8421Operater;
    private static final ByteKit mInstance = new ByteKit();


    public static ByteKit getInstance() {
        return mInstance;
    }
    private ByteKit(){
        bitOperator = new BitOperator();
        bcd8421Operater = new BCD8421Operater();
    }


    public float parseFloatFromBytes(byte[] data, int startIndex, int length) {
        return this.parseFloatFromBytes(data, startIndex, length, 0f);
    }

    private float parseFloatFromBytes(byte[] data, int startIndex, int length, float defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byte2Float(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public String parseHexStringFormBytes(byte[] data, int startIndex, int length) {
        return this.parseHexStringFormBytes(data, startIndex, length, null);
    }

    private String parseHexStringFormBytes(byte[] data, int startIndex, int length, String defaultValue) {
        try {
            byte[] tmp = new byte[length];
            System.arraycopy(data, startIndex, tmp, 0, length);
            return ConvertUtils.bytes2HexString(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultValue;
        }
    }

    public String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, "gbk");
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseBcdStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return this.bcd8421Operater.bcd2String(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }


    public int parseIntFromBytes(byte[] data, int startIndex, int length) {
        return this.parseIntFromBytes(data, startIndex, length, 0);
    }

    private int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byteToInteger(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return defaultVal;
        }
    }

    public Long parseLongFromBytes(byte[] data, int startIndex, int length) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.fourBytesToLong(tmp);
        } catch (Exception e) {
            e.printStackTrace();
            return 0L;
        }
    }

    public byte[] str2Bcd(String asc) {
        int len = asc.length();
        int mod = len % 2;

        if (mod != 0) {
            asc = "0" + asc;
            len = asc.length();
        }

        byte abt[] = new byte[len];
        if (len >= 2) {
            len = len / 2;
        }

        byte bbt[] = new byte[len];
        abt = asc.getBytes();
        int j, k;

        for (int p = 0; p < asc.length()/2; p++) {
            if ( (abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
                j = abt[2 * p] - '0';
            } else if ( (abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
                j = abt[2 * p] - 'a' + 0x0a;
            } else {
                j = abt[2 * p] - 'A' + 0x0a;
            }

            if ( (abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
                k = abt[2 * p + 1] - '0';
            } else if ( (abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
                k = abt[2 * p + 1] - 'a' + 0x0a;
            }else {
                k = abt[2 * p + 1] - 'A' + 0x0a;
            }

            int a = (j << 4) + k;
            byte b = (byte) a;
            bbt[p] = b;
        }
        return bbt;
    }
}

语音协议的数据是从第10位开始,数据长度是4,5两位,做如下处理

  int dataLength = ByteKit.getInstance().parseIntFromBytes(data, 4, 2);
                    byte[] result = new byte[dataLength];
                    System.arraycopy(data, 10, result, 0, result.length);

                    ibleResponse.CONTROL_AUDIO(result);

byte[]转pcm文件

public class ByteToPcmConverter {

    public static void convertByteToPcm(byte[] bytes, String filePath) throws IOException {

        File file = new File(filePath);
        FileOutputStream outputStream = new FileOutputStream(file,true);
        outputStream.write(bytes);
        outputStream.close();
    }

//    // 使用示例
//    public static void main(String[] args) {
//        byte[] bytes = {/* 这里填入你的byte数组 */};
//        String pcmFilePath = "/path/to/your/pcmfile.pcm";
//        String outputPath= FileUtil.getSDPath(App.getInstance(),"");
//        try {
//            convertByteToPcm(bytes, pcmFilePath);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }


}

在获取数据之前,比如在点击录音按钮的时候,需要生产文件,文件保存到应用私密空间,在android高版本上,需要特殊处理

outputPath= FileUtil.getSDPath(App.getInstance(),name+".pcm");
      File file=new File(outputPath);
        if(!file.exists()){
            try {
                file.createNewFile();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

接收数据,并转换,我这还做了个页面提示

 @Override
    public void CONTROL_AUDIO(byte[] bytes) {

            try {

                ByteToPcmConverter.convertByteToPcm(bytes, outputPath);
                File file = new File(outputPath);
                if (file.exists() && file.isFile()) {
                    long length = file.length();
                    double mb = (double) length / (1024 * 1024);
                    String format = String.format("%.4f", mb);
                    tv_write_progress.setText("录音成功,文件大小"+ format +"mb");
                }

            } catch (IOException e) {
                e.printStackTrace();

            }
    }
    /**
     * 获取SDCard文件路径
     * @param ctx
     * @return
     */
    public static String getSDPath(Context ctx,String fileName) {
       return getSDPath(ctx,"",fileName);
    }
    public static String getSDPath(Context ctx,String path,String fileName) {
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);// 判断sd卡是否存在
        if (sdCardExist) {
            if (Build.VERSION.SDK_INT >= 29) {
                //Android10之后
                sdDir = ctx.getExternalFilesDir(null);
//                sdDir=  ctx.getExternalCacheDir();
            } else {
                sdDir= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            }
        } else {
            sdDir = Environment.getRootDirectory();// 获取跟目录
        }
        if(!TextUtils.isEmpty(fileName)){
            if(!TextUtils.isEmpty(path))
                return sdDir.toString()+path+"/"+fileName;
            return sdDir.toString()+"/"+fileName;
        }
        if(!TextUtils.isEmpty(path))
            return sdDir.toString()+path;
        return sdDir.toString();
    }

录音文件页面播放,暂停和文件切换播放

重点是播放pcm文件

public class PcmAudioUtils {

    private AudioTrack audioTrack;

    private String filePath;
    private File file;
    private  int bufferSizeInBytes;
    private Thread audioTrackThread;
    private int currentPosition;
    private double lastTime;
    private double elapsedSeconds;//已播放时间
    private double audioDurationInSeconds;//总时长
    int sampleRateInHz = 8000; // 采样率
    int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 声道配置
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频格式
    boolean pauseTag=false;
    private IAudioProgress iAudioProgress;
    private String  playState="none";


    public PcmAudioUtils(String filePath) {
        file = new File(filePath);
        init();
    }

    /**
     * 初始化
     */
    public void init() {

        if (!file.exists()) return;

        int streamType = AudioManager.STREAM_MUSIC; // 音频流类型

         bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小

        /**
         * 设置音频信息属性
         * 1.设置支持多媒体属性,比如audio,video
         * 2.设置音频格式,比如 music
         */
        AudioAttributes attributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
        /**
         * 设置音频格式
         * 1. 设置采样率
         * 2. 设置采样位数
         * 3. 设置声道
         */
        AudioFormat format = new AudioFormat.Builder()
                .setSampleRate(sampleRateInHz)
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setChannelMask(channelConfig)
                .build();
        audioTrack = new AudioTrack(attributes,format,bufferSizeInBytes,AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE);

    }

    /**
     * 停止播放录音,并释放资源
     */
    public void stopPlay() {
        init();
        playState="stopPlay";
        if (audioTrack != null) {
            audioTrack.release();
        }
    }

    /**
     * 暂停
     */
    public void pausePlay() {
        init();
        if (audioTrack != null) {
            audioTrack.pause();
        }
//        if(audioTrackThread!=null){
//            audioTrackThread.interrupt();
//        }
        pauseTag=true;
        playState="pausePlay";
    }


    public void play(){
        init();
        long currentTimeMillis = SystemClock.elapsedRealtime();
        pauseTag=false;

        if(audioTrack==null){
            return;
        }
        audioTrack.play();
        playState="play";
        audioTrackThread=   new Thread(() -> {
            FileInputStream fileInputStream = null;
            lastTime=(double)currentTimeMillis / 1000;
            try {
                fileInputStream = new FileInputStream(file);
                byte[] buffer = new byte[bufferSizeInBytes];
                Log.i("PcmAudioUtils", "playAudio: "+bufferSizeInBytes);
                if (currentPosition > 0) {
                    fileInputStream.skip(currentPosition);
                }
                int read = 0;
                while (read != -1&&!pauseTag) {
                    read = fileInputStream.read(buffer);
                    //将缓冲区buffer写入audioTrack进行播放
                    int write = audioTrack.write(buffer, 0, buffer.length);
                    currentPosition+=write;
//                    if(iAudioProgress!=null){
//                        iAudioProgress.playProgress();
//                    }
                    elapsedSeconds+=( (double) SystemClock.elapsedRealtime() /1000-lastTime);

                   // Log.d("AudioTrack", "已播放时间:" +elapsedSeconds + "秒");
                }
                if(!pauseTag){
                    audioTrack.stop();
                    audioTrack.release();
                    if(iAudioProgress!=null){
                        iAudioProgress.setStop();
                    }
                    currentPosition=0;
                }

            } catch (Throwable e) {

            }

        });
        audioTrackThread.start();
    }

    public void setiAudioProgress(IAudioProgress iAudioProgress) {
        this.iAudioProgress = iAudioProgress;
    }

    public interface IAudioProgress{
        void playProgress(String progress);
        void setStop();
    }

    public void stop() {
        init();
        playState="none";
        audioTrack.stop();
        audioTrack.release();

    }

    public String getPlayState() {
        return playState;
    }
}

每次在播放或者暂停的时候,都需要调用一下init()方法,要不然回报未初始化的错误,这块正常播放应该没事,就是切换播放状态,会报错,不知道有没有其他处理办法。
剩下的就简单了,大概逻辑就是:

读取本地存储的pcm文件,在列表上展示,然后点击播放,再次点击暂停,再点击继续播放,播放完成,播放按钮变成未播放,播放途中,点击其他文件播放,正在播放的文件停止。长按文件可以删除

activity_voice_recording.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F7F7F7"
>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="vertical"
    android:gravity="center"
   >


    <include layout="@layout/layout_toolbar" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="@dimen/dp_10" />


    <ImageView
        android:id="@+id/iv_audio"
        android:layout_width="@dimen/dp_70"
        android:layout_height="@dimen/dp_70"
        android:layout_marginBottom="20dp"
        android:src="@mipmap/icon_start_recording"/>
    <TextView
        android:id="@+id/tv_write_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:layout_marginLeft="@dimen/dp_10"
        android:layout_marginRight="@dimen/dp_10"
        android:textColor="@color/text_gray"
        tools:text="录音已写入"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:layout_margin="@dimen/dp_10"
        android:textColor="@color/text_gray"
        android:text="*点击列表左侧图标进行播放,长按进行删除"
      />
</LinearLayout>

   <RelativeLayout
       android:id="@+id/rl_recording"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <ImageView
           android:id="@+id/iv_recording"
           android:layout_width="@dimen/dp_90"
           android:layout_height="@dimen/dp_90"
           android:layout_marginBottom="220dp"
           android:visibility="gone"
           android:src="@drawable/audio_anim_image"
           android:layout_centerHorizontal="true"
           android:layout_alignParentBottom="true"
           />
   </RelativeLayout>
</FrameLayout>

VoiceRecordingActivity.java

public class VoiceRecordingActivity extends BaseActivity  {

    RecyclerView recycler_view;
    ImageView iv_audio;
    ImageView iv_recording;
    TextView tv_write_progress;
    RelativeLayout rl_recording;

    private List<AudioBean> dataEntityList = new ArrayList<>();
    private AudioAdapter adapter;
    private  String folderName="BLEAUDIO";
    private AnimationDrawable animationDrawable;
    private String outputPath;
    private String platFilePath;//播放音乐的文件地址
    private PcmAudioUtils audioPlay = null;
    private int selectItemPosition=-1;//已选中的列表项
    @Override
    public int getLayoutId() {
        return R.layout.activity_voice_recording;
    }

    @Override
    public void initViews(Bundle savedInstanceState) {
        iv_audio = findViewById(R.id.iv_audio);
        iv_recording = findViewById(R.id.iv_recording);
        recycler_view = findViewById(R.id.recycler_view);
        tv_write_progress= findViewById(R.id.tv_write_progress);
        setToolBarInfo("语音录制", true);


        // 初始化列表
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recycler_view.setLayoutManager(linearLayoutManager);
        recycler_view.setHasFixedSize(true);
        recycler_view.setItemAnimator(new DefaultItemAnimator());
        recycler_view.addItemDecoration(new SpacesItemDecoration(ConvertUtils.dp2px(this, 5)));
        adapter = new AudioAdapter(dataEntityList);
        recycler_view.setAdapter(adapter);
        adapter.setOnLongClickListener(new OnItemLongClickListener() {
            @Override
            public void OnLongClickListener(Object o, int position) {
                AudioBean audioBean=(AudioBean)o;
                new XPopup.Builder(VoiceRecordingActivity.this).asConfirm(getRsString(R.string.hint), getString(R.string.notice_delete),
                        new OnConfirmListener() {
                            @Override
                            public void onConfirm() {
                                if(audioPlay!=null){
                                    audioPlay.stopPlay();
                                    audioPlay=null;
                                }

                               String  outputPath= FileUtil.getSDPath(App.getInstance(),audioBean.getAudioInfo());
                                File file=new File(outputPath);
                                if(file.exists()){
                                    try {
                                        file.delete();
                                        getFileList();
                                        ToastUtils.show(R.string.operate_successfully);
                                    } catch (Exception e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            }
                        }).show();
            }
        });

        adapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(Object o, int position) {
                AudioBean audioBean=(AudioBean)o;
                String filePath=FileUtil.getSDPath(App.getInstance(),audioBean.getAudioInfo());
                if(!filePath.equals(platFilePath)){
                    if(audioPlay!=null){
                        audioPlay.stop();
                    }
                    audioPlay=null;

                }
                platFilePath =filePath;
                if(audioPlay==null){
                    audioPlay = new PcmAudioUtils(platFilePath);
                }
                if(selectItemPosition==-1){//初始阶段,没有选中播放,直接播放选中的音频
                    audioPlay.play();
                    selectItemPosition=position;
                }else if(selectItemPosition==position){//如果点击的是上一次点击过的文件
                    if(audioPlay.getPlayState().equals("none")){//已经停止播放,重新播放
                        audioPlay.play();
                        selectItemPosition=position;
                    }else if(audioPlay.getPlayState().equals("play")){//已经在播放了,暂停播放
                        audioPlay.pausePlay();
                        selectItemPosition=-1;//播放停止,还原成未播放状态
                    }else if(audioPlay.getPlayState().equals("pausePlay")){//已经暂停播放,继续播放
                        audioPlay.play();
                        selectItemPosition=position;
                    }

                }else{//选中的是其他列表项,把正在播放的列表项停止,变成未播放状态,播放选中列表项的文件
                    audioPlay.stop();
                    audioPlay=null;
                    audioPlay = new PcmAudioUtils(platFilePath);
                    audioPlay.play();
                    selectItemPosition=position;
                }
                dataEntityList.get(position).setAudioProgress("");
                adapter.setPlayPosition(selectItemPosition);
                audioPlay.setiAudioProgress(new PcmAudioUtils.IAudioProgress() {
                    @Override
                    public void playProgress(String progress) {

                    }

                    @Override
                    public void setStop() {
                        final Handler mainHandler = new Handler(Looper.getMainLooper());
                        // 在子线程中更新数据并刷新RecyclerView
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                // 这里进行数据的更新操作
                                // updateYourData();

                                // 使用Handler将刷新操作切换到UI线程
                                mainHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // 在UI线程中刷新RecyclerView的适配器
                                        selectItemPosition = -1;//播放停止,还原成未播放状态
                                        adapter.setPlayPosition(selectItemPosition);
                                        audioPlay=null;
                                    }
                                });
                            }
                        }).start();

                    }
                });
            }
        });


        getFileList();
        iv_audio.setTag(R.mipmap.icon_start_recording);
        iv_audio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!BLEUtils.isGetToken()){
                    ToastUtils.show(R.string.ble_break_retry);
                    iv_audio.setTag(R.mipmap.icon_start_recording);
                    iv_audio.setImageResource(R.mipmap.icon_start_recording);
                    stopAudio();
                    return;
                }
                Integer resourceId = (Integer) iv_audio.getTag();
                if(resourceId==R.mipmap.icon_start_recording){
                    iv_audio.setTag(R.mipmap.icon_end_recording);
                    iv_audio.setImageResource(R.mipmap.icon_end_recording);
                    startAudio();
                }else{
                    iv_audio.setTag(R.mipmap.icon_start_recording);
                    iv_audio.setImageResource(R.mipmap.icon_start_recording);
                    stopAudio();
                }
            }
        });

    }
    private void startAudio(){
        iv_recording.setVisibility(View.VISIBLE);
        tv_write_progress.setVisibility(View.VISIBLE);
         animationDrawable = (AnimationDrawable) iv_recording.getDrawable();
        //直接就开始执行
        animationDrawable.start();

        String address = PreferencesUtils.getString("address");
        String name= address+"--"+ DateUtils.getCurrentTime();

        //保存到本地
        outputPath= com.smart.bing.utils.FileUtil.getSDPath(App.getInstance(),name+".pcm");
        File file=new File(outputPath);
        if(!file.exists()){
            try {
                file.createNewFile();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x1);
        }

    }

    private void stopAudio(){
        iv_recording.setVisibility(View.GONE);
        tv_write_progress.setVisibility(View.GONE);
        if(animationDrawable!=null){
            animationDrawable.stop();
            animationDrawable=null;
        }

        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x0);
        }
        getFileList();
    }

    public void getFileList() {
        dataEntityList.clear();
        String outputPath=FileUtil.getSDPath(App.getInstance(),"");
        File folder = new File(outputPath);
        File[] files = folder.listFiles();
        if (files != null) {
            Arrays.sort(files, new Comparator<File>() {
                @Override
                public int compare(File file1, File file2) {
                    return Long.compare(file2.lastModified(), file1.lastModified());
                }
            });

            for (File file : files) {

                String fileName = file.getName();
                String filePath = file.getAbsolutePath();
                // 处理文件逻辑,比如打印文件名和文件路径
                Log.d("File", "File Name: " + fileName + ", File Path: " + filePath);
                String fileExtension = file.getName().substring(file.getName().lastIndexOf(".") + 1);
                if("pcm".equals(fileExtension)){
                    AudioBean audioBean=new AudioBean();
                    audioBean.setAudioInfo(fileName);
                    audioBean.setAudioProgress("");
                    dataEntityList.add(audioBean);
                }

            }
        }
        adapter.notifyDataSetChanged();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
       //关闭录音
        if(BLEUtils.isGetToken()){
            LmAPI.CONTROL_AUDIO((byte) 0x0);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(audioPlay!=null){
            audioPlay.stopPlay();
            audioPlay=null;
        }
    }

    @Override
    public void CONTROL_AUDIO(byte[] bytes) {

            try {

                ByteToPcmConverter.convertByteToPcm(bytes, outputPath);
                File file = new File(outputPath);
                if (file.exists() && file.isFile()) {
                    long length = file.length();
                    double mb = (double) length / (1024 * 1024);
                    String format = String.format("%.4f", mb);
                    tv_write_progress.setText("录音成功,文件大小"+ format +"mb");
                }

            } catch (IOException e) {
                e.printStackTrace();

            }
    }

这一块有个坑,是在子线程更新adapter,直接更新会无效,需要实现子线程到主线程的转换。重点代码是:

final Handler mainHandler = new Handler(Looper.getMainLooper());
                        // 在子线程中更新数据并刷新RecyclerView
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                // 这里进行数据的更新操作
                                // updateYourData();

                                // 使用Handler将刷新操作切换到UI线程
                                mainHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // 在UI线程中刷新RecyclerView的适配器
                                        selectItemPosition = -1;//播放停止,还原成未播放状态
                                        adapter.setPlayPosition(selectItemPosition);
                                        audioPlay=null;
                                    }
                                });
                            }
                        }).start();

AudioAdapter.java

public class AudioAdapter extends RecyclerView.Adapter<AudioAdapter.StrokeHolder> {

    private OnItemClickListener onItemClickListener;
    private OnItemLongClickListener onLongClickListener;
    private List<AudioBean> dataEntityList = new ArrayList<>();

    private int playPosition=-1;//显示播放按钮的列表项
    public AudioAdapter(List<AudioBean> dataEntityList) {
        this.dataEntityList = dataEntityList;
    }

    @Override
    public StrokeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.item_audio_layout, null);

        return new StrokeHolder(view, onItemClickListener);
    }

    @Override
    public void onBindViewHolder(StrokeHolder holder, int position) {
        AudioBean resultEntity = dataEntityList.get(position);
        holder.setData(resultEntity);
        if(playPosition==position){
            holder.ivAudioStart.setImageResource(R.mipmap.icon_end);
            holder.ivAudioStart.setTag(R.mipmap.icon_end); // 存储资源 ID
        }else{
            holder.ivAudioStart.setImageResource(R.mipmap.icon_start);
            holder.ivAudioStart.setTag(R.mipmap.icon_start); // 存储资源 ID
        }
    }

    @Override
    public int getItemCount() {
        return dataEntityList.size();
    }

    @SuppressLint("NotifyDataSetChanged")
    public void setPlayPosition(int position){
        playPosition=position;
        notifyDataSetChanged();
    }
    public AudioBean getItemBean(int position) {
        return dataEntityList.get(position);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public void setOnLongClickListener(OnItemLongClickListener onLongClickListener) {
        this.onLongClickListener = onLongClickListener;
    }

    public void updateData(AudioBean audioBean) {
        if (!dataEntityList.contains(audioBean)) {
            dataEntityList.add(audioBean);
            notifyDataSetChanged();
        }
    }

    public void clearData() {
        dataEntityList.clear();
        notifyDataSetChanged();
    }

    class StrokeHolder extends RecyclerView.ViewHolder implements View.OnClickListener ,View.OnLongClickListener{
        TextView tvAudioInfo;
        TextView tvAudioProgress;
        ImageView ivAudioStart;
        OnItemClickListener onItemClick;
        public StrokeHolder(View itemView, OnItemClickListener onItemClickListener) {
            super(itemView);
            tvAudioInfo = (TextView) itemView.findViewById(R.id.tv_audio_info);
            tvAudioProgress = (TextView) itemView.findViewById(R.id.tv_audio_progress);
            ivAudioStart = itemView.findViewById(R.id.iv_audio_start);
            itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

            this.onItemClick = onItemClickListener;
            itemView.setOnClickListener(this);
            itemView.setOnLongClickListener(this);



        }

        public void setData(AudioBean resultEntity) {
            tvAudioInfo.setText(resultEntity.getAudioInfo());
            tvAudioProgress.setText(resultEntity.getAudioProgress());

        }
        @Override
        public void onClick(View v) {
            if (onItemClick != null) {
                onItemClick.onItemClick(dataEntityList.get(getPosition()), getPosition());
            }
        }

        @Override
        public boolean onLongClick(View v) {
            if(onLongClickListener !=null){
                onLongClickListener.OnLongClickListener(dataEntityList.get(getPosition()), getPosition());
            }
            return true;
        }
    }
}