歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~
一、問題背景與分析
不久前,團隊發現其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個選擇方案都無法識別“內容異常”的音訊流。
五、問題解決方案
因此,處理該問題,需要從音源上進行修復和規避,我們的建議是從源頭杜絕,從終端規避:
- 編輯重新上架正常音源;
- 短期內增加雙音訊流的檢測上報,幫助後臺、編輯進行復查;
- 長遠看由後臺開發工具,分別對存量視訊進行雙音訊流檢測和對增量視訊保證只轉碼單音訊流;
參考資料
- ffmpeg.org/doxygen/2.8…
- github.com/google/ExoP…
- www.jianshu.com/p/daf0a61cc…
- www.jianshu.com/p/a6a4bf59c…
- km.oa.com/articles/sh…
- codeday.me/bug/2017071…
相關閱讀
此文已由作者授權騰訊雲+社群釋出,更多原文請點選
搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社群!