Mobile Development 9 min read

Investigation and Resolution of Android Audio Playback Noise Issue in HLS Streams

The Android app’s HLS video produced an electric‑like noise because FFmpeg’s av_find_best_stream mistakenly selected the second audio track in a dual‑stream file, a track lacking 1‑8 kHz frequencies, whereas iOS and PC used the correct first track; fixing requires publishing videos with a single audio track and adding detection tools.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Investigation and Resolution of Android Audio Playback Noise Issue in HLS Streams

Background: An Android app playing an HLS‑encoded MV video ("凤凰花开的路口") exhibited a persistent electric‑like noise, while the same content played without noise on iOS and PC platforms.

Initial observations:

Noise occurs only on Android.

All platforms use the same HLS source.

Investigation steps included outlining the playback pipeline and identifying potential failure points.

Playback Process Overview

The pipeline consists of player initialization, data reading, audio decoding, and audio rendering. Key components created during initialization are:

Read thread: read_thread

Queue for pre‑decoded audio data: audioq

Queue for decoded audio frames: sampq

Data reading involves creating a context, probing the protocol ( avformat_open_input ), finding stream info ( avformat_find_stream_info ), selecting streams ( av_find_best_stream ), opening decoders ( stream_component_open ), and reading packets ( av_read_frame(ic, pkt) ) which are then pushed into audioq .

Audio decoding runs in audio_thread , converting packets from audioq into AVFrame objects stored in sampq . Audio rendering uses aout_thread_n to invoke sdl_audio_callback , converting frames to PCM and feeding them to AudioTrack .

Problem Decomposition

The team isolated four possible problematic stages:

File download integrity.

Data reading.

Audio decoding logic.

AudioTrack configuration.

1. File download

TS files on Android matched those on other platforms, confirming the download stage was fine.

2. AudioTrack configuration

Log checks showed that sample rate, bit depth, and channel count matched the audio stream, so this stage was also correct.

3. Audio decoding logic

PCM data analysis revealed that the problematic stream lacked frequency components in the 1‑8 kHz range, which is highly audible. Waveform comparison suggested that the decoding process itself was not the root cause, leading to the hypothesis that the issue lay earlier in data reading.

4. Data reading

Additional logging showed an anomaly during av_find_best_stream . The TS segment contained two audio streams. When each stream was forced to play separately:

Stream 1 produced normal PCM.

Stream 2 produced noisy PCM.

Android selected Stream 2 for playback.

This confirmed that the data‑reading stage chose the wrong audio stream.

Root Cause: Audio Stream Selection

The selection is performed by av_find_best_stream , which prefers streams with a higher number of decoded frames ( codec_info_nb_frames ) and higher bitrate. In the examined file:

codec_info_nb_frames

bit_rate

audio_stream 1

38

122625

audio_stream 2

39

126375

Because Stream 2 has both a higher frame count and bitrate, the algorithm selects it, leading to the noisy playback on Android.

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; // decoded frames
        bitrate = avctx->bit_rate;       // bitrate
        multiframe = FFMIN(5, count);
        // compare decoded frames first, then bitrate
        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; // selected stream index
        best_decoder = decoder;
    }
    return ret;
}

Comparison with Other Platforms

iOS and PC players default to the first audio stream, which exposed the issue quickly. Android’s FFmpeg‑based ExoPlayer selects the stream with better metrics, which inadvertently chose the defective stream.

Solution

Edit and re‑publish the video with a single, correct audio track.

In the short term, add detection and reporting for dual‑audio‑stream files to help content reviewers catch such problems.

Long term, implement backend tooling to scan existing videos for multiple audio streams and ensure future transcoding produces only one audio track.

References

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

debuggingAndroidffmpegExoPlayerHLSAudio PlaybackAudio Stream Selection
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.