我在我的Android应用程序中使用ExoPlayer来流式传输HLS音频,我试图使用我的主HLS播放列表来播放自适应流,该列表包含相同音轨的各种变体,但根据合适的可用网络带宽以不同的比特率播放.

但我看到的是,ExoPlayer开始播放主播放列表中的初始变量,然后立即移动到音频的最高比特率变量,每次都是如此.它不使用任何其他比特率,即使我已经将网络改为2G、3G或任何其他较低的带宽,它在任何情况下都不适应较低的比特率,并遵循上述模式.它将保持缓冲很长时间,但永远不会使用任何较低的轨道变量根据带宽.

下面是我的ExoPlayer实现的代码-

private void initialisePlayer(){
        if(exoPlayer == null) {
            AdaptiveTrackSelection.Factory videoTrackSelectionFactory =  new AdaptiveTrackSelection.Factory();
            TrackSelector trackSelector = new DefaultTrackSelector(this, videoTrackSelectionFactory);
            LoadControl loadControl = new DefaultLoadControl();
            exoPlayer = new ExoPlayer.Builder(getApplicationContext())
                    .setTrackSelector(trackSelector).setLoadControl(loadControl).build();
            exoPlayer.addListener(listener);
            exoPlayer.setAudioAttributes(audioAttributes, /* handleAudioFocus= */ true);
            exoPlayer.setHandleAudioBecomingNoisy(true);
        }
    }

   private void playAudio(){

    DefaultBandwidthMeter bandwidthMeter =  DefaultBandwidthMeter.getSingletonInstance(this);
    DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this).setTransferListener(bandwidthMeter);

    HlsMediaSource audioSource = HlsMediaSource.Factory(dataSourceFactory)
                    .setLoadErrorHandlingPolicy(new CustomPolicy())
                    .createMediaSource(MediaItem.fromUri("my-hls-master-url"));

    exoPlayer.setMediaSource(audioSource);
    exoPlayer.prepare();
    exoPlayer.setPlayWhenReady(true);

  
   }

我在用-

implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation "androidx.media3:media3-exoplayer-hls:1.1.1"

我有我的主播放列表,就像-

#EXT-X-VERSION:4

# Audio Renditions

#EXT-X-STREAM-INF:BANDWIDTH=54000,CODECS="mp4a.40.5"
songs54/audio.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=84000,CODECS="mp4a.40.5"
songs84/audio.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=126000,CODECS="mp4a.40.2"
songs126/audio.m3u8


#EXT-X-STREAM-INF:BANDWIDTH=160000,CODECS="mp4a.40.5"
songs160/audio.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=320000,CODECS="mp4a.40.2"
songs320/audio.m3u8

我一直在努力解决这个问题,从过go 的两天,任何帮助都会非常感激.

推荐答案

要更多地了解为什么您的ExoPlayer没有像您预期的那样适应网络条件,您可以从在曲目 Select 器中包含带宽计量器开始.这有助于ExoPlayer做出适应性决策.

AdaptiveTrackSelection.Factory videoTrackSelectionFactory =  new AdaptiveTrackSelection.Factory(bandwidthMeter);

虽然DefaultLoadControl在大多数情况下应该足够了,但您可能希望设置缓冲区大小、缓冲区持续时间等的自定义值,以微调自适应行为.

您需要使用曲目 Select 中包含的带宽测量器更新initialisePlayerplayAudio方法:

private void initialisePlayer(){
    TrackSelector trackSelector = new DefaultTrackSelector(this);
    LoadControl loadControl = new DefaultLoadControl();
    exoPlayer = new ExoPlayer.Builder(this)
            .setTrackSelector(trackSelector)
            .setLoadControl(loadControl)
            .build();
    exoPlayer.addListener(listener);
    exoPlayer.addAnalyticsListener(new EventLogger(trackSelector));
    exoPlayer.setAudioAttributes(audioAttributes, /* handleAudioFocus= */ true);
    exoPlayer.setHandleAudioBecomingNoisy(true);
}

private void playAudio(){
    DefaultBandwidthMeter bandwidthMeter =  DefaultBandwidthMeter.getSingletonInstance(this);
    DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this)
            .setTransferListener(bandwidthMeter);
    
    HlsMediaSource audioSource = new HlsMediaSource.Factory(dataSourceFactory)
                .setLoadErrorHandlingPolicy(new CustomPolicy())
                .createMediaSource(MediaItem.fromUri("my-hls-master-url"));

    exoPlayer.setMediaSource(audioSource);
    exoPlayer.prepare();
    exoPlayer.setPlayWhenReady(true);
}

The DefaultBandwidthMeter should provide you with bandwidth estimates. You could log these to see if they change when you switch networks. If the bandwidth estimates do not change, that might be the issue.
Even though you can't inject the BandwidthMeter into the AdaptiveTrackSelection.Factory, you can still add an event listener to it to log changes in estimated bandwidth.

bandwidthMeter.addEventListener(new Handler(Looper.getMainLooper()), new BandwidthMeter.EventListener() {
    @Override
    public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
    Log.d("BANDWIDTH_SAMPLE", "bitrate = " + bitrate);
    }
});

I have added the bandwidthMeter.addEventListener and have seen that bitrate logged is always returning a higher number like - 3200000 or 3099619 or 2929804 and even when I change to 2G network the bitrate is something between 117245 and 165935.
But exoplayer is still not switching the audio variants.

您在2G网络上的带宽估计值显示为117245和165935,因此...ExoPlayer认为带宽足以播放您的主播放列表中可用的最高比特率,根据您发布的内容,该列表的最大比特率为320000.

测试您是否可以手动设置曲目,以确认问题是否与ExoPlayer的自适应机制或其他什么有关.

MappingTrackSelector.MappedTrackInfo mappedTrackInfo = ((DefaultTrackSelector) trackSelector).getCurrentMappedTrackInfo();
if (mappedTrackInfo != null) {
    int rendererIndex = 0; // For audio. Change it as per your needs
    int trackIndexToBeUsed = 1; // Use 0, 1, 2... based on available tracks
    DefaultTrackSelector.SelectionOverride override = new DefaultTrackSelector.SelectionOverride(trackIndexToBeUsed, 0);
    DefaultTrackSelector.ParametersBuilder parametersBuilder = trackSelector.buildUponParameters();
    parametersBuilder.setSelectionOverride(rendererIndex, mappedTrackInfo.getTrackGroups(rendererIndex), override);
    trackSelector.setParameters(parametersBuilder);
}

将上面的代码片段添加到准备了exoPlayer.prepare();的播放器之后,手动设置曲目.


我用了exoplayer.prepare();后面的代码MappingTrackSelector.MappedTrackInfo .... trackSelector.setParameters(parametersBuilder);,但还是找不到任何区别.

ExoPlayer仍然在初始加载中使用第一个音频变量,然后立即移动到最高变量音频,并且无论带宽如何,永远不会更改音频变量.

我可以看到,在2G网络中,比特率最初被带宽计多次记录为-990000,然后缓慢变化,然后在同一网络中下降到更低的比特率,如39000到189000.

有时,曲目 Select 的问题可能与您调用某些方法的确切时间有关.确保您拨打的是手动曲目 Select 代码after,exoPlayer.prepare()呼叫已完成.当播放器状态更改为Player.STATE_READY时,您可能希望将曲目 Select 代码放在Player.Listener回调中.

exoPlayer.addListener(new Player.Listener() {
    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        if (playbackState == Player.STATE_READY) {
            // Manual track selection code here
        }
    }
});

虽然ExoPlayer本身应该处理这一点,但出于调试目的,您可以显式设置一些自适应参数,例如开始或继续播放时需要缓冲的媒体的最短持续时间.ExoPlayer允许您将这样的阈值设置为DefaultLoadControl.


现在我可以手动 Select 曲目,但它现在只 Select 音频曲目的较低变体,即使在5G网络中也是如此.

如果您的手动曲目 Select 成功,但仅导致播放器即使在5G这样的高速网络上也使用较低比特率的曲目,这表明曲目 Select 覆盖优先于ExoPlayer自己的自适应机制.当您设置明确的曲目 Select 覆盖时,ExoPlayer将坚持该 Select ,不会根据网络条件进行调整.

因此,删除用于自适应的手动覆盖,并try 有条件的手动覆盖:实现基于特定条件手动切换轨道的逻辑,例如带宽是否低于特定阈值.通过这种方式,您可以混合使用手动和自适应行为.

bandwidthMeter.addEventListener(new Handler(Looper.getMainLooper()), new BandwidthMeter.EventListener() {
    @Override
    public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
        if (bitrate > SOME_THRESHOLD) {
            // Revert to adaptive track selection
            DefaultTrackSelector.ParametersBuilder parametersBuilder = trackSelector.buildUponParameters();
            parametersBuilder.clearSelectionOverrides(/* rendererIndex= */ 0);
            trackSelector.setParameters(parametersBuilder);
        }
    }
});

再说一次,这是为了测试:虽然可以混合手动和自适应曲目 Select ,但这样做会使玩家的行为更难预测和调试.


每当使用我的手动音轨 Select 的比特率较低时, Select 最低比特率,当比特率高于我的阈值时,自适应行为只 Select 最高比特率,但不 Select 介于两者之间的比特率.

问题是自适应曲目 Select 仍然不能正常工作,我有时看到比特率为102773336,网速为32兆比特.我认为onBandwidthSample%的比特率也是错误的.

另外,如果自适应音轨 Select 不起作用,那么我认为唯一的方法就是手动完成所有的音轨 Select ,基于BandwidthMeter.EventListener中的从onBandwidthSample开始的比特率方法.但该方法返回的错误比特率也是这个问题的另一个问题.

作为最后的手段,如果ExoPlayer的自适应算法不能满足您的需求,并且您也无法获得可靠的带宽估计,您可能确实必须接管对曲目 Select 的完全控制.

在这种情况下,您可以编写将带宽估计映射到特定磁道的逻辑.但是,请确保对带宽估计应用某种形式的平滑或平均,以避免快速切换轨道,这可能会对用户造成干扰.

bandwidthMeter.addEventListener(new Handler(Looper.getMainLooper()), new BandwidthMeter.EventListener() {
    @Override
    public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
        // Your own logic to select a track based on the current bitrate
        int trackIndexToBeUsed = mapBitrateToTrack(bitrate);  // Implement this function based on your requirements
        DefaultTrackSelector.SelectionOverride override = new DefaultTrackSelector.SelectionOverride(trackIndexToBeUsed, 0);
        DefaultTrackSelector.ParametersBuilder parametersBuilder = trackSelector.buildUponParameters();
        parametersBuilder.setSelectionOverride(0, trackGroups, override);
        trackSelector.setParameters(parametersBuilder);
    }
});

作为另一种 Select ,你可以try 使用Android的网络功能:Android中的ConnectivityManager类可以提供有关当前网络连接类型和速度的一般信息.此信息不如带宽估计精确,但仍可用于在高、中、低质量流之间进行 Select .

ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkCapabilities nc = cm.getNetworkCapabilities(cm.getActiveNetwork());
int downSpeed = nc.getLinkDownstreamBandwidthKbps();
int upSpeed = nc.getLinkUpstreamBandwidthKbps();

或者使用第三方网络测量库,如square/okhttp.


You could also implement your own bandwidth estimation logic.
One approach is to download a small file at the beginning of the application and measure the download speed. Be cautious with this approach as it introduces latency and could vary based on server speeds, concurrent network operations, or other factors.

Java相关问答推荐

@从类文件中删除JsonProperty—Java

Mat. n_Delete()和Mat. n_release的区别

对某一Hyroby控制器禁用@cacheable

CompleteableFuture是否运行在不同的内核上?

在AVL树的Remove方法中使用NoSuchElementException时遇到问题

如何使用带有谓词参数的方法,而不使用lambda表达式

如何对多个字段进行分组和排序?

如何在Microronaut中将 map 读取为 map

使用OAuth 2.0资源服务器JWT时的授权(授权)问题

MimeMessage emlMessage=new MimeMessage(Session,emlInputStream);抛出InvocationTargetException

有效的公式或值列表必须少于或等于255个字符

是否为计划任务补偿系统睡眠?

二进制数据的未知编码/序列化

为什么在下面的Java泛型方法中没有类型限制?

在Eclipse中可以使用外部字体吗?

JavaFX:为什么我的ComboBox添加了一个不必要的单元格的一部分?

如何使用我的RLE程序解决此问题

Cucumber中第二个网页的类对象未初始化

将天数添加到ZonedDateTime不会更改时间

如何使用 JDBC 更改 Postgres Enum 类型