- 核心概念:Red5 是什么?
- Android + Red5 的应用场景
- 工作流程:数据是如何在 Android 和 Red5 之间流动的?
- 核心技术点
- 实战步骤:从零开始搭建一个简单的 Android-Red5 应用
- Red5 的优缺点与替代方案
核心概念:Red5 是什么?
Red5 是一个用 Java 编写的开源 Flash Media Server,它的核心功能是:

- 实时流媒体处理:支持 RTMP (Real Time Messaging Protocol)、HLS (HTTP Live Streaming)、WebRTC 等协议。
- 服务器端 ActionScript (AS):允许你在服务器上编写和执行类似 JavaScript 的脚本,实现复杂的业务逻辑,比如用户管理、权限控制、视频录制、实时数据处理等。
- 多路并发:能够同时处理成千上万的客户端连接。
虽然它的名字里有 "Flash",但它并不局限于 Flash,Red5 的 RTMP 协议可以被很多客户端库支持,包括 Android。
Android + Red5 的应用场景
这种组合非常适合需要 低延迟、双向、实时通信 的应用:
- 音视频通话:类似微信、Skype 的 1 对 1 或多人视频聊天。
- 视频直播:主播通过 App 推送视频流到 Red5,观众通过 App 或网页观看。
- 在线教育/培训:讲师可以共享屏幕、摄像头,与学生进行实时互动。
- 互动直播:观众可以实时发送弹幕、点赞、送礼,主播可以实时看到并响应。
- 多人在线游戏:同步游戏状态、玩家位置等信息。
- 视频会议:企业内部的远程会议系统。
- 安防监控:将摄像头的实时视频流推送到服务器,用户可以随时随地查看。
工作流程
一个典型的 Android-Red5 应用数据流如下:
- Android 客户端 (App):
- 使用 RTMP 客户端库连接到 Red5 服务器。
- 推流:调用
publish()方法,将手机摄像头和麦克风的音视频数据编码后,通过 RTMP 协议发送到服务器的一个指定 "应用" 和 "流" 名称。 - 拉流:调用
play()方法,从服务器拉取指定 "流" 名称的音视频数据,并在屏幕上播放。
- Red5 服务器:
- 接收来自 Android 客户端的 RTMP 连接请求。
- 接收并处理推流数据(
publish),可以将其录制下来、转码,或者直接转发给其他拉流的客户端。 - 处理拉流请求(
play),将对应的流数据发送给请求的客户端。 - 运行服务器端脚本(如
handler.asc),处理连接、断开、发布、播放等事件,实现自定义逻辑。
核心技术点
Android 客户端技术
你需要一个 Android 库来处理 RTMP 协议,目前最流行和推荐的是 ijkplayer。

-
IjkPlayer:
- 优点:基于 FFmpeg,功能极其强大,支持几乎所有音视频格式和协议(RTMP, HLS, RTSP 等),性能优秀,硬件解码支持好,社区活跃。
- 缺点:集成相对复杂一些,需要编译 FFmpeg 库。
- GitHub 地址:https://github.com/Bilibili/ijkplayer
-
Vitamio (老牌库):
曾经非常流行,但现在维护较少,不推荐新项目使用。
-
原生 RTMP 库:
(图片来源网络,侵删)可以使用 JNI 调用 C/C++ 的 RTMP 库(如 librtmp),但这需要深厚的底层开发能力,不推荐普通开发者使用。
Red5 服务器端技术
- Java 运行环境:Red5 是一个 Java 应用,需要 JDK 运行。
- 服务器端 ActionScript (AS):
- Red5 的核心扩展能力,在
webapps/[你的应用名]/scripts目录下,你可以编写一个handler.asc文件。 - 这个脚本会定义一系列回调函数,
appStart(): 应用启动时调用。appStop(): 应用停止时调用。onConnect(client, app): 客户端连接时调用,可以在这里进行用户认证。onDisconnect(client): 客户端断开时调用。onPublish(client, stream, streamName, mode): 客户端开始推流时调用。onPlay(client, streamName, start, duration, reset): 客户端开始拉流时调用。
- Red5 的核心扩展能力,在
实战步骤:搭建一个简单的 Android-Red5 应用
搭建 Red5 服务器
-
下载 Red5:从 Red5 官网 下载最新版本的 Server。
-
运行 Red5:解压下载的文件,进入
red5目录,运行red5.sh(Linux/Mac) 或red5.bat(Windows),默认端口为5080。 -
创建你的应用:
- Red5 的 Web 应用位于
red5/webapps目录下。 - 复制
red5/webapps/install目录,重命名为myapp。 - 你的应用名为
myapp。
- Red5 的 Web 应用位于
-
编写服务器端脚本:
-
进入
red5/webapps/myapp/scripts目录,创建一个名为handler.asc的文件。 -
编写一个简单的脚本,打印日志并允许所有连接:
// handler.asc application.onAppStart = function() { trace("MyApp application started!"); }; application.onConnect = function(client, app) { trace("Client connected: " + client.id); // 接受连接 acceptConnection(client); }; application.onDisconnect = function(client) { trace("Client disconnected: " + client.id); }; application.onPublish = function(client, stream, streamName, mode) { trace("Client " + client.id + " is publishing stream: " + streamName); }; application.onPlay = function(client, streamName, start, duration, reset) { trace("Client " + client.id + " is playing stream: " + streamName); };
-
-
重启 Red5 服务器,让
myapp应用生效。
配置 Android Studio 项目
-
新建项目:创建一个 Android Studio 项目。
-
集成 IjkPlayer:
-
最简单的方式是使用 JitPack,在项目的
build.gradle文件中添加:allprojects { repositories { ... maven { url 'https://jitpack.io' } } } -
在 App 模块的
build.gradle文件中添加依赖:dependencies { implementation 'com.github.Bilibili:ijkplayer-java:0.8.8' // 版本号可能更新 implementation 'com.github.Bilibili:ijkplayer-armv7a:0.8.8' // 如果需要支持其他架构,可以添加 ijkplayer-arm64, ijkplayer-x86 等 } -
注意:IjkPlayer 需要加载原生库,你需要在
Application类或MainActivity的onCreate中初始化它:import tv.danmaku.ijkplayer.widget.IjkVideoView; import tv.danmaku.ijkplayer.common.IjkMediaPlayer; // 在你的 Application 或 Activity 中 public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.nativeProfileBegin("libijkplayer.so"); } }
-
编写 Android 客户端代码
-
布局文件 (
activity_main.xml):添加一个IjkVideoView和几个按钮。<tv.danmaku.ijkplayer.widget.IjkVideoView android:id="@+id/ijk_video_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_publish" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="开始推流" /> <Button android:id="@+id/btn_play" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="开始拉流" /> </LinearLayout> -
Java 代码 (
MainActivity.java):import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import tv.danmaku.ijkplayer.widget.IjkVideoView; import tv.danmaku.ijkplayer.player.IjkMediaPlayer; public class MainActivity extends AppCompatActivity implements IjkMediaPlayer.OnErrorListener, IjkMediaPlayer.OnInfoListener { private static final String TAG = "MainActivity"; private static final int PERMISSION_REQUEST_CODE = 1; private static final String RTMP_URL_PUBLISH = "rtmp://你的服务器IP:1935/myapp/mystream"; // 推流地址 private static final String RTMP_URL_PLAY = "rtmp://你的服务器IP:1935/myapp/mystream"; // 拉流地址 private IjkVideoView mVideoView; private Button mBtnPublish, mBtnPlay; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mVideoView = findViewById(R.id.ijk_video_view); mBtnPublish = findViewById(R.id.btn_publish); mBtnPlay = findViewById(R.id.btn_play); // 检查权限 if (!checkPermission()) { requestPermission(); } mBtnPublish.setOnClickListener(v -> startPublishing()); mBtnPlay.setOnClickListener(v -> startPlaying()); } private void startPublishing() { // IjkVideoView 主要用于播放,推流需要更复杂的配置 // 这里简化,实际上推流需要自己获取摄像头数据并编码 // 但我们可以用 IjkMediaPlayer 的原生 API 来演示 IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer(); try { ijkMediaPlayer.setDataSource(this, RTMP_URL_PUBLISH); ijkMediaPlayer.setLiveStream(true); ijkMediaPlayer.prepareAsync(); ijkMediaPlayer.start(); Toast.makeText(this, "开始推流...", Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "推流失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } private void startPlaying() { mVideoView.setVideoPath(RTMP_URL_PLAY); mVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW); // 使用 SurfaceView 渲染 mVideoView.start(); mVideoView.setOnErrorListener(this); mVideoView.setOnInfoListener(this); Toast.makeText(this, "开始拉流...", Toast.LENGTH_SHORT).show(); } // ... 权限检查代码 ... private boolean checkPermission() { return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; } private void requestPermission() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (!checkPermission()) { Toast.makeText(this, "请授予权限以使用摄像头和麦克风", Toast.LENGTH_LONG).show(); finish(); } } } @Override protected void onPause() { super.onPause(); mVideoView.pause(); } @Override protected void onResume() { super.onResume(); mVideoView.resume(); } @Override protected void onDestroy() { super.onDestroy(); mVideoView.stopPlayback(); } @Override public boolean onError(IjkMediaPlayer ijkMediaPlayer, int i, int i1) { Log.e(TAG, "IjkPlayer Error: " + i + ", " + i1); runOnUiThread(() -> Toast.makeText(this, "播放出错", Toast.LENGTH_SHORT).show()); return true; } @Override public boolean onInfo(IjkMediaPlayer ijkMediaPlayer, int i, int i1) { Log.d(TAG, "IjkPlayer Info: " + i + ", " + i1); return true; } } -
别忘了在
AndroidManifest.xml中添加权限:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
运行与测试
- 确保你的电脑和手机在同一 Wi-Fi 网络下。
- 将代码中的
你的服务器IP替换为你电脑的局域网 IP 地址(168.1.100)。 - 在 Android 手机上运行 App。
- 测试流程:
- 在一台设备上点击 "开始推流",Red5 服务器的
handler.asc中的onPublish日志应该会被打印。 - 在另一台设备(或同一台设备)上点击 "开始拉流",你应该能看到黑屏(表示已连接),
handler.asc中的onPlay日志会被打印。 - 注意:这个例子中的推流是模拟的,要真正推送摄像头画面,你需要使用
Camera2API 获取视频帧,并通过MediaCodec进行 H.264 硬件编码,然后将编码后的数据喂给 IjkMediaPlayer 的原生接口,这非常复杂,对于实际项目,可以考虑使用更高级的框架。
- 在一台设备上点击 "开始推流",Red5 服务器的
Red5 的优缺点与替代方案
优点
- 开源免费:无需支付服务器费用。
- 功能强大:支持录制、转码、点播、直播等多种功能。
- 扩展性好:通过服务器端脚本可以灵活实现业务逻辑。
- 社区成熟:存在多年,有大量的文档和案例可供参考。
缺点
- 部署和维护复杂:需要自己管理 Java 应用、服务器环境,问题排查相对困难。
- 性能瓶颈:单机性能和可扩展性不如一些商业云服务,如果需要高并发,需要自己进行集群部署和负载均衡。
- 技术栈较旧:核心是基于 Java 和 ActionScript,对于现代开发者来说可能感觉有些“古老”。
- 文档相对陈旧:官方文档和社区讨论可能跟不上最新的版本。
替代方案
根据你的需求,可以考虑以下替代方案:
-
商业云服务 (最推荐)
- 腾讯云 TRTC、阿里云 RTC、声网 Agora、网易云信。
- 优点:开箱即用,全球节点,性能稳定可靠,提供完整的 SDK 和 7x24 小时技术支持,有强大的控制台进行管理。
- 缺点:按量或按并发数收费,成本随用户量增长。
- 适用场景:对稳定性和性能要求高的商业项目。
-
自研媒体服务器
- 使用 SRS (Simple RTMP Server)、Ant Media Server、MediaMTX (原 RTMPSimple) 等现代开源媒体服务器。
- 优点:比 Red5 更轻量、性能更好、更专注于流媒体,部署简单,社区更活跃。
- 缺点:扩展性可能不如商业云服务。
- 适用场景:有一定技术能力,希望自建服务器,但对性能有更高要求的项目。
| 特性 | Android + Red5 | Android + 商业云服务 (如腾讯云TRTC) |
|---|---|---|
| 成本 | 服务器硬件成本 | 按使用量付费,有免费额度 |
| 部署 | 复杂,需自管 | 极简,注册即可用 |
| 性能/稳定性 | 依赖自建环境,需自己优化 | 高,全球CDN,SLA保障 |
| 技术支持 | 社区支持 | 专业技术支持团队 |
| 功能丰富度 | 需自己开发 | SDK集成,功能丰富(美颜、滤镜等) |
| 适用人群 | 学习研究、有较强运维能力的团队 | 几乎所有开发者,尤其是商业项目 |
对于 初学者或学习目的,Android + Red5 是一个非常好的选择,它能让你深入理解 RTMP 协议和流媒体服务器的原理。
对于 商业项目,强烈建议优先考虑 商业云服务,它们能让你专注于应用本身的业务逻辑开发,而不是底层基础设施的维护。
