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

需求描述

最近在做蓝牙与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);}}

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

 @Overridepublic 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"
><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"><include layout="@layout/layout_toolbar" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:padding="@dimen/dp_10" /><ImageViewandroid: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"/><TextViewandroid: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="录音已写入"/><TextViewandroid: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><RelativeLayoutandroid:id="@+id/rl_recording"android:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid: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;//已选中的列表项@Overridepublic int getLayoutId() {return R.layout.activity_voice_recording;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void playProgress(String progress) {}@Overridepublic void setStop() {final Handler mainHandler = new Handler(Looper.getMainLooper());// 在子线程中更新数据并刷新RecyclerViewnew Thread(new Runnable() {@Overridepublic void run() {// 这里进行数据的更新操作// updateYourData();// 使用Handler将刷新操作切换到UI线程mainHandler.post(new Runnable() {@Overridepublic 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() {@Overridepublic 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>() {@Overridepublic 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();}@Overrideprotected void onDestroy() {super.onDestroy();//关闭录音if(BLEUtils.isGetToken()){LmAPI.CONTROL_AUDIO((byte) 0x0);}}@Overrideprotected void onPause() {super.onPause();if(audioPlay!=null){audioPlay.stopPlay();audioPlay=null;}}@Overridepublic 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());// 在子线程中更新数据并刷新RecyclerViewnew Thread(new Runnable() {@Overridepublic void run() {// 这里进行数据的更新操作// updateYourData();// 使用Handler将刷新操作切换到UI线程mainHandler.post(new Runnable() {@Overridepublic 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;}@Overridepublic StrokeHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = View.inflate(parent.getContext(), R.layout.item_audio_layout, null);return new StrokeHolder(view, onItemClickListener);}@Overridepublic 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}}@Overridepublic 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());}@Overridepublic void onClick(View v) {if (onItemClick != null) {onItemClick.onItemClick(dataEntityList.get(getPosition()), getPosition());}}@Overridepublic boolean onLongClick(View v) {if(onLongClickListener !=null){onLongClickListener.OnLongClickListener(dataEntityList.get(getPosition()), getPosition());}return true;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1550806.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ansible之playbook\shell\script模块远程自动安装nginx

通过shell模块&#xff0c; 编写安装nginx脚本&#xff0c;为yaml脚本&#xff0c;远程到135机器上安装并启动nginx - hosts: 192.168.45.135remote_user: roottasks:- name: 安装Nginx依赖环境和库文件yum: namewget,tar,make,gcc,pcre-devel,pcre,zlib-devel stateinstalle…

tr命令:替换文本中的字符

一、命令简介 ​tr​ 命令用于转换或删除文件中的字符。它可以从标准输入中读取数据&#xff0c;对数据进行字符替换、删除或压缩&#xff0c;并将结果输出到标准输出。 ‍ 二、命令参数 格式 tr [选项] [集合1] [集合2]选项和参数 ​ ​-c​​: 指定 集合 1 的补集。​ …

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】 一、上篇回顾二、项目准备2.1 准备模板项目2.2 支持计时功能2.3 配置UART4引脚2.4 支持printf重定向到UART42.5 支持printf输出浮点数2.6 支持printf不带\r的换行2.7 支持ccache编译缓存 三、TFLM集成3.1 添加tfli…

“卷”智能, 从高质量算力开始

算力即国力&#xff0c;这已是产业共识。 当人工智能浪潮席卷全球之际&#xff0c;大家深刻感受到发展算力产业的重要性和紧迫性&#xff0c;高质量的人工智能算力已经与国家竞争、产业升级和企业转型息息相关。 去年&#xff0c;《算力基础设施高质量发展行动计划》的颁布&a…

springboot整合MybatisPlus+MySQL

上一篇&#xff1a;springboot整合sentinel和对feign熔断降级 文章目录 一、准备二、主要工作三、具体步骤3.1 准备数据库环境3.20 pre引入依赖3.2 引入依赖3.3 bootstrap.yml配置mybatisplus3.40 pre引入service、mapper3.4 引入实体类、service、mapper 四、测试目录结构 五…

InnoDB 死锁

文章目录 死锁案例等待超时时间InnoDB 状态信息死锁日志 死锁检测死锁日志分析 死锁是指多个事务无法继续进行的情况&#xff0c;因为每个事务都持有另一个事务所需的锁。因为所有涉及的事务都在等待同一资源可用&#xff0c;所以它们都不会释放它所持有的锁。 当事务锁定多个…

MongoDB 工具包安装(mongodb-database-tools)

首先到官网下载工具包&#xff0c;进入下面页面&#xff0c;复制连接地址&#xff0c;使用wget下载 cd /usr/local/mongodb5.0.14/wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel70-x86_64-100.6.1.tgz 安装 tar -zxvf mongodb-database-tools-rhel70-…

Python库matplotlib之五

Python库matplotlib之五 小部件(widget)RangeSlider构造器APIs应用实列 TextBox构造器APIs应用实列 小部件(widget) 小部件(widget)可与任何GUI后端一起工作。所有这些小部件都要求预定义一个Axes实例&#xff0c;并将其作为第一个参数传递。 Matplotlib不会试图布局这些小部件…

LeetCode 热题 100 回顾2

干货分享&#xff0c;感谢您的阅读&#xff01;原文见&#xff1a;LeetCode 热题 100 回顾_力code热题100-CSDN博客 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标…

自制CANTool_DBC_Layout仿制_布局读取Signal(三)

1、读取DBC中解析格式空格问题报错解决方法 原来解析方式&#xff1a;BO_ 258 EPS_CANFD_StrWhlASts: 8 Test 有的DBC中数据格式&#xff1a;BO_ 80 GW_50: 8 GW &#xff08;多了一个空格&#xff09; 解析匹配规则修订为&#xff1a; string MessageRegex "BO…

手机改IP地址怎么弄?全面解析与操作指南

在当今数字化时代&#xff0c;IP地址作为设备在网络中的唯一标识&#xff0c;其重要性不言而喻。有时候&#xff0c;出于隐私保护、网络访问需求或其他特定原因&#xff0c;我们可能需要更改手机的IP地址。然而&#xff0c;对于大多数普通用户来说&#xff0c;如何操作可能还是…

一文说完c++全部基础知识,IO流(二)

一、IO流 流、一连串连续不断的数据集合。 看下图&#xff0c;继承关系 using namespace 流类的构造函数 eg:ifstream::ifstream (const char* szFileName, int mode ios::in, int); #include <iostream> #include <fstream> using namespace std; int main()…

云计算 Cloud Computing

文章目录 1、云计算2、背景3、云计算的特点4、云计算的类型&#xff1a;按提供的服务划分5、云计算的类型&#xff1a;按部署的形式划分 1、云计算 定义&#xff1a; 云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c;进入可…

0基础学习QT——配置开发环境

大纲 安装Qt配置Visual Studio 2022安装插件配置 测试 Qt框架&#xff0c;以其跨平台、高性能以及丰富的UI组件库而著称&#xff0c;是开发图形用户界面应用程序的理想选择。Visual Studio 2022提供了对Qt项目的深度支持&#xff0c;包括智能代码提示、代码导航、调试工具等&am…

矩阵奇异值

一、ATA 任给一个矩阵A&#xff0c;都有&#xff1a; ATA 为一个对称矩阵 例子&#xff1a;A为一个mn的矩阵&#xff0c;A的转置为一个nm的矩阵 对称矩阵的重要性质如下&#xff1a; ① 对称矩阵的特征值全为实数&#xff08;实数特征根&#xff09; ② 任意一个n阶对称矩阵…

基于微信小程序的旧衣回收系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

C++ string的基本运用详细解剖

string的基本操作 一.与C语言中字符串的区别二.标准库中的string三.string中常用接口的介绍1.string中常用的构造函数2.string类对象的容量操作函数3.string类对象的访问及遍历操作4.string类对象的修改操作5.string类的非成员函数6.string中的其他一些操作 一.与C语言中字符串…

Windows下VScode快速配置OpenCV开发环境 【快乐篇】

1.前言 由于业务需要本人通过vscode快速迭代配置了一版OpenCV的开发环境&#xff0c;因为要快所以直接用大佬们构建好的openCV就行。本人这里是64位的Window11下配置的。 2.前置工具 vscode IDE工具 3.安装VScode插件 C/CC/C Extension PackC/C ThemesCMakeCMake Tools 4.…

云服务架构与华为云架构

目录 1.云服务架构是什么&#xff1f; 1.1 云服务模型 1.2 云部署模型 1.3 云服务架构的组件 1.4 云服务架构模式 1.5 关键设计考虑 1.6 优势 1.7 常见的云服务架构实践 2.华为云架构 2.1 华为云服务模型 2.2 华为云部署模型 2.3 华为云服务架构的核心组件 2.4 华…

MySQL-MySQL访问

文章目录 前言一、使用步骤1.MYSQL *mysql_init(MYSQL *mysql);2.MYSQL *mysql_real_connectint mysql_query(MYSQL *mysql, const char *q);MYSQL_RES *mysql_store_result(MYSQL *mysql);my_ulonglong mysql_num_rows(MYSQL_RES *res);unsigned int mysql_num_fields(MYSQL_R…