做的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