机顶盒播放udp/rtp马赛克

做的Android studio项目,播放udp/rtp直播流时容易马赛克,而且容易死机,静帧等。还是AI牛逼,灵码+豆包就解决了问题(问对正确的问题),还是记录一下。

rtp流在播放时(该流用ts分析软件看pcr精度错误比较多),有些片段有马赛克,用vlc播放却很正常(统计信息里面没有报错)。


复制代码
PCR 精度错误处理
问题原因分析
PCR 精度错误 → 导致音视频同步困难
IJKPlayer 的时钟恢复策略不够灵活 → 无法像 VLC 那样智能处理有问题的时间戳
网络抖动 + 有问题的时间戳 → 双重影响导致马赛克

核心保留逻辑:
1).全程开启硬解(mediacodec=1),适配机顶盒性能需求;只保留ignidx+igndts+genpts+discardcorrupt
  【RTP 流一开 nobuffer 必马赛克】;flush_packets=0 不强制丢包,让缓冲区吸收 PCR 错误;硬解全开 + err_detect=ignore_err,
  让硬解忽略坏帧,不花屏。
2).fflags 仅配置 1 次(option15),避免多次覆盖导致配置失效,核心容错 PCR 错误;
3).关闭 framedrop(不丢帧)、关闭 flush_packets(不强制刷包),解决硬解场景下的马赛克;
配置顺序:按「传输→音频→缓冲→PCR 容错→不丢帧→流探测→硬解→解码容错→同步→重连」分类,逻辑清晰,便于你后续调整。

java 复制代码
/**
     * 初始化基础配置(机顶盒硬解版 + RTP流PCR错误/马赛克优化)
     */
    private void initBasePlayerOptions() {
        BASE_PLAYER_OPTIONS = new ArrayList<>();

        // ========== RTP/UDP 传输配置 ==========
        VideoOptionModel option1 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "udp");
        VideoOptionModel option2 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", "1");
        VideoOptionModel option3 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "udp_timeout", "30000000");
        VideoOptionModel option4 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "20000");
        VideoOptionModel option5 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "rtp,udp,rtcp,rtsp,tcp,http");

        // ========== 音频配置 ==========
        VideoOptionModel option6 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "acodec", "ffmpeg");
        VideoOptionModel option7 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "audio-pull-stream", "1");
        VideoOptionModel option8 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "0");
        VideoOptionModel option9 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "audio_buffer_size", "8192");

        // ========== 缓冲配置(抗马赛克核心) ==========
        VideoOptionModel option10 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "1");
        VideoOptionModel option11 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");
        VideoOptionModel option12 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");
        VideoOptionModel option13 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", "4194304");
        VideoOptionModel option14 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "buffer_size", "4194304");

        // ========== PCR错误容错核心配置 ==========
        VideoOptionModel option15 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "ignidx+igndts+genpts+discardcorrupt");

        // ========== 不丢帧配置(机顶盒硬解必需) ==========
        VideoOptionModel option16 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "0");
        VideoOptionModel option17 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", "0");

        // ========== 流探测配置(兼容坏PCR) ==========
        VideoOptionModel option18 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "3000000");
        VideoOptionModel option19 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", "2048000");
        VideoOptionModel option20 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fpsprobesize", "0");
        VideoOptionModel option21 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "find_stream_info", "1");

        // ========== 禁止强制刷包(避免帧破碎) ==========
        VideoOptionModel option22 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", "0");

        // ========== 硬解码配置(机顶盒必需,开启容错) ==========
        VideoOptionModel option23 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "mediacodec", "1");
        VideoOptionModel option24 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "mediacodec-auto-rotate", "1");
        VideoOptionModel option25 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "mediacodec-handle-resolution-change", "1");
        VideoOptionModel option26 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", "1");
        VideoOptionModel option27 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "enable_mpeg2_video", "1");

        // ========== 解码容错配置 ==========
        VideoOptionModel option28 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "err_detect", "ignore_err");
        VideoOptionModel option29 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "strict", "-2");

        // ========== 同步与启动配置 ==========
        VideoOptionModel option30 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "sync-av-start", "0");
        VideoOptionModel option31 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");
        VideoOptionModel option32 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", "1");

        // ========== 重连配置 ==========
        VideoOptionModel option33 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect_delay_max", "10");
        VideoOptionModel option34 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "listen_timeout", "5000");

        // ========== 添加所有配置到列表 ==========
        BASE_PLAYER_OPTIONS.add(option1);
        BASE_PLAYER_OPTIONS.add(option2);
        BASE_PLAYER_OPTIONS.add(option3);
        BASE_PLAYER_OPTIONS.add(option4);
        BASE_PLAYER_OPTIONS.add(option5);
        BASE_PLAYER_OPTIONS.add(option6);
        BASE_PLAYER_OPTIONS.add(option7);
        BASE_PLAYER_OPTIONS.add(option8);
        BASE_PLAYER_OPTIONS.add(option9);
        BASE_PLAYER_OPTIONS.add(option10);
        BASE_PLAYER_OPTIONS.add(option11);
        BASE_PLAYER_OPTIONS.add(option12);
        BASE_PLAYER_OPTIONS.add(option13);
        BASE_PLAYER_OPTIONS.add(option14);
        BASE_PLAYER_OPTIONS.add(option15);
        BASE_PLAYER_OPTIONS.add(option16);
        BASE_PLAYER_OPTIONS.add(option17);
        BASE_PLAYER_OPTIONS.add(option18);
        BASE_PLAYER_OPTIONS.add(option19);
        BASE_PLAYER_OPTIONS.add(option20);
        BASE_PLAYER_OPTIONS.add(option21);
        BASE_PLAYER_OPTIONS.add(option22);
        BASE_PLAYER_OPTIONS.add(option23);
        BASE_PLAYER_OPTIONS.add(option24);
        BASE_PLAYER_OPTIONS.add(option25);
        BASE_PLAYER_OPTIONS.add(option26);
        BASE_PLAYER_OPTIONS.add(option27);
        BASE_PLAYER_OPTIONS.add(option28);
        BASE_PLAYER_OPTIONS.add(option29);
        BASE_PLAYER_OPTIONS.add(option30);
        BASE_PLAYER_OPTIONS.add(option31);
        BASE_PLAYER_OPTIONS.add(option32);
        BASE_PLAYER_OPTIONS.add(option33);
        BASE_PLAYER_OPTIONS.add(option34);
    }

private void initPlayer() {
        GSYVideoType.enableMediaCodec();
        GSYVideoType.enableMediaCodecTexture();
        GSYVideoType.setShowType(GSYVideoType.SCREEN_MATCH_FULL);
        GSYVideoType.setRenderType(GSYVideoType.SURFACE);

        PlayerFactory.setPlayManager(IjkPlayerManager.class);

        initBasePlayerOptions();
        GSYVideoManager.instance().setOptionModelList(BASE_PLAYER_OPTIONS);

        binding.liveVideoPlayer.setIsTouchWigetFull(true);
        binding.liveVideoPlayer.setVideoAllCallBack(new GSYSampleCallBack() {
            @Override
            public void onPrepared(String url, Object... objects) {
                super.onPrepared(url, objects);
                isPlaying = true;
                streamLostCount = 0;

                if (first_play) {
                    play_start_time = App.newtime;
                    if (App.newtime - 60 >= play_start_time) {
                        first_play = false;
                    }
                } else {
                    if (App.newtime - 60 >= play_start_time) {
                        uploadLiveWatchData();
                    }
                    play_start_time = App.newtime;
                }
                if (binding.progressBarLoadingVideo.getVisibility() == View.VISIBLE) {
                    binding.progressBarLoadingVideo.setVisibility(View.GONE);
                    binding.tvNetspeed.setVisibility(View.GONE);
                }
                mHandler.sendEmptyMessageDelayed(GET_TACK, 1000);
                BuglyLog.d(TAG, "播放准备完成:" + url);
            }

            @Override
            public void onAutoComplete(String url, Object... objects) {
                super.onAutoComplete(url, objects);
                BuglyLog.w(TAG, "播放自动完成(可能流中断):" + url);
                isPlaying = false;

                mHandler.postDelayed(() -> {
                    if (!isPlaying && mLive != null && mLive.getPlaylist() != null
                            && currentPlayPosition >= 0 && currentPlayPosition < mLive.getPlaylist().size()) {
                        String currentUrl = mLive.getPlaylist().get(currentPlayPosition).getUrl();
                        BuglyLog.i(TAG, "检测到流中断,尝试自动重连..." + currentUrl);
                        playNewUrl(currentUrl, mLive.getPlaylist().get(currentPlayPosition).getService_name());
                    }
                }, 2000);
            }

            @Override
            public void onPlayError(String url, Object... objects) {
                super.onPlayError(url, objects);
                BuglyLog.e(TAG, "播放错误:" + url);
                isPlaying = false;

                try {
                    binding.liveVideoPlayer.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                streamLostCount++;
                if (streamLostCount <= MAX_STREAM_LOST_COUNT) {
                    BuglyLog.i(TAG, "播放错误,尝试第" + streamLostCount + "次重连...");
                    ToastUtils.show("信号中断,正在重连...(" + streamLostCount + "/" + MAX_STREAM_LOST_COUNT + ")");

                    mHandler.postDelayed(() -> {
                        if (mLive != null && mLive.getPlaylist() != null
                                && currentPlayPosition >= 0 && currentPlayPosition < mLive.getPlaylist().size()) {
                            String currentUrl = mLive.getPlaylist().get(currentPlayPosition).getUrl();
                            playNewUrl(currentUrl, mLive.getPlaylist().get(currentPlayPosition).getService_name());
                        }
                    }, 3000 * streamLostCount);
                } else {
                    BuglyLog.e(TAG, "节目无法播放,超过最大重试次数:" + url);
                    ToastUtils.show(R.string.the_program_cannot_be_played);
                    streamLostCount = 0;
                }
            }

            @Override
            public void onClickBlank(String url, Object... objects) {
                showList();
                showInfo();
                super.onClickBlank(url, objects);
            }

            @Override
            public void onEnterFullscreen(String url, Object... objects) {
                super.onEnterFullscreen(url, objects);
                isPlaying = true;
            }

            @Override
            public void onQuitFullscreen(String url, Object... objects) {
                super.onQuitFullscreen(url, objects);
                isPlaying = true;
            }
        });
    }

9:55 2026/3/13

相关推荐
Arya_aa1 小时前
多个对象通过集合实现io流的读写
java
白云如幻2 小时前
【JDBC】集合、反射和泛型复习-3
java·开发语言·jdbc
lied16636348062 小时前
配置网站HTTP 自动跳转 HTTPS
网络协议·http·https
tang777892 小时前
哪些行业用动态代理ip?哪些行业用静态代理IP?怎样区分动态ip和静态ip?(互联网人必码·实用长文)
大数据·网络·爬虫·python·网络协议·tcp/ip·智能路由器
Du_chong_huan2 小时前
《网络是怎样连接的》精读版 第四章总述
网络·网络协议·计算机网络
tang777892 小时前
爬虫代理IP池到底有啥用?
网络·爬虫·python·网络协议·tcp/ip·ip
冬夜戏雪2 小时前
实习面经摘录(六)
java
九硕智慧建筑一体化厂家2 小时前
DDC:看似普通的存在,在楼宇自控系统中却主宰智能建筑高效运行?
大数据·运维·人工智能·网络协议·制造·设计规范
把你毕设抢过来2 小时前
基于Spring Boot的演唱会购票系统的设计与实现(源码+文档)
java·spring boot·后端