Android音頻編輯之音頻轉(zhuǎn)換PCM與WAV
前言
本篇開(kāi)始講解在Android平臺(tái)上進(jìn)行的音頻編輯開(kāi)發(fā),首先需要對(duì)音頻相關(guān)概念有基礎(chǔ)的認(rèn)識(shí)。所以本篇要講解以下內(nèi)容:
1. 常用音頻格式簡(jiǎn)介
2. WAV和PCM的區(qū)別和聯(lián)系
3. WAV文件頭信息
4. 采樣率簡(jiǎn)介
5. 聲道數(shù)和采樣位數(shù)下的PCM編碼
6. 音頻文件解碼
7. PCM文件轉(zhuǎn)WAV文件
現(xiàn)在先給出音頻編輯的效果圖,看看能不能提高大家的積極性~,哈哈
常用音頻格式簡(jiǎn)介
在Android平臺(tái)上進(jìn)行音頻開(kāi)發(fā),首先需要對(duì)常用的音頻格式有個(gè)大致的了解。在Android平臺(tái)上,常用的音頻格式有:
- WAV
WAV格式是微軟公司開(kāi)發(fā)的一種聲音文件格式,也叫波形聲音文件,是最早的數(shù)字音頻格式,被Windows平臺(tái)及其應(yīng)用程序廣泛支持。
WAV格式支持許多壓縮算法,支持多種音頻位數(shù)、采樣頻率和聲道,采用44.1kHz的采樣頻率,16位量化位數(shù),因此WAV的音質(zhì)與CD相差無(wú)幾,但WAV格式對(duì)存儲(chǔ)空間需求太大不便于交流和傳播。
補(bǔ)充:無(wú)損格式,缺點(diǎn):體積十分大!
- MP3
MP3的全稱(chēng)是Moving Picture Experts Group Audio Layer III。簡(jiǎn)單的說(shuō),MP3就是一種音頻壓縮技術(shù),由于這種壓縮方式的全稱(chēng)叫MPEG Audio Layer3,所以人們把它簡(jiǎn)稱(chēng)為MP3。
MP3是利用 MPEG Audio Layer 3 的技術(shù),將音樂(lè)以1:10 甚至 1:12 的壓縮率,壓縮成容量較小的file,換句話(huà)說(shuō),能夠在音質(zhì)丟失很小的情況下把文件壓縮到更小的程度。而且還非常好的保持了原來(lái)的音質(zhì)。
正是因?yàn)镸P3體積小,音質(zhì)高的特點(diǎn)使得MP3格式幾乎成為網(wǎng)上音樂(lè)的代名詞。每分鐘音樂(lè)的MP3格式只有1MB左右大小,這樣每首歌的大小只有3-4MB。使用MP3播放器對(duì)MP3文件進(jìn)行實(shí)時(shí)的解壓縮(解碼),這樣,高品質(zhì)的MP3音樂(lè)就播放出來(lái)了。
補(bǔ)充:最高比特率320K,高頻部分一刀切是他的缺點(diǎn)。音質(zhì)不高!
- AMR
全稱(chēng)Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用于移動(dòng)設(shè)備的音頻,壓縮比比較大,但相對(duì)其他的壓縮格式質(zhì)量比較差,多用于人聲,通話(huà),效果還是很不錯(cuò)的。
- Ogg
Ogg全稱(chēng)應(yīng)該是OGG Vobis(ogg Vorbis) 是一種新的音頻壓縮格式,類(lèi)似于MP3等現(xiàn)有的音樂(lè)格式。
但有一點(diǎn)不同的是,它是完全免費(fèi)、開(kāi)放和沒(méi)有專(zhuān)利限制的。OGG Vobis有一個(gè)很出眾的特點(diǎn),就是支持多聲道,隨著它的流行,以后用隨身聽(tīng)來(lái)聽(tīng)DTS編碼的多聲道作品將不會(huì)是夢(mèng)想。
Vorbis 是這種音頻壓縮機(jī)制的名字,而Ogg則是一個(gè)計(jì)劃的名字,該計(jì)劃意圖設(shè)計(jì)一個(gè)完全開(kāi)放性的多媒體系統(tǒng)。目前該計(jì)劃只實(shí)現(xiàn)了OggVorbis這一部分。
Ogg Vorbis文件的擴(kuò)展名是.OGG。這種文件的設(shè)計(jì)格式是非常先進(jìn)的。現(xiàn)在創(chuàng)建的OGG文件可以在未來(lái)的任何播放器上播放,因此,這種文件格式可以不斷地進(jìn)行大小和音質(zhì)的改良,而不影響舊有的編碼器或播放器。
補(bǔ)充:目前最好的有損格式之一,MP3部分支持,智能手機(jī)裝軟件部分可以支持,最高比特率500kbps。
- AAC
AAC(Advanced Audio Coding),中文稱(chēng)為“高級(jí)音頻編碼”,出現(xiàn)于1997年,基于 MPEG-2的音頻編碼技術(shù)。
優(yōu)點(diǎn):相對(duì)于mp3,AAC格式的音質(zhì)更佳,文件更小。
不足:AAC屬于有損壓縮的格式,與時(shí)下流行的APE、FLAC等無(wú)損格式相比音質(zhì)存在“本質(zhì)上”的差距。加之,目前傳輸速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC頭上“小巧”的光環(huán)不復(fù)存在了。
前景:以發(fā)展的眼光來(lái)看,正如“高清”正在被越來(lái)越多的人所接受一樣,“無(wú)損”必定是未來(lái)音樂(lè)格式的絕對(duì)主流。AAC這種“有損”格式的前景不容樂(lè)觀
- FLAC
FLAC即是Free Lossless Audio Codec的縮寫(xiě),中文可解為無(wú)損音頻壓縮編碼。
FLAC是一套著名的自由音頻壓縮編碼,其特點(diǎn)是無(wú)損壓縮。不同于其他有損壓縮編碼如MP3 及 AAC,它不會(huì)破任何原有的音頻資訊,所以可以還原音樂(lè)光盤(pán)音質(zhì)?,F(xiàn)在它已被很多軟件及硬件音頻產(chǎn)品所支持。簡(jiǎn)而言之,F(xiàn)LAC與MP3相仿,但是是無(wú)損壓縮的,也就是說(shuō)音頻以FLAC方式壓縮不會(huì)丟失任何信息。這種壓縮與Zip的方式類(lèi)似,但是FLAC將給你更大的壓縮比率,因?yàn)镕LAC是專(zhuān)門(mén)針對(duì)音頻的特點(diǎn)設(shè)計(jì)的壓縮方式,并且你可以使用播放器播放FLAC壓縮的文件,就象通常播放你的MP3文件一樣。
補(bǔ)充:為無(wú)損格式,較ape而言,他體積大點(diǎn),但是兼容性好,編碼速度快,播放器支持更廣。
WAV和PCM的區(qū)別和聯(lián)系
在Android平臺(tái)上要進(jìn)行音頻編輯操作(比如裁剪,插入,合成等),通常都是需要將音頻文件解碼為WAV格式的音頻文件或者PCM文件。那么WAV和PCM之間有什么關(guān)系,這里有必要了解一下。
PCM(Pulse Code Modulation—-脈碼調(diào)制錄音)。所謂PCM錄音就是將聲音等模擬信號(hào)變成符號(hào)化的脈沖列,再予以記錄。PCM信號(hào)是由[1]、[0]等符號(hào)構(gòu)成的數(shù)字信號(hào),而未經(jīng)過(guò)任何編碼和壓縮處理。與模擬信號(hào)比,它不易受傳送系統(tǒng)的雜波及失真的影響。動(dòng)態(tài)范圍寬,可得到音質(zhì)相當(dāng)好的影響效果。也就是說(shuō),PCM就是沒(méi)有壓縮的編碼方式,PCM文件就是采用PCM這種沒(méi)有壓縮的編碼方式編碼的音頻數(shù)據(jù)文件。
WAV是由微軟開(kāi)發(fā)的一種音頻格式。WAV符合 PIFF Resource Interchange File Format規(guī)范。所有的WAV都有一個(gè)文件頭,這個(gè)文件頭音頻流的編碼參數(shù)。WAV對(duì)音頻流的編碼沒(méi)有硬性規(guī)定,除了PCM之外,還有幾乎所有支持ACM規(guī)范的編碼都可以為WAV的音頻流進(jìn)行編碼。WAV也可以使用多種音頻編碼來(lái)壓縮其音頻流,不過(guò)我們常見(jiàn)的都是音頻流被PCM編碼處理的WAV,但這不表示W(wǎng)AV只能使用PCM編碼,MP3編碼同樣也可以運(yùn)用在WAV中,和AVI一樣,只要安裝好了相應(yīng)的Decode,就可以欣賞這些WAV了。
在Windows平臺(tái)下,基于PCM編碼的WAV是被支持得最好的音頻格式,所有音頻軟件都能完美支持,由于本身可以達(dá)到較高的音質(zhì)的要求,因此,WAV也是音樂(lè)編輯創(chuàng)作的首選格式,適合保存音樂(lè)素材。因此,基于PCM編碼的WAV被作為了一種中介的格式,常常使用在其他編碼的相互轉(zhuǎn)換之中,例如MP3轉(zhuǎn)換成WMA。
如上引用的描述,也就是說(shuō)我們對(duì)音頻進(jìn)行編輯操作,其實(shí)就是音頻解碼后的PCM音頻采樣數(shù)據(jù)進(jìn)行操作,因?yàn)镻CM記錄的就是采樣后的音頻信息,而我們常說(shuō)的WAV文件是在PCM數(shù)據(jù)的基礎(chǔ)上添加一組頭信息,用于描述這個(gè)WAV文件的采樣率,聲道數(shù),采樣位數(shù),音頻數(shù)據(jù)大小等信息,這樣這個(gè)WAV就可以被音頻播放器正確讀取并播放,而單純的PCM文件因?yàn)橹挥芯幋a的音頻數(shù)據(jù),沒(méi)有其他描述信息,所以無(wú)法被音頻播放器識(shí)別播放。
WAV文件頭信息
接下來(lái)有必要了解一下WAV文件頭信息是什么樣的格式信息。
WAV文件頭信息由大小44個(gè)字節(jié)的數(shù)據(jù)組成:
4字節(jié)數(shù)據(jù),內(nèi)容為“RIFF”,表示資源交換文件標(biāo)識(shí)
4字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)整數(shù),表示從下個(gè)地址開(kāi)始到文件尾的總字節(jié)數(shù)
4字節(jié)數(shù)據(jù),內(nèi)容為“WAVE”,表示W(wǎng)AV文件標(biāo)識(shí)
4字節(jié)數(shù)據(jù),內(nèi)容為“fmt ”,表示波形格式標(biāo)識(shí)(fmt ),最后一位空格。
4字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)整數(shù),表示PCMWAVEFORMAT的長(zhǎng)度
2字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)短整數(shù),表示格式種類(lèi)(值為1時(shí),表示數(shù)據(jù)為線(xiàn)性PCM編碼)
2字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)短整數(shù),表示通道數(shù),單聲道為1,雙聲道為2
4字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)整數(shù),表示采樣率,比如44100
4字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)整數(shù),表示波形數(shù)據(jù)傳輸速率(每秒平均字節(jié)數(shù)),大小為 采樣率 * 通道數(shù) * 采樣位數(shù)
2字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)短整數(shù),表示DATA數(shù)據(jù)塊長(zhǎng)度,大小為 通道數(shù) * 采樣位數(shù)
2字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)短整數(shù),表示采樣位數(shù),即PCM位寬,通常為8位或16位
4字節(jié)數(shù)據(jù),內(nèi)容為“data”,表示數(shù)據(jù)標(biāo)記符
4字節(jié)數(shù)據(jù),內(nèi)容為一個(gè)整數(shù),表示接下來(lái)聲音數(shù)據(jù)的總大小
由以上信息可知,對(duì)于一個(gè)PCM文件來(lái)說(shuō),只要知道它的大小,采樣率,聲道數(shù),采樣位數(shù),就可以通過(guò)添加一個(gè)WAV文件頭得到一個(gè)WAV文件了。
采樣率簡(jiǎn)介
那么采樣率是什么意思,我們來(lái)了解下。
音頻采樣率是指錄音設(shè)備在一秒鐘內(nèi)對(duì)聲音信號(hào)的采樣次數(shù),采樣頻率越高聲音的還原就越真實(shí)越自然。在當(dāng)今的主流采集卡上,采樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個(gè)等級(jí),22.05KHz只能達(dá)到FM廣播的聲音品質(zhì),44.1KHz則是理論上的CD音質(zhì)界限,48KHz則更加精確一些。
在數(shù)字音頻領(lǐng)域,常用的采樣率有:
8,000 Hz - 電話(huà)所用采樣率, 對(duì)于人的說(shuō)話(huà)已經(jīng)足夠
11,025 Hz
22,050 Hz - 無(wú)線(xiàn)電廣播所用采樣率
32,000 Hz - miniDV 數(shù)碼視頻 camcorder、DAT (LP mode)所用采樣率
44,100 Hz - 音頻 CD, 也常用于 MPEG-1 音頻(VCD, SVCD, MP3)所用采樣率
47,250 Hz - 商用 PCM 錄音機(jī)所用采樣率
48,000 Hz - miniDV、數(shù)字電視、DVD、DAT、電影和專(zhuān)業(yè)音頻所用的數(shù)字聲音所用采樣率
50,000 Hz - 商用數(shù)字錄音機(jī)所用采樣率
96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音軌、BD-ROM(藍(lán)光盤(pán))音軌、和 HD-DVD (高清晰度 DVD)音軌所用所用采樣率
2.8224 MHz - Direct Stream Digital 的 1 位 sigma-delta modulation 過(guò)程所用采樣率。
通常歌曲的采樣率是44100,而Android平臺(tái)的人聲錄音支持8000,16000,32000三種采樣率。
聲道數(shù)和采樣位數(shù)下的PCM編碼
接下來(lái)再了解下聲道數(shù)和采樣位數(shù)代表什么意思,在PCM編碼中是如何應(yīng)用的。
聲道通??梢苑譃閱温暤篮碗p聲道,雙聲道又分為左聲道和右聲道。
采樣位數(shù)表示一個(gè)采樣數(shù)據(jù)用多少位來(lái)表示,通常為8位和16位,對(duì)于8位表示一個(gè)字節(jié)來(lái)表示一個(gè)采樣數(shù)據(jù),16位表示用兩個(gè)字節(jié)表示一個(gè)采樣數(shù)據(jù),兩個(gè)字節(jié)為低位字節(jié)和高位字節(jié),通常低位字節(jié)在前,高位字節(jié)在后。
因此結(jié)合聲道和采樣字節(jié)數(shù)(采樣位數(shù)),可以組成下圖的PCM數(shù)據(jù)格式:
可以看到8位單聲道的PCM數(shù)據(jù),只需要一個(gè)字節(jié)就能表示一個(gè)采樣數(shù)據(jù),而16位雙聲道(立體聲)的PCM數(shù)據(jù),需要4個(gè)字節(jié)來(lái)表示一個(gè)采樣數(shù)據(jù)。那么計(jì)算一個(gè)PCM大小的方法就很簡(jiǎn)單了。
對(duì)于8位單聲道,采樣率為8000,1分鐘的PCM音頻來(lái)說(shuō),大小是
//采樣率 * 通道數(shù) * 采樣位數(shù)/8 * 秒數(shù) 8000 * 1 * 8/8 * 60 = 480000,大約480k
對(duì)于16位雙聲道,采樣率為44100,1分鐘的PCM音頻來(lái)說(shuō),大小是
//采樣率 * 通道數(shù) * 采樣位數(shù)/8 * 秒數(shù) 44100 * 2 * 16/8 * 60 = 10584000,大約10M
而WAV文件的大小就是比PCM多出44個(gè)字節(jié)數(shù)。
音頻文件解碼
有了以上音頻相關(guān)知識(shí)的了解之后,現(xiàn)在可以來(lái)對(duì)android上常用音頻文件進(jìn)行解碼和信息提取了。這里涉及了三個(gè)音頻相關(guān)的類(lèi):
- MediaExtractor 媒體文件數(shù)據(jù)提取器,負(fù)責(zé)媒體文件數(shù)據(jù)的提取操作。
- MediaFormat 媒體文件格式信息,負(fù)責(zé)讀取媒體文件的格式(如采樣率,時(shí)長(zhǎng),聲道數(shù)等)信息。
- MediaCodec 媒體文件編解碼類(lèi),負(fù)責(zé)媒體文件數(shù)據(jù)的編解碼操作。
解碼器支持解碼常用的音頻格式,如mp3, wav, 3gpp, 3gp, amr, aac, m4a, ogg, flac等,解碼后的數(shù)據(jù)是PCM編碼的數(shù)據(jù)。下面用代碼實(shí)現(xiàn)下如何用上述類(lèi)實(shí)現(xiàn)音頻文件的解碼操作,得到一個(gè)PCM數(shù)據(jù)文件
/** * 將音樂(lè)文件解碼 * * @param musicFileUrl 源文件路徑 * @param decodeFileUrl 解碼文件路徑 * @param startMicroseconds 開(kāi)始時(shí)間 微秒 * @param endMicroseconds 結(jié)束時(shí)間 微秒 * @param decodeOperateInterface 解碼過(guò)程回調(diào) */ private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl, long startMicroseconds, long endMicroseconds, DecodeOperateInterface decodeOperateInterface) { //采樣率,聲道數(shù),時(shí)長(zhǎng),音頻文件類(lèi)型 int sampleRate = 0; int channelCount = 0; long duration = 0; String mime = null; //MediaExtractor, MediaFormat, MediaCodec MediaExtractor mediaExtractor = new MediaExtractor(); MediaFormat mediaFormat = null; MediaCodec mediaCodec = null; //給媒體信息提取器設(shè)置源音頻文件路徑 try { mediaExtractor.setDataSource(musicFileUrl); }catch (Exception ex){ ex.printStackTrace(); try { mediaExtractor.setDataSource(new FileInputStream(musicFileUrl).getFD()); } catch (Exception e) { e.printStackTrace(); LogUtil.e("設(shè)置解碼音頻文件路徑錯(cuò)誤"); } } //獲取音頻格式軌信息 mediaFormat = mediaExtractor.getTrackFormat(0); //從音頻格式軌信息中讀取 采樣率,聲道數(shù),時(shí)長(zhǎng),音頻文件類(lèi)型 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; mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) : ""; LogUtil.i("歌曲信息Track info: mime:" + mime + " 采樣率sampleRate:" + sampleRate + " channels:" + channelCount + " duration:" + duration); if (TextUtils.isEmpty(mime) || !mime.startsWith("audio/")) { LogUtil.e("解碼文件不是音頻文件mime:" + mime); return false; } if (mime.equals("audio/ffmpeg")) { mime = "audio/mpeg"; mediaFormat.setString(MediaFormat.KEY_MIME, mime); } if (duration <= 0) { LogUtil.e("音頻文件duration為" + duration); return false; } //解碼的開(kāi)始時(shí)間和結(jié)束時(shí)間 startMicroseconds = Math.max(startMicroseconds, 0); endMicroseconds = endMicroseconds < 0 ? duration : endMicroseconds; endMicroseconds = Math.min(endMicroseconds, duration); if (startMicroseconds >= endMicroseconds) { return false; } //創(chuàng)建一個(gè)解碼器 try { mediaCodec = MediaCodec.createDecoderByType(mime); mediaCodec.configure(mediaFormat, null, null, 0); } catch (Exception e) { LogUtil.e("解碼器configure出錯(cuò)"); return false; } //得到輸出PCM文件的路徑 decodeFileUrl = decodeFileUrl.substring(0, decodeFileUrl.lastIndexOf(".")); String pcmFilePath = decodeFileUrl + ".pcm"; //后續(xù)解碼操作 getDecodeData(mediaExtractor, mediaCodec, pcmFilePath, sampleRate, channelCount, startMicroseconds, endMicroseconds, decodeOperateInterface); return true; }
以上操作創(chuàng)建了MediaExtractor,獲取MediaFormat用于讀取音頻文件的相關(guān)信息如采樣率,文件類(lèi)型,聲道數(shù)等。然后創(chuàng)建了MediaCodec用于后續(xù)和MediaExtractor一起進(jìn)行音頻的解碼操作。接下來(lái)看看具體的解碼過(guò)程:
/** * 解碼數(shù)據(jù) */ private void getDecodeData(MediaExtractor mediaExtractor, MediaCodec mediaCodec, String decodeFileUrl, int sampleRate, int channelCount, final long startMicroseconds, final long endMicroseconds, final DecodeOperateInterface decodeOperateInterface) { //初始化解碼狀態(tài),未解析完成 boolean decodeInputEnd = false; boolean decodeOutputEnd = false; //當(dāng)前讀取采樣數(shù)據(jù)的大小 int sampleDataSize; //當(dāng)前輸入數(shù)據(jù)的ByteBuffer序號(hào),當(dāng)前輸出數(shù)據(jù)的ByteBuffer序號(hào) int inputBufferIndex; int outputBufferIndex; //音頻文件的采樣位數(shù)字節(jié)數(shù),= 采樣位數(shù)/8 int byteNumber; //上一次的解碼操作時(shí)間,當(dāng)前解碼操作時(shí)間,用于通知回調(diào)接口 long decodeNoticeTime = System.currentTimeMillis(); long decodeTime; //當(dāng)前采樣的音頻時(shí)間,比如在當(dāng)前音頻的第40秒的時(shí)候 long presentationTimeUs = 0; //定義編解碼的超時(shí)時(shí)間 final long timeOutUs = 100; //存儲(chǔ)輸入數(shù)據(jù)的ByteBuffer數(shù)組,輸出數(shù)據(jù)的ByteBuffer數(shù)組 ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; //當(dāng)前編解碼器操作的 輸入數(shù)據(jù)ByteBuffer 和 輸出數(shù)據(jù)ByteBuffer,可以從targetBuffer中獲取解碼后的PCM數(shù)據(jù) ByteBuffer sourceBuffer; ByteBuffer targetBuffer; //獲取輸出音頻的媒體格式信息 MediaFormat outputFormat = mediaCodec.getOutputFormat(); MediaCodec.BufferInfo bufferInfo; byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8; //開(kāi)始解碼操作 mediaCodec.start(); //獲取存儲(chǔ)輸入數(shù)據(jù)的ByteBuffer數(shù)組,輸出數(shù)據(jù)的ByteBuffer數(shù)組 inputBuffers = mediaCodec.getInputBuffers(); outputBuffers = mediaCodec.getOutputBuffers(); mediaExtractor.selectTrack(0); //當(dāng)前解碼的緩存信息,里面的有效數(shù)據(jù)在offset和offset+size之間 bufferInfo = new MediaCodec.BufferInfo(); //獲取解碼后文件的輸出流 BufferedOutputStream bufferedOutputStream = FileFunction.getBufferedOutputStreamFromFile(decodeFileUrl); //開(kāi)始進(jìn)入循環(huán)解碼操作,判斷讀入源音頻數(shù)據(jù)是否完成,輸出解碼音頻數(shù)據(jù)是否完成 while (!decodeOutputEnd) { if (decodeInputEnd) { return; } decodeTime = System.currentTimeMillis(); //間隔1秒通知解碼進(jìn)度 if (decodeTime - decodeNoticeTime > Constant.OneSecond) { final int decodeProgress = (int) ((presentationTimeUs - startMicroseconds) * Constant.NormalMaxProgress / endMicroseconds); if (decodeProgress > 0) { notifyProgress(decodeOperateInterface, decodeProgress); } decodeNoticeTime = decodeTime; } try { //操作解碼輸入數(shù)據(jù) //從隊(duì)列中獲取當(dāng)前解碼器處理輸入數(shù)據(jù)的ByteBuffer序號(hào) inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs); if (inputBufferIndex >= 0) { //取得當(dāng)前解碼器處理輸入數(shù)據(jù)的ByteBuffer sourceBuffer = inputBuffers[inputBufferIndex]; //獲取當(dāng)前ByteBuffer,編解碼器讀取了多少采樣數(shù)據(jù) sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0); //如果當(dāng)前讀取的采樣數(shù)據(jù)<0,說(shuō)明已經(jīng)完成了讀取操作 if (sampleDataSize < 0) { decodeInputEnd = true; sampleDataSize = 0; } else { presentationTimeUs = mediaExtractor.getSampleTime(); } //然后將當(dāng)前ByteBuffer重新加入到隊(duì)列中交給編解碼器做下一步讀取操作 mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize, presentationTimeUs, decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); //前進(jìn)到下一段采樣數(shù)據(jù) if (!decodeInputEnd) { mediaExtractor.advance(); } } else { //LogUtil.e("inputBufferIndex" + inputBufferIndex); } //操作解碼輸出數(shù)據(jù) //從隊(duì)列中獲取當(dāng)前解碼器處理輸出數(shù)據(jù)的ByteBuffer序號(hào) outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs); if (outputBufferIndex < 0) { //輸出ByteBuffer序號(hào)<0,可能是輸出緩存變化了,輸出格式信息變化了 switch (outputBufferIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = mediaCodec.getOutputBuffers(); LogUtil.e( "MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED [AudioDecoder]output buffers have changed."); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: outputFormat = mediaCodec.getOutputFormat(); sampleRate = outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? outputFormat.getInteger( MediaFormat.KEY_SAMPLE_RATE) : sampleRate; channelCount = outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? outputFormat.getInteger( MediaFormat.KEY_CHANNEL_COUNT) : channelCount; byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8; LogUtil.e( "MediaCodec.INFO_OUTPUT_FORMAT_CHANGED [AudioDecoder]output format has changed to " + mediaCodec.getOutputFormat()); break; default: //LogUtil.e("error [AudioDecoder] dequeueOutputBuffer returned " + outputBufferIndex); break; } continue; } //取得當(dāng)前解碼器處理輸出數(shù)據(jù)的ByteBuffer targetBuffer = outputBuffers[outputBufferIndex]; byte[] sourceByteArray = new byte[bufferInfo.size]; //將解碼后的targetBuffer中的數(shù)據(jù)復(fù)制到sourceByteArray中 targetBuffer.get(sourceByteArray); targetBuffer.clear(); //釋放當(dāng)前的輸出緩存 mediaCodec.releaseOutputBuffer(outputBufferIndex, false); //判斷當(dāng)前是否解碼數(shù)據(jù)全部結(jié)束了 if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { decodeOutputEnd = true; } //sourceByteArray就是最終解碼后的采樣數(shù)據(jù) //接下來(lái)可以對(duì)這些數(shù)據(jù)進(jìn)行采樣位數(shù),聲道的轉(zhuǎn)換,但這是可選的,默認(rèn)是和源音頻一樣的聲道和采樣位數(shù) if (sourceByteArray.length > 0 && bufferedOutputStream != null) { if (presentationTimeUs < startMicroseconds) { continue; } //采樣位數(shù)轉(zhuǎn)換,按自己需要是否實(shí)現(xiàn) byte[] convertByteNumberByteArray = convertByteNumber(byteNumber, Constant.ExportByteNumber, sourceByteArray); //聲道轉(zhuǎn)換,按自己需要是否實(shí)現(xiàn) byte[] resultByteArray = convertChannelNumber(channelCount, Constant.ExportChannelNumber, Constant.ExportByteNumber, convertByteNumberByteArray); //將解碼后的PCM數(shù)據(jù)寫(xiě)入到PCM文件 try { bufferedOutputStream.write(resultByteArray); } catch (Exception e) { LogUtil.e("輸出解壓音頻數(shù)據(jù)異常" + e); } } if (presentationTimeUs > endMicroseconds) { break; } } catch (Exception e) { LogUtil.e("getDecodeData異常" + e); } } if (bufferedOutputStream != null) { try { bufferedOutputStream.close(); } catch (IOException e) { LogUtil.e("關(guān)閉bufferedOutputStream異常" + e); } } //重置采樣率,按自己需要是否實(shí)現(xiàn) if (sampleRate != Constant.ExportSampleRate) { Resample(sampleRate, decodeFileUrl); } notifyProgress(decodeOperateInterface, 100); //釋放mediaCodec 和 mediaExtractor if (mediaCodec != null) { mediaCodec.stop(); mediaCodec.release(); } if (mediaExtractor != null) { mediaExtractor.release(); } }
以上操作是在一個(gè)循環(huán)中,不斷取得源音頻輸入數(shù)據(jù),加入到輸入隊(duì)列中,交給MediaCodec處理,然后再?gòu)慕獯a后的輸出隊(duì)列中取得輸出數(shù)據(jù),寫(xiě)入到文件中,其中要判斷源音頻輸入數(shù)據(jù)是否讀取完畢,解碼后的輸出數(shù)據(jù)是否完成,來(lái)終止這個(gè)循環(huán)。后續(xù)的采樣位數(shù)轉(zhuǎn)換,聲道數(shù)轉(zhuǎn)換,以及采樣率轉(zhuǎn)換都是可選的,不是必須的,默認(rèn)不實(shí)現(xiàn)的話(huà),輸出的PCM數(shù)據(jù)和源音頻是一樣的采樣位數(shù),聲道數(shù),和采樣率。
PCM文件轉(zhuǎn)WAV文件
現(xiàn)在我們得到了解碼后的PCM文件,但是它是不可直接播放的,因?yàn)椴粠б纛l相關(guān)的格式信息,下面我們將PCM和指定的音頻相關(guān)格式信息去轉(zhuǎn)換得到一個(gè)可播放的WAV文件:
/** * PCM文件轉(zhuǎn)WAV文件 * @param inPcmFilePath 輸入PCM文件路徑 * @param outWavFilePath 輸出WAV文件路徑 * @param sampleRate 采樣率,例如44100 * @param channels 聲道數(shù) 單聲道:1或雙聲道:2 * @param bitNum 采樣位數(shù),8或16 */ public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate, int channels, int bitNum) { FileInputStream in = null; FileOutputStream out = null; byte[] data = new byte[1024]; try { //采樣字節(jié)byte率 long byteRate = sampleRate * channels * bitNum / 8; in = new FileInputStream(inPcmFilePath); out = new FileOutputStream(outWavFilePath); //PCM文件大小 long totalAudioLen = in.getChannel().size(); //總大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小 long totalDataLen = totalAudioLen + 36; writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate); int length = 0; while ((length = in.read(data)) > 0) { out.write(data, 0, length); } } catch (Exception e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 輸出WAV文件 * @param out WAV輸出文件流 * @param totalAudioLen 整個(gè)音頻PCM數(shù)據(jù)大小 * @param totalDataLen 整個(gè)數(shù)據(jù)大小 * @param sampleRate 采樣率 * @param channels 聲道數(shù) * @param byteRate 采樣字節(jié)byte率 * @throws IOException */ private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException { byte[] header = new byte[44]; header[0] = 'R'; // RIFF header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff);//數(shù)據(jù)大小 header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W';//WAVE header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; //FMT Chunk header[12] = 'f'; // 'fmt ' header[13] = 'm'; header[14] = 't'; header[15] = ' ';//過(guò)渡字節(jié) //數(shù)據(jù)大小 header[16] = 16; // 4 bytes: size of 'fmt ' chunk header[17] = 0; header[18] = 0; header[19] = 0; //編碼方式 10H為PCM編碼格式 header[20] = 1; // format = 1 header[21] = 0; //通道數(shù) header[22] = (byte) channels; header[23] = 0; //采樣率,每個(gè)通道的播放速度 header[24] = (byte) (sampleRate & 0xff); header[25] = (byte) ((sampleRate >> 8) & 0xff); header[26] = (byte) ((sampleRate >> 16) & 0xff); header[27] = (byte) ((sampleRate >> 24) & 0xff); //音頻數(shù)據(jù)傳送速率,采樣率*通道數(shù)*采樣深度/8 header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); // 確定系統(tǒng)一次要處理多少個(gè)這樣字節(jié)的數(shù)據(jù),確定緩沖區(qū),通道數(shù)*采樣位數(shù) header[32] = (byte) (channels * 16 / 8); header[33] = 0; //每個(gè)樣本的數(shù)據(jù)位數(shù) header[34] = 16; header[35] = 0; //Data chunk header[36] = 'd';//data 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); }
上面操作其實(shí)也很簡(jiǎn)單,只要你知道了WAV文件頭信息的格式,將采樣率,聲道數(shù),采樣位數(shù),PCM音頻數(shù)據(jù)大小等信息填充進(jìn)去,然后將這個(gè)44個(gè)字節(jié)數(shù)據(jù)拼接到PCM文件的開(kāi)頭,就得到了一個(gè)可播放的WAV文件了。
總結(jié)
上文講解了常用音頻文件的格式,采樣率,聲道,采樣位數(shù)概念,以及PCM數(shù)據(jù)是如何構(gòu)成等內(nèi)容。然后是如何從音頻文件解碼為PCM數(shù)據(jù)文件,以及得到PCM編碼的WAV文件,有了以上的理解后,后續(xù)進(jìn)行音頻文件的裁剪,插入,合成等編輯操作就更容易理解了。請(qǐng)繼續(xù)關(guān)注后續(xù)的音頻編輯操作處理。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android音頻編輯之音頻合成功能
- android采用FFmpeg實(shí)現(xiàn)音頻混合與拼接剪切
- Android 判斷網(wǎng)絡(luò)狀態(tài)對(duì)音頻靜音的實(shí)現(xiàn)方法
- Android使用MediaRecorder類(lèi)實(shí)現(xiàn)視頻和音頻錄制功能
- Android視頻/音頻緩存框架AndroidVideoCache(Okhttp)詳解
- Android音頻系統(tǒng)AudioTrack使用方法詳解
- Android線(xiàn)程中Handle的使用講解
- Android傳感器SensorEventListener之加速度傳感器
- Android亮屏速度分析總結(jié)
- Android添加音頻的幾種方法
相關(guān)文章
Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解
這篇文章主要為大家介紹了Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Android自定義Spinner下拉列表(使用ArrayAdapter和自定義Adapter實(shí)現(xiàn))
這篇文章主要介紹了Android自定義Spinner下拉列表(使用ArrayAdapter和自定義Adapter實(shí)現(xiàn))的相關(guān)資料,需要的朋友可以參考下2015-10-10Android使用系統(tǒng)自帶的相機(jī)實(shí)現(xiàn)一鍵拍照功能
這篇文章主要介紹了Android使用系統(tǒng)自帶的相機(jī)實(shí)現(xiàn)一鍵拍照功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-01-01Android基礎(chǔ)教程數(shù)據(jù)存儲(chǔ)之文件存儲(chǔ)
這篇文章主要介紹了Android基礎(chǔ)教程數(shù)據(jù)存儲(chǔ)之文件存儲(chǔ)的相關(guān)資料,數(shù)據(jù)存儲(chǔ)是Android開(kāi)發(fā)的重要的知識(shí),這里提供了實(shí)例,需要的朋友可以參考下2017-07-07Android Studio中配置OpenCV庫(kù)開(kāi)發(fā)環(huán)境的教程
這篇文章主要介紹了Android Studio中配置OpenCV庫(kù)開(kāi)發(fā)環(huán)境的教程,OpenCV有Java接口,因而也經(jīng)常被用來(lái)做安卓開(kāi)發(fā),需要的朋友可以參考下2016-05-05Android關(guān)于SeekBar無(wú)法點(diǎn)擊到最大值問(wèn)題解決方法記錄(推薦)
這篇文章主要介紹了Android關(guān)于SeekBar無(wú)法點(diǎn)擊到最大值問(wèn)題解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Android利用Glide獲取圖片真正的寬高的實(shí)例
本篇文章主要介紹了Android利用Glide獲取圖片真正的寬高的實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08