首页 > 解决方案 > 对于 Exoplayer 的 AdaptiveTrackSelection,我应该切换到具有多个比特率的单个轨道而不是具有单独比特率的四个轨道吗?

问题描述

目前,我有一台服务器可以传输四个 RTMP MediaSource,一个是 720p 视频源,一个是 360p 视频源,一个是 180p 视频源,一个是纯音频源。如果我想切换分辨率,我必须停止ExoPlayer实例,准备我想切换到的另一个轨道,然后播放。

我用来准备ExoPlayer实例的代码:

    TrackSelection.Factory adaptiveTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory);

    RtmpDataSourceFactory rtmpDataSourceFactory = new RtmpDataSourceFactory(bandwidthMeter);
    ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
    factory = new AVControlExtractorMediaSource.Factory(rtmpDataSourceFactory);
    factory.setExtractorsFactory(extractorsFactory);

    createSource();

    //noinspection deprecation
    mPlayer = ExoPlayerFactory.newSimpleInstance(mActivity, trackSelector, new DefaultLoadControl(
            new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
            1000,  // min buffer
            2000, // max buffer
            1000, // playback
            1000,   //playback after rebuffer
            DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES,
            true
    ));

    vwExoPlayer.setPlayer(mPlayer);

    mPlayer.addAnalyticsListener(mAnalyticsListener);

存在createSource()

private void createSource() {
    factory.setTrackPlaybackFlag(AVControlExtractorMediaSource.PLAYBACK_BOTH_AV);
    mMediaSource180 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_180()));
    mMediaSource180.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource180"));

    mMediaSource360 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_360()));
    mMediaSource360.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource360"));

    mMediaSource720 = factory.createMediaSource(Uri.parse(API.GAME_VIDEO_STREAM_URL_720()));
    mMediaSource720.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSource720"));

    factory.setTrackPlaybackFlag(AVControlExtractorMediaSource.PLAYBACK_AUDIO_ONLY);
    mMediaSourceAudio = factory.createMediaSource(Uri.parse(API.GAME_AUDIO_STREAM_URL()));
    mMediaSourceAudio.addEventListener(getHandler(), new MSourceDebuggerListener("GameMediaSourceAudio"));
}

private void releaseSource() {
    mMediaSource180.releaseSource(null);
    mMediaSource360.releaseSource(null);
    mMediaSource720.releaseSource(null);
    mMediaSourceAudio.releaseSource(null);
}

我目前用来在这些之间切换的代码MediaSources是:

private void changeTrack(MediaSource source) {
    if (currentMediaSource == source) return;

    try {
        this.currentMediaSource = source;
        mPlayer.stop(true);
        mPlayer.prepare(source, true, true);
        mPlayer.setPlayWhenReady(true);

        if (source == mMediaSourceAudio) {
            if (!audioOnly) {
                try {
                    TransitionManager.beginDelayedTransition(rootView);
                } catch (Exception ignored) {

                }

                layAudioOnly.setVisibility(View.VISIBLE);
                vwExoPlayer.setVisibility(View.INVISIBLE);
                audioOnly = true;

                try {
                    GameQnAFragment fragment = findFragment(GameQnAFragment.class);
                    if (fragment != null) {
                        fragment.signAudioOnly();
                    }
                } catch (Exception e) {
                    Trace.e(e);
                }

                try {
                    GamePollingFragment fragment = findFragment(GamePollingFragment.class);
                    if (fragment != null) {
                        fragment.signAudioOnly();
                    }
                } catch (Exception e) {
                    Trace.e(e);
                }
            }
        } else {
            if (audioOnly) {
                TransitionManager.beginDelayedTransition(rootView);
                layAudioOnly.setVisibility(View.GONE);
                vwExoPlayer.setVisibility(View.VISIBLE);
                audioOnly = false;
            }
        }
    } catch (Exception ignore) {

    }
}

我想实现这些MediaSources之间的无缝切换,这样我就不需要停下来重新准备,但是ExoPlayer似乎不支持这个功能。

此外,MediaSource使用以下代码记录每个结构:

MappingTrackSelector.MappedTrackInfo info = ((DefaultTrackSelector)trackSelector).getCurrentMappedTrackInfo();
    if(info != null) {
        for (int i = 0; i < info.getRendererCount(); i++) {
            TrackGroupArray trackGroups = info.getTrackGroups(i);
            if (trackGroups.length != 0) {
                for(int j = 0; j < trackGroups.length; j++) {
                    TrackGroup tg = trackGroups.get(j);
                    for(int k = 0; k < tg.length; k++) {
                        Log.i("track_info_"+i+"-"+j+"-"+k, tg.getFormat(k)+"");
                    }
                }
            }
        }
    }

只为我提供 1 种视频格式和 1 种音频格式。

我目前的解决方法是在后台准备另一个ExoPlayer实例,在准备完成后用当前运行的实例替换当前运行的实例,然后释放旧实例。这减少了两者之间的滞后MediaSources,但与 Youtube 等实现无缝分辨率变化相差甚远。

我应该实现自己的TrackSelector并将所有 4 个源打包到其中,我应该实现另一个处理所有 4 个源的 MediaSource,还是应该告诉维护流的同事只切换到一个MediaSource带有某种清单的RTMP列出所有可用于AdaptiveTrackSelection在它们之间切换的分辨率?

标签: androidstreamingrtmpexoplayer

解决方案


自适应比特率流媒体旨在允许在不同比特率流之间轻松切换,但它需要对流进行分段,并且播放器需要逐段下载视频。

通过这种方式,播放器可以根据当前的网络状况(以及设备显示大小和 t 类型)来决定为下一段选择哪种比特率。除了不同的比特率和质量外,播放器还可以通过这种方式从一种比特率无缝切换到另一种比特率。

有关更多信息,请参见此处:https ://stackoverflow.com/a/42365034/334402

以上所有依赖于支持这种分段和不同比特率流的传输协议。今天最常见的是 HLS 和 MPEG-DASH。

支持我认为您正在寻找的最简单的方法是为您提供流的同事使用 HLS 和/或 DASH 来提供它。

请注意,目前,HLS 和 DASH 都是必需的,因为苹果设备需要 HLS,而其他设备往往默认使用 DASH。传统上,HLS 使用 TS 作为片段中视频的容器,而 DASH 使用分段 MP4,但现在两者都使用 CMAF,本质上是分段 MP4。

因此,理论上,现在 HLS 和 DASH 可以使用一组比特率视频 - 实际上,这取决于您的内容是否加密,因为 HLS 和苹果过去使用一种加密模式,而其他人则使用另一种加密模式。这种情况现在也在发生变化,但在所有设备都支持新方法之前需要时间,所有设备都可以支持相同的加密模式,所以如果你的流是加密的,目前这是一个额外的复杂性。


推荐阅读