布谷WebRTC一对一视频直播Android源码深度解析
简介:这是一套完整的Android端一对一视频直播社交平台源码,面向移动音视频开发工程师与Android进阶学习者,解决实时音视频通信、社交互动与商业化功能集成等核心问题。资源包含1155个文件,以494个flat资源文件(含UI布局与媒体素材)、138个dex与136个class字节码(构成运行逻辑)、118个json配置(支撑动态模块与接口映射)、68个jar依赖库(涵盖IM、音视频编解码及第三方SDK)及34个xml(定义界面与权限)为主干,整体包体79.14MB。已有450人学习下载,适合开展音视频App二次开发、研究直播业务闭环(如预约、守护、打赏、美颜、轮盘抽奖)及理解社交动态、小视频、私信语音/视频等多模态交互实现。源码结构清晰,含完整_app、_src、_res、_libs模块及_arm64-v8a/_armeabi-v7a原生支持,便于快速构建可商用的一对一视频交友产品原型。
1. 布谷一对一视频直播系统不是“开箱即用”的APP,而是需要深度理解信令流、媒体路径与Android生命周期的可定制直播底座
你下载的 布谷一对一视频直播系统手机视频直播平台android源码.zip ,本质是一套基于WebRTC核心能力封装的Android端点工程,不是成品App安装包。它不依赖任何第三方云服务SDK(如腾讯云TRTC、声网Agora),所有信令协商、ICE候选者交换、音视频编解码控制、SurfaceView/TextureView渲染链路都由Java/Kotlin层与JNI层协同实现。这意味着: 你能完全掌控从摄像头采集到远端画面显示的每一帧延迟、每一路带宽分配、每一次重连策略 ——但也意味着,直接在Android Studio里点击Run,大概率会卡在“连接信令服务器失败”或“本地预览黑屏”。这套源码真正价值在于:为教育陪练、远程医疗问诊、私密社交等强实时、低延迟、高隐私要求的一对一场景,提供可审计、可裁剪、可与自有用户体系深度集成的客户端基座。适合已有信令服务(WebSocket或HTTP长轮询)、熟悉Android音视频开发流程、且明确拒绝将用户音视频流经由第三方公有云中转的团队。新手若仅想快速上线直播功能,应优先评估成熟SDK;而本源码面向的是需要把“一对一”这个业务语义,从协议栈底层就固化进客户端行为逻辑的工程师。
2. 解析布谷源码结构:从AndroidManifest.xml定位入口,用Gradle依赖锁定WebRTC版本
2.1 源码目录层级与关键模块映射关系
解压后典型结构如下(非标准Android Studio项目结构,需手动识别):
├── app/ # 主模块,含Activity、Fragment、核心Service
│ ├── src/main/
│ │ ├── AndroidManifest.xml ← 关键!声明Camera、AudioRecord、Internet权限及主Activity
│ │ ├── java/com/bugu/ ← 核心包名,注意命名空间一致性
│ │ │ ├── activity/ ← MainActivity、CallActivity(主呼叫界面)
│ │ │ ├── service/ ← CallService(后台音视频通道管理)
│ │ │ ├── webrtc/ ← WebRTC封装层(PeerConnectionFactory、MediaStreamHandler)
│ │ │ └── util/ ← 自定义工具类(LogUtil、NetworkUtil)
│ │ └── res/ ← 布局文件集中在layout/call_activity.xml等
├── libs/ # 静态库目录,重点看libwebrtc.a或webrtc.jar
├── jniLibs/ # ARM64-v8a、armeabi-v7a等ABI目录,含.so文件
└── build.gradle # 重点关注dependencies和android.ndk配置
提示 :该工程大概率使用预编译WebRTC Native库(
.so),而非从源码编译。jniLibs/下的libjingle_peerconnection_so.so是核心动态库,其ABI必须与目标设备匹配。若运行时报java.lang.UnsatisfiedLinkError,首要检查设备架构(adb shell getprop ro.product.cpu.abi)与jniLibs中是否存在对应目录。
2.2 Gradle配置中的WebRTC版本与NDK约束
打开 app/build.gradle ,关键配置段落如下:
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.bugu.live"
minSdkVersion 21 // 注意:WebRTC官方要求minSdkVersion >= 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
// NDK配置决定.so加载路径
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a' // 必须与jniLibs目录严格一致
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) // 加载libs/webrtc.jar
// 若使用AAR方式,此处应为implementation(name: 'webrtc', ext: 'aar')
}
WebRTC版本识别方法(无源码时):
- 查看
libs/webrtc.jar的META-INF/MANIFEST.MF,搜索Implementation-Version字段; - 或反编译
webrtc.jar,检查org/webrtc/BuildConfig.class中的VERSION常量; - 常见版本:
110.x(2023年主流)、109.x(2022年末)。 版本差异直接影响API调用方式 ,例如PeerConnection.IceServer构造在110+需传入Arrays.asList(),而旧版支持单个对象。
2.3 AndroidManifest.xml中的权限与组件声明
必须确保以下权限已声明(缺失将导致采集失败):
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Android 12+需额外声明 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
关键Activity声明示例:
<activity
android:name=".activity.CallActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait" <!-- 一对一场景通常锁定竖屏 -->
android:theme="@style/Theme.AppCompat.NoActionBar" />
注意 :
android:exported="true"是Android 12+强制要求,若未设置,startActivity()将抛出SecurityException。同时configChanges属性必须包含orientation,否则屏幕旋转会销毁Activity并重建,导致WebRTC连接中断。
3. 信令层对接:用OkHttp实现WebSocket握手,解析布谷自定义JSON协议
3.1 信令协议逆向分析:从CallActivity切入网络请求
在 CallActivity.java 中搜索 WebSocket 或 OkHttpClient ,典型初始化代码如下:
private void initSignaling() {
String wsUrl = "wss://signaling.bugu.com/ws?room_id=" + roomId + "&user_id=" + userId;
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
WebSocket webSocket = client.newWebSocket(
new Request.Builder().url(wsUrl).build(),
new CustomWebSocketListener()); // 自定义监听器
}
布谷信令JSON消息格式(实测常见字段):
| 方向 | 类型 | 示例JSON | 说明 |
|---|---|---|---|
| Client → Server | offer | {"type":"offer","sdp":"v=0\r\no=- ...","roomId":"abc123","from":"u1001","to":"u1002"} | SDP Offer字符串需Base64编码?需验证源码中 JsonUtils.toJson() 是否启用编码 |
| Server → Client | answer | {"type":"answer","sdp":"v=0\r\no=- ...","roomId":"abc123","from":"u1002","to":"u1001"} | 收到answer后必须调用 peerConnection.setRemoteDescription() |
| Client → Server | candidate | {"type":"candidate","candidate":"candidate:...","sdpMid":"0","sdpMLineIndex":0,"roomId":"abc123"} | ICE候选者需逐条发送,不可合并 |
提示 :
sdpMid和sdpMLineIndex必须与Offer/Answer中的媒体轨道索引严格对应,否则addIceCandidate()失败。布谷源码中MediaStreamHandler.java通常负责解析这些字段。
3.2 WebSocket连接状态机与重连策略
CustomWebSocketListener 需覆盖关键方法:
@Override
public void onOpen(WebSocket webSocket, Response response) {
Log.d("Signaling", "WebSocket connected");
// 发送登录认证消息(如有token)
sendMessage("{\"type\":\"login\",\"token\":\"xxx\"}");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
try {
JSONObject json = new JSONObject(text);
String type = json.optString("type");
switch (type) {
case "offer":
handleOffer(json); // 触发createAnswer()
break;
case "answer":
handleAnswer(json); // 调用setRemoteDescription()
break;
case "candidate":
handleCandidate(json); // 调用addIceCandidate()
break;
case "hangup":
endCall(); // 主动关闭PeerConnection
break;
}
} catch (JSONException e) {
Log.e("Signaling", "Parse error", e);
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
Log.e("Signaling", "WebSocket failed", t);
// 实现指数退避重连:第一次1s,第二次2s,第三次4s...
scheduleReconnect(++reconnectCount);
}
重连参数建议表:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 初始延迟 | 1000ms | 避免瞬时网络抖动触发重连 |
| 最大重试次数 | 5次 | 超过则提示“网络异常,请检查Wi-Fi/移动数据” |
| 退避因子 | 2.0 | 第n次重连延迟 = 初始延迟 × (2^(n-1)) |
| 连接超时 | ≤10s | 长于10s的握手视为失败,避免阻塞UI线程 |
4. WebRTC媒体管道调试:用logcat过滤关键标签,定位采集/渲染失败点
4.1 启用WebRTC原生日志并定向捕获
在 CallActivity.onCreate() 中插入:
// 必须在创建PeerConnectionFactory前调用
PeerConnectionFactory.InitializationOptions options =
PeerConnectionFactory.InitializationOptions.builder(this)
.setEnableInternalTracer(true) // 启用内部追踪
.createInitializationOptions();
PeerConnectionFactory.initialize(options);
// 设置日志级别(DEBUG级输出最详细SDP/ICE信息)
Log.e("WebRTC", "WebRTC logging enabled at DEBUG level");
// 注意:实际日志需通过logcat -v threadtime -s org.webrtc:V 过滤
关键logcat过滤命令(终端执行):
# 捕获所有WebRTC相关日志(含Native层)
adb logcat -v threadtime -s org.webrtc:V RTCEventLog:V
# 仅查看ICE连接状态变化
adb logcat -v threadtime | grep -E "(ICE|ice|candidate|connect|disconnected)"
# 监控摄像头采集帧率
adb logcat -v threadtime | grep -E "(CameraCapturer|VideoCapturer)"
4.2 常见失败场景与精准定位指令
场景1:本地预览黑屏,但远端能听到声音
排查路径 :
- 执行
adb shell dumpsys media.camera确认摄像头硬件可用; - 在logcat中搜索
CameraCapturer,确认是否输出onFrameCaptured日志; - 若无此日志,检查
CameraHelper.java中openCamera()是否成功,重点看cameraManager.openCamera()回调; - 若有
onFrameCaptured但SurfaceView无画面,检查SurfaceView.getHolder().getSurface()是否为空,或videoTrack.addSink(videoSink)是否被调用。
场景2:远端收不到视频,仅音频通
关键日志线索 :
- 搜索
VideoEncoder:若无encode日志,说明编码器未启动; - 搜索
RTP:若无send RTP packet,说明网络层未发送; - 检查
PeerConnection创建时MediaConstraints是否禁用了视频:MediaConstraints pcConstraints = new MediaConstraints(); pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")); // 必须为true
场景3:ICE连接长时间停留在 checking 状态
诊断步骤 :
- 在logcat中提取完整ICE candidate列表:
adb logcat | grep -A 5 -B 5 "candidate:" - 检查候选者类型:若全为
host(本地IP),说明STUN/TURN未生效; - 验证STUN服务器地址:在
PeerConnectionFactory.Options中确认stunServers数组非空; - 使用
curl测试STUN连通性:curl -v "stun:stun.l.google.com:19302" # 应返回400 Bad Request(正常)
5. 性能优化与生产环境适配:动态码率控制、后台保活与Android 12+适配
5.1 基于CPU负载的动态视频码率调节
布谷源码默认使用固定码率(如1200kbps),但在弱网下易卡顿。需在 MediaStreamHandler.java 中注入动态调节逻辑:
// 在PeerConnection创建后,获取VideoSender
RtpSender videoSender = peerConnection.getSenders().get(0);
VideoTrack videoTrack = (VideoTrack) videoSender.track();
// 创建自定义BitrateAdjuster
BitrateAdjuster adjuster = new CpuLoadBitrateAdjuster();
videoTrack.setBitrateAdjuster(adjuster);
// CpuLoadBitrateAdjuster核心逻辑
public class CpuLoadBitrateAdjuster implements BitrateAdjuster {
private static final int MIN_BITRATE_KBPS = 400;
private static final int MAX_BITRATE_KBPS = 2000;
@Override
public int getSuggestedBitrateBps() {
float cpuLoad = getCpuLoad(); // 通过/proc/stat计算
if (cpuLoad > 0.8f) {
return MIN_BITRATE_KBPS * 1000; // 降为最低码率
} else if (cpuLoad < 0.3f) {
return MAX_BITRATE_KBPS * 1000; // 提升至最高码率
}
return (int) ((MAX_BITRATE_KBPS - MIN_BITRATE_KBPS) * cpuLoad + MIN_BITRATE_KBPS) * 1000;
}
}
注意 :
getCpuLoad()需读取/proc/stat中cpu行,计算1秒内idle时间占比。此方案比单纯依赖网络质量反馈(如RTCP REMB)更及时,尤其适用于低端Android设备。
5.2 Android 12+后台限制下的音视频保活方案
当用户切换到其他App时, CallService 可能被系统杀死。解决方案:
方案A:前台Service + Notification(推荐)
// 在CallService.onStartCommand()中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(1, createNotification()); // 必须提供Notification
} else {
startForeground(1, null); // 低版本兼容
}
private Notification createNotification() {
Intent intent = new Intent(this, CallActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(this, "call_channel")
.setContentTitle("布谷直播进行中")
.setContentText("点击返回通话界面")
.setSmallIcon(R.drawable.ic_call)
.setContentIntent(pendingIntent)
.build();
}
方案B:WorkManager兜底重连(网络恢复后)
// 当检测到网络断开时触发
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ReconnectWorker.class)
.setInputData(new Data.Builder()
.putString("room_id", roomId)
.putString("user_id", userId)
.build())
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
WorkManager.getInstance(this).enqueue(work);
5.3 屏幕共享与摄像头切换的线程安全处理
在 CallActivity 中切换摄像头时,必须确保在 VideoCapturer 线程执行:
// 错误:直接在主线程调用
cameraCapturer.switchCamera(); // 可能崩溃
// 正确:投递到Capturer线程
cameraCapturer.invokeMethod(new Runnable() {
@Override
public void run() {
cameraCapturer.switchCamera();
}
});
同时,屏幕共享需申请 MediaProjection 权限,并在 onActivityResult() 中获取 virtualDisplay :
// 创建VirtualDisplay时指定尺寸与密度
virtualDisplay = mediaProjection.createVirtualDisplay(
"screen-capture",
screenWidth, screenHeight, density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
virtualSurface, null, null);
关键参数说明 :
density必须与当前DisplayMetrics.density匹配,否则画面拉伸;VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR确保横竖屏自动适配,避免手动旋转Surface导致性能下降。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)