You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Agora视频通话黑屏偶现画面、音频正常问题排查求助

Agora视频通话黑屏问题排查

集成Agora视频通话功能时出现异常:通话界面显示黑屏,仅偶尔能捕获到画面,但音频功能正常,且双方均能成功加入频道。以下是实现代码和布局文件,请求排查问题根源。

核心实现代码(VideoCallActivity.java)

public class VideoCallActivity extends AppCompatActivity implements WebServiceInterface {

Session session;
private AgoraVideoViewer agView = null;
private String appId = "xxxx";
private String appCertificate = "xxxx";
private String channelName = "VideoCallTestChannelNew";
private String token = "xxxx";

private int uid = 5;
private boolean isJoined = false;

private RtcEngine agoraEngine;
private SurfaceView localSurfaceView;
private SurfaceView remoteSurfaceView;

FloatingActionButton fabLeaveChannel;
String userID, doctorID, userName, doctorName, appointmentID;

private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS =
        {
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.CAMERA
        };
private int GET_APPOINTMENT_REQUEST_CODE = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_video_call);

    init();

    fabLeaveChannel.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            leaveChannel();
            finish();
        }
    });

}

private void init() {

    session = new Session(this);

    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    TextView textViewToolbarTitle = toolbar.findViewById(R.id.textViewToolbarTitle);
    textViewToolbarTitle.setText("OnGoing Call");
    getSupportActionBar().setTitle(null);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getSupportActionBar().setDisplayShowHomeEnabled(true);

    userID = getIntent().getStringExtra(LinksAndKeys.USER_ID_KEY);
    userName = getIntent().getStringExtra(LinksAndKeys.USER_NAME_KEY);
    doctorID = getIntent().getStringExtra(LinksAndKeys.DOCTOR_ID_KEY);
    doctorName = getIntent().getStringExtra(LinksAndKeys.DOCTOR_NAME_KEY);
    appointmentID = getIntent().getStringExtra(LinksAndKeys.APPOINTMENT_ID_KEY);

    channelName = userID + ":" + userName + ":" + appointmentID + ":" + doctorID + ":" + doctorName;
    channelName = channelName.replaceAll(" ", "_").toLowerCase();

    fabLeaveChannel = findViewById(R.id.fabLeaveChannel);

    try {
        agView = new AgoraVideoViewer(this, new AgoraConnectionData(appId, token), AgoraVideoViewer.Style.FLOATING, new AgoraSettings(), null);
    } catch (Exception e) {
        Log.e("AgoraVideoViewer",
                "Could not initialize AgoraVideoViewer. Check that your app Id is valid.");
        Log.e("Exception", e.toString());
        return;
    }

    if (!checkSelfPermission()) {
        ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
    }
    setupVideoSDKEngine();

}

private boolean checkSelfPermission() {
    if (ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[1]) != PackageManager.PERMISSION_GRANTED) {
        return false;
    }
    return true;
}

void showMessage(String message) {
    runOnUiThread(() ->
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
}

private void setupVideoSDKEngine() {
    try {
        RtcEngineConfig config = new RtcEngineConfig();
        config.mContext = getBaseContext();
        config.mAppId = appId;
        config.mEventHandler = mRtcEventHandler;
        agoraEngine = RtcEngine.create(config);
        agoraEngine.enableVideo();

        sendGetAppointmentRequest();
    } catch (Exception e) {
        showMessage(e.toString());
    }
}

private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
    @Override
    // Listen for the remote host joining the channel to get the uid of the host.
    public void onUserJoined(int uid, int elapsed) {
        showMessage("Remote user joined " + uid);
        runOnUiThread(() -> setupRemoteVideo(uid));
    }

    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
        isJoined = true;
        showMessage("Joined Channel " + channel);
    }

    @Override
    public void onUserOffline(int uid, int reason) {
        showMessage("Remote user offline " + uid + " " + reason);
        runOnUiThread(() -> remoteSurfaceView.setVisibility(View.GONE));
    }
};

private void setupRemoteVideo(int uid) {
    FrameLayout container = findViewById(R.id.remote_video_view_container);
    remoteSurfaceView = new SurfaceView(getBaseContext());
    remoteSurfaceView.setZOrderMediaOverlay(true);
    container.addView(remoteSurfaceView);
    agoraEngine.setupRemoteVideo(new VideoCanvas(remoteSurfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
    remoteSurfaceView.setVisibility(View.VISIBLE);
}

private void setupLocalVideo() {
    FrameLayout container = findViewById(R.id.local_video_view_container);
    localSurfaceView = new SurfaceView(getBaseContext());
    container.addView(localSurfaceView);
    agoraEngine.setupLocalVideo(new VideoCanvas(localSurfaceView, VideoCanvas.RENDER_MODE_FILL, 0));
}

public void joinChannel() {
    if (checkSelfPermission()) {
        ChannelMediaOptions options = new ChannelMediaOptions();
//            options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION;
//            options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
        setupLocalVideo();
        localSurfaceView.setVisibility(View.VISIBLE);
        agoraEngine.startPreview();
        agoraEngine.joinChannel(token, channelName, String.valueOf(options), uid);
    } else {
        Toast.makeText(getApplicationContext(), "Permissions was not granted", Toast.LENGTH_SHORT).show();
    }
}

public void leaveChannel() {
    if (!isJoined) {
        showMessage("Join a channel first");
    } else {
        agoraEngine.leaveChannel();
        showMessage("You left the channel");
        // Stop remote video rendering.
        if (remoteSurfaceView != null) remoteSurfaceView.setVisibility(View.GONE);
        // Stop local video rendering.
        if (localSurfaceView != null) localSurfaceView.setVisibility(View.GONE);
        isJoined = false;
    }
}

private void sendGetAppointmentRequest() {
    HashMap<String, String> paramsList = new HashMap<>();

    WebServiceController webServiceController = new WebServiceController(VideoCallActivity.this, VideoCallActivity.this);
    String hitURL = LinksAndKeys.GET_APPOINTMENT_BY_ID_URL + appointmentID;
    webServiceController.sendGETRequest(session.getToken(), LinksAndKeys.REQUEST_MESSAGE, hitURL, paramsList, GET_APPOINTMENT_REQUEST_CODE);
}

@Override
public void getResponse(int responseCode, String responseString, String requestType, int requestCode) {
    if (responseCode == 200) {
        generateToken();
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            onBackPressed();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

protected void onDestroy() {
    super.onDestroy();
    agoraEngine.stopPreview();
    agoraEngine.leaveChannel();

    // Destroy the engine in a sub-thread to avoid congestion
    new Thread(() -> {
        RtcEngine.destroy();
        agoraEngine = null;
    }).start();
}

public void generateToken() {

    RtcTokenBuilder2 tokenBuilder = new RtcTokenBuilder2();
    long expirationTimeInSeconds = 3600;
    int timestamp = (int) (System.currentTimeMillis() / 1000 + expirationTimeInSeconds);

    System.out.println("UID token");
    String result = tokenBuilder.buildTokenWithUid(appId, appCertificate,
            channelName, uid, RtcTokenBuilder2.Role.ROLE_PUBLISHER, timestamp, timestamp);
    System.out.println("The new Token Is - " + result);
    token = result;
    joinChannel();
}

}

布局文件(activity_video_call.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimary"
    android:orientation="vertical">

    <include
        android:id="@+id/toolbar"
        layout="@layout/layout_toolbar" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".VideoCallActivity">

        <FrameLayout
            android:id="@+id/remote_video_view_container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:layout_margin="@dimen/size_medium"
            android:background="@android:color/darker_gray" />

        <FrameLayout
            android:id="@+id/local_video_view_container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:layout_gravity="bottom|right"
            android:layout_margin="@dimen/size_large"
            android:background="@android:color/darker_gray" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fabLeaveChannel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="@dimen/size_extra_large"
            android:src="@android:drawable/ic_menu_call"
            android:tint="@color/white"
            app:backgroundTint="@color/highlightRed" />


    </LinearLayout>

</LinearLayout>

排查修复建议

  • 移除重复的AgoraVideoViewer实例:代码中同时初始化了AgoraVideoViewer和原生RtcEngine,两者功能重叠会导致资源冲突,直接删除AgoraVideoViewer相关代码即可,你已经基于原生RtcEngine实现了视频逻辑。
  • 补充权限授权回调处理:当前仅请求了权限,但未处理用户授权后的逻辑。重写onRequestPermissionsResult方法,在权限通过后调用joinChannel:
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQ_ID) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            if (allGranted) {
                joinChannel();
            } else {
                showMessage("权限未全部授予,无法开启视频");
            }
        }
    }
    
  • 正确配置ChannelMediaOptions:取消注释channelProfile和clientRoleType的配置,确保使用通信模式且角色为发布者,否则可能无法正常发布视频流:
    ChannelMediaOptions options = new ChannelMediaOptions();
    options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION;
    options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
    
  • 调整本地预览布局:当前本地预览容器和远程容器平分屏幕,不符合常规视频通话布局。修改local_video_view_container的布局参数,设置固定宽高并定位到右下角:
    <FrameLayout
        android:id="@+id/local_video_view_container"
        android:layout_width="150dp"
        android:layout_height="200dp"
        android:layout_gravity="bottom|right"
        android:layout_margin="@dimen/size_large"
        android:background="@android:color/darker_gray" />
    
  • 统一UID配置:setupLocalVideo中使用的UID是0,但joinChannel和generateToken用的是自定义UID5,保持一致,将setupLocalVideo中的UID改为uid变量:
    agoraEngine.setupLocalVideo(new VideoCanvas(localSurfaceView, VideoCanvas.RENDER_MODE_FILL, uid));
    
  • 检查Token生成逻辑:确保generateToken中使用的频道名、UID和joinChannel时完全一致,避免因Token不匹配导致视频流无法正常传输。

内容的提问来源于stack exchange,提问作者Aishvarya Jaiswal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 21:29:50