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
相关产品推荐
相关产品推荐

