麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 系統 > Android > 正文

Android音頻編輯之音頻轉換PCM與WAV

2019-10-21 21:26:52
字體:
來源:轉載
供稿:網友

前言

本篇開始講解在Android平臺上進行的音頻編輯開發,首先需要對音頻相關概念有基礎的認識。所以本篇要講解以下內容:

1. 常用音頻格式簡介 
2. WAV和PCM的區別和聯系 
3. WAV文件頭信息 
4. 采樣率簡介 
5. 聲道數和采樣位數下的PCM編碼 
6. 音頻文件解碼 
7. PCM文件轉WAV文件

現在先給出音頻編輯的效果圖,看看能不能提高大家的積極性~,哈哈

Android,音頻編輯,音頻轉換,PCM,WAV

Android,音頻編輯,音頻轉換,PCM,WAV

Android,音頻編輯,音頻轉換,PCM,WAV

常用音頻格式簡介

在Android平臺上進行音頻開發,首先需要對常用的音頻格式有個大致的了解。在Android平臺上,常用的音頻格式有:

  • WAV

WAV格式是微軟公司開發的一種聲音文件格式,也叫波形聲音文件,是最早的數字音頻格式,被Windows平臺及其應用程序廣泛支持。

WAV格式支持許多壓縮算法,支持多種音頻位數、采樣頻率和聲道,采用44.1kHz的采樣頻率,16位量化位數,因此WAV的音質與CD相差無幾,但WAV格式對存儲空間需求太大不便于交流和傳播。

補充:無損格式,缺點:體積十分大!

  • MP3

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,高頻部分一刀切是他的缺點。音質不高!

  • AMR

全稱Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用于移動設備的音頻,壓縮比比較大,但相對其他的壓縮格式質量比較差,多用于人聲,通話,效果還是很不錯的。

  • Ogg

Ogg全稱應該是OGG Vobis(ogg Vorbis) 是一種新的音頻壓縮格式,類似于MP3等現有的音樂格式。 
但有一點不同的是,它是完全免費、開放和沒有專利限制的。OGG Vobis有一個很出眾的特點,就是支持多聲道,隨著它的流行,以后用隨身聽來聽DTS編碼的多聲道作品將不會是夢想。

Vorbis 是這種音頻壓縮機制的名字,而Ogg則是一個計劃的名字,該計劃意圖設計一個完全開放性的多媒體系統。目前該計劃只實現了OggVorbis這一部分。

Ogg Vorbis文件的擴展名是.OGG。這種文件的設計格式是非常先進的?,F在創建的OGG文件可以在未來的任何播放器上播放,因此,這種文件格式可以不斷地進行大小和音質的改良,而不影響舊有的編碼器或播放器。 
補充:目前最好的有損格式之一,MP3部分支持,智能手機裝軟件部分可以支持,最高比特率500kbps。

  • AAC

AAC(Advanced Audio Coding),中文稱為“高級音頻編碼”,出現于1997年,基于 MPEG-2的音頻編碼技術。

優點:相對于mp3,AAC格式的音質更佳,文件更小。

不足:AAC屬于有損壓縮的格式,與時下流行的APE、FLAC等無損格式相比音質存在“本質上”的差距。加之,目前傳輸速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC頭上“小巧”的光環不復存在了。

前景:以發展的眼光來看,正如“高清”正在被越來越多的人所接受一樣,“無損”必定是未來音樂格式的絕對主流。AAC這種“有損”格式的前景不容樂觀

  • FLAC

FLAC即是Free Lossless Audio Codec的縮寫,中文可解為無損音頻壓縮編碼。

FLAC是一套著名的自由音頻壓縮編碼,其特點是無損壓縮。不同于其他有損壓縮編碼如MP3 及 AAC,它不會破任何原有的音頻資訊,所以可以還原音樂光盤音質?,F在它已被很多軟件及硬件音頻產品所支持。簡而言之,FLAC與MP3相仿,但是是無損壓縮的,也就是說音頻以FLAC方式壓縮不會丟失任何信息。這種壓縮與Zip的方式類似,但是FLAC將給你更大的壓縮比率,因為FLAC是專門針對音頻的特點設計的壓縮方式,并且你可以使用播放器播放FLAC壓縮的文件,就象通常播放你的MP3文件一樣。

補充:為無損格式,較ape而言,他體積大點,但是兼容性好,編碼速度快,播放器支持更廣。

WAV和PCM的區別和聯系

在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文件頭信息是什么樣的格式信息。

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編碼
接下來再了解下聲道數和采樣位數代表什么意思,在PCM編碼中是如何應用的。

聲道通常可以分為單聲道和雙聲道,雙聲道又分為左聲道和右聲道。

采樣位數表示一個采樣數據用多少位來表示,通常為8位和16位,對于8位表示一個字節來表示一個采樣數據,16位表示用兩個字節表示一個采樣數據,兩個字節為低位字節和高位字節,通常低位字節在前,高位字節在后。

因此結合聲道和采樣字節數(采樣位數),可以組成下圖的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上常用音頻文件進行解碼和信息提取了。這里涉及了三個音頻相關的類: 
- MediaExtractor 媒體文件數據提取器,負責媒體文件數據的提取操作。 
- MediaFormat 媒體文件格式信息,負責讀取媒體文件的格式(如采樣率,時長,聲道數等)信息。 
- MediaCodec 媒體文件編解碼類,負責媒體文件數據的編解碼操作。

解碼器支持解碼常用的音頻格式,如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文件轉WAV文件
現在我們得到了解碼后的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文件,有了以上的理解后,后續進行音頻文件的裁剪,插入,合成等編輯操作就更容易理解了。請繼續關注后續的音頻編輯操作處理。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 欧日韩 | 手机免费看一级片 | 91国内精品久久久久免费影院 | 国产九色视频在线观看 | 欧美日韩1区2区3区 黄片毛片一级 | 一区二区久久久久草草 | 2021狠狠操 | 综合毛片 | 久久国产成人精品国产成人亚洲 | 青草伊人网 | 日韩黄色精品视频 | 精品亚洲一 | 国产精品久久久久久久久久iiiii | 久久国产精品影视 | 72pao成人国产永久免费视频 | 欧洲精品色 | 久草视频在线资源 | 欧美日韩1区2区 | 主播粉嫩国产在线精品 | 国产精品视频不卡 | 日本欧美视频 | 久久艹国产精品 | 久久草在线视频国产 | 少妇色诱麻豆色哟哟 | 国产污污视频 | 视频一区 中文字幕 | 久久精品欧美视频 | 五月天影院,久久综合, | 羞羞视频2023 | 国产精品av久久久久久无 | 九九黄色| 三人弄娇妻高潮3p视频 | 99精品视频久久精品视频 | 深夜毛片免费看 | 国产成人在线观看网站 | 九九热在线视频观看 | 日本a∨精品中文字幕在线 欧美1—12sexvideos | 国产精品一区二区在线 | 一本免费视频 | 成人免费观看av | 欧美一级成人一区二区三区 |