Android 实现音频的裁剪,拼接和混音

article/2025/5/8 23:13:53

Android 实现音频的裁剪,拼接和混音

基本流程

在项目中我们的音频一般都是指的mp3的文件,mp3文件作为一种编码压缩过的文件格式并不能直接对音频的数据进行很好的操作,我们都知道这种压缩过后的文件播放的时候也必须通过解码器才能播放,而解码出来的原始数据就是pcm数据。pcm数据包含了音频最原始的信息,对实现对pcm数据的处理就能实现对音频的处理。所以对MP3音频流程如下图

Created with Raphaël 2.1.2 mp3文件 解码 pcm文件 进行数据处理 编码 mp3文件

pcm文件关键属性

pcm文件有这样几个关键参数,分别是采样率,采样大小,和声道数。这几个指标是十分关键的,我们可以通过他们的乘积得到这段音频的码率,或者通过文件大小除以码率算出这段音频的长度。并且如果要对两端音频进行拼接或混音操作必须保证这几个关键参数的一致,这样才能将两段音频进行处理,当然如果采样率不同怎么办呢?这里就需要SSRC来对采样率进行转换了。我们在实际应用的过程中尽量要避免进行采样率的转换,因为对于现在手机来说,这个过程是十分耗时的。

Android中的解码器

Android系统本身为我们提供了非常好的解码器来对Mp3文件进行解码通过MediaExtractor类和mediaCodec类我们可以进行解码工作。MediaCodec:负责媒体文件的编码和解码工作,内部方法均为native,MediaExtractor:负责将指定类型的媒体文件从文件中找到轨道和媒体信息,并填充到MediaCodec的缓冲区中。
获取关于一个mp3的完整信息

   MediaExtractor mediaExtractor = new MediaExtractor();try {mediaExtractor.setDataSource(musicFileUrl);} catch (Exception e) {Log.e(TAG,e.toString());return false;}mediaFormat = mediaExtractor.getTrackFormat(0);//采样率sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100;//通道数             channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;//音频长度             duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong(MediaFormat.KEY_DURATION): 0;//mimemime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) : "";try {   //得到进行解码的解码器  mediaCodec = MediaCodec.createDecoderByType(mime);mediaCodec.configure(mediaFormat, null, null, 0);} catch (Exception e) {Log.e(TAG,e.toString());return false;}

进行解码

     MediaFormat outputFormat = mediaCodec.getOutputFormat();MediaCodec.BufferInfo bufferInfo;mediaCodec.start();int byteNumber1 =(outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-    width"):0) / 8;int byteNumber2 = (outputFormat.containsKey("pcm-encoding") ? outputFormat.getInteger("pcmencoding") :0) / 8;if(byteNumber1 != 0){byteNumber = byteNumber1;} else {byteNumber = byteNumber2;}inputBuffers = mediaCodec.getInputBuffers();outputBuffers = mediaCodec.getOutputBuffers();mediaExtractor.selectTrack(0);bufferInfo = new MediaCodec.BufferInfo();BufferedOutputStream bufferedOutputStream = FileFunction.GetBufferedOutputStreamFromFile(decodeFileUrl);while (!decodeOutputEnd) {if (decodeInputEnd) {break;}  inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);  if (inputBufferIndex >= 0) {sourceBuffer = inputBuffers[inputBufferIndex];sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0);if (sampleDataSize < 0) {decodeInputEnd = true;sampleDataSize = 0;} else {presentationTimeUs = mediaExtractor.getSampleTime();}mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize,presentationTimeUs,decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);if (!decodeInputEnd) {mediaExtractor.advance();}}outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs);if (outputBufferIndex < 0) {switch (outputBufferIndex) {case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:outputBuffers = mediaCodec.getOutputBuffers();break;case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:outputFormat = mediaCodec.getOutputFormat();if(byteNumber3 != 0){byteNumber = byteNumber3;} else {byteNumber = byteNumber4;}break;default:break;}continue;}targetBuffer = outputBuffers[outputBufferIndex];byte[] sourceByteArray = new byte[bufferInfo.size];targetBuffer.get(sourceByteArray);targetBuffer.clear();mediaCodec.releaseOutputBuffer(outputBufferIndex, false);if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {decodeOutputEnd = true;}if (sourceByteArray.length > 0 && bufferedOutputStream != null) {if (presentationTimeUs < startMicroseconds) {continue;}byte[] convertByteNumberByteArray = ConvertByteNumber(byteNumber, 2,sourceByteArray);byte[] resultByteArray =ConvertChannelNumber(channelCount,1,2,convertByteNumberByteArray);try {bufferedOutputStream.write(resultByteArray);} catch (Exception e) {Log.e(TAG,e.toString());}}if (presentationTimeUs > endMicroseconds) {break;}} catch (Exception e) {Log.e(TAG,e.toString());}}if (bufferedOutputStream != null) {try {bufferedOutputStream.close();} catch (IOException e) {Log.e(TAG,e.toString());}}//当采样率不是44100的时候通过SSRC将采样率变成一样的if (sampleRate != 44100) {Resample(sampleRate, decodeFileUrl);}if (mediaCodec != null) {mediaCodec.stop();mediaCodec.release();}if (mediaExtractor != null) {mediaExtractor.release();}}         

pcm的处理

下图是pcm数据的储存
image
将两个pcm数据进行合并只需将这俩个pcm数据直接连续写入到一个文件中就可以得到一个拼接好的数据。但是这里要注意的是要控制好写入的buffer大小这样才能保证数据写入的速度,这里用的1kb的buffer可以保证非常快的写入速度。

                DataInputStream ins1 = new DataInputStream(new BufferedInputStream(new FileInputStream(file1.file)));DataInputStream ins2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file2.file)));DataOutputStream outputStream  = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));byte[] bytes = new byte[1024];byte[] bytes2 = new byte[1024];boolean isfinish1= false;boolean isfinish2 = false;while(!isfinish1){int temp = ins1.read(bytes);if(temp <= 0){isfinish1 = true;} else {outputStream.write(bytes);}}while(!isfinish2){int temp = ins2.read(bytes2);if(temp <= 0){isfinish2 = true;} else {outputStream.write(bytes2);}}ins1.close();ins2.close();outputStream.close();

裁剪pcm和拼接pcm类似,一个是将两个文件写入一个文件,令一个则是将一个文件中的一段写入另一个新的文件,这里主要的问题是如何确定数据所代表的时间点。这里要通过计算得到开始写入和结束写入的数据位置。通过采样率 声道数 和 bit的数来得到每秒的数据量

=bitnum/1000/8 每 秒 数 据 量 = 采 样 率 ∗ 声 倒 数 ∗ b i t n u m / 1000 / 8

下面是裁剪的代码

           DataInputStream inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(source.file)));DataOutputStream outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));int sum = 0;int audioFormat = 0;if(source.bit_num == 16){audioFormat = AudioFormat.ENCODING_PCM_16BIT;} else {audioFormat = AudioFormat.ENCODING_PCM_8BIT;}int bufferSize = AudioRecord.getMinBufferSize(source.sample_rage_hz,source.channel,audioFormat);byte[] buffer = new byte[bufferSize * 2];int sample = source.getSampleNuit();while(sum < end){inputStream.read(buffer,0,buffer.length);if(sum > start) {outputStream.write(buffer, 0, buffer.length);}sum = (bufferSize * 2 + sum * sample) / sample;}inputStream.close();outputStream.close();

混音,对于pcm的混音原理比较简单 就是数据的加和,但是不同加和得到的效果是不一样的。我这里为了减少噪声,减少计算就用了最简单的线性叠加后求平均。就是两个pcm文件对应的数值向加除以2,当然为了可以调节这两个音频声音大小,可以在每个数据之前乘以一个系数。得到如下的公式

=pcm11)+pcm22/2 数 据 结 果 = ( ( p c m 数 据 1 ∗ 系 数 1 ) + ( p c m 数 据 2 ∗ 系 数 2 ) ) / 2

当然除了最简单的方法,还有复杂的方法比如归一化混音和重新采样法等。

            short[] outPutShortArray = new short[512];byte[] mp3Buffer = new byte[(int)(7200 + (1024 * 1.25))];int firstAudioReadNumber;int secondAudioReadNumber;int outputShortArrayLength = 0;boolean firstAudioFinish = false;while(!firstAudioFinish){secondAudioReadNumber = second.read(secondBuffer);if(secondAudioReadNumber >= 0) {} else {second =   new FileInputStream(new File(bg_source));secondBuffer = new byte[1024];second.read(secondBuffer);}firstAudioReadNumber = first.read(firstBuffer);if(firstAudioReadNumber > 0) {outputShortArrayLength = firstAudioReadNumber / 2;for (int index = 0; index < outputShortArrayLength; index++) {outPutShortArray[index] = CommonFunction.WeightShort(firstBuffer[index * 2],firstBuffer[index * 2+1],secondBuffer[index * 2],secondBuffer[index * 2 + 1],source_weight,bg_weight,isbig);}} else if(firstAudioReadNumber < 0){firstAudioFinish =true;}}

pcm转换成mp3文件

现在我们有了处理好的pcm,需要将pcm转换成MP3,这个需要用到另一个开源库lame,我们可以很方便的从网站下载到lame的原代码下载地址。
将源码引入到项目,编写一个native-lib.cpp的文件,然后将所以有关的.c文件添加到编译的代码里面中。

add_library(native-libSHAREDsrc/main/cpp/native-lib.cppsrc/main/cpp/bitstream.csrc/main/cpp/encoder.csrc/main/cpp/gain_analysis.csrc/main/cpp/lame.csrc/main/cpp/id3tag.csrc/main/cpp/mpglib_interface.csrc/main/cpp/newmdct.csrc/main/cpp/presets.csrc/main/cpp/psymodel.csrc/main/cpp/quantize.csrc/main/cpp/fft.csrc/main/cpp/quantize_pvt.csrc/main/cpp/reservoir.csrc/main/cpp/set_get.csrc/main/cpp/tables.csrc/main/cpp/takehiro.csrc/main/cpp/util.csrc/main/cpp/vbrquantize.csrc/main/cpp/VbrTag.csrc/main/cpp/version.c)

然后对lame中的代码进行一下小修改
1)删除fft.c文件的47行的”include “vector/lame_intrin.h”“

2)修改set_get.h文件的24行的#include“lame.h”

3)将util.h文件的574行的”extern ieee754_float32_t fast_log2(ieee754_float32_t x);”
替换为 “extern float fast_log2(float x);”

这些跟ndk-builder是一样的,网上有很多教程。

然后,需要修改app -> build.gradle文件

android {
...defaultConfig {...externalNativeBuild{cmake{cFlags "-DSTDC_HEADERS"}}}
}

然后进行调用

static lame_global_flags *glf = NULL;extern "C"
JNIEXPORT void JNICALL
Java_audio_jhon_com_myaudio_unit_LameUnit_close(JNIEnv *env, jclass type) {lame_close(glf);glf = NULL;
}extern "C"
JNIEXPORT jint JNICALL
Java_audio_jhon_com_myaudio_unit_LameUnit_flush(JNIEnv *env, jclass type, jbyteArray mp3buf_) {jbyte *mp3buf = env->GetByteArrayElements(mp3buf_, NULL);const jsize mp3buf_size = env->GetArrayLength(mp3buf_);int result = lame_encode_flush(glf, (u_char*)mp3buf, mp3buf_size);env->ReleaseByteArrayElements(mp3buf_, mp3buf, 0);return result;
}extern "C"
JNIEXPORT void JNICALL
Java_audio_jhon_com_myaudio_unit_LameUnit_init__IIIII(JNIEnv *env, jclass type, jint inSampleRate,jint outChannel, jint outSampleRate,jint outBitrate, jint quality) {if(glf != NULL){lame_close(glf);glf = NULL;}glf = lame_init();lame_set_in_samplerate(glf, inSampleRate);lame_set_num_channels(glf, outChannel);lame_set_out_samplerate(glf, outSampleRate);lame_set_brate(glf, outBitrate);lame_set_quality(glf, quality);lame_init_params(glf);
}extern "C"
JNIEXPORT jint JNICALL
Java_audio_jhon_com_myaudio_unit_LameUnit_encode(JNIEnv *env, jclass type, jshortArray buffer_l_,jshortArray buffer_r_, jint samples,jbyteArray mp3buf_) {jshort *buffer_l = env->GetShortArrayElements(buffer_l_, NULL);jshort *buffer_r = env->GetShortArrayElements(buffer_r_, NULL);jbyte *mp3buf = env->GetByteArrayElements(mp3buf_, NULL);const jsize mp3buf_size = env->GetArrayLength(mp3buf_);int result = lame_encode_buffer(glf,buffer_l,buffer_r,samples,(u_char*)mp3buf,mp3buf_size);env->ReleaseShortArrayElements(buffer_l_, buffer_l, 0);env->ReleaseShortArrayElements(buffer_r_, buffer_r, 0);env->ReleaseByteArrayElements(mp3buf_, mp3buf, 0);return result;
}

对应的java代码

public class LameUnit {static {System.loadLibrary("native-lib");}public native static void close();public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);public native static int flush(byte[] mp3buf);/***进行初始化传入采样率,和声道数,输出的采样率和码率**/public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);}
}

LameUnit可以帮助我们将pcm合成的数据转换成经过编码的MP3数据。直接读取pcm的数据然后通过lame进行编码后写入到新的文件里。

 boolean isBig = false;if(ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN){isBig = true;}LameUnit.init(44100, 1,44100, 128);FileInputStream inputStream = new FileInputStream(in1.file);DataOutputStream outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(output)));byte[] buffer = new byte[1024];short[] buffer2 = new short[512];byte[] mp3Buffer = new byte[(int)(7200 + (1024 * 1.25))];boolean isFinish =false;while(!isFinish){int num = inputStream.read(buffer);if(num > 0){for(int i = 0;i<(num /2);i++){buffer2[i] = CommonFunction.GetShort(buffer[i*2],buffer[i*2 + 1],isBig);}} else if(num < 0) {isFinish = true;}if(num > 0){int encodedSize = LameUnit.encode(buffer2,buffer2,num / 2,mp3Buffer);if(encodedSize > 0){outputStream.write(mp3Buffer,0,encodedSize);}}}final int flushResult = LameUnit.flush(mp3Buffer);if (flushResult > 0) {outputStream.write(mp3Buffer, 0, flushResult);}inputStream.close();outputStream.close();LameUnit.close();

特别注意的问题

1.编码过程中设计了大量二进制文件转换转换成short,或者short转换为二进制,这里设计到一个概念就是内存大小端问题,对于大小端要进行不同的数据转换处理。在Android中我们可以很简单的得到内存是以大端存储还是以小端存储。

 boolean isbig = false;if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {isbig = true;} else {isbig = false;}

2.关于文件的读写,一定要用有缓存的那个读写方式,并且保证好缓存的大小,太大浪费太小效率又不高。


http://chatgpt.dhexx.cn/article/gwTEPDrK.shtml

相关文章

利用Pydub自动分割音频

&#x1f3b5; &#x1f3b5; &#x1f3b5; 当音乐遇上Python&#xff1a;用Pydub自动分割音频 随着短视频应用的普及&#xff0c;越来越多人开始了解并尝试制作自己的短视频作品。而在制作短视频时&#xff0c;背景音乐的选择和使用也是非常重要的一步。很多人喜欢选择一首长…

java音频剪切_Java使用IO流实现音频的剪切和拼接

需求&#xff1a;使用IO流将指定目录下的若干个音频文件的高潮部分&#xff0c;进行剪切&#xff0c;并重新拼接成一首新的音频文件 思路(以两首歌为例)&#xff1a; 第一首歌有一个输入流对象bis1。第二首歌有一个输入流对象bis2&#xff0c;他们公用一条输出流对象bos(在选择…

怎么裁剪音频?这个方法建议收藏备用

如今已然发展成了一个自媒体时代&#xff0c;短视频用户急速剧增&#xff0c;这也使不少人加入了创作视频的队伍。但录制视频容易&#xff0c;后期制作可没那么容易了&#xff0c;例如我们需要为其添加一段背景音乐&#xff0c;但是找不到一首歌的节奏能刚好跟视频中的卡点相结…

音频裁剪软件有哪些?来看看这几个实用软件

在剪辑视频的时候&#xff0c;我们通常会添加一段音乐来丰富视频的内容&#xff0c;但是我们可能只需要音频其中的一小段&#xff0c;剩下的音频需要被裁剪掉。那你们知道怎么裁剪音频&#xff1f;今天我来教你两个简单的音频裁剪技巧&#xff0c;你们走过路过&#xff0c;不要…

android音频裁剪(2)——Wav裁剪

原创不易&#xff0c;请尊重每一位原创&#xff0c;让我们更有分享的动力&#xff0c;转载请注明 转载链接 在android音频裁剪&#xff08;1&#xff09;——MP3裁剪一文中我分享了对mp3文件裁剪方法。在本文中我将分享对另外一种音频格式——wav格式音频的裁剪。不同于mp3格…

音视频系列--音频基本操作(音频裁剪,音频和音频混合,音频和视频混合)

前面介绍了音频的基本原理&#xff0c;这篇文章继续来总结下音频的基本操作&#xff0c;包括裁剪&#xff0c;混音和音频和视频的混合操作。 一、裁剪 下面Demo将一段输入mp3文件&#xff0c;根据startTime和endTime&#xff0c;进行裁剪&#xff0c;先解码成PCM文件,然后转码…

android音频资源,android音频编辑之音频裁剪的示例代码

前言 本篇开始讲解音频编辑的具体操作&#xff0c;从相对简单的音频裁剪开始。要进行音频裁剪&#xff0c;我的方案是开启一个Service服务用于音频裁剪的耗时操作&#xff0c;主界面发送裁剪命令&#xff0c;同时注册EventBus接受裁剪的消息(当然也可以使用广播接受的方式)。因…

android裁剪控件,Android 仿抖音音频裁剪控件

效果图 QQ图片20201126164657.jpg 功能要求:绘制音频效果,音乐播放后进度滚动,控件可拖动,拖动后获取新的起始时间 (后期会加入根据音乐各个时段分贝大小来动态显示音律线的长短) 控件功能实现具体代码: package com.cj.customwidget.widget import android.content.Conte…

如何剪切音乐的一部分?来试试这个方法

音频剪切和合并是音频编辑中常见的操作&#xff0c;它可以用来去除不必要的部分或者将多个音频片段组合成一个完整的作品。在今天的数字化时代&#xff0c;有许多软件和工具可以帮助我们完成这个任务。 那你们知道具体的音频剪切合并的方法是什么吗&#xff1f;如果还不太清楚…

如何裁剪音频文件?裁剪音频的方法有什么?

通常我们在剪辑视频时&#xff0c;为了让视频更加有感染力&#xff0c;我们会加上各种各样的音频丰富视频的内容&#xff0c;而且在选取音频时&#xff0c;一般都是会采用它的高潮部分。那么如何裁剪音频文件来达到想要的效果呢&#xff1f;裁剪音频的方法又有什么&#xff1f;…

常用停用词表整理(哈工大停用词表,百度停用词表等)

辣鸡CSDN https://github.com/goto456/stopwords https://zhuanlan.zhihu.com/p/30002654 转载于:https://www.cnblogs.com/0n-the-way/p/10544285.html

文本分析--停用词集合(结合哈工大停用词表、四川大学机器智能实验室停用词库、百度停用词表等)

文本分析过程中&#xff0c;中文文本分析是一个非常重要的环节&#xff0c;而停用词表的选择也是非常关键的&#xff0c;网络流行了多种版本的停用词表&#xff0c;都具有各自的特点&#xff0c;现在对网络流行的多种停用词表继续去重处理&#xff0c;综合实现新的停用词表。 …

【python】构建停用词表(文末附链接)

构建停用词表 构建停用词表是数据预处理的必要步骤&#xff0c;可以减小不必要的开销。 哈工大、百度、川大等停用词表见GitHub链接&#xff1a;https://github.com/goto456/stopwords 经实验和观察证明&#xff0c;’cn_stopwords.txt‘文件的停用词大多是否定词&#xff1…

stopwords.txt中英文数据集,四川大学机器智能实验室停用词库,哈工大停用词表,中文停用词表,百度停用词表百度网盘下载

今天找stopwords.txt数据集找了好长时间&#xff0c;真是气死了&#xff0c;好多都是需要金币&#xff0c;这数据集不是应该共享的么。故搜集了一些数据集&#xff0c;主要包括四川大学机器智能实验室停用词库,哈工大停用词表,中文停用词表,百度停用词表和一些其他的stopword.t…

python停用词表整理_python停用词表

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! stop_words:设置停用词表,这样的词我们就不会统计出来(多半是虚拟词,冠词等等),需要列表结构,所以代码中定义了一个函数来处理停用词表...前言前文给大家…

python文本分析--停用词表的使用

之前听说停用词表&#xff0c;没有上手使用过&#xff0c;真正操作的时候发现有很多东西没有学透彻。这里总结一下&#xff0c;去停用词的思想&#xff1a;在原始文本集中去掉不需要的词汇&#xff0c;字符。虽然有通用的停用词表&#xff0c;但是如果想提高后续的分词效果&…

uniapp使用阿里图标

效果图&#xff1a; 前言 随着uniApp的深入人心&#xff0c;我司也陆续做了几个使用uniapp做的移动端跨平台软件&#xff0c;在学习使用的过程中深切的感受到了其功能强大和便捷&#xff0c;今日就如何在uniapp项目中使用阿里字体图标的问题为大家献上我的一点心得&#xff0…

iconfont—阿里图标的使用

阿里图标库为我们提供了许多丰富精美的图标&#xff0c; 可以通过代码引入的方式将图标库引入到我们的项目中&#xff0c;用来美化我们的界面。iconfont 的使用方式有以下几种&#xff1a; 方式一&#xff1a;font-class 在线引入 打开网址进入首页&#xff0c;输入我们想要的…

java前端中的icon_阿里巴巴矢量图标库Iconfont的使用方法

前言 现在网络上有很多矢量图标库&#xff0c;但是能自定义的却很少&#xff0c;不能自定义的体积就很大&#xff0c;造成不必要的浪费。阿里巴巴矢量图标库Iconfont很好的规避了这个问题&#xff0c;能够自定义添加图标到你定义的项目中&#xff0c;运用也很简单。 选择图标 打…

MUI项目中使用阿里巴巴矢量图标库(保姆篇)

话不多说,直接进入主题. 一、要在MUI项目中使用阿里图标库&#xff0c; 就得先进入阿里图标库的官网 这里是官网网址: https://www.iconfont.cn/ 下图是首页的样子 二、使用阿里图标的方法有很多种,&#xff0c;这里就说一下我使用的这种 1.在搜索框中输入关键字&#xff0c;…