直播
你想要了解 Android 直播项目的完整技术栈(服务端 + Android 客户端),并获取一个可落地的完整项目架构方案。直播项目是典型的音视频实时交互场景,涉及音视频采集、编码、推流、服务端转发、拉流解码、互动连麦等核心环节,下面我会从技术栈选型、核心模块拆解、完整代码示例、部署方案四个维度,给出一套可落地的直播项目方案。
整体技术栈选型(主流 & 易落地)
1. 客户端(Android)
| 技术领域 | 核心技术 / 框架 |
|---|---|
| 音视频采集 / 编码 | Android 原生 CameraX(摄像头)、MediaRecorder/MediaCodec(硬编)、FFmpeg(软编) |
| 推流 / 拉流 | RTMP/FLV(低延迟)、WebRTC(连麦)、阿里云 / 腾讯云直播 SDK(快速落地) |
| 网络传输 | OkHttp(API 请求)、WebSocket(实时互动)、RTMP/HTTP-FLV(音视频流) |
| UI 交互 | Jetpack Compose(UI)、ExoPlayer(拉流播放)、SurfaceView/TextureView(视频渲染) |
| 实时互动 | WebRTC(连麦)、Socket.IO(弹幕 / 点赞)、IM 即时通讯(聊天) |
| 其他 | 美颜(FaceUnity / 腾讯优图)、音频降噪(WebRTC AEC/NS)、H.264/H.265 编码 |
2. 服务端
| 技术领域 | 核心技术 / 框架 |
|---|---|
| 流媒体服务 | SRS/Nginx-RTMP(RTMP 推流)、MediaSoup/Janus(WebRTC 连麦)、FFmpeg(转码) |
| 后端框架 | Spring Boot/Spring Cloud(业务逻辑)、Netty(长连接) |
| 实时通信 | WebSocket(弹幕 / 点赞)、Socket.IO(房间管理)、Redis(在线状态) |
| 存储 | MySQL(用户 / 房间数据)、Redis(缓存 / 排行榜)、MinIO/OSS(视频回放) |
| 容器化部署 | Docker + Docker Compose(快速部署)、Nginx(反向代理 / 负载均衡) |
| 监控 / 日志 | Prometheus + Grafana(监控)、ELK(日志) |
3. 可选商用方案(快速落地,避免重复造轮子)
- 阿里云直播 / 视频通信 RTC
- 腾讯云直播 / TRTC
- 网易云信直播 SDK
核心模块拆解(服务端 + Android 端)
服务端核心模块
1. 流媒体服务(SRS 为例,核心推流 / 拉流)
SRS 是轻量级、高性能的流媒体服务器,支持 RTMP/FLV/HLS,是直播项目的核心依赖。
2. 业务服务(Spring Boot)
- 用户模块:注册、登录、鉴权(JWT)
- 房间模块:创建房间、房间列表、上下线状态
- 互动模块:弹幕、点赞、礼物、聊天消息转发
- 连麦模块:WebRTC 信令转发(房间管理、ICE 协商)
- 回放模块:录制直播流、转码、存储
3. 实时通信(WebSocket)
- 房间内消息广播(弹幕、点赞)
- 用户上下线通知
- 连麦请求 / 响应信令
Android 客户端核心模块
1. 直播推流端(主播侧)
- 音视频采集:CameraX 采集视频,AudioRecord 采集音频
- 编码:MediaCodec 硬编 H.264/AAC
- 推流:RTMP 协议推送到 SRS 服务器
- 辅助功能:美颜、滤镜、麦克风静音、摄像头切换
2. 直播拉流端(观众侧)
- 拉流:ExoPlayer 播放 HTTP-FLV 流(低延迟)
- 渲染:TextureView 渲染视频画面
- 互动:发送 / 接收弹幕、点赞、礼物
- 连麦:WebRTC 与主播实时连麦
3. 实时互动模块
- WebSocket 连接服务端,收发房间消息
- 连麦信令交互(请求、接受、挂断)
- 礼物 / 点赞动画展示
完整项目实现(核心代码示例)
服务端实现
1. SRS 部署(Docker 快速启动)
# 拉取 SRS 镜像
docker pull ossrs/srs:5
# 启动 SRS 容器(支持 RTMP/FLV)
docker run -d --name srs -p 1935:1935 -p 8080:8080 -p 1985:1985 ossrs/srs:5 ./objs/srs -c conf/rtmp.conf
- RTMP 推流地址:
rtmp://你的服务器IP:1935/live/房间ID - FLV 拉流地址:
http://你的服务器IP:8080/live/房间ID.flv
2. Spring Boot 业务服务(核心代码)
(1)依赖(pom.xml)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>
(2)WebSocket 房间消息处理
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 房间消息处理器,允许跨域
registry.addHandler(roomMessageHandler(), "/ws/room/{roomId}")
.setAllowedOrigins("*");
}
@Bean
public WebSocketHandler roomMessageHandler() {
return new RoomMessageHandler();
}
}
// 房间消息处理器
public class RoomMessageHandler extends TextWebSocketHandler {
// 存储房间ID -> 连接集合
private static final Map<String, Set<WebSocketSession>> ROOM_SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 获取房间ID
String roomId = getRoomIdFromSession(session);
// 将连接加入房间
ROOM_SESSIONS.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(session);
// 广播用户上线
broadcastMessage(roomId, "用户" + session.getId() + "进入房间");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String roomId = getRoomIdFromSession(session);
// 广播消息(弹幕/点赞/礼物)
broadcastMessage(roomId, message.getPayload());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String roomId = getRoomIdFromSession(session);
ROOM_SESSIONS.getOrDefault(roomId, Collections.emptySet()).remove(session);
// 广播用户下线
broadcastMessage(roomId, "用户" + session.getId() + "离开房间");
}
// 广播消息到房间所有用户
private void broadcastMessage(String roomId, String message) {
Set<WebSocketSession> sessions = ROOM_SESSIONS.getOrDefault(roomId, Collections.emptySet());
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 从URL中提取房间ID
private String getRoomIdFromSession(WebSocketSession session) {
Map<String, String> pathVariables = (Map<String, String>) session.getAttributes().get("uriVariables");
return pathVariables.get("roomId");
}
}
(3)房间管理接口
@RestController
@RequestMapping("/api/room")
public class RoomController {
// 模拟房间列表
private static final Map<String, Room> ROOM_MAP = new ConcurrentHashMap<>();
// 创建房间
@PostMapping("/create")
public ResponseEntity<Room> createRoom(@RequestParam String userId, @RequestParam String title) {
String roomId = UUID.randomUUID().toString().substring(0, 8);
Room room = new Room(roomId, userId, title, LocalDateTime.now(), true);
ROOM_MAP.put(roomId, room);
return ResponseEntity.ok(room);
}
// 获取房间列表
@GetMapping("/list")
public ResponseEntity<List<Room>> getRoomList() {
return ResponseEntity.ok(new ArrayList<>(ROOM_MAP.values()));
}
// 关闭房间
@PostMapping("/close")
public ResponseEntity<Void> closeRoom(@RequestParam String roomId) {
Room room = ROOM_MAP.get(roomId);
if (room != null) {
room.setLiveStatus(false);
ROOM_MAP.put(roomId, room);
}
return ResponseEntity.ok().build();
}
// 房间实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Room {
private String roomId;
private String userId;
private String title;
private LocalDateTime createTime;
private boolean liveStatus;
}
}
Android 客户端实现(Jetpack Compose + 推流 / 拉流)
1. 依赖(build.gradle.kts)
dependencies {
// Compose
implementation(platform("androidx.compose:compose-bom:2024.04.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
// CameraX
implementation("androidx.camera:camera-camera2:1.3.2")
implementation("androidx.camera:camera-lifecycle:1.3.2")
implementation("androidx.camera:camera-view:1.3.2")
// 推流(基于 RTMP)
implementation("com.rtmpstreamer:rtmp-stream-client:1.0.0") // 第三方RTMP推流库
// 拉流播放(ExoPlayer)
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
implementation("com.google.android.exoplayer:exoplayer-hls:2.19.1")
implementation("com.google.android.exoplayer:exoplayer-flv:2.19.1")
// WebSocket
implementation("org.java-websocket:Java-WebSocket:1.5.4")
// 网络
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}
2. 推流端(主播侧)核心代码
@Composable
fun LivePushScreen(roomId: String, userId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var isPushing by remember { mutableStateOf(false) }
val rtmpUrl = "rtmp://你的服务器IP:1935/live/$roomId"
// CameraX 预览
val previewView = remember { PreviewView(context) }
LaunchedEffect(Unit) {
// 初始化 CameraX
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
// 绑定相机
cameraProvider.bindToLifecycle(
(context as LifecycleOwner),
CameraSelector.DEFAULT_FRONT_CAMERA,
preview
)
}, ContextCompat.getMainExecutor(context))
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 相机预览
AndroidView(
factory = { previewView },
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
// 推流控制按钮
Button(
onClick = {
coroutineScope.launch {
if (!isPushing) {
// 开始推流
RTMPStreamClient.startPush(
url = rtmpUrl,
previewView = previewView,
audioSource = AudioSource.MIC,
videoEncoder = VideoEncoder.H264_HARDWARE
)
isPushing = true
} else {
// 停止推流
RTMPStreamClient.stopPush()
isPushing = false
}
}
},
modifier = Modifier.padding(16.dp)
) {
Text(if (isPushing) "停止直播" else "开始直播")
}
}
}
// 简易 RTMP 推流客户端(封装)
object RTMPStreamClient {
private var rtmpPublisher: RTMPPublisher? = null
fun startPush(
url: String,
previewView: PreviewView,
audioSource: AudioSource,
videoEncoder: VideoEncoder
) {
rtmpPublisher = RTMPPublisher.Builder()
.setUrl(url)
.setPreviewView(previewView)
.setAudioSource(audioSource)
.setVideoEncoder(videoEncoder)
.setVideoSize(1280, 720)
.setFps(30)
.build()
rtmpPublisher?.start()
}
fun stopPush() {
rtmpPublisher?.stop()
rtmpPublisher = null
}
}
3. 拉流端(观众侧)核心代码
@Composable
fun LivePullScreen(roomId: String) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var messageText by remember { mutableStateOf("") }
val messageList = remember { mutableStateListOf<String>() }
// ExoPlayer 初始化
val exoPlayer = remember {
ExoPlayer.Builder(context)
.build()
.apply {
val mediaItem = MediaItem.fromUri("http://你的服务器IP:8080/live/$roomId.flv")
setMediaItem(mediaItem)
prepare()
play()
}
}
// WebSocket 连接(弹幕/聊天)
LaunchedEffect(roomId) {
val webSocket = WebSocketClient(URI("ws://你的服务器IP:8080/ws/room/$roomId"))
webSocket.connect()
// 接收消息
webSocket.setWebSocketListener(object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, message: String) {
coroutineScope.launch(Dispatchers.Main) {
messageList.add(message)
}
}
})
// 销毁时关闭连接
onDispose {
webSocket.close()
}
}
Column(modifier = Modifier.fillMaxSize()) {
// 视频播放
AndroidView(
factory = {
StyledPlayerView(it).apply {
player = exoPlayer
useController = true
}
},
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
// 弹幕/聊天区域
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(0.3f)
) {
items(messageList) { msg ->
Text(text = msg, modifier = Modifier.padding(8.dp))
}
}
// 发送消息
Row(modifier = Modifier.padding(8.dp)) {
OutlinedTextField(
value = messageText,
onValueChange = { messageText = it },
modifier = Modifier.weight(1f)
)
Button(
onClick = {
// 发送WebSocket消息
coroutineScope.launch {
val webSocket = WebSocketClient(URI("ws://你的服务器IP:8080/ws/room/$roomId"))
webSocket.connectBlocking()
webSocket.send(messageText)
messageText = ""
}
},
modifier = Modifier.padding(start = 8.dp)
) {
Text("发送")
}
}
}
// 生命周期管理
DisposableEffect(Unit) {
onDispose {
exoPlayer.stop()
exoPlayer.release()
}
}
}
4. 房间列表页面
kotlin
@Composable
fun RoomListScreen(navController: NavController) {
val context = LocalContext.current
val roomList = remember { mutableStateListOf<Room>() }
// 加载房间列表
LaunchedEffect(Unit) {
val okHttpClient = OkHttpClient()
val request = Request.Builder()
.url("http://你的服务器IP:8080/api/room/list")
.build()
val response = okHttpClient.newCall(request).execute()
val responseBody = response.body?.string()
// 解析JSON(简化示例,实际用Gson/Moshi)
val rooms = Gson().fromJson(responseBody, Array<Room>::class.java).toList()
roomList.addAll(rooms)
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(roomList) { room ->
ListItem(
headlineContent = { Text(room.title) },
supportingContent = { Text("主播:${room.userId}") },
modifier = Modifier
.clickable {
// 进入拉流页面
navController.navigate("live/pull/${room.roomId}")
}
.padding(8.dp)
)
}
}
// 创建房间按钮
FloatingActionButton(
onClick = {
// 跳转到推流页面(简化:直接生成房间ID)
val roomId = UUID.randomUUID().toString().substring(0, 8)
navController.navigate("live/push/$roomId/user123")
},
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp)
) {
Icon(Icons.Default.Add, contentDescription = "创建房间")
}
}
// 房间实体
data class Room(
val roomId: String,
val userId: String,
val title: String,
val createTime: String,
val liveStatus: Boolean
)
部署与测试流程
1. 服务端部署
- 服务器要求:至少 2 核 4G,带宽 ≥ 5Mbps(推荐云服务器:阿里云 ECS、腾讯云 CVM);
- 安装 Docker + Docker Compose;
- 启动 SRS 容器(如上);
- 打包 Spring Boot 服务为 Jar 包,启动:
java -jar live-server.jar; - 配置 Nginx 反向代理(可选,用于负载均衡)。
2. 客户端测试
- 确保 Android 设备 / 模拟器能访问服务器(关闭防火墙,开放 1935/8080 端口);
- 主播侧:打开 App → 创建房间 → 开始推流;
- 观众侧:打开 App → 进入房间列表 → 选择房间 → 拉流观看,发送弹幕 / 点赞。
3. 进阶优化(生产环境)
- 音视频优化:开启 H.265 编码、音频降噪、美颜;
- 低延迟优化:使用 WebRTC 替代 RTMP(延迟从 2-3s 降到 100-300ms);
- 扩容:SRS 集群、Spring Boot 服务集群 + Redis 分布式锁;
- 监控:SRS 自带监控面板(http:// 服务器 IP:1985/console)、Prometheus 监控服务状态。
总结
- 直播项目核心分为服务端流媒体转发(SRS 为主)、业务逻辑(Spring Boot)、客户端推流 / 拉流(Android)三大模块,RTMP/FLV 是基础传输协议,WebRTC 用于低延迟连麦;
- 快速落地优先选择商用 SDK(阿里云 / 腾讯云),自研需重点处理音视频编码、网络抖动、低延迟等问题;
- 核心技术点:音视频采集 / 编码、RTMP 推流 / 拉流、WebSocket 实时互动、房间管理、流媒体服务部署。
这套方案是最小可行的直播项目架构,覆盖了推流、拉流、实时互动核心功能,可在此基础上扩展连麦、礼物系统、直播回放、美颜等高级功能。