机顶盒播放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

相关推荐
还是奇怪10 分钟前
Simon Willison 用 DSPy 优化 Datasette Agent 提示词:提示工程正在变成可测试的软件工程
java·开发语言·软件工程
zfoo-framework25 分钟前
1.ansible安装 2.虚拟机克隆
java
码上解惑35 分钟前
从 Dify 工作流说起:常用节点怎么选、怎样组合?
java·人工智能·ai·agent·dify·智能体·spring ai
上海云盾-高防顾问1 小时前
SD-WAN 跨境加速,真实日常使用体验
网络·网络安全
青山木2 小时前
Hot 100 --- 岛屿数量
java·数据结构·算法·leetcode·深度优先·广度优先
2401_873479402 小时前
SOC告警日志中IP归属不明怎么办?部署IP离线库三步提升响应效率
网络·网络协议·tcp/ip
Multipath7122 小时前
多链路聚合 + 宽带自组网 + 卫星便携站,构筑应急通信“铁三角”乾元通多链路聚合路由破局“三断”绝境,重构应急通信生命线
网络·5g·安全·智能路由器·实时音视频
程序员-珍2 小时前
报错下载android sdk失败
android·java
nVisual2 小时前
机柜PDU安装位置与空间建模方案
大数据·网络·数据库·信息可视化·数据中心基础设施管理
糖果店的幽灵2 小时前
langgraph分支之 - 动态分支(Dynamic Branch)
java·前端·javascript·人工智能·langgraph