开源一款超级好用的mp3剪切器app
本文已授權(quán)微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創(chuàng)首發(fā)。
app&技術(shù)介紹
該app使用了MD規(guī)范,界面風(fēng)格簡潔,功能上mp3剪切鈴聲制作,實(shí)用性比較強(qiáng)。 功能上雖然簡潔,但是技術(shù)上該項(xiàng)目“麻雀雖小,五臟俱全”。
下面從技術(shù)層面上做一些簡單介紹:
- 首頁使用了CoordinatorLayout+AppBarLayout+DrawerLayout+NavigationView的經(jīng)典MD設(shè)計(jì)風(fēng)格。
- 項(xiàng)目整體采用了MVP+databinding+rxjava2+rxandroid2+dagger2框架設(shè)計(jì),數(shù)據(jù)緩存使用了greendao。
- 音頻頻譜的繪制主要是通過Visualizer中獲取到的波形數(shù)據(jù)來進(jìn)行繪制。
- 剪切功能上,mp3剪切核心功能使用了jaudiotagger jar包獲取mp3元數(shù)據(jù)獲取字節(jié)位置并進(jìn)行文件io操作生成目標(biāo)文件。此功能作為重點(diǎn),本文后續(xù)會做詳細(xì)的說明。
- 動畫方面,歡迎頁使用了lottie動畫,如感興趣可以看這篇博客做了詳盡的步驟介紹,制作lottie動畫并應(yīng)用到android項(xiàng)目。 項(xiàng)目中文件選擇頁以及關(guān)于頁面使用了屬性動畫和屬性動畫組件AVLoadingIndicatorView。
- 自定義控件,范圍選取控件CustomRangeSeekBar,不是本文重點(diǎn)可以看之前的博文android 自定義范圍選取控件CustomRangeSeekBar。
使用說明+gif
Step1. 選擇mp3文件
Step2. 通過滑塊選擇剪切范圍然后點(diǎn)擊剪切按鈕
Tips:主界面上可以看到三個按鈕,從左到右的功能分別為:
- 播放\暫停
- 切換播放的滑塊(切換當(dāng)前播放的位置,前滑塊or后滑塊)
- 音樂剪切
mp3剪切實(shí)現(xiàn)思想
實(shí)現(xiàn)思想主要有兩點(diǎn)
- 獲取mp3開始時間(要剪切的開始時間)所在的文件字節(jié)位置及結(jié)束時間所在文件的字節(jié)位置
- 根據(jù)開始時間的字節(jié)位置和結(jié)束時間的字節(jié)位置結(jié)合源文件生成我們的目標(biāo)文件
mp3剪切實(shí)現(xiàn)技術(shù)點(diǎn)
那么如何來獲取mp3開始時間所在文件的字節(jié)位置呢? 這里用到了jaudiotagger.jar。 它的主頁是這樣描述它的
Jaudiotagger is a Java API for audio metatagging. Both a common API and format specific APIs are available, currently supports reading and writing metadata for:Mp3、Flac、OggVorbis、Mp4、Aiff、Wav、Wma、Dsf
它是一個音頻元標(biāo)記的java庫,可以支持mp3等特定格式進(jìn)行讀寫元數(shù)據(jù)操作。
mp3剪切實(shí)現(xiàn)細(xì)節(jié):
一、我們要做的事通過Jaudiotagger獲取到mp3的元數(shù)據(jù),通過元數(shù)據(jù)取到mp3的首幀字節(jié)位置以及比特率。然后根據(jù)首幀字節(jié)位置以及比特率和開始時間可以其對應(yīng)文件的字節(jié)位置。最后得到開始字節(jié)位置和結(jié)束字節(jié)位置。
可能你會問,什么是比特率?
比特率是每秒傳輸?shù)谋忍?#xff08;bit)數(shù)
來看我們?nèi)p3比特率的方法看注釋
long bitRate = header.getBitRateAsNumber(); 看該方法源碼注釋如下:
通過注釋得知,此方法返回的比特率單位為kbps(每秒千字節(jié)) ,而我們需要的比特率的單位是(每毫秒位),下一步進(jìn)行單位轉(zhuǎn)換計(jì)算。
這里我們需要換算它為每毫秒位數(shù),1字節(jié)是8位,1秒是1000毫秒,千字節(jié)是1024字節(jié),那么轉(zhuǎn)換后算到的也就是getBitRateAsNumber() *1024L / 8L / 1000L。代碼如下:
這個值就是開始時間所在文件的字節(jié)位置嗎?當(dāng)然不是,我們的mp3文件當(dāng)中并不只包含音樂的數(shù)據(jù),還包含有音樂的信息頭數(shù)據(jù)。同樣我們可以從頭信息中取到我們的mp3首幀字節(jié)位置。首幀字節(jié)位置+每毫秒位為單位比特率,就是我們要的mp3開始字節(jié)位置了。代碼如下:
同理, 利用上面計(jì)算出來的開始字節(jié)beginType+時間差(剪切結(jié)束時間-開始時間)的比特率(單位為每毫秒位)就可以計(jì)算出結(jié)束的字節(jié)位置了,代碼入下:
long endIndex(截取結(jié)束字節(jié)位置) = beginIndex(截取開始字節(jié)位置) + bitRate *1024L / 8L / 1000L(比特率每毫秒位) * (endTime - beginTime)(截取的時長毫秒單位);
二、 有了開始時間的字節(jié)位置和結(jié)束時間的字節(jié)位置,那我們就可以結(jié)合源文件生成我們的目標(biāo)文件拉。讀寫文件我們可以使用RandomAccessFile實(shí)現(xiàn)隨機(jī)的讀寫操作,通過RandomAccessFile.seek()方法調(diào)到指定位置。
- 問題&解決方案
如果我們要操作的mp3文件很大,比如我們截取的字節(jié)大小為100MB,這時候我們的app就會因?yàn)镺OM直接crash掉了。
這里我的解決方案是通過一個緩存數(shù)組來限制每次讀寫的數(shù)據(jù)大小,每次操作指定大小的數(shù)據(jù),這樣無論文件多大,我們都不會出現(xiàn)OOM問題啦。
到這里就結(jié)束啦,能力有限,寫的不對好的地方,請多提意見。
項(xiàng)目計(jì)劃講一直進(jìn)行維護(hù)升級,謝謝您的關(guān)注!!!
源碼&apk
-
代碼已上傳Github
-
github APK下載
-
蒲公英 APK下載
單元測試
如果沒有手機(jī)或其他原因不方便使用app。項(xiàng)目中提供了單元測試和mp3文件,可以通過單元測試來體驗(yàn)mp3剪切功能。
- laozi.mp3是源mp3
- test.mp3是運(yùn)行完單元測試,生成的mp3文件。
- startTime、endTime為剪切的開始時間及結(jié)束時間
后續(xù)
博文被鴻洋發(fā)布后,github受到了很多關(guān)注,有人提了issue, “部分MP3文件剪切失敗”。原因是之前的mp3剪切中只是對恒定比特率做了支持,在可變比特率那一塊邏輯沒有實(shí)現(xiàn),直接拋了異常。
public void generateNewMp3ByTime(String targetFileStr, long beginTime, long endTime) throws Exception {MP3File mp3 = new MP3File(this.mSourceMp3File);MP3AudioHeader header = (MP3AudioHeader) mp3.getAudioHeader();if (header.isVariableBitRate()) {throw new Exception("This is nonsupport variableBitRate!!!");} else {...} } 復(fù)制代碼可以看到之前版本并沒有支持可變比特率。這里講述一下對實(shí)現(xiàn)可變比特率mp3剪切的實(shí)現(xiàn)思想。
- 重要的一點(diǎn):每幀的時間是相等的
- 公式:每幀比特大小 = ( 每幀采樣次數(shù) × 比特率(bit/s) ÷ 8 ÷采樣率) + Padding
- mp3總比特大小 = mp3幀數(shù)*每幀比特大小
- 開始時間占總時長比例 = 開始時間/mp3總時長 、結(jié)束時間占總時長比例 = 結(jié)束時間/mp3總時長
- 開始時間對應(yīng)比特 = mp3總比特大小 *開始時間占總時長比例、結(jié)束時間對應(yīng)比特 = mp3總比特大小*結(jié)束時間占總時長比例
上代碼:
/*** 根據(jù)時間和源文件生成MP3文件 (源文件mp3 比特率為vbr可變比特率)** @param header* @param targetFileStr* @param beginTime* @param endTime* @throws IOException*/private void generateMp3ByTimeAndVBR(MP3AudioHeader header, String targetFileStr, long beginTime, long endTime) throws IOException {long frameCount = header.getNumberOfFrames();int sampleRate = header.getSampleRateAsNumber();int sampleCount = 1152;//header.getNoOfSample();int paddingLength = header.isPadding() ? 1 : 0;//幀大小 = ( 每幀采樣次數(shù) × 比特率(bit/s) ÷ 8 ÷采樣率) + Padding//getBitRateAsNumber 返回的為kbps 所以要*1000float frameSize = sampleCount * header.getBitRateAsNumber() / 8f / sampleRate * 1000 + paddingLength;//獲取音軌時長int trackLengthMs = header.getTrackLength() * 1000;//開始時間與總時間的比值float beginRatio = (float) beginTime / (float) trackLengthMs;//結(jié)束時間與總時間的比值float endRatio = (float) endTime / (float) trackLengthMs;long startFrameSize = (long) (beginRatio * frameCount * frameSize);long endFrameSize = (long) (endRatio * frameCount * frameSize);//返回音樂數(shù)據(jù)的第一個字節(jié)long firstFrameByte = header.getMp3StartByte();generateTargetMp3File(targetFileStr, startFrameSize, endFrameSize, firstFrameByte);} 復(fù)制代碼感謝
- jaudiotagger
- RXJava
- RxAndroid
- greendao
- StatusBarUtil
- Dagger2
- PermissionsDispatcher
- logger
- AVLoadingIndicatorView
- baseAdapter
- CustomRangeSeekBar
License
Mp3Cutter is under CC BY-NC-SA license.
總結(jié)
以上是生活随笔為你收集整理的开源一款超级好用的mp3剪切器app的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity自定义美术字体(图片字体fon
- 下一篇: 路由器密码太弱,IP 被黑客利用发虐童图