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

article/2025/5/8 23:03:00

=====原创不易,请尊重每一位原创,让我们更有分享的动力,转载请注明=====

转载链接

在android音频裁剪(1)——MP3裁剪一文中我分享了对mp3文件裁剪方法。在本文中我将分享对另外一种音频格式——wav格式音频的裁剪。不同于mp3格式的裁剪,对于wav裁剪并不是通过android提供的多媒体库对音频进行处理,而是直接通过java代码对wav音频进行裁剪,所以以下方法对于wav音频裁剪具有平台通用性。
俗话说的好,知己知彼,百战不殆,要对wav文件进行处理,首先要去详细了解wav的文件结构。关于wav文件结构的网上资源很多,但是很杂乱,这里推荐一个地址,http://www.topherlee.com/software/pcm-tut-wavformat.html,个人感觉这里说的算是最简单明了的了。下图是一个该资料的一个关键部分。
What is the header?
The header is the beginning of a WAV (RIFF) file. The header is used to provide specifications on the file type, sample rate, sample size and bit size of the file, as well as its overall length.

The header of a WAV (RIFF) file is 44 bytes long and has the following format:

PositionsSample ValueDescription
1 - 4“RIFF”Marks the file as a riff file. Characters are each 1 byte long.
5 - 8File size (integer)Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you’d fill this in after creation.
9 -12“WAVE”File Type Header. For our purposes, it always equals “WAVE”.
13-16“fmt “Format chunk marker. Includes trailing null
17-2016Length of format data as listed above
21-221Type of format (1 is PCM) - 2 byte integer
23-242Number of Channels - 2 byte integer
25-2844100Sample Rate - 32 byte integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.
29-32176400(Sample Rate * BitsPerSample * Channels) / 8.
33-344(BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo
35-3616Bits per sample
37-40“data”“data” chunk header. Marks the beginning of the data section.
41-44File size (data)Size of the data section.

表格说得很清楚,如果还有疑惑,也可以看看这位朋友的分享http://blog.csdn.net/bluesoal/article/details/932395。当了解了wav文件的结构之后,再去处理wav文件就变得轻而易举。类似bmp图片的处理,bmp图片的骨骼精髓就在于它的header,wav文件也是一样,当获取了Wav的文件头,就基本知道了整个wav文件架构。所以希望各位朋友先了解wav结构之后再往下看代码。

既然说到裁剪,肯定是有一个新的裁剪得到的文件,按照正常的产品需求来说,裁剪后的音频文件除了时间长度和大小之外其他信息都应该和源音频保持一致。所以生成裁剪文件的时候肯定需要先读取原始音频的header.所以首先要定义一个header类。

public class WavHeader {//RITF标志public String mRitfWaveChunkID;//wav文件大小(总大小-8)public int mRitfWaveChunkSize;//wav格式public String mWaveFormat;//格式数据块ID:值为"fmt "(注意后面有个空格)public String mFmtChunk1ID;//格式数据块大小,一般为16public int mFmtChunkSize;//数据格式,一般为1,表示音频是pcm编码public short mAudioFormat;//声道数public short mNumChannel;//采样率public int mSampleRate;//每秒字节数public int mByteRate;//数据块对齐单位public short mBlockAlign;//采样位数public short mBitsPerSample;//data块,音频的真正数据块public String mDataChunkID;//音频实际数据大小public int mDataChunkSize;
}

上面的数据结构是按照之前表格定义的,因为wav中的FACT数据块不是必须的,所以在数据结构中并没有关于她们的定义,但对于一半的wav音频,上面header的数据结构已经是完备。下面是一个完整的裁剪流程:
1,创建文件流,读取原音频header
2,创建裁剪保存文件,并根据上面读取的header写入新的音频header(其实就是header 拷贝过去)
3,文件流移至裁剪起点,开始读取数据,将读取数据写入裁剪文件,直到到达裁剪终点,期间需要纪录读取的数据大小
4,将3中纪录的数据大小写入裁剪文件的Header,关闭文件

下面对每一个步骤进行详解。

1,创建文件流,读取原音频header

    //创建原音频文件的文件流mDataInputStream = new DataInputStream(new FileInputStream(filepath));//读取headerprivate boolean readHeader() {if (mDataInputStream == null) {return false;}WavHeader header = new WavHeader();byte[] buffer = new byte[4];byte[] shortBuffer = new byte[2];try {mDataInputStream.read(buffer);header.mRitfWaveChunkID = new String(buffer);Log.d(TAG, "Read file chunkID:" + header.mRitfWaveChunkID);mDataInputStream.read(buffer);header.mRitfWaveChunkSize = byteArrayToInt(buffer);Log.d(TAG, "Read file chunkSize:" + header.mRitfWaveChunkSize);mDataInputStream.read(buffer);header.mWaveFormat = new String(buffer);Log.d(TAG, "Read file format:" + header.mWaveFormat);mDataInputStream.read(buffer);header.mFmtChunk1ID = new String(buffer);Log.d(TAG, "Read fmt chunkID:" + header.mFmtChunk1ID);mDataInputStream.read(buffer);header.mFmtChunkSize = byteArrayToInt(buffer);Log.d(TAG, "Read fmt chunkSize:" + header.mFmtChunkSize);mDataInputStream.read(shortBuffer);header.mAudioFormat = byteArrayToShort(shortBuffer);Log.d(TAG, "Read audioFormat:" + header.mAudioFormat);mDataInputStream.read(shortBuffer);header.mNumChannel = byteArrayToShort(shortBuffer);Log.d(TAG, "Read channel number:" + header.mNumChannel);mDataInputStream.read(buffer);header.mSampleRate = byteArrayToInt(buffer);Log.d(TAG, "Read samplerate:" + header.mSampleRate);mDataInputStream.read(buffer);header.mByteRate = byteArrayToInt(buffer);Log.d(TAG, "Read byterate:" + header.mByteRate);mDataInputStream.read(shortBuffer);header.mBlockAlign = byteArrayToShort(shortBuffer);Log.d(TAG, "Read blockalign:" + header.mBlockAlign);mDataInputStream.read(shortBuffer);header.mBitsPerSample = byteArrayToShort(shortBuffer);Log.d(TAG, "Read bitspersample:" + header.mBitsPerSample);mDataInputStream.read(buffer);header.mDataChunkID = new String(buffer);Log.d(TAG, "Read data chunkID:" + header.mDataChunkID);mDataInputStream.read(buffer);header.mDataChunkSize = byteArrayToInt(buffer);Log.d(TAG, "Read data chunkSize:" + header.mDataChunkSize);} catch (Exception e) {e.printStackTrace();return false;}mWavHeader = header;return true;}

看代码的log,就知道每个读取的数据的含义,我就不重复啰嗦一遍了。下面是我从网上下载的一首wav的歌曲,读取到的文件头:

这里写图片描述

2,创建裁剪保存文件,并根据上面读取的header写入新的音频header(其实就是header 拷贝过去)

//创建裁剪文件
mDataOutputStream = new DataOutputStream(new FileOutputStream(filepath));//写入header
public boolean writeHeader(WavHeader header) throws IOException {if (mDataOutputStream == null) {return false;}if(header == null){return false;}mDataOutputStream.writeBytes(header.mRitfWaveChunkID);mDataOutputStream.write(intToByteArray((int) header.mRitfWaveChunkSize), 0, 4);mDataOutputStream.writeBytes(header.mWaveFormat);mDataOutputStream.writeBytes(header.mFmtChunk1ID);mDataOutputStream.write(intToByteArray((int) header.mFmtChunkSize), 0, 4);mDataOutputStream.write(shortToByteArray((short) header.mAudioFormat), 0, 2);mDataOutputStream.write(shortToByteArray((short) header.mNumChannel), 0, 2);mDataOutputStream.write(intToByteArray((int) header.mSampleRate), 0, 4);mDataOutputStream.write(intToByteArray((int) header.mByteRate), 0, 4);mDataOutputStream.write(shortToByteArray((short) header.mBlockAlign), 0, 2);mDataOutputStream.write(shortToByteArray((short) header.mBitsPerSample), 0, 2);mDataOutputStream.writeBytes(header.mDataChunkID);mDataOutputStream.write(intToByteArray((int) header.mDataChunkSize), 0, 4);return true;
}

写入header的过程其实就是读取header的反过程。

3,文件流移至裁剪起点,开始读取数据,将读取数据写入裁剪文件,直到到达裁剪终点,期间需要纪录读取的数据大小

    //移动流到起始点public void skip(int time) throws IOException {int skip = caculateSkip(time);if(mDataInputStream == null){return;}if(skip >= mDataInputStream.available()){return;}mDataInputStream.skipBytes(skip);}//给定一个时间,计算这个时间对应的文件字节private int caculateSkip(int time) {double duration = getDuation();return (int) ((time * 1.0 / duration) * mWavHeader.mDataChunkSize);}//获取音频的时长private double getDuation() {return mWavHeader.mDataChunkSize * 1.0 / mWavHeader.mSampleRate / 4;}//计算这个时间段对应的音频数据字节数public int getIntervalSize(int interval) {double duration = getDuation();double rate = interval / duration;return (int) (mWavHeader.mDataChunkSize * rate);}//写入数据到裁剪文件public boolean writeData(byte[] buffer, int offset, int count) {if (mDataOutputStream == null) {return false;}try {mDataOutputStream.write(buffer, offset, count);mDataSize += count;//纪录写入裁剪文件的实际数据大小} catch (Exception e) {e.printStackTrace();return false;}return true;}

这个步骤涉及到上面几个关键函数,首先原始音频文件文件流需要通过skip()函数移动到对应的裁剪起点,裁剪终点减去裁剪起点得到裁剪时间段,通过getIntervalSize()函数就能计算出这个时间段对应的音频数据的字节数量,在读取的过程中通过writeData()函数把数据写入的裁剪文件中,写入过程中需要纪录写入的数据大小,当读取的字节数量超过裁剪时间段对应的字节数量,说明裁剪结束。

4,将3中纪录的数据大小写入裁剪文件的Header,关闭文件

回头看看wav header的定义

 public class WavHeader {//RITF标志public String mRitfWaveChunkID;//wav文件大小(总大小-8)public int mRitfWaveChunkSize;//wav格式public String mWaveFormat;//格式数据块ID:值为"fmt "(注意后面有个空格)public String mFmtChunk1ID;//格式数据块大小,一般为16public int mFmtChunkSize;//数据格式,一般为1,表示音频是pcm编码public short mAudioFormat;//声道数public short mNumChannel;//采样率public int mSampleRate;//每秒字节数public int mByteRate;//数据块对齐单位public short mBlockAlign;//采样位数public short mBitsPerSample;//data块,音频的真正数据块public String mDataChunkID;//音频实际数据大小public int mDataChunkSize;
}

裁剪出来的文件和原始音频文件有两个地方是不同的,一个是mRitfWaveChunkSize(wav文件大小),一个是mDataChunkSize(音频实际数据大小),所以这两个地方在裁剪文件中修改,根据在写入的时候纪录的数据大小(mDataSize)就能得到这些数据,通过RandomAccessFile把它们写入:

    private boolean writeDataSize() {if (mDataOutputStream == null) {return false;}try {RandomAccessFile wavFile = new RandomAccessFile(mFilepath, "rw");//偏移mRitfWaveChunkID 占用的4字节wavFile.seek(4);//写入mRitfWaveChunkSize(wav文件大小)wavFile.write(intToByteArray((int) (mDataSize + 44 - 8)), 0, 4);//偏移40字节,到mDataChunkSize对应的字节位置wavFile.seek(40);//写入裁剪音频的实际数据大小wavFile.write(intToByteArray((int) (mDataSize)), 0, 4);wavFile.close();} catch (FileNotFoundException e) {e.printStackTrace();return false;} catch (IOException e) {e.printStackTrace();return false;}return true;}//裁剪文件关闭的时候写入实际音频数据大小public boolean closeFile() throws IOException {boolean ret = true;if (mDataOutputStream != null) {ret = writeDataSize();mDataOutputStream.close();mDataOutputStream = null;}return ret;}

总结,相信以上步骤能很轻易明白wav音频的裁剪原理,下面是一个综合过程:

public class WavHelper {public static boolean clip(String src, String dst, int start, int end){try {//创建原始音频文件流WavReader reader = new WavReader();reader.openFile(src);//读取headerWavHeader header = reader.getmWavFileHeader();//创建裁剪文件输出文件流WavWriter writer = new WavWriter();writer.openFile(dst, header);//BYTE_PER_READ 指的是每次读取的字节数,可以自定义byte[] buffer = new byte[WavReader.BYTE_PER_READ];int size = -1;//移动至裁剪起点reader.skip(start);//获取裁剪时间段对应的字节大小int dataSize = reader.getIntervalSize(end - start);int sizeCount = 0;while(true){size = reader.readData(buffer, 0, buffer.length);//当到达裁剪时间段大小时候结束读取if(size < 0 || sizeCount >= dataSize){//在close时候写入实际音频数据大小writer.closeFile();reader.closeFile();return true;}//写入音频数据到裁剪文件writer.writeData(buffer, 0, size);//计算读取的字节数,注意,因为BYTE_PER_READ的原因,读取的字节数和实际的音频大小未必相同,//不能把它直接当作实际音频数据大小sizeCount += size;}} catch (IOException e) {e.printStackTrace();}return false;}
}

最后,通过以上步骤可以很轻易就完成wav音频的裁剪过程,希望能帮到有需要的朋友。有什么不明白的地方,欢迎私信或者发表评论,大家一起研究,共同进步。如果需要裁剪mp3音频的朋友,可以参考我上一篇文章:android音频裁剪(1)——MP3裁剪


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

相关文章

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

前面介绍了音频的基本原理&#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;…

微信小程序如何使用阿里妈妈iconfont图标库

1、首先进入iconfont首页&#xff0c;没有账号的先注册账号 http://www.iconfont.cn/ 2、选择需要的图标&#xff0c;加入到你的小车中 3、在这里新建一个项目将图标加进去&#xff0c;这个时候就可以查看并且下载下来了 4、点击download code将图标代码下载下来&#xff0c;…

基于Java的阿里妈妈数据抓取技术

基于Java的阿里妈妈数据抓取技术 前言&#xff1a; 对于需要登录的网站爬虫最大的困难就是需要登录&#xff0c;然后才能获取到数据&#xff0c;如微博&#xff0c;阿里妈妈&#xff0c;webqq等。之前也有看过使用浏览器登录到网站后直接从浏览器中获取cookie的文章&#xff0…

uni-app中引入iconfont阿里巴巴矢量图标库

一&#xff1a;首先看一下图标 二&#xff1a;将icon.css文件放到项目中。 在static下面新建icon.css文件&#xff08;一般是建在common文件下面&#xff0c;建在其他位置上也可以&#xff09;&#xff0c;将iconfont里面的内容复制到icon.css。 这里要对icon.css内容做一些改变…

引入阿里iconfont图标方法以及注意事项

背景 在我们做日常项目时&#xff0c;通常会用到icon图标或者是一些图标字体&#xff0c;阿里iconfont是我们选择的较多的一种&#xff0c;下面我将会介绍使用方法和几种常用的引用方式 iconfont新建项目 官网&#xff1a;https://www.iconfont.cn/ 在首页选择【图标管理】-…

如何在代码里添加并使用阿里巴巴矢量图标-iconfont,在此常用有三种引入方法

iconfont-阿里巴巴矢量图库 在登录好账号的前提下进行以下操作&#xff1a; 添加icon&#xff1a; 首先搜索你想要的icon名&#xff0c;比如&#xff1a;首页选好你想要的图——加入购物车——添加至项目&#xff08;没有项目的话可以新建项目&#xff0c;如果需要很多icon&a…