㈠ 直播時有雜音滋滋滋是什麼原因直播雜音怎麼回事
我們重點看看直播過程中出現的雜音、噪音和回聲等問題。
相比於視頻而言,音頻要敏感得多,視頻畫面有噪點、馬賽克都還是可以勉強被接受,而聲音一旦有任何瑕疵,人耳都會特別容易感覺到,而且難以忍受。
問題現象
常見的音頻問題現象描述如下:
- 電流音,爆音,滋滋聲或者嘟嘟聲
- 聲音斷斷續續,聽不清楚
- 回聲,能聽到自己說話的聲音
問題排查
1.參數配置問題
上面也有提到,音頻是一個特別敏感的東西,涉及到許多參數配置,一旦配置不太匹配,就會導致聲音聽起來非常詭異(比如:采樣率是 32000Hz 的音頻,給播放器配置為 8000Hz 或者 44100Hz,就明顯會出現音頻慢放或者快放的效果)。
常見的音頻參數和基本原理,可以參考文章:《android音頻開發(1):基礎知識》
我們只需要注意的是,無論是採集和播放,都要給系統的 API 以及第三方的庫配置正確的參數,如:采樣率、位寬、聲道數等等。
2.代碼層面的原因
常見的代碼層面的問題有如下幾種:
- 音頻 buffer 大小不匹配,一段 1024 bytes 的音頻,放到了 2048 bytes 的數組,導致尾部有隨機數
- 音頻 resample 重采樣的演算法問題,導致采樣出來的數據出了問題
- Android 的 ByteBuffer 取出數組,是不能直接用 .array() 方法的,而需要用 .get() 方法
- iOS 系統,其他 app 通過系統 API 更改了 AudioSession 采樣率的配置
追答
3.網路波動
視頻是一幀一幀連續的圖像構成的,在播放過程中,如果無法按時渲染,則會出現卡頓的效果;如果丟失幾幀畫面,則會出現快進效果。
而音頻是流式的,雖然也被切分為了一個個音頻幀,但如果無法按時播放或者連續丟失較多的音頻幀,則會明顯聽到斷斷續續的聲音出現。特別是在弱網、丟包率高等不穩定網路環境下,很容易出現這種情況。
4.回聲消除
回聲一般出現在同時有音頻的採集和播放的場景,比如:連麥互動、混音返聽等等,採集到的音頻通過揚聲器又播放出來了,同時又被採集了進去,從而產生了回聲或者嘯叫聲。
這樣的場景下,一般需要通過系統的回聲消除 API,或者第三方回聲消除庫(如:speexdsp,webrtc 等)進行處理。
注意:很多 Android 機型硬體自帶的回聲消除效果並不是很好。
5.混音越界
音頻的 PCM 數據,通常用 short 數組來存放,當我們做一些多路音頻的混音功能的時候,如果不注意處理 short 類型的大小越界,則往往帶來爆音的問題。下面是一段參考 webrtc 的混音代碼,專門針對混音越界做了簡單處理,
㈡ android直接播放pcm語音為什麼會有噪音
最近在做手機客戶端用G726編碼庫向機台發送語音消息的DEMO,弄了一周左右才解決.
中間碰到的問題賊多,主要是用AudioRecord採集聲音的時候,然後用AudioTrack播放經常會出現噪音,這樣的情況讓人實在是無法接受。
後來查谷歌實在是沒折了,於是再次翻查了下sipdroid的代碼,發現sipdroid在採集聲音後,每次都會調用一個函數,於是我猜測,這個函數應該跟去除噪音有關,於是寫了個DEMO,測試了一下,發現噪音還真消除了.
噪音消除演算法:
void calc1(short[] lin,int off,int len) {
int i,j;
for (i = 0; i < len; i++) {
j = lin[i+off];
lin[i+off] = (short)(j>>2);
}
}
自己錄制PCM,播放PCM的DEMO。
㈢ Android 多媒體音頻開發中怎麼將PCM轉成ADPCM
Adensoft Audio MP3 Converter
Adensoft.Audio.MP3.Converter是一款強大的音頻轉換工具,能將當前主流的音頻格式如WAV PCM, WAV ,GSM, ADPCM, DSP ,MP2,WMA ( win Media Audio ),Ogg Vorbis,VOX ( Dialogic ADPCM ),RAW ( PCM, A-LAW, U-LAW ),MPC (MusicPack),AVI (audio track),G.721,G.723,G.726,AIFF ,AU (UNIX audio format)無損的轉換成mp3(支持批量轉換)。軟體同時提供良好的人機界面和完整的tag編輯功能。
㈣ android中mediamuxer和mediacodec的區別
Android中MediaMuxer和MediaCodec用例
在Android的多媒體類中,MediaMuxer和MediaCodec算是比較年輕的,它們是JB 4.1和JB 4.3才引入的。前者用於將音頻和視頻進行混合生成多媒體文件。缺點是目前只能支持一個audio track和一個video track,而且僅支持mp4輸出。不過既然是新生事物,相信之後的版本應該會有大的改進。MediaCodec用於將音視頻進行壓縮編碼,它有個比較牛X的地方是可以對Surface內容進行編碼,如KK 4.4中屏幕錄像功能就是用它實現的。
注意它們和其它一些多媒體相關類的關系和區別:MediaExtractor用於音視頻分路,和MediaMuxer正好是反過程。MediaFormat用於描述多媒體數據的格式。MediaRecorder用於錄像+壓縮編碼,生成編碼好的文件如mp4, 3gpp,視頻主要是用於錄制Camera preview。MediaPlayer用於播放壓縮編碼後的音視頻文件。AudioRecord用於錄制PCM數據。AudioTrack用於播放PCM數據。PCM即原始音頻采樣數據,可以用如vlc播放器播放。當然了,通道采樣率之類的要自己設,因為原始采樣數據是沒有文件頭的,如:
vlc --demux=rawaud --rawaud-channels 2 --rawaud-samplerate 44100 audio.pcm
回到MediaMuxer和MediaCodec這兩個類,它們的參考文檔見http://developer.android.com/reference/android/media/MediaMuxer.html和http://developer.android.com/reference/android/media/MediaCodec.html,里邊有使用的框架。這個組合可以實現很多功能,比如音視頻文件的編輯(結合MediaExtractor),用OpenGL繪制Surface並生成mp4文件,屏幕錄像以及類似Camera app里的錄像功能(雖然這個用MediaRecorder更合適)等。
這里以一個很無聊的功能為例,就是在一個Surface上畫圖編碼生成視頻,同時用MIC錄音編碼生成音頻,然後將音視頻混合生成mp4文件。程序本身沒什麼用,但是示例了MediaMuxer和MediaCodec的基本用法。本程序主要是基於兩個測試程序:一個是Grafika中的SoftInputSurfaceActivity和HWEncoderExperiments。它們一個是生成視頻,一個生成音頻,這里把它們結合一下,同時生成音頻和視頻。基本框架和流程如下:
首先是錄音線程,主要參考HWEncoderExperiments。通過AudioRecord類接收來自麥克風的采樣數據,然後丟給Encoder准備編碼:
AudioRecord audio_recorder;
audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size);
// ...
audio_recorder.startRecording();
while (is_recording) {
byte[] this_buffer = new byte[frame_buffer_size];
read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data
// …
presentationTimeStamp = System.nanoTime() / 1000;
audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp); // feed to audio encoder
}
這里也可以設置AudioRecord的回調(通過())來觸發音頻數據的讀取。offerAudioEncoder()里主要是把audio采樣數據送入音頻MediaCodec的InputBuffer進行編碼:
ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(this_buffer);
...
mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0);
}
下面,參考Grafika-SoftInputSurfaceActivity,並加入音頻處理。主循環大體分四部分:
try {
// Part 1
prepareEncoder(outputFile);
...
// Part 2
for (int i = 0; i < NUM_FRAMES; i++) {
generateFrame(i);
drainVideoEncoder(false);
drainAudioEncoder(false);
}
// Part 3
...
drainVideoEncoder(true);
drainAudioEncoder(true);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} finally {
// Part 4
releaseEncoder();
}
第1部分是准備工作,除了video的MediaCodec,這里還初始化了audio的MediaCodec:
MediaFormat audioFormat = new MediaFormat();
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
...
mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
mAudioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mAudioEncoder.start();
第2部分進入主循環,app在Surface上直接繪圖,由於這個Surface是從MediaCodec中用createInputSurface()申請來的,所以畫完後不用顯式用queueInputBuffer()交給Encoder。drainVideoEncoder()和drainAudioEncoder()分別將編碼好的音視頻從buffer中拿出來(通過dequeueOutputBuffer()),然後交由MediaMuxer進行混合(通過writeSampleData())。注意音視頻通過PTS(Presentation time stamp,決定了某一幀的音視頻數據何時顯示或播放)來同步,音頻的time stamp需在AudioRecord從MIC採集到數據時獲取並放到相應的bufferInfo中,視頻由於是在Surface上畫,因此直接用dequeueOutputBuffer()出來的bufferInfo中的就行,最後將編碼好的數據送去MediaMuxer進行多路混合。
注意這里Muxer要等把audio track和video track都加入了再開始。MediaCodec在一開始調用dequeueOutputBuffer()時會返回一次INFO_OUTPUT_FORMAT_CHANGED消息。我們只需在這里獲取該MediaCodec的format,並注冊到MediaMuxer里。接著判斷當前audio track和video track是否都已就緒,如果是的話就啟動Muxer。
總結來說,drainVideoEncoder()的主邏輯大致如下,drainAudioEncoder也是類似的,只是把video的MediaCodec換成audio的MediaCodec即可。
while(true) {
int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
...
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
encoderOutputBuffers = mVideoEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = mAudioEncoder.getOutputFormat();
mAudioTrackIndex = mMuxer.addTrack(newFormat);
mNumTracksAdded++;
if (mNumTracksAdded == TOTAL_NUM_TRACKS) {
mMuxer.start();
}
} else if (encoderStatus < 0) {
...
} else {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
...
if (mBufferInfo.size != 0) {
mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
}
mVideoEncoder.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
第3部分是結束錄制,發送EOS信息,這樣在drainVideoEncoder()和drainAudioEncoder中就可以根據EOS退出內循環。第4部分為清理工作。把audio和video的MediaCodec,MediaCodec用的Surface及MediaMuxer對象釋放。
最後幾點注意:
1. 在AndroidManifest.xml里加上錄音許可權,否則創建AudioRecord對象時鐵定失敗:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
2. 音視頻通過PTS同步,兩個的單位要一致。
3. MediaMuxer的使用要按照Constructor -> addTrack -> start -> writeSampleData -> stop 的順序。如果既有音頻又有視頻,在stop前兩個都要writeSampleData()過。
Code references:
Grafika: https://github.com/google/grafika
Bigflake: http://bigflake.com/mediacodec/
HWEncoderExperiments:https://github.com/OnlyInAmerica/HWEncoderExperiments/tree/audioonly/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments
Android test:http://androidxref.com/4.4.2_r2/xref/cts/tests/tests/media/src/android/media/cts/
http://androidxref.com/4.4.2_r2/xref/pdk/apps/TestingCamera2/src/com/android/testingcamera2/CameraRecordingStream.java