官方公告
Agora Native SDK 现已更新至1.10版本,查看发版说明
推荐
  • 多人视频开发教程-Android [附源码]
    这篇文章是转自掘金开发者社区,是一个热心开发者基于Agora SDK开发的多人视频应用,以下开始正文:   GitHub地址 https://github.com/uncleleonfan/LaoTieParty   叔想做个直播demo很久了,最近终于得空,做了一个视频群聊Demo,以飨观众。 直播云有很多大厂在做,经老铁介绍,Agora不错,遂入坑。Agora提供多种模式,一个频道可以设置一种模式, 切换方便:   Agora SDK集成 叔专注SDK集成几十年,Agora SDK集成也并没有搞什么事情,大家按照下面步骤上车就行。   1. 导入库文件 将libs文件夹的下的文件导入Android Studio, 最终效果如下: [attach]446[/attach] 2. 添加必要权限   在AndroidManifest.xml中添加如下权限 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> 3. 配置APP ID 在values文件夹下创建strings-config.xml, 配置在官网创建应用的App ID。 <resources> <string name="private_app_id">YOU APP ID</string> </resources> 至此,Agoria SDK集成已经完毕,似不似如丝般顺滑?如果官方能够提供Gradle依赖,还可以省掉下载SDK和导入库文件的步骤,那就更滑了。接下来我们就可以愉快地和SDK玩耍了。   主界面(MainActivity) [attach]453[/attach]   在主界面,我们需要检查先Camera和Audio权限,以适配Andriod6.0及以上版本。 private static final int PERMISSION_REQ_ID_RECORD_AUDIO = 0; private static final int PERMISSION_REQ_ID_CAMERA = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //检查Audio权限 if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO)) { //检查Camera权限 checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID_CAMERA); } } public boolean checkSelfPermission(String permission, int requestCode) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode); return false; } return true; } 频道界面 (ChannelActivity) 点击开PA!,进入频道选择界面 [attach]455[/attach] 创建频道列表 这里使用RecyclerView创建频道列表。 /** * 初始化频道列表 */ private void initRecyclerView() { mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); mRecyclerView.setHasFixedSize(true); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.setAdapter(new ChannelAdapter(this, mockChannelList())); } 前置摄像头预览 频道界面背景为前置摄像头预览,这个可以使用Android SDK自己实现。但Agora SDK提供了相关API可以直接实现前置摄像头预览的功能。具体实现如下: 1. 初始化RtcEngine RtcEngine是Agora SDK的核心类,叔用一个管理类AgoraManager进行了简单的封装,提供操作RtcEngine的核心功能。 初始化如下: /** * 初始化RtcEngine */ public void init(Context context) { //创建RtcEngine对象, mRtcEventHandler为RtcEngine的回调 mRtcEngine = RtcEngine.create(context, context.getString(R.string.private_app_id), mRtcEventHandler); //开启视频功能 mRtcEngine.enableVideo(); //视频配置,设置为360P mRtcEngine.setVideoProfile(Constants.VIDEO_PROFILE_360P, false); mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);//设置为通信模式(默认) //mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);设置为直播模式 //mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_GAME);设置为游戏模式 } /** * 在Application类中初始化RtcEngine,注意在AndroidManifest.xml中配置下Application */ public class LaoTieApplication extends Application { @Override public void onCreate() { super.onCreate(); AgoraManager.getInstance().init(getApplicationContext()); } } 2. 设置本地视频​ /** * 设置本地视频,即前置摄像头预览 */ public AgoraManager setupLocalVideo(Context context) { //创建一个SurfaceView用作视频预览 SurfaceView surfaceView = RtcEngine.CreateRendererView(context); //将SurfaceView保存起来在SparseArray中,后续会将其加入界面。key为视频的用户id,这里是本地视频, 默认id是0 mSurfaceViews.put(mLocalUid, surfaceView); //设置本地视频,渲染模式选择VideoCanvas.RENDER_MODE_HIDDEN,如果选其他模式会出现视频不会填充满整个SurfaceView的情况, //具体渲染模式的区别是什么,官方也没有详细的说明 mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_HIDDEN, mLocalUid)); return this;//返回AgoraManager以作链式调用 } 3. 添加SurfaceView到布局 @Override protected void onResume() { super.onResume(); //先清空容器 mFrameLayout.removeAllViews(); //设置本地前置摄像头预览并启动 AgoraManager.getInstance().setupLocalVideo(getApplicationContext()).startPreview(); //将本地摄像头预览的SurfaceView添加到容器中 mFrameLayout.addView(AgoraManager.getInstance().getLocalSurfaceView()); } 4. 停止预览   /** * 停止预览 */ @Override protected void onPause() { super.onPause(); AgoraManager.getInstance().stopPreview(); } 聊天室 (PartyRoomActivity) 点击频道列表中的选项,跳转到聊天室界面。聊天室界面显示规则是:1个人是全屏,2个人是2分屏,3-4个人是4分屏,5-6个人是6分屏, 4分屏和6分屏模式下,双击一个小窗,窗会变大,其余小窗在底部排列。最多支持六人同时聊天。基于这种需求,叔决定写一个自定义控件PartyRoomLayout来完成。PartyRoomLayout直接继承ViewGroup,根据不同的显示模式来完成孩子的测量和布局。 1人全屏 [attach]454[/attach]   1人全屏其实就是前置摄像头预览效果。 前置摄像头预览   //设置前置摄像头预览并开启 AgoraManager.getInstance() .setupLocalVideo(getApplicationContext()) .startPreview(); //将摄像头预览的SurfaceView加入PartyRoomLayout mPartyRoomLayout.addView(AgoraManager.getInstance().getLocalSurfaceView()); PartyRoomLayout处理1人全屏 /** * 测量一个孩子的情况,孩子的宽高和父容器即PartyRoomLayout一样 */ private void measureOneChild(int widthMeasureSpec, int heightMeasureSpec) { View child = getChildAt(0); child.measure(widthMeasureSpec, heightMeasureSpec); } /** * 布局一个孩子的情况 */ private void layoutOneChild() { View child = getChildAt(0); child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); } 加入频道 从频道列表跳转过来后,需要加入到用户所选的频道。 //更新频道的TextView mChannel = (TextView) findViewById(R.id.channel); String channel = getIntent().getStringExtra(“Channel”); mChannel.setText(channel); //在AgoraManager中封装了加入频道的API AgoraManager.getInstance() .setupLocalVideo(getApplicationContext()) .joinChannel(channel)//加入频道 .startPreview(); 挂断 [attach]447[/attach] 当用户点击挂断按钮可以退出频道 mEndCall = (ImageButton) findViewById(R.id.end_call); mEndCall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //AgoraManager里面封装了挂断的API, 退出频道 AgoraManager.getInstance().leaveChannel(); finish(); } }); 二分屏   [attach]451[/attach] 事件监听器   IRtcEngineEventHandler类里面封装了Agora SDK里面的很多事件回调,在AgoraManager中我们创建了IRtcEngineEventHandler的一个对象mRtcEventHandler,并在创建RtcEngine时传入。   private IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { /** * 当获取用户uid的远程视频的回调 */ @Override public void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) { if (mOnPartyListener != null) { mOnPartyListener.onGetRemoteVideo(uid); } } /** * 加入频道成功的回调 */ @Override public void onJoinChannelSuccess(String channel, int uid, int elapsed) { if (mOnPartyListener != null) { mOnPartyListener.onJoinChannelSuccess(channel, uid); } } /** * 退出频道 */ @Override public void onLeaveChannel(RtcStats stats) { if (mOnPartyListener != null) { mOnPartyListener.onLeaveChannelSuccess(); } } /** * 用户uid离线时的回调 */ @Override public void onUserOffline(int uid, int reason) { if (mOnPartyListener != null) { mOnPartyListener.onUserOffline(uid); } } }; 同时,我们也提供了一个接口,暴露给AgoraManager外部。 public interface OnPartyListener { void onJoinChannelSuccess(String channel, int uid); void onGetRemoteVideo(int uid); void onLeaveChannelSuccess(); void onUserOffline(int uid); } 在PartyRoomActivity中监听事件​   AgoraManager.getInstance() .setupLocalVideo(getApplicationContext()) .setOnPartyListener(mOnPartyListener)//设置监听 .joinChannel(channel) .startPreview(); 设置远程用户视频 private AgoraManager.OnPartyListener mOnPartyListener = new AgoraManager.OnPartyListener() { /** * 获取远程用户视频的回调 */ @Override public void onGetRemoteVideo(final int uid) { //操作UI,需要切换到主线程 runOnUiThread(new Runnable() { @Override public void run() { //设置远程用户的视频 AgoraManager.getInstance().setupRemoteVideo(PartyRoomActivity.this, uid); //将远程用户视频的SurfaceView添加到PartyRoomLayout中,这会触发PartyRoomLayout重新走一遍绘制流程 mPartyRoomLayout.addView(AgoraManager.getInstance().getSurfaceView(uid)); } }); } }; 测量布局二分屏 当第一次回调onGetRemoteVideo时,说明现在有两个用户了,所以在PartyRoomLayout中需要对二分屏模式进行处理 /** * 二分屏时的测量 */ private void measureTwoChild(int widthMeasureSpec, int heightMeasureSpec) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int size = MeasureSpec.getSize(heightMeasureSpec); //孩子高度为父容器高度的一半 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(size / 2, MeasureSpec.EXACTLY); child.measure(widthMeasureSpec, childHeightMeasureSpec); } } /** * 二分屏模式的布局 */ private void layoutTwoChild() { int left = 0; int top = 0; int right = getMeasuredWidth(); int bottom = getChildAt(0).getMeasuredHeight(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(left, top, right, bottom); top += child.getMeasuredHeight(); bottom += child.getMeasuredHeight(); } } 用户离线时的处理 当有用户离线时,我们需要移除该用户视频对应的SurfaceView private AgoraManager.OnPartyListener mOnPartyListener = new AgoraManager.OnPartyListener() { @Override public void onUserOffline(final int uid) { runOnUiThread(new Runnable() { @Override public void run() { //从PartyRoomLayout移除远程视频的SurfaceView mPartyRoomLayout.removeView(AgoraManager.getInstance().getSurfaceView(uid)); //清除缓存的SurfaceView AgoraManager.getInstance().removeSurfaceView(uid); } }); } }; 四分屏和六分屏 当有3个或者4个老铁开趴,界面显示成四分屏, 当有5个或者6个老铁开趴,界面切分成六分屏 [attach]452[/attach] [attach]449[/attach] 由于之前已经处理了新进用户就会创建SurfaceView加入PartyRoomLayout的逻辑,所以这里只需要处理四六分屏时的测量和布局 四六分屏测量 private void measureMoreChildSplit(int widthMeasureSpec, int heightMeasureSpec) { //列数为两列,计算行数 int row = getChildCount() / 2; if (getChildCount() % 2 != 0) { row = row + 1; } //根据行数平分高度 int childHeight = MeasureSpec.getSize(heightMeasureSpec) / row; //宽度为父容器PartyRoomLayout的宽度一般,即屏宽的一半 int childWidth = MeasureSpec.getSize(widthMeasureSpec) / 2; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } 四六分屏布局 private void layoutMoreChildSplit() { int left = 0; int top = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int right = left + child.getMeasuredWidth(); int bottom = top + child.getMeasuredHeight(); child.layout(left, top, right, bottom); if ( (i + 1 )% 2 == 0) {//满足换行条件,更新left和top,布局下一行 left = 0; top += child.getMeasuredHeight(); } else { //不满足换行条件,更新left值,继续布局一行中的下一个孩子 left += child.getMeasuredWidth(); } } } 双击上下分屏布局 [attach]448[/attach] [attach]450[/attach] 在四六分屏模式下,双击一个小窗,窗会变大,其余小窗在底部排列, 成上下分屏模式。实现思路就是监听PartyRoomLayout的触摸时间,当是双击时,则重新布局。 触摸事件处理​ /** * 拦截所有的事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } /** * 让GestureDetector处理触摸事件 */ @Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); return true; } //四六分屏模式 private static int DISPLAY_MODE_SPLIT = 0; //上下分屏模式 private static int DISPLAY_MODE_TOP_BOTTOM = 1; //显示模式的变量,默认是四六分屏 private int mDisplayMode = DISPLAY_MODE_SPLIT; //上下分屏时上面View的下标 private int mTopViewIndex = -1; private GestureDetector.SimpleOnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { handleDoubleTap(e);//处理双击事件 return true; } private void handleDoubleTap(MotionEvent e) { //遍历所有的孩子 for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); //获取孩子view的矩形 Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); if (rect.contains((int)e.getX(), (int)e.getY())) {//找到双击位置的孩子是谁 if (mTopViewIndex == i) {//如果点击的位置就是上面的view, 则切换成四六分屏模式 mDisplayMode = DISPLAY_MODE_SPLIT; mTopViewIndex = -1;//重置上面view的下标 } else { //切换成上下分屏模式, mTopViewIndex = i;//保存双击位置的下标,即上面View的下标 mDisplayMode = DISPLAY_MODE_TOP_BOTTOM; } requestLayout();//请求重新布局 break; } } } }; 上下分屏测量 处理完双击事件后,切换显示模式,请求重新布局,这时候又会触发测量和布局。 /** * 上下分屏模式的测量 */ private void measureMoreChildTopBottom(int widthMeasureSpec, int heightMeasureSpec) { for (int i = 0; i < getChildCount(); i++) { if (i == mTopViewIndex) { //测量上面View measureTopChild(widthMeasureSpec, heightMeasureSpec); } else { //测量下面View measureBottomChild(i, widthMeasureSpec, heightMeasureSpec); } } } /** * 上下分屏模式时上面View的测量 */ private void measureTopChild(int widthMeasureSpec, int heightMeasureSpec) { int size = MeasureSpec.getSize(heightMeasureSpec); //高度为PartyRoomLayout的一半 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(size / 2, MeasureSpec.EXACTLY); getChildAt(mTopViewIndex).measure(widthMeasureSpec, childHeightMeasureSpec); } /** * 上下分屏模式时底部View的测量 */ private void measureBottomChild(int i, int widthMeasureSpec, int heightMeasureSpec) { //除去顶部孩子后还剩的孩子个数 int childCountExcludeTop = getChildCount() - 1; //当底部孩子个数小于等于3时 if (childCountExcludeTop <= 3) { //平分孩子宽度 int childWidth = MeasureSpec.getSize(widthMeasureSpec) / childCountExcludeTop; int size = MeasureSpec.getSize(heightMeasureSpec); //高度为PartyRoomLayout的一半 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(size / 2, MeasureSpec.EXACTLY); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec); } else if (childCountExcludeTop == 4) {//当底部孩子个数为4个时 int childWidth = MeasureSpec.getSize(widthMeasureSpec) / 2;//宽度为PartyRoomLayout的一半 int childHeight = MeasureSpec.getSize(heightMeasureSpec) / 4;//高度为PartyRoomLayout的1/4 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec); } else {//当底部孩子大于4个时 //计算行的个数 int row = childCountExcludeTop / 3; if (row % 3 != 0) { row ++; } //孩子的宽度为PartyRoomLayout宽度的1/3 int childWidth = MeasureSpec.getSize(widthMeasureSpec) / 3; //底部孩子平分PartyRoomLayout一半的高度 int childHeight = (MeasureSpec.getSize(heightMeasureSpec) / 2) / row; int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec); } } 上下分屏布局​ private void layoutMoreChildTopBottom() { //布局上面View View topView = getChildAt(mTopViewIndex); topView.layout(0, 0, topView.getMeasuredWidth(), topView.getMeasuredHeight()); int left = 0; int top = topView.getMeasuredHeight(); for (int i = 0; i < getChildCount(); i++) { //上面已经布局过上面的View, 这里就跳过 if (i == mTopViewIndex) { continue; } View view = getChildAt(i); int right = left + view.getMeasuredWidth(); int bottom = top + view.getMeasuredHeight(); //布局下面的一个View view.layout(left, top, right, bottom); left = left + view.getMeasuredWidth(); if (left >= getWidth()) {//满足换行条件则换行 left = 0; top += view.getMeasuredHeight(); } } }
  • 图像视频旋转裁剪缩放操作说明
    如果你遇到视频图像拉伸、变形,此文能告诉你原因。   在整个引擎的流程中,主要在三处会对图像做一些处理:    采集端,即从摄像头出来的时候;  编码端,视频源在进入编码器的时候;  显示端,即本地显示的时候或者对方解码后显示的时候。    在这三处发生的处理大概是三种操作:a.旋转;b.裁剪;c.缩放。 采集端只可能发生操作 a.旋转; 编码端和显示端行为类似,可能发 生操作 b.裁剪和 c.缩放。 采集端出来的图像可能带有旋转信息。旋转操作不损失图像内容, 只是方向变化。   如下图,640x480的图像顺时针旋转90度后变成480x640,图像大小并没有变化。图像旋转的角度并没有固定,但根据摄像头的物理信息,旋转角度只能是90度的倍数。 [attach]432[/attach] 编码端和显示端行为类似,下面以编码端为例描述发生在这里的操作。   对编码端而言,它的输入是某个分辨率的图像/视频。这些分辨率可能多种多样,我们以宽高比来划分分辨率种类。例如  16:9 的图像/视频可能有1280x720,640x360,320x180,160x90等等不同的实际分辨率; 4:3 的图像/视频可能有 1280x960,640x480, 320x240 等等不同的实际分辨率。 当编码器要求的分辨率和送入它的分辨率不同的时候,就要求对输入的图像/视频做处理。编码端的图像操作可以分别两步:裁剪,缩放。   第一步,裁剪。   裁剪是在原图上截取一部分,能满足目标分辨率的宽高比,同时尽可能多的保留原图像内容。注意,对于不同的分辨率宽高比,为了方便比较,我们可以把它们“归一化”表达。例如宽高比 16:9,4:3,16:10,17:10 以及 5:3,可以统一表达成 16:9, 16:12,,16:10,,16:9.4 以及 16:9.8。   假设输入的宽高比是 16:10,那么所有可能的输出宽高比,除了 16:10 以外可以分成两类,第一类 是 16:9,16:9.4 和 16:9.8;第二类是 16:12。假如输出宽高比也是 16:10,那么原图像不需要任何处理。假如输出宽高比是第一类, 以 16:9 为例,下图展示了其裁剪过程,图像上下会有部分内容被剪掉。 [attach]431[/attach] 假如输出宽高比是第二类,以 16:12 为例,下图展示了其裁剪过程, 图像左右会有部分内容被剪掉。 [attach]430[/attach] 第二步,缩放。   经过第一步裁剪得到的图像,其宽高比已经和目标分辨率的宽高比匹配。在此基础上,如果实际分辨率大小也一样则不需要缩放;如果实际分辨率大小不同则需要放大或者缩小。下图 “缩放 A”以 16:9 的宽高比为例,展示了目标分辨率比输入分辨率小 的时候所需要的缩小处理。 [attach]434[/attach] 下图“缩放 B” 以 16:9 的宽高比为例,展示了目标分辨率比输入分辨率大的时候所需要的放大处理。 [attach]433[/attach] 综上,对于编码器而言,假如其输入的分辨率为 640x360 而编码器想要的分辨率为 640x480。这时,首先需要按照 4:3 的宽高比把 640x360 图像裁剪成 480x360,再将 480x360 图像放大 1.33 倍至 640x480。 显示端作为一个独立的单元,其行为和编码器类似,如果显示窗口的分辨率和解码后送过来的视频分辨率不一致,也需要经过上述裁剪和缩放的处理。 如果是手机的摄像头,拍出来的视频是90度(或者270度)旋转的。先确定是否需要设置 PreRotation:如果没有涉及到 RTMP 推流, PreRotation=false;否则 PreRotation=true。然后确定编码分辨率: 如果 PreRotation==true,设置成 480x640;否则设置成 640x480。 如果是 PC/Mac,拍出来的视频是0度的。PreRotation设置为false, 编码分辨率设置为640x480是最佳的。    [attach]429[/attach] [attach]428[/attach]
  • Android & iOS Native SDK 编译与集成常见问题
    Hi everyone,   本文搜集了大家在 Android & iOS Native SDK 的编译与集成过程中的容易遇到的一些常见问题,方便大家检索定位。如果你在编译集成中还遇到了其它问题,欢迎随时留言和我联系。   在大家开始编译和集成之前,请确保已经认真阅读了 Agora文档中心 的相关文档。   BTW ,常有人问我通信与直播 SDK 的差别。其实 SDK 是一个,区别主要在于 SDK 会在 setChannelProfile 这一步设置频道模式,并进行隔离,然后直播会有一个 setClientRole 为主播/观众的概念。 而语音 + 直播 SDK 与视频 + 直播 SDK 相比,前者没有视频功能,包会小一些。   Android   在进行编译前,请先注意以下几点: A. 确保是从官网而非其它任何途径下载的 SDK ; B. 下载完成后,不要单独移动 samples 文件夹,因为与它同级的 libs 文件夹中的文件会被引用到,导致路径错误; C. 开始编译前,请先配置好 NDK ; D. 项目文件所在的目录不要太深。   1. A problem occured configuring project ':App'.   [attach]398[/attach] NDK 未配置时,AS 会报如图的错误。 解决方法:请先配置好 NDK 再开始编译(所以上面都已经讲过了呀!)。 2. LOCAL_SRC_FILES points to a missing file [attach]401[/attach] 引用路径错误。 解决方法:请确保下载 SDK 之后没有单独移动 samples 文件夹,仔细阅读 集成通讯 - Android 中各库正确的放置路径并检查 .mk 文件中的路径信息。路径错误并不是 Agora SDK 本身导致的,请善用 Google (or Baidu if you'd like to) 了解相关信息。   3. Java.lang.UnsatisfiedLinkError: dlopen failed: "HDACEngine.so" [attach]399[/attach] 路径错误的一种,某些 64 位设备上会出现这个问题。 解决方法:请参考文档 可以在Android设备上做64位兼容吗?   4. unauthorized access to "libcrypto.so" [attach]400[/attach] 可能会出现如图的弹窗。 解决方法:libcrypto.so 为加密库。如果不需要使用加密功能,可以删除该库,缩小 SDK 包的大小。如果删除后弹窗仍然出现,请检查 .mk 文件,取消相关的引用。 如果需要使用加密功能,请参考文档 加密数据 。对于旧版本的 Android SDK ,在部分机型上会出现加密库加载失败的问题。我们已经在 1.9.1 版本中进行了修复,请更新至 1.9.1 版。 iOS 如果出现 AgoraRtcCryptoLoader.framework 相关的问题,也请参考以上文档。   5. 组件缺失 请仔细查看 SDK 包中的各组件。如果包中有该组件却无法引用到,通常是路径问题,参见问题 2 和问题 3; 如果缺失的组件为 libvideoprp.so/videoprp.jar/AgoraYuvEnhancer.so, 则为美颜组件缺失。如果需要美颜功能,请前往官网下载中心下载组件。   iOS   iOS 上目前提供 Swift 与 Objective-C (下载)两种语言的 SDK ,但之后的开发重心会在 Swift 上,请 OC 开发者知悉。   1. AgoraRtcEngineKit.framework 缺失 解决方法:该问题通常只出现在 OC 版 SDK 中。如果遇到,请前往官网下载中心下载 Swift 版本的 SDK ,从中拷贝出这个库即可。   2. videoprp.framework 缺失 解决方法:该组件为美颜组件。如果需要美颜功能,请前往官网下载中心下载组件。   3. linker error [attach]404[/attach] 如果 Xcode 报如图的错误,是因为集成 iOS SDK 时,新增了一个依赖库 libresolv.tbd 用于去除对 dlopen 和 dlsym 的依赖。 解决方法:请仔细阅读 集成通信 - iOS   最后祝大家早日上线啦!    

个别电脑推流ios黑屏有声音 1 小时前

iOS/macOS ios看不到视频 关注2 回答7 浏览63  2017-05-29 23:44 • 来自相关话题

cocoapods 缺少crypto 15 小时前

iOS/macOS cocoapods 关注2 回答1 浏览16  2017-05-29 09:28 • 来自相关话题

编译时无数 Dsymutil Warning: 15 小时前

视频通话 cocoapods 关注2 回答1 浏览13  2017-05-29 09:27 • 来自相关话题

编译时无数 Dsymutil Warning: 15 小时前

视频通话 cocoapods 关注2 回答1 浏览13  2017-05-29 09:27 • 来自相关话题

android平台1对1聊天,怎么监听对方有声音过来 15 小时前

视频通话 有画面无声音 关注2 回答1 浏览20  2017-05-29 09:22 • 来自相关话题

Android端openvideocall这个demo崩溃 15 小时前

视频通话 安卓视频通话崩溃 关注2 回答1 浏览24  2017-05-29 09:19 • 来自相关话题

通信录制视频结束但没有生成record-done.txt文件 2 天前

iOS/macOS 通信录制 关注2 回答2 浏览26  2017-05-27 10:30 • 来自相关话题

windows 直播api中 startRecordingDeviceTest 的疑问 3 天前

Windows windows sdk 关注2 回答1 浏览36  2017-05-26 20:37 • 来自相关话题

关于声网sdk获取摄像头视频流时是否存在自动放大视频流的问题 3 天前

其他 摄像头拍摄缩放 关注2 回答1 浏览21  2017-05-26 20:33 • 来自相关话题

iOS开发 startAudioMixing 播放伴奏问题 3 天前

iOS/macOS 播放伴奏问题 关注2 回答1 浏览24  2017-05-26 19:30 • 来自相关话题