一、项目介绍
1. 背景与意义
随着移动互联网和社交媒体的发展,“实时直播”已成为视频领域的核心功能:电商带货、在线教学、游戏直播、活动直播、社交视频等等场景都离不开直播功能。在 Android 端实现直播功能,不仅需要采集摄像头和麦克风数据,还要对视频音频进行实时编码、网络传输、服务器分发,并兼顾延迟、带宽、兼容性与稳定性等多重挑战。
一个完整的 Android 直播方案,通常包括以下模块:
采集:摄像头(Camera1/Camera2/CameraX)、麦克风音频
编码:硬件或软件编码(H.264、AAC)
传输协议:RTMP、WebRTC、SRT、HLS-DASH 等
推流Client:使用 MediaCodec + Socket 或第三方库(e.g. SRS Rtmp-Client、LFLiveKit)
播放:在 Android 端用 ExoPlayer/MediaPlayer 播放 HLS/DASH/WebRTC
服务器:Nginx-RTMP、SRS、Janus、Kurento、Agora、Zego 等分发或 P2P 转发
交互功能:弹幕、聊天室、低延迟互动
质量保证:丢包重传、码率自适应、NetworkCallback 监测
本文将从技术原理、方案对比、环境搭建、核心代码、性能优化与常见问题等多个维度,深入剖析 Android 端实现实时直播的全过程,并提供一套可运行的示例项目。全文约一万字,所有代码统一整合在一个代码块中,用注释分隔不同文件,便于一键复制使用。
二、相关基础知识
2.1 摄像头采集
Camera1 API:兼容性好,但回调线程不灵活
Camera2 API:控制更细粒度,支持 YUV 直接输出
CameraX:Jetpack 组件,封装 Camera2,易用性高
采集到的原始数据通常为 ImageFormat.NV21 或 YUV_420_888,需要转换为 ByteBuffer 给编码器。
2.2 音频采集
使用 AudioRecord 以 PCM 格式获取麦克风输入
参数:sampleRate=44100|16000、channelConfig=CHANNEL_IN_MONO、audioFormat=ENCODING_PCM_16BIT
2.3 媒体编码
MediaCodec(硬件加速)
配置 MIME_VIDEO_AVC(H.264)、MIME_AUDIO_AAC
输出 ByteBuffer 原始帧,需要打包成 RTMP FLV tag 或 WebRTC packet
FFmpeg(软件编码,兼容较老设备,但性能消耗高)
2.4 传输协议
RTMP(实时消息协议)
经典直播协议,基于 TCP,延迟在 1-3 秒
推流到 RTMP 服务器(Nginx-RTMP、SRS),服务器转 HLS/DASH 或推 CDN
HLS/DASH
基于 HTTP 切片,延迟在 10-30 秒,不适合互动
WebRTC
P2P 或 SFU 架构,基于 SRTP/DTLS,端到端低延迟(<500ms),适合互动
SRT/RTSP、QUIC:新协议,延迟/穿透表现各异
2.5 第三方 SDK
Agora/Zego 等商业 SDK,封装一切,免运维
开源库:SRS rtmp-rtsp-stream-client-java、Ant Media Android SDK、Kurento Client
三、方案对比
方案延迟兼容性复杂度运维成本MediaCodec+RTMP1–3 秒所有 Android(API 18+)中高需部署 RTMP 服务器WebRTC<500msAPI 21+高部署 SFU(Janus/Kurento)HLS/DASH10–30 秒所有 Android低CDN 成本商用 SDK (Agora…)<200ms广泛支持低按量付费
本文示例采用 MediaCodec+RTMP 方案:基于开源库 com.pedro.rtplibrary:rtplibrary:2.1.8(SRS 社区版),配合 Camera2 与 MediaCodec 实现一套轻量高效的推流端,服务器端推荐使用 SRS(Simple Realtime Server)。
四、环境与依赖
// app/build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 34
defaultConfig {
applicationId "com.example.rtmpdemo"
minSdk 21
targetSdk 34
}
buildFeatures.viewBinding true
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// RTMP 推流库
implementation 'com.pedro.rtplibrary:rtplibrary:2.1.8'
// Camera2 封装(可选)
implementation 'com.otaliastudios:cameraview:2.7.1'
}
五、完整代码整合
// =======================================================
// 文件:AndroidManifest.xml
// 描述:申请 CAMERA、RECORD_AUDIO、FOREGROUND_SERVICE 权限
// =======================================================
package="com.example.rtmpdemo"> android:name=".App" android:theme="@style/Theme.RtmpDemo"> android:exported="true">
// =======================================================
// 文件:App.kt
// 描述:Application,用于初始化日志、通道等
// =======================================================
package com.example.rtmpdemo
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
class App : Application() {
companion object {
const val CHANNEL_ID = "rtmp_service_channel"
}
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val ch = NotificationChannel(
CHANNEL_ID, "RTMP Service", NotificationManager.IMPORTANCE_LOW)
getSystemService(NotificationManager::class.java).createNotificationChannel(ch)
}
}
}
// =======================================================
// 文件:res/layout/activity_main.xml
// 描述:主界面布局:CameraView + 控制按钮
// =======================================================
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> android:id="@+id/cameraView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/controlPanel" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:cameraAudio="on" app:cameraFacing="back" app:cameraAutoFocus="on" /> android:id="@+id/controlPanel" android:orientation="horizontal" android:gravity="center" android:layout_width="0dp" android:layout_height="wrap_content" android:padding="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent">
// =======================================================
// 文件:MainActivity.kt
// 描述:核心逻辑:Camera2 + RTMP 推流
// =======================================================
package com.example.rtmpdemo
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.example.rtmpdemo.databinding.ActivityMainBinding
import com.pedro.encoder.input.video.CameraHelper
import com.pedro.encoder.input.video.CameraListener
import com.pedro.rtplibrary.rtmp.RtmpCamera2
import net.ossrs.rtmp.ConnectCheckerRtmp
class MainActivity : AppCompatActivity(), ConnectCheckerRtmp {
private lateinit var binding: ActivityMainBinding
private lateinit var rtmpCamera2: RtmpCamera2
override fun onCreate(s: Bundle?) {
super.onCreate(s)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 权限请求
requestPermissions()
// 初始化 RtmpCamera2
rtmpCamera2 = RtmpCamera2(binding.cameraView, this)
binding.cameraView.setLifecycleOwner(this)
binding.cameraView.addCameraListener(object: CameraListener {
override fun onCameraOpened(width: Int, height: Int, orientation: Int) {}
override fun onCameraClosed() {}
override fun onCameraError(error: Exception) {
Toast.makeText(this@MainActivity, "Camera error: $error", Toast.LENGTH_SHORT).show()
}
override fun onCameraDisconnected() { }
})
binding.btnStart.setOnClickListener {
val streamUrl = "rtmp://你的服务器地址/live/streamKey"
if (!rtmpCamera2.isStreaming) {
// 开始推流前启动预览
if (rtmpCamera2.isRecording || rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo()) {
rtmpCamera2.startStream(streamUrl)
} else {
Toast.makeText(this, "Error preparing stream", Toast.LENGTH_SHORT).show()
}
}
}
binding.btnStop.setOnClickListener {
if (rtmpCamera2.isStreaming) {
rtmpCamera2.stopStream()
}
}
}
private fun requestPermissions() {
val needed = mutableListOf
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
needed += Manifest.permission.CAMERA
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
needed += Manifest.permission.RECORD_AUDIO
}
if (needed.isNotEmpty()) {
ActivityCompat.requestPermissions(this, needed.toTypedArray(), 1)
}
}
override fun onAuthSuccessRtmp() {
runOnUiThread { Toast.makeText(this, "RTMP Connected", Toast.LENGTH_SHORT).show() }
}
override fun onAuthErrorRtmp() {
runOnUiThread { Toast.makeText(this, "RTMP Auth Error", Toast.LENGTH_SHORT).show() }
}
override fun onConnectionFailedRtmp(reason: String) {
runOnUiThread {
Toast.makeText(this, "Connection failed: $reason", Toast.LENGTH_SHORT).show()
rtmpCamera2.stopStream()
}
}
override fun onNewBitrateRtmp(bitrate: Long) { }
override fun onDisconnectRtmp() {
runOnUiThread { Toast.makeText(this, "RTMP Disconnected", Toast.LENGTH_SHORT).show() }
}
override fun onNetworkWeak() {
runOnUiThread { Toast.makeText(this, "Network Weak", Toast.LENGTH_SHORT).show() }
}
override fun onNetworkResume() {
runOnUiThread { Toast.makeText(this, "Network Resume", Toast.LENGTH_SHORT).show() }
}
}
// =======================================================
// 文件:res/values/themes.xml
// 描述:应用主题
// =======================================================
// =======================================================
// 文件:res/values/colors.xml
// 描述:配色
// =======================================================
六、核心代码解读
CameraView
使用 com.otaliastudios:cameraview 简化 Camera2 预览与生命周期管理;
将 CameraView 传给 RtmpCamera2,内部使用 MediaCodec 编码
RtmpCamera2
来自 com.pedro.rtplibrary,封装音视频编码、FLV 打包与 RTMP 推流;
prepareAudio() / prepareVideo():配置 AAC/H.264 参数;
startStream(url):启动 RTMP 推流到指定流地址
权限请求
运行时请求 CAMERA 与 RECORD_AUDIO,否则推流失败
ConnectCheckerRtmp
回调 RTMP 连接状态,可用来更新 UI 或重试逻辑
七、性能与优化
硬件编码参数
根据手机能力选择合适分辨率(720p/480p)、码率(500–1500kbps)、帧率(15–30fps)
码率自适应
监听网络带宽变化,动态调用 rtmpCamera2.setVideoBitrate() 调整码率
网络质量监测
实现 onNetworkWeak() 回调,降低分辨率或码率;
线程与内存优化
RtmpCamera2 内部使用线程池,避免 UI 阻塞;释放资源时调用 stopStream() 和 cameraView.stop()
八、项目总结与扩展思路
本文示例实现了基于 MediaCodec + RTMP 的 Android 直播推流功能,并结合 CameraView 简化摄像头接入,使用 RtmpCamera2 库封装了编码与网络传输。通过示例,你可以进一步:
集成美颜与滤镜:在 CameraView 或编码前对原始帧做 GPUImage 滤镜处理
多路流合流:将屏幕录制与摄像头、麦克风流合并后推送
互动弹幕:结合 WebSocket 在直播间加入实时弹幕
切换协议:尝试 WebRTC 方案(使用 org.webrtc 库)以实现低延迟互动
边录制边推流:在推流的同时保存本地 MP4 录像
九、FAQ
Q1:推流到公共平台(如斗鱼、Bilibili)如何配置?
A:在平台获取的 RTMP 推流地址填入 startStream(url) 即可推送;注意带有 streamKey 的完整地址。
Q2:为什么在 Android 10+ 上需要声明 FOREGROUND_SERVICE 权限?
A:如果将推流逻辑放入前台 Service,需要在 manifest 中声明,避免系统主动杀掉。
Q3:如何解决推流花屏或黑帧?
A:检查编码参数是否支持当前分辨率;部分设备对特定分辨率有硬件限制,必要时降分辨率或使用软件编码。
Q4:如何进行连麦或多主播?
A:建议使用 WebRTC 或商用 SDK(Agora、Zego),因为 RTMP 本身不适合 P2P 音视频交互。
Q5:如何在慢网络下保证流畅?
A:监听 onNetworkWeak(),动态降低分辨率/码率;或在编码层加入 FEC(前向纠错)机制。