android音频编辑(裁剪,合成)(2)

article/2025/5/8 23:25:50

.. .-..—…-. -.—–..- (“I love you ”莫斯电码),这是逛知乎的时候看到程序员的表白情书,感觉我们码农情商好高啊!哈哈,好了开始搬砖。

上一篇介绍了音频的采集过程。之后产品经理找我谈了话,表示功能跟界面都凑合!但是(听到“但是”表示头皮发麻),需要再加一个功能,就是音频在录制的过程中,可以暂停,并且可以删除到上次暂停的地方(此刻内心亿万头草泥马飞奔而过,官大一级压死人啊!)。下面先对上次的音频采集过程中出现的bug进行一下简单的修改。

一、音频的标记操作
上次的操作标记的移动速度是固定的,也就是每次surfaceView进行绘制(每隔20ms)时,标记点位向左平移3个像素,这就导致标记点位的移动速度与波形图不能形成相同速度的平移,由于刷新的频率较高,所以标记点在以肉眼的可见的偏移量偏离标记的位置。

这就很尴尬了。做出的修改就是在,计算录音采集的字节数跟总的画布长度的时候,计算每次移除list集合的字节数,再进行标记点位移。

下面是源码截图,只需要加一个参数即可:
这里写图片描述
这里写图片描述

二、音频采集的回删操作,下面是定下来的界面:

主要的改变也只是在音频采集的下面加了一个跑动的条形,每次暂停的时候,会在在这个条上画一个分隔线,删除的时候,条向右移动,

录制的时候,条形是向左平移的,它的平移速度跟上面的时间刻度条是一致的。

回删的操作,其实是在录制完成后,剪辑形成合并形成的,所以请看下面。
这里写图片描述

三、进入正题,音频的编辑。
先看界面如下:
这里写图片描述

操作者可以在底部那个左右滑动,控制切割点的位置,时间轴的生成方式与编辑的时间轴是不一样的,这个时间轴是动态的,是用

linerLayout动态添加子View生成的,很简单,每个刻度我这里的是60dp,你自己可以根据需要更改,ll_wave_content是包裹

timeLine的父控件。代码如下:

/*** 音频的时间刻度*/private void timeSize() {timeLine = (LinearLayout)this.findViewById(R.id.ll_time_counter);tv_totalTime.setText(formatTime(totalTime)+"");timeLine.removeAllViews();totleLength = totalTime*DensityUtil.dip2px(60);
//      timeLine1.removeAllViews();ll_wave_content1.setLayoutParams(new FrameLayout.LayoutParams(totalTime*DensityUtil.dip2px
(60),LayoutParams.MATCH_PARENT));ll_wave_content.setLayoutParams(new FrameLayout.LayoutParams(totalTime*DensityUtil.dip2px
(60),LayoutParams.MATCH_PARENT));timeLine1.setLayoutParams(new RelativeLayout.LayoutParams(totalTime*DensityUtil.dip2px
(60),LayoutParams.MATCH_PARENT));for(int i=0;i<totalTime;i++){LinearLayout line1=new LinearLayout(this);line1.setOrientation(LinearLayout.HORIZONTAL);line1.setLayoutParams(new LayoutParams(DensityUtil.dip2px
(60),LinearLayout.LayoutParams.WRAP_CONTENT));line1.setGravity(Gravity.CENTER);TextView timeText=new TextView(this);timeText.setText(formatTime(i));timeText.setWidth(DensityUtil.dip2px(60)-2);timeText.setGravity(Gravity.CENTER_HORIZONTAL);TextPaint paint = timeText.getPaint();paint.setFakeBoldText(true); //字体加粗设置timeText.setTextColor(Color.rgb(204, 204, 204));View line2=new View(this);line2.setBackgroundColor(Color.rgb(204, 204, 204));line2.setPadding(0, 10, 0, 0);line1.addView(timeText);line1.addView(line2);timeLine.addView(line1);}

相对其他格式的音频文件,wav格式的相对比较简单,只是在pcm之上添加了头部,wav的头部格式如下:
这里写图片描述

好,看的不明白的同学可自行百度活谷歌,有很多文章介绍;

既然pcm格式加上wav的头部就可,那剪辑或者合成就很方便了,合成的方法奉上:

        /*** merge *.wav files * @param target  output file* @param paths the files that need to merge* @return whether merge files success*/public static boolean mergeAudioFiles(String target,List<String> paths) {try {FileOutputStream fos = new FileOutputStream(target);            int size=0;byte[] buf = new byte[1024 * 1000];int PCMSize = 0;for(int i=0;i<paths.size();i++){FileInputStream fis = new FileInputStream(paths.get(i));size = fis.read(buf);while (size != -1){PCMSize += size;size = fis.read(buf);}fis.close();}PCMSize=PCMSize-paths.size()*44;WaveHeader header = new WaveHeader();header.fileLength = PCMSize + (44 - 8);header.FmtHdrLeth = 16;header.BitsPerSample = 16;header.Channels = 1;header.FormatTag = 0x0001;header.SamplesPerSec = 16000;header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;header.DataHdrLeth = PCMSize;byte[] h = header.getHeader();assert h.length == 44;fos.write(h, 0, h.length);for(int j=0;j<paths.size();j++){FileInputStream fis = new FileInputStream(paths.get(j));size = fis.read(buf);boolean isFirst=true;while (size != -1){if(isFirst){fos.write(buf, 44, size-44);size = fis.read(buf);isFirst=false;}else{fos.write(buf, 0, size);size = fis.read(buf);}}fis.close();}fos.close();} catch (Exception e) {e.printStackTrace();return false;}return true;}

剪辑的类,注意的是,需要先将你操作的wav文件塞进去,进行头文件的格式解析,之后就可算出你需要删除的帧区间,之后就是相关的逻辑运算了,这里我就不一一啰嗦了:

public class CheapWAV extends CheapSoundFile {public static Factory getFactory() {return new Factory() {public CheapSoundFile create() {return new CheapWAV();}public String[] getSupportedExtensions() {return new String[] { "wav" };}};}// Member variables containing frame infoprivate int mNumFrames;private int[] mFrameOffsets;private int[] mFrameLens;private int[] mFrameGains;private int mFrameBytes;private int mFileSize;private int mSampleRate;private int mChannels;// Member variables used during initializationprivate int mOffset;public CheapWAV() {}public int getNumFrames() {return mNumFrames;}public int getSamplesPerFrame() {return mSampleRate / 50;}public int[] getFrameOffsets() {return mFrameOffsets;}public int[] getFrameLens() {return mFrameLens;}public int[] getFrameGains() {return mFrameGains;}public int getFileSizeBytes() {return mFileSize;        }public int getAvgBitrateKbps() {return mSampleRate * mChannels * 2 / 1024;}public int getSampleRate() {return mSampleRate;}public int getChannels() {return mChannels;}public String getFiletype() {return "WAV";}//    public int secondsToFrames(double seconds) {
//        return (int)(1.0 * seconds * mSampleRate / mSamplesPerFrame + 0.5);
//    }public void ReadFile(File inputFile)throws java.io.FileNotFoundException,java.io.IOException {super.ReadFile(inputFile);mFileSize = (int)mInputFile.length();if (mFileSize < 128) {throw new java.io.IOException("File too small to parse");}FileInputStream stream = new FileInputStream(mInputFile);byte[] header = new byte[12];stream.read(header, 0, 12);mOffset += 12;if (header[0] != 'R' ||header[1] != 'I' ||header[2] != 'F' ||header[3] != 'F' ||header[8] != 'W' ||header[9] != 'A' ||header[10] != 'V' ||header[11] != 'E') {throw new java.io.IOException("Not a WAV file");}mChannels = 0;mSampleRate = 0;while (mOffset + 8 <= mFileSize) {byte[] chunkHeader = new byte[8];stream.read(chunkHeader, 0, 8);mOffset += 8;int chunkLen =((0xff & chunkHeader[7]) << 24) |((0xff & chunkHeader[6]) << 16) |((0xff & chunkHeader[5]) << 8) |((0xff & chunkHeader[4]));if (chunkHeader[0] == 'f' &&chunkHeader[1] == 'm' &&chunkHeader[2] == 't' &&chunkHeader[3] == ' ') {if (chunkLen < 16 || chunkLen > 1024) {throw new java.io.IOException("WAV file has bad fmt chunk");}byte[] fmt = new byte[chunkLen];stream.read(fmt, 0, chunkLen);mOffset += chunkLen;int format =((0xff & fmt[1]) << 8) |((0xff & fmt[0]));mChannels =((0xff & fmt[3]) << 8) |((0xff & fmt[2]));mSampleRate =((0xff & fmt[7]) << 24) |((0xff & fmt[6]) << 16) |((0xff & fmt[5]) << 8) |((0xff & fmt[4]));if (format != 1) {throw new java.io.IOException("Unsupported WAV file encoding");}} else if (chunkHeader[0] == 'd' &&chunkHeader[1] == 'a' &&chunkHeader[2] == 't' &&chunkHeader[3] == 'a') {if (mChannels == 0 || mSampleRate == 0) {throw new java.io.IOException("Bad WAV file: data chunk before fmt chunk");}int frameSamples = (mSampleRate * mChannels) / 50;mFrameBytes = frameSamples * 2;mNumFrames = (chunkLen + (mFrameBytes - 1)) / mFrameBytes;mFrameOffsets = new int[mNumFrames];mFrameLens = new int[mNumFrames];mFrameGains = new int[mNumFrames];byte[] oneFrame = new byte[mFrameBytes];int i = 0;int frameIndex = 0;while (i < chunkLen) {int oneFrameBytes = mFrameBytes;if (i + oneFrameBytes > chunkLen) {i = chunkLen - oneFrameBytes;}stream.read(oneFrame, 0, oneFrameBytes);int maxGain = 0;for (int j = 1; j < oneFrameBytes; j += 4 * mChannels) {int val = java.lang.Math.abs(oneFrame[j]);if (val > maxGain) {maxGain = val;}}mFrameOffsets[frameIndex] = mOffset;mFrameLens[frameIndex] = oneFrameBytes;mFrameGains[frameIndex] = maxGain;frameIndex++;mOffset += oneFrameBytes;i += oneFrameBytes;if (mProgressListener != null) {boolean keepGoing = mProgressListener.reportProgress(i * 1.0 / chunkLen);if (!keepGoing) {break;}}}} else {stream.skip(chunkLen);mOffset += chunkLen;}}}public void WriteFile(File outputFile, int startFrame, int numFrames)throws java.io.IOException {outputFile.createNewFile();FileInputStream in = new FileInputStream(mInputFile);FileOutputStream out = new FileOutputStream(outputFile);long totalAudioLen = 0;for (int i = 0; i < numFrames; i++) {totalAudioLen += mFrameLens[startFrame + i];}long totalDataLen = totalAudioLen + 36;long longSampleRate = mSampleRate;long byteRate = mSampleRate * 2 * mChannels;byte[] header = new byte[44];header[0] = 'R';  // RIFF/WAVE headerheader[1] = 'I';header[2] = 'F';header[3] = 'F';header[4] = (byte) (totalDataLen & 0xff);header[5] = (byte) ((totalDataLen >> 8) & 0xff);header[6] = (byte) ((totalDataLen >> 16) & 0xff);header[7] = (byte) ((totalDataLen >> 24) & 0xff);header[8] = 'W';header[9] = 'A';header[10] = 'V';header[11] = 'E';header[12] = 'f';  // 'fmt ' chunkheader[13] = 'm';header[14] = 't';header[15] = ' ';header[16] = 16;  // 4 bytes: size of 'fmt ' chunkheader[17] = 0;header[18] = 0;header[19] = 0;header[20] = 1;  // format = 1header[21] = 0;header[22] = (byte) mChannels;header[23] = 0;header[24] = (byte) (longSampleRate & 0xff);header[25] = (byte) ((longSampleRate >> 8) & 0xff);header[26] = (byte) ((longSampleRate >> 16) & 0xff);header[27] = (byte) ((longSampleRate >> 24) & 0xff);header[28] = (byte) (byteRate & 0xff);header[29] = (byte) ((byteRate >> 8) & 0xff);header[30] = (byte) ((byteRate >> 16) & 0xff);header[31] = (byte) ((byteRate >> 24) & 0xff);header[32] = (byte) (2 * mChannels);  // block alignheader[33] = 0;header[34] = 16;  // bits per sampleheader[35] = 0;header[36] = 'd';header[37] = 'a';header[38] = 't';header[39] = 'a';header[40] = (byte) (totalAudioLen & 0xff);header[41] = (byte) ((totalAudioLen >> 8) & 0xff);header[42] = (byte) ((totalAudioLen >> 16) & 0xff);header[43] = (byte) ((totalAudioLen >> 24) & 0xff);out.write(header, 0, 44);byte[] buffer = new byte[mFrameBytes];int pos = 0;for (int i = 0; i < numFrames; i++) {int skip = mFrameOffsets[startFrame + i] - pos;int len = mFrameLens[startFrame + i];if (skip < 0) {continue;}if (skip > 0) {in.skip(skip);pos += skip;}in.read(buffer, 0, len);out.write(buffer, 0, len);pos += len;}in.close();out.close();}
};

好了,最近一段时间确实太忙了,其他项目的维护升级什么的,搞的头皮发麻。有什么问题可以留言交流。

声明:音频的裁剪这个类的原作者的一个开源小项目叫音乐快剪,我只是在其基础上进行了修改!其他格式的音频MP3的话还好,但是ACC或者M4a格式的裁剪就比较麻烦,需要进行重新编码,建议使用FFMPEG进行格式重新编码裁剪,至于FFMPEG的android平台移植,GITHUB上有很多,很多人的博客也有介绍,个人建议不要自己编译(您时间富裕除外),很多已经编译好了,直接使用即可。

Github地址(大家下载的时候顺便给个star也是对作者劳动成果的肯定,谢谢):
https://github.com/T-chuangxin/VideoMergeDemo


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

相关文章

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

Android 实现音频的裁剪&#xff0c;拼接和混音 基本流程 在项目中我们的音频一般都是指的mp3的文件&#xff0c;mp3文件作为一种编码压缩过的文件格式并不能直接对音频的数据进行很好的操作&#xff0c;我们都知道这种压缩过后的文件播放的时候也必须通过解码器才能播放&…

利用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;运用也很简单。 选择图标 打…