轉載請標明出處:
http://blog.csdn.net/hesong1120/article/details/79043482
本文出自:hesong的專欄android
本篇開始講解在Android平臺上進行的音頻編輯開發,首先須要對音頻相關概念有基礎的認識。因此本篇要講解如下內容:git
如今先給出音頻編輯的效果圖,看看能不能提升你們的積極性~,哈哈github
在Android平臺上進行音頻開發,首先須要對經常使用的音頻格式有個大體的瞭解。在Android平臺上,經常使用的音頻格式有:算法
WAV格式是微軟公司開發的一種聲音文件格式,也叫波形聲音文件,是最先的數字音頻格式,被Windows平臺及其應用程序普遍支持。
WAV格式支持許多壓縮算法,支持多種音頻位數、採樣頻率和聲道,採用44.1kHz的採樣頻率,16位量化位數,所以WAV的音質與CD相差無幾,但WAV格式對存儲空間需求太大不便於交流和傳播。
補充:無損格式,缺點:體積十分大!數組
MP3的全稱是Moving Picture Experts Group Audio Layer III。簡單的說,MP3就是一種音頻壓縮技術,因爲這種壓縮方式的全稱叫MPEG Audio Layer3,因此人們把它簡稱爲MP3。 MP3是利用 MPEG Audio Layer 3 的技術,將音樂以1:10 甚至 1:12 的壓縮率,壓縮成容量較小的file,換句話說,可以在音質丟失很小的狀況下把文件壓縮到更小的程度。並且還很是好的保持了原來的音質。
正是由於MP3體積小,音質高的特色使得MP3格式幾乎成爲網上音樂的代名詞。每分鐘音樂的MP3格式只有1MB左右大小,這樣每首歌的大小隻有3-4MB。使用MP3播放器對MP3文件進行實時的解壓縮(解碼),這樣,高品質的MP3音樂就播放出來了。
補充:最高比特率320K,高頻部分一刀切是他的缺點。音質不高!緩存
全稱Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用於移動設備的音頻,壓縮比比較大,但相對其餘的壓縮格式質量比較差,多用於人聲,通話,效果仍是很不錯的。bash
Ogg全稱應該是OGG Vobis(ogg Vorbis) 是一種新的音頻壓縮格式,相似於MP3等現有的音樂格式。 但有一點不一樣的是,它是徹底免費、開放和沒有專利限制的。OGG Vobis有一個很出衆的特色,就是支持多聲道,隨着它的流行,之後用隨身聽來聽DTS編碼的多聲道做品將不會是夢想。
Vorbis 是這種音頻壓縮機制的名字,而Ogg則是一個計劃的名字,該計劃意圖設計一個徹底開放性的多媒體系統。目前該計劃只實現了OggVorbis這一部分。
Ogg Vorbis文件的擴展名是.OGG。這種文件的設計格式是很是先進的。如今建立的OGG文件能夠在將來的任何播放器上播放,所以,這種文件格式能夠不斷地進行大小和音質的改良,而不影響舊有的編碼器或播放器。 補充:目前最好的有損格式之一,MP3部分支持,智能手機裝軟件部分能夠支持,最高比特率500kbps。微信
AAC(Advanced Audio Coding),中文稱爲「高級音頻編碼」,出現於1997年,基於 MPEG-2的音頻編碼技術。
優勢:相對於mp3,AAC格式的音質更佳,文件更小。
不足:AAC屬於有損壓縮的格式,與時下流行的APE、FLAC等無損格式相比音質存在「本質上」的差距。加之,目前傳輸速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC頭上「小巧」的光環不復存在了。
前景:以發展的眼光來看,正如「高清」正在被愈來愈多的人所接受同樣,「無損」一定是將來音樂格式的絕對主流。AAC這種「有損」格式的前景不容樂觀less
FLAC便是Free Lossless Audio Codec的縮寫,中文可解爲無損音頻壓縮編碼。
FLAC是一套著名的自由音頻壓縮編碼,其特色是無損壓縮。不一樣於其餘有損壓縮編碼如MP3 及 AAC,它不會破任何原有的音頻資訊,因此能夠還原音樂光盤音質。如今它已被不少軟件及硬件音頻產品所支持。簡而言之,FLAC與MP3相仿,可是是無損壓縮的,也就是說音頻以FLAC方式壓縮不會丟失任何信息。這種壓縮與Zip的方式相似,可是FLAC將給你更大的壓縮比率,由於FLAC是專門針對音頻的特色設計的壓縮方式,而且你可使用播放器播放FLAC壓縮的文件,就象一般播放你的MP3文件同樣。
補充:爲無損格式,較ape而言,他體積大點,可是兼容性好,編碼速度快,播放器支持更廣。ide
在Android平臺上要進行音頻編輯操做(好比裁剪,插入,合成等),一般都是須要將音頻文件解碼爲WAV格式的音頻文件或者PCM文件。那麼WAV和PCM之間有什麼關係,這裏有必要了解一下。
PCM(Pulse Code Modulation----脈碼調製錄音)。所謂PCM錄音就是將聲音等模擬信號變成符號化的脈衝列,再予以記錄。PCM信號是由[1]、[0]等符號構成的數字信號,而未通過任何編碼和壓縮處理。與模擬信號比,它不易受傳送系統的雜波及失真的影響。動態範圍寬,可獲得音質至關好的影響效果。也就是說,PCM就是沒有壓縮的編碼方式,PCM文件就是採用PCM這種沒有壓縮的編碼方式編碼的音頻數據文件。
WAV是由微軟開發的一種音頻格式。WAV符合 PIFF Resource Interchange File Format規範。全部的WAV都有一個文件頭,這個文件頭音頻流的編碼參數。WAV對音頻流的編碼沒有硬性規定,除了PCM以外,還有幾乎全部支持ACM規範的編碼均可覺得WAV的音頻流進行編碼。WAV也可使用多種音頻編碼來壓縮其音頻流,不過咱們常見的都是音頻流被PCM編碼處理的WAV,但這不表示WAV只能使用PCM編碼,MP3編碼一樣也能夠運用在WAV中,和AVI同樣,只要安裝好了相應的Decode,就能夠欣賞這些WAV了。
在Windows平臺下,基於PCM編碼的WAV是被支持得最好的音頻格式,全部音頻軟件都能完美支持,因爲自己能夠達到較高的音質的要求,所以,WAV也是音樂編輯創做的首選格式,適合保存音樂素材。所以,基於PCM編碼的WAV被做爲了一種中介的格式,經常使用在其餘編碼的相互轉換之中,例如MP3轉換成WMA。
如上引用的描述,也就是說咱們對音頻進行編輯操做,其實就是音頻解碼後的PCM音頻採樣數據進行操做,由於PCM記錄的就是採樣後的音頻信息,而咱們常說的WAV文件是在PCM數據的基礎上添加一組頭信息,用於描述這個WAV文件的採樣率,聲道數,採樣位數,音頻數據大小等信息,這樣這個WAV就能夠被音頻播放器正確讀取並播放,而單純的PCM文件由於只有編碼的音頻數據,沒有其餘描述信息,因此沒法被音頻播放器識別播放。
接下來有必要了解一下WAV文件頭信息是什麼樣的格式信息。
WAV文件頭信息由大小44個字節的數據組成:
- 4字節數據,內容爲「RIFF」,表示資源交換文件標識
- 4字節數據,內容爲一個整數,表示從下個地址開始到文件尾的總字節數
- 4字節數據,內容爲「WAVE」,表示WAV文件標識
- 4字節數據,內容爲「fmt 」,表示波形格式標識(fmt ),最後一位空格。
- 4字節數據,內容爲一個整數,表示PCMWAVEFORMAT的長度
- 2字節數據,內容爲一個短整數,表示格式種類(值爲1時,表示數據爲線性PCM編碼)
- 2字節數據,內容爲一個短整數,表示通道數,單聲道爲1,雙聲道爲2
- 4字節數據,內容爲一個整數,表示採樣率,好比44100
- 4字節數據,內容爲一個整數,表示波形數據傳輸速率(每秒平均字節數),大小爲 採樣率 * 通道數 * 採樣位數
- 2字節數據,內容爲一個短整數,表示DATA數據塊長度,大小爲 通道數 * 採樣位數
- 2字節數據,內容爲一個短整數,表示採樣位數,即PCM位寬,一般爲8位或16位
- 4字節數據,內容爲「data」,表示數據標記符
- 4字節數據,內容爲一個整數,表示接下來聲音數據的總大小
由以上信息可知,對於一個PCM文件來講,只要知道它的大小,採樣率,聲道數,採樣位數,就能夠經過添加一個WAV文件頭獲得一個WAV文件了。
那麼採樣率是什麼意思,咱們來了解下。
音頻採樣率是指錄音設備在一秒鐘內對聲音信號的採樣次數,採樣頻率越高聲音的還原就越真實越天然。在當今的主流採集卡上,採樣頻率通常共分爲22.05KHz、44.1KHz、48KHz三個等級,22.05KHz只能達到FM廣播的聲音品質,44.1KHz則是理論上的CD音質界限,48KHz則更加精確一些。
在數字音頻領域,經常使用的採樣率有:
8,000 Hz - 電話所用採樣率, 對於人的說話已經足夠 11,025 Hz
22,050 Hz - 無線電廣播所用採樣率
32,000 Hz - miniDV 數碼視頻 camcorder、DAT (LP mode)所用採樣率
44,100 Hz - 音頻 CD, 也經常使用於 MPEG-1 音頻(VCD, SVCD, MP3)所用採樣率
47,250 Hz - 商用 PCM 錄音機所用採樣率
48,000 Hz - miniDV、數字電視、DVD、DAT、電影和專業音頻所用的數字聲音所用採樣率
50,000 Hz - 商用數字錄音機所用採樣率
96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音軌、BD-ROM(藍光盤)音軌、和 HD-DVD (高清晰度 DVD)音軌所用所用採樣率
2.8224 MHz - Direct Stream Digital 的 1 位 sigma-delta modulation 過程所用採樣率。
一般歌曲的採樣率是44100,而Android平臺的人聲錄音支持8000,16000,32000三種採樣率。
接下來再瞭解下聲道數和採樣位數表明什麼意思,在PCM編碼中是如何應用的。
聲道一般能夠分爲單聲道和雙聲道,雙聲道又分爲左聲道和右聲道。
採樣位數表示一個採樣數據用多少位來表示,一般爲8位和16位,對於8位表示一個字節來表示一個採樣數據,15位表示用兩個字節表示一個採樣數據,兩個字節爲低位字節和高位字節,一般低位字節在前,高位字節在後。
所以結合聲道和採樣字節數(採樣位數),能夠組成下圖的PCM數據格式:
能夠看到8位單聲道的PCM數據,只須要一個字節就能表示一個採樣數據,而16位雙聲道(立體聲)的PCM數據,須要4個字節來表示一個採樣數據。那麼計算一個PCM大小的方法就很簡單了。
對於8位單聲道,採樣率爲8000,1分鐘的PCM音頻來講,大小是
//採樣率 * 通道數 * 採樣位數/8 * 秒數
8000 * 1 * 8/8 * 60 = 480000,大約480k
複製代碼
對於16位雙聲道,採樣率爲44100,1分鐘的PCM音頻來講,大小是
//採樣率 * 通道數 * 採樣位數/8 * 秒數
44100 * 2 * 16/8 * 60 = 10584000,大約10M
複製代碼
而WAV文件的大小就是比PCM多出44個字節數。
有了以上音頻相關知識的瞭解以後,如今能夠來對android上經常使用音頻文件進行解碼和信息提取了。這裏涉及了三個音頻相關的類:
解碼器支持解碼經常使用的音頻格式,如mp3, wav, 3gpp, 3gp, amr, aac, m4a, ogg, flac等,解碼後的數據是PCM編碼的數據。下面用代碼實現下如何用上述類實現音頻文件的解碼操做,獲得一個PCM數據文件
/**
* 將音樂文件解碼
*
* @param musicFileUrl 源文件路徑
* @param decodeFileUrl 解碼文件路徑
* @param startMicroseconds 開始時間 微秒
* @param endMicroseconds 結束時間 微秒
* @param decodeOperateInterface 解碼過程回調
*/
private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl,
long startMicroseconds, long endMicroseconds, DecodeOperateInterface decodeOperateInterface) {
//採樣率,聲道數,時長,音頻文件類型
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;
//給媒體信息提取器設置源音頻文件路徑
try {
mediaExtractor.setDataSource(musicFileUrl);
}catch (Exception ex){
ex.printStackTrace();
try {
mediaExtractor.setDataSource(new FileInputStream(musicFileUrl).getFD());
} catch (Exception e) {
e.printStackTrace();
LogUtil.e("設置解碼音頻文件路徑錯誤");
}
}
//獲取音頻格式軌信息
mediaFormat = mediaExtractor.getTrackFormat(0);
//從音頻格式軌信息中讀取 採樣率,聲道數,時長,音頻文件類型
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;
}
//解碼的開始時間和結束時間
startMicroseconds = Math.max(startMicroseconds, 0);
endMicroseconds = endMicroseconds < 0 ? duration : endMicroseconds;
endMicroseconds = Math.min(endMicroseconds, duration);
if (startMicroseconds >= endMicroseconds) {
return false;
}
//建立一個解碼器
try {
mediaCodec = MediaCodec.createDecoderByType(mime);
mediaCodec.configure(mediaFormat, null, null, 0);
} catch (Exception e) {
LogUtil.e("解碼器configure出錯");
return false;
}
//獲得輸出PCM文件的路徑
decodeFileUrl = decodeFileUrl.substring(0, decodeFileUrl.lastIndexOf("."));
String pcmFilePath = decodeFileUrl + ".pcm";
//後續解碼操做
getDecodeData(mediaExtractor, mediaCodec, pcmFilePath, sampleRate, channelCount,
startMicroseconds, endMicroseconds, decodeOperateInterface);
return true;
}
複製代碼
以上操做建立了MediaExtractor,獲取MediaFormat用於讀取音頻文件的相關信息如採樣率,文件類型,聲道數等。而後建立了MediaCodec用於後續和MediaExtractor一塊兒進行音頻的解碼操做。接下來看看具體的解碼過程:
/**
* 解碼數據
*/
private void getDecodeData(MediaExtractor mediaExtractor, MediaCodec mediaCodec,
String decodeFileUrl, int sampleRate, int channelCount, final long startMicroseconds,
final long endMicroseconds, final DecodeOperateInterface decodeOperateInterface) {
//初始化解碼狀態,未解析完成
boolean decodeInputEnd = false;
boolean decodeOutputEnd = false;
//當前讀取採樣數據的大小
int sampleDataSize;
//當前輸入數據的ByteBuffer序號,當前輸出數據的ByteBuffer序號
int inputBufferIndex;
int outputBufferIndex;
//音頻文件的採樣位數字節數,= 採樣位數/8
int byteNumber;
//上一次的解碼操做時間,當前解碼操做時間,用於通知回調接口
long decodeNoticeTime = System.currentTimeMillis();
long decodeTime;
//當前採樣的音頻時間,好比在當前音頻的第40秒的時候
long presentationTimeUs = 0;
//定義編解碼的超時時間
final long timeOutUs = 100;
//存儲輸入數據的ByteBuffer數組,輸出數據的ByteBuffer數組
ByteBuffer[] inputBuffers;
ByteBuffer[] outputBuffers;
//當前編解碼器操做的 輸入數據ByteBuffer 和 輸出數據ByteBuffer,能夠從targetBuffer中獲取解碼後的PCM數據
ByteBuffer sourceBuffer;
ByteBuffer targetBuffer;
//獲取輸出音頻的媒體格式信息
MediaFormat outputFormat = mediaCodec.getOutputFormat();
MediaCodec.BufferInfo bufferInfo;
byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8;
//開始解碼操做
mediaCodec.start();
//獲取存儲輸入數據的ByteBuffer數組,輸出數據的ByteBuffer數組
inputBuffers = mediaCodec.getInputBuffers();
outputBuffers = mediaCodec.getOutputBuffers();
mediaExtractor.selectTrack(0);
//當前解碼的緩存信息,裏面的有效數據在offset和offset+size之間
bufferInfo = new MediaCodec.BufferInfo();
//獲取解碼後文件的輸出流
BufferedOutputStream bufferedOutputStream =
FileFunction.getBufferedOutputStreamFromFile(decodeFileUrl);
//開始進入循環解碼操做,判斷讀入源音頻數據是否完成,輸出解碼音頻數據是否完成
while (!decodeOutputEnd) {
if (decodeInputEnd) {
return;
}
decodeTime = System.currentTimeMillis();
//間隔1秒通知解碼進度
if (decodeTime - decodeNoticeTime > Constant.OneSecond) {
final int decodeProgress =
(int) ((presentationTimeUs - startMicroseconds) * Constant.NormalMaxProgress
/ endMicroseconds);
if (decodeProgress > 0) {
notifyProgress(decodeOperateInterface, decodeProgress);
}
decodeNoticeTime = decodeTime;
}
try {
//操做解碼輸入數據
//從隊列中獲取當前解碼器處理輸入數據的ByteBuffer序號
inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);
if (inputBufferIndex >= 0) {
//取得當前解碼器處理輸入數據的ByteBuffer
sourceBuffer = inputBuffers[inputBufferIndex];
//獲取當前ByteBuffer,編解碼器讀取了多少採樣數據
sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0);
//若是當前讀取的採樣數據<0,說明已經完成了讀取操做
if (sampleDataSize < 0) {
decodeInputEnd = true;
sampleDataSize = 0;
} else {
presentationTimeUs = mediaExtractor.getSampleTime();
}
//而後將當前ByteBuffer從新加入到隊列中交給編解碼器作下一步讀取操做
mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize, presentationTimeUs,
decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
//前進到下一段採樣數據
if (!decodeInputEnd) {
mediaExtractor.advance();
}
} else {
//LogUtil.e("inputBufferIndex" + inputBufferIndex);
}
//操做解碼輸出數據
//從隊列中獲取當前解碼器處理輸出數據的ByteBuffer序號
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs);
if (outputBufferIndex < 0) {
//輸出ByteBuffer序號<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;
}
//取得當前解碼器處理輸出數據的ByteBuffer
targetBuffer = outputBuffers[outputBufferIndex];
byte[] sourceByteArray = new byte[bufferInfo.size];
//將解碼後的targetBuffer中的數據複製到sourceByteArray中
targetBuffer.get(sourceByteArray);
targetBuffer.clear();
//釋放當前的輸出緩存
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
//判斷當前是否解碼數據所有結束了
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
decodeOutputEnd = true;
}
//sourceByteArray就是最終解碼後的採樣數據
//接下來能夠對這些數據進行採樣位數,聲道的轉換,但這是可選的,默認是和源音頻同樣的聲道和採樣位數
if (sourceByteArray.length > 0 && bufferedOutputStream != null) {
if (presentationTimeUs < startMicroseconds) {
continue;
}
//採樣位數轉換,按本身須要是否實現
byte[] convertByteNumberByteArray =
convertByteNumber(byteNumber, Constant.ExportByteNumber, sourceByteArray);
//聲道轉換,按本身須要是否實現
byte[] resultByteArray = convertChannelNumber(channelCount, Constant.ExportChannelNumber,
Constant.ExportByteNumber, convertByteNumberByteArray);
//將解碼後的PCM數據寫入到PCM文件
try {
bufferedOutputStream.write(resultByteArray);
} catch (Exception e) {
LogUtil.e("輸出解壓音頻數據異常" + e);
}
}
if (presentationTimeUs > endMicroseconds) {
break;
}
} catch (Exception e) {
LogUtil.e("getDecodeData異常" + e);
}
}
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
LogUtil.e("關閉bufferedOutputStream異常" + e);
}
}
//重置採樣率,按本身須要是否實現
if (sampleRate != Constant.ExportSampleRate) {
Resample(sampleRate, decodeFileUrl);
}
notifyProgress(decodeOperateInterface, 100);
//釋放mediaCodec 和 mediaExtractor
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
}
if (mediaExtractor != null) {
mediaExtractor.release();
}
}
複製代碼
以上操做是在一個循環中,不斷取得源音頻輸入數據,加入到輸入隊列中,交給MediaCodec處理,而後再從解碼後的輸出隊列中取得輸出數據,寫入到文件中,其中要判斷源音頻輸入數據是否讀取完畢,解碼後的輸出數據是否完成,來終止這個循環。後續的採樣位數轉換,聲道數轉換,以及採樣率轉換都是可選的,不是必須的,默認不實現的話,輸出的PCM數據和源音頻是同樣的採樣位數,聲道數,和採樣率。
如今咱們獲得瞭解碼後的PCM文件,可是它是不可直接播放的,由於不帶音頻相關的格式信息,下面咱們將PCM和指定的音頻相關格式信息去轉換獲得一個可播放的WAV文件:
/**
* PCM文件轉WAV文件
* @param inPcmFilePath 輸入PCM文件路徑
* @param outWavFilePath 輸出WAV文件路徑
* @param sampleRate 採樣率,例如44100
* @param channels 聲道數 單聲道:1或雙聲道:2
* @param bitNum 採樣位數,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 {
//採樣字節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 整個音頻PCM數據大小
* @param totalDataLen 整個數據大小
* @param sampleRate 採樣率
* @param channels 聲道數
* @param byteRate 採樣字節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);//數據大小
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] = ' ';//過渡字節
//數據大小
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;
//通道數
header[22] = (byte) channels;
header[23] = 0;
//採樣率,每一個通道的播放速度
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
//音頻數據傳送速率,採樣率*通道數*採樣深度/8
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) (channels * 16 / 8);
header[33] = 0;
//每一個樣本的數據位數
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);
}
複製代碼
上面操做其實也很簡單,只要你知道了WAV文件頭信息的格式,將採樣率,聲道數,採樣位數,PCM音頻數據大小等信息填充進去,而後將這個44個字節數據拼接到PCM文件的開頭,就獲得了一個可播放的WAV文件了。
上文講解了經常使用音頻文件的格式,採樣率,聲道,採樣位數概念,以及PCM數據是如何構成等內容。而後是如何從音頻文件解碼爲PCM數據文件,以及獲得PCM編碼的WAV文件,有了以上的理解後,後續進行音頻文件的裁剪,插入,合成等編輯操做就更容易理解了。請繼續關注後續的音頻編輯操做處理。
須要源碼的親們請關注個人微信公衆號【hesong】,那裏有源碼地址。創做不易,前進的路上須要親們的支持哦。
個人博客
GitHub
個人簡書
羣號:194118438,歡迎入羣
微信公衆號 hesong ,微信掃一掃下方二維碼便可關注:![]()