日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

开源一款超级好用的mp3剪切器app

發布時間:2024/3/26 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 开源一款超级好用的mp3剪切器app 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。

app&技術介紹

該app使用了MD規范,界面風格簡潔,功能上mp3剪切鈴聲制作,實用性比較強。 功能上雖然簡潔,但是技術上該項目“麻雀雖小,五臟俱全”。
下面從技術層面上做一些簡單介紹:

  • 首頁使用了CoordinatorLayout+AppBarLayout+DrawerLayout+NavigationView的經典MD設計風格。
  • 項目整體采用了MVP+databinding+rxjava2+rxandroid2+dagger2框架設計,數據緩存使用了greendao。
  • 音頻頻譜的繪制主要是通過Visualizer中獲取到的波形數據來進行繪制。
  • 剪切功能上,mp3剪切核心功能使用了jaudiotagger jar包獲取mp3元數據獲取字節位置并進行文件io操作生成目標文件。此功能作為重點,本文后續會做詳細的說明。
  • 動畫方面,歡迎頁使用了lottie動畫,如感興趣可以看這篇博客做了詳盡的步驟介紹,制作lottie動畫并應用到android項目。 項目中文件選擇頁以及關于頁面使用了屬性動畫和屬性動畫組件AVLoadingIndicatorView。
  • 自定義控件,范圍選取控件CustomRangeSeekBar,不是本文重點可以看之前的博文android 自定義范圍選取控件CustomRangeSeekBar。

使用說明+gif

Step1. 選擇mp3文件

Step2. 通過滑塊選擇剪切范圍然后點擊剪切按鈕

Tips:主界面上可以看到三個按鈕,從左到右的功能分別為:

  • 播放\暫停
  • 切換播放的滑塊(切換當前播放的位置,前滑塊or后滑塊)
  • 音樂剪切

mp3剪切實現思想

實現思想主要有兩點

  • 獲取mp3開始時間(要剪切的開始時間)所在的文件字節位置及結束時間所在文件的字節位置
  • 根據開始時間的字節位置和結束時間的字節位置結合源文件生成我們的目標文件

mp3剪切實現技術點

那么如何來獲取mp3開始時間所在文件的字節位置呢? 這里用到了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

它是一個音頻元標記的java庫,可以支持mp3等特定格式進行讀寫元數據操作。

mp3剪切實現細節:

一、我們要做的事通過Jaudiotagger獲取到mp3的元數據,通過元數據取到mp3的首幀字節位置以及比特率。然后根據首幀字節位置以及比特率和開始時間可以其對應文件的字節位置。最后得到開始字節位置和結束字節位置。

  • 獲取mp3元數據
  • MP3File mp3 = new MP3File(this.mp3File); //獲取mp3的元數據 MP3AudioHeader header = (MP3AudioHeader) mp3.getAudioHeader(); 復制代碼
  • 根據元數據獲取mp3比特率
  • //根據元數據獲取比特率 long bitRateKbps = header.getBitRateAsNumber(); 復制代碼

    可能你會問,什么是比特率?

    比特率是每秒傳輸的比特(bit)數

    來看我們取mp3比特率的方法看注釋
    long bitRate = header.getBitRateAsNumber(); 看該方法源碼注釋如下:

    /**** @return bitrate in kbps, no indicator is provided as to * whether or not it is vbr*/public long getBitRateAsNumber(){return bitrate;} 復制代碼

    通過注釋得知,此方法返回的比特率單位為kbps(每秒千字節) ,而我們需要的比特率的單位是(每毫秒位),下一步進行單位轉換計算。

  • 轉換比特率
    這里我們需要換算它為每毫秒位數,1字節是8位,1秒是1000毫秒,千字節是1024字節,那么轉換后算到的也就是getBitRateAsNumber() *1024L / 8L / 1000L。代碼如下:
  • //計算出開始字節位置 long bitRatebpm = bitRateKbps *1024L / 8L / 1000L * beginTime; 復制代碼
  • 計算開始字節
    這個值就是開始時間所在文件的字節位置嗎?當然不是,我們的mp3文件當中并不只包含音樂的數據,還包含有音樂的信息頭數據。同樣我們可以從頭信息中取到我們的mp3首幀字節位置。首幀字節位置+每毫秒位為單位比特率,就是我們要的mp3開始字節位置了。代碼如下:
  • long firstFrameByte = header.getMp3StartByte(); long beginByte = firstFrameByte + beginBitRateBpm; 復制代碼
  • 計算結束字節位置
    同理, 利用上面計算出來的開始字節beginType+時間差(剪切結束時間-開始時間)的比特率(單位為每毫秒位)就可以計算出結束的字節位置了,代碼入下:
  • //計算出結束字節位置 long endByte = beginByte + convertKbpsToBpm(bitRateKbps) * (endTime - beginTime); 復制代碼

    long endIndex(截取結束字節位置) = beginIndex(截取開始字節位置) + bitRate *1024L / 8L / 1000L(比特率每毫秒位) * (endTime - beginTime)(截取的時長毫秒單位);

    二、 有了開始時間的字節位置和結束時間的字節位置,那我們就可以結合源文件生成我們的目標文件拉。讀寫文件我們可以使用RandomAccessFile實現隨機的讀寫操作,通過RandomAccessFile.seek()方法調到指定位置。

    • 問題&解決方案
      如果我們要操作的mp3文件很大,比如我們截取的字節大小為100MB,這時候我們的app就會因為OOM直接crash掉了。
      這里我的解決方案是通過一個緩存數組來限制每次讀寫的數據大小,每次操作指定大小的數據,這樣無論文件多大,我們都不會出現OOM問題啦。
  • 首先我們寫一個工具方法,以緩存的方式來生成目標文件,源文件讀取指定大小的數據讀取寫入到目標文件,代碼如下:
  • /*** ** @param targetFile 輸出的文件* @param sourceFile 讀取的文件* @param buffer 輸入輸出的緩存容器* @param offset 讀入文件時seek的偏移值*/private static void writeSourceToTargetFile(RandomAccessFile targetFile, RandomAccessFile sourceFile,byte buffer[], long offset) throws Exception {sourceFile.seek(offset);sourceFile.read(buffer);long fileLength = targetFile.length();// 將寫文件指針移到文件尾。targetFile.seek(fileLength);targetFile.write(buffer);} 復制代碼
  • 需要根據需要剪切文件的字節大小,分別考慮小于緩存以及大于等于緩存的情況,分別進行操作。代碼如下:
  • private static void writeSourceToTargetFileWithBuffer(RandomAccessFile targetFile, RandomAccessFile sourceFile,long totalSize, long offset) throws Exception {//緩存大小,每次寫入指定數據防止內存泄漏int buffersize = BUFFER_SIZE;long count = totalSize / buffersize;if (count <= 1) {//文件總長度小于小于緩存大小情況writeSourceToTargetFile(targetFile, sourceFile, new byte[(int) totalSize], offset);} else {//計算出整除后剩余的數據數long remainSize = totalSize % buffersize;byte data[] = new byte[buffersize];//讀入文件時seek的偏移量for (int i = 0; i < count; i++) {writeSourceToTargetFile(targetFile, sourceFile, data, offset);offset += BUFFER_SIZE;}//寫入剩余數據if (remainSize > 0) {writeSourceToTargetFile(targetFile, sourceFile, new byte[(int) remainSize], offset);}}} 復制代碼
  • 最后要考慮不但要講mp3樂音幀相關數據寫入, 還要講頭信息寫入進去,代碼如下:
  • /*** 生成目標mp3文件** @param targetFile* @param beginByte* @param endByte* @param firstFrameByte* @throws Exception*/private void generateTargetMp3File(RandomAccessFile targetFile,long beginByte, long endByte, long firstFrameByte) throws Exception {RandomAccessFile sourceFile = new RandomAccessFile(mSourceMp3File, "rw");try {//write mp3 header infowriteSourceToTargetFileWithBuffer(targetFile, sourceFile, firstFrameByte, 0);//write mp3 frame infoint size = (int) (endByte - beginByte);writeSourceToTargetFileWithBuffer(targetFile, sourceFile, size, beginByte);} catch (Exception e) {e.printStackTrace();} finally {if (sourceFile != null)sourceFile.close();}}復制代碼

    到這里就結束啦,能力有限,寫的不對好的地方,請多提意見。
    項目計劃講一直進行維護升級,謝謝您的關注!!!

    源碼&apk

    • 代碼已上傳Github

    • github APK下載

    • 蒲公英 APK下載

    單元測試

    如果沒有手機或其他原因不方便使用app。項目中提供了單元測試和mp3文件,可以通過單元測試來體驗mp3剪切功能。

    • laozi.mp3是源mp3
    • test.mp3是運行完單元測試,生成的mp3文件。
    • startTime、endTime為剪切的開始時間及結束時間

    后續

    博文被鴻洋發布后,github受到了很多關注,有人提了issue, “部分MP3文件剪切失敗”。原因是之前的mp3剪切中只是對恒定比特率做了支持,在可變比特率那一塊邏輯沒有實現,直接拋了異常。

    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 {...} } 復制代碼

    可以看到之前版本并沒有支持可變比特率。這里講述一下對實現可變比特率mp3剪切的實現思想。

    • 重要的一點:每幀的時間是相等的
    • 公式:每幀比特大小 = ( 每幀采樣次數 × 比特率(bit/s) ÷ 8 ÷采樣率) + Padding
    • mp3總比特大小 = mp3幀數*每幀比特大小
    • 開始時間占總時長比例 = 開始時間/mp3總時長 、結束時間占總時長比例 = 結束時間/mp3總時長
    • 開始時間對應比特 = mp3總比特大小 *開始時間占總時長比例、結束時間對應比特 = mp3總比特大小*結束時間占總時長比例

    上代碼:

    /*** 根據時間和源文件生成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;//幀大小 = ( 每幀采樣次數 × 比特率(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;//結束時間與總時間的比值float endRatio = (float) endTime / (float) trackLengthMs;long startFrameSize = (long) (beginRatio * frameCount * frameSize);long endFrameSize = (long) (endRatio * frameCount * frameSize);//返回音樂數據的第一個字節long firstFrameByte = header.getMp3StartByte();generateTargetMp3File(targetFileStr, startFrameSize, endFrameSize, firstFrameByte);} 復制代碼

    感謝

    • jaudiotagger
    • RXJava
    • RxAndroid
    • greendao
    • StatusBarUtil
    • Dagger2
    • PermissionsDispatcher
    • logger
    • AVLoadingIndicatorView
    • baseAdapter
    • CustomRangeSeekBar

    License

    Mp3Cutter is under CC BY-NC-SA license.

    總結

    以上是生活随笔為你收集整理的开源一款超级好用的mp3剪切器app的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。