歡迎大家前往 "騰訊雲+社區" ,獲取更多騰訊海量技術實踐乾貨哦~ 本文由 "QQ音樂技術團隊" 發表於 "雲+社區專欄" 一、問題背景與分析 不久前,團隊發現其Android平臺App在播放MV視頻《鳳凰花開的路口》時,會帶有如電流聲一般的雜音,這影響了用戶體驗。 研發同學在初步定位時,發現有如下 ...
歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~
一、問題背景與分析
不久前,團隊發現其Android平臺App在播放MV視頻《鳳凰花開的路口》時,會帶有如電流聲一般的雜音,這影響了用戶體驗。 研發同學在初步定位時,發現有如下特征:
- Android平臺雜音問題必現;
- iOS、PC平臺能正常播放,沒有噪音。
然而,各平臺都是統一用HLS格式播放,即源頭都是一樣的。對於該問題,我們的定位思路如下:
- 梳理視頻播放流程;
- 找到切入點排查。
二、播放流程概覽
分析播放流程如上圖(圖中內容從左往右),概括其關鍵步驟如下:
- 播放器初始化:
- 創建讀數據線程:
read_thread
; - 創建存放audio解碼前數據的隊列:
audioq
; - 創建存放audio解碼後數據的隊列:
sampq
。
- 創建讀數據線程:
- 數據讀取:
- ①創建context;
- ②探測協議類型:
avformat_open_input
; - ③探測媒體類型:
avformat_find_stream_info
; - ④獲取音視頻流:
av_find_best_stream
; - ⑤打開媒體解碼器:
stream_component_open
; - ⑥讀取媒體數據,獲得AVPacket:
av_read_frame(ic, pkt)
; - ⑦音視頻數據分別送入
audioq
中; - 重覆⑥、⑦步驟到數據完畢。
- 音頻解碼:
- 在
audio_thread
中對audioq
中的數據進行decoder_decode_frame
解碼; - 解碼後的幀
AVFrame
存放到sampq
中;
- 在
- 音頻播放:
aout_thread_n
中,通過調用回調介面sdl_audio_callback
,對sampq
中的音頻幀數據進行解碼成PCM數據;- 寫入PCM數據到buffer數組,並由
AudioTrack
播放。
三、問題分解與切入
在梳理出播放流程後,標記出找到有可能出錯的環節,方便進行“分層定位”(圖中黃色標記)
- 播放下載文件是否有問題;
- 數據讀取是否有問題;
- 音頻解碼邏輯是否有問題;
AudioTrack
的設置是否有問題;
接下來,根據難易程度,對上述環節逐個驗證。
1、播放下載文件是否正常
把Android平臺播放的ts文件與各平臺的進行比對,發現兩者一樣,該環節正常。
2、AudioTrack設置是否正常
通過日誌檢查AudioTrack
以下配置參數:
- 採樣率
- 位深
- 頻道
以上參數設置的值與音頻流的相符合,該環節正常。
3、音頻解碼邏輯是否有問題
驗證解碼邏輯是否有問題,可以通過對PCM數據進行分析來確認。 對aout_thread_n
進行修改,將PCM數據額外輸出到本地,並與正常的PCM數據進行對比。
正常PCM數據頻譜圖:
異常PCM數據頻譜圖:
正常PCM數據波形圖:
異常PCM數據波形圖:
對比分析可得出:
- 從頻譜圖中看出,異常的PCM在人耳十分敏感的頻響(1000~8000Hz )區域內的音頻數據嚴重缺失,導致“雜音問題”
- 從波形圖中看出,異常的與正常的無聲區和有聲區都吻合,若解封裝、解碼邏輯出現異常,極大幾率是呈現無波動(一條直線的形式)情況。因此可以先大膽假設解碼、解封裝邏輯是符合預期的
若解碼邏輯正常,再結合之前已經驗證文件下載正常。可以推測是數據讀取環節出現異常。
4、數據讀取是否有問題
通過對數據讀取的各步驟增加日誌後,發現在av_find_best_stream
音頻流選擇時出現異常: ffmpeg -i
發現,該視頻ts分片有2個音頻流
通過強制分別讀取兩條音頻流數據播放,發現:
- 第一條正常播放(PCM數據正常)
- 第二條播放雜音(PCM數據異常)
- Android平臺選擇了第二條進行播放
基於此,也就驗證了在第3步中的假設是正確的。
由上分析,可以得出結論:Android平臺選擇了第二條數據有問題的流進行播放。
四、問題根源:音頻流選擇
1、選擇方式
分析代碼,大致如下所列,av_find_best_stream
函數選擇音頻流,該函數會根據2個主要參數進行選擇:
- 各音頻流的在探測媒體類型(
avformat_find_stream_info
)時,額外解碼出來的幀數(選擇多的) - 各音頻流的比特率(選擇高的)
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
int wanted_stream_nb, int related_stream,
AVCodec **decoder_ret, int flags)
{
for (i = 0; i < nb_streams; i++) {
count = st->codec_info_nb_frames; //音頻流探測中解碼的幀數
bitrate = avctx->bit_rate;//音頻流的比特率
multiframe = FFMIN(5, count);
//先比較解碼幀數,再比較音頻流比特率,誰大誰選
if ((best_multiframe > multiframe) ||
(best_multiframe == multiframe && best_bitrate > bitrate) ||
(best_multiframe == multiframe && best_bitrate == bitrate && best_count >= count))
continue;
best_count = count;
best_bitrate = bitrate;
best_multiframe = multiframe;
ret = real_stream_index;//最後選擇的流index
best_decoder = decoder;
}
return ret;
}
在該視頻中,我們可以看到:
codec_info_nb_frames | bit_rate | |
---|---|---|
audio_stream 1 | 38 | 122625 |
audio_stream 2 | 39 | 126375 |
第二條流的解碼幀數和比特率要比第一條高,因此選擇了第二條流播放
2、對比同類方案
分析了以上選擇規則後,我們對各平臺、框架進行了選擇規則的對比:
備註:
- ExoPlayer對多音頻流的ts分片支持不完善(issue),因此測試時需要調整相關介面。但選擇規則依然以上述所示(DefaultTrackSelector)
- iOS和PC平臺採用閉源組件,因此測試時使用了“互換兩條音頻流順序”的方法進行測試。互換後,兩平臺都播放了雜音音頻流
ffmpeg -i INPUT_FILE -map 0:0 -map 0:2 -map 0:1 -c copy -y OUTPUT_FILE
- QuickTime同樣是閉源,互換音頻流後無法明顯差別,通過合成第三條音頻流,來驗證是它是對所有音頻流全播放
ffmpeg -i INPUT_FILE_1 -i INPUT_FILE_2 -map 0:0 -map 0:1 -map 0:2 -map 1:0 -c copy OUTPUT_FILE
3、總結
從以上數據看到,iOS和PC平臺會預設選擇第一條流,而在Android平臺的FFmpeg和ExoPlayer會根據音頻流屬性來選擇數值更好的一條。
- “預設選擇第一條”方案能更容易地把音源問題暴露。
- “比較音頻流屬性”方案能更大幾率地選擇質量更好的流來提升用戶體驗。
但以上2個選擇方案都無法識別“內容異常”的音頻流。
五、問題解決方案
因此,處理該問題,需要從音源上進行修複和規避,我們的建議是從源頭杜絕,從終端規避:
- 編輯重新上架正常音源;
- 短期內增加雙音頻流的檢測上報,幫助後臺、編輯進行複查;
- 長遠看由後臺開發工具,分別對存量視頻進行雙音頻流檢測和對增量視頻保證只轉碼單音頻流;
參考資料
- https://ffmpeg.org/doxygen/2.8/
- https://github.com/google/ExoPlayer
- https://www.jianshu.com/p/daf0a61cc1e0
- https://www.jianshu.com/p/a6a4bf59cdae
- http://km.oa.com/articles/show/319627
- https://codeday.me/bug/20170711/39603.html
相關閱讀
wamp2.0配置Zend Optimizer
藏匿在郵件里的“壞小子”
打造一個個人閱讀追蹤系統
【每日課程推薦】機器學習實戰!快速入門線上廣告業務及CTR相應知識
此文已由作者授權騰訊雲+社區發佈,更多原文請點擊
搜索關註公眾號「雲加社區」,第一時間獲取技術乾貨,關註後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社區!