凌峰创科服务平台

android red5服务器

  1. 核心概念:Red5 是什么?
  2. Android + Red5 的应用场景
  3. 工作流程:数据是如何在 Android 和 Red5 之间流动的?
  4. 核心技术点
  5. 实战步骤:从零开始搭建一个简单的 Android-Red5 应用
  6. Red5 的优缺点与替代方案

核心概念:Red5 是什么?

Red5 是一个用 Java 编写的开源 Flash Media Server,它的核心功能是:

android red5服务器-图1
(图片来源网络,侵删)
  • 实时流媒体处理:支持 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 应用数据流如下:

  1. Android 客户端 (App)
    • 使用 RTMP 客户端库连接到 Red5 服务器。
    • 推流:调用 publish() 方法,将手机摄像头和麦克风的音视频数据编码后,通过 RTMP 协议发送到服务器的一个指定 "应用" 和 "流" 名称。
    • 拉流:调用 play() 方法,从服务器拉取指定 "流" 名称的音视频数据,并在屏幕上播放。
  2. Red5 服务器
    • 接收来自 Android 客户端的 RTMP 连接请求。
    • 接收并处理推流数据(publish),可以将其录制下来、转码,或者直接转发给其他拉流的客户端。
    • 处理拉流请求(play),将对应的流数据发送给请求的客户端。
    • 运行服务器端脚本(如 handler.asc),处理连接、断开、发布、播放等事件,实现自定义逻辑。

核心技术点

Android 客户端技术

你需要一个 Android 库来处理 RTMP 协议,目前最流行和推荐的是 ijkplayer

android red5服务器-图2
(图片来源网络,侵删)
  • IjkPlayer

    • 优点:基于 FFmpeg,功能极其强大,支持几乎所有音视频格式和协议(RTMP, HLS, RTSP 等),性能优秀,硬件解码支持好,社区活跃。
    • 缺点:集成相对复杂一些,需要编译 FFmpeg 库。
    • GitHub 地址https://github.com/Bilibili/ijkplayer
  • Vitamio (老牌库)

    曾经非常流行,但现在维护较少,不推荐新项目使用。

  • 原生 RTMP 库

    android red5服务器-图3
    (图片来源网络,侵删)

    可以使用 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): 客户端开始拉流时调用。

实战步骤:搭建一个简单的 Android-Red5 应用

搭建 Red5 服务器

  1. 下载 Red5:从 Red5 官网 下载最新版本的 Server。

  2. 运行 Red5:解压下载的文件,进入 red5 目录,运行 red5.sh (Linux/Mac) 或 red5.bat (Windows),默认端口为 5080

  3. 创建你的应用

    • Red5 的 Web 应用位于 red5/webapps 目录下。
    • 复制 red5/webapps/install 目录,重命名为 myapp
    • 你的应用名为 myapp
  4. 编写服务器端脚本

    • 进入 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);
      };
  5. 重启 Red5 服务器,让 myapp 应用生效。

配置 Android Studio 项目

  1. 新建项目:创建一个 Android Studio 项目。

  2. 集成 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 类或 MainActivityonCreate 中初始化它:

      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 客户端代码

  1. 布局文件 (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>
  2. 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;
        }
    }
  3. 别忘了在 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" />

运行与测试

  1. 确保你的电脑和手机在同一 Wi-Fi 网络下。
  2. 将代码中的 你的服务器IP 替换为你电脑的局域网 IP 地址(168.1.100)。
  3. 在 Android 手机上运行 App。
  4. 测试流程
    • 在一台设备上点击 "开始推流",Red5 服务器的 handler.asc 中的 onPublish 日志应该会被打印。
    • 在另一台设备(或同一台设备)上点击 "开始拉流",你应该能看到黑屏(表示已连接),handler.asc 中的 onPlay 日志会被打印。
    • 注意:这个例子中的推流是模拟的,要真正推送摄像头画面,你需要使用 Camera2 API 获取视频帧,并通过 MediaCodec 进行 H.264 硬件编码,然后将编码后的数据喂给 IjkMediaPlayer 的原生接口,这非常复杂,对于实际项目,可以考虑使用更高级的框架。

Red5 的优缺点与替代方案

优点

  • 开源免费:无需支付服务器费用。
  • 功能强大:支持录制、转码、点播、直播等多种功能。
  • 扩展性好:通过服务器端脚本可以灵活实现业务逻辑。
  • 社区成熟:存在多年,有大量的文档和案例可供参考。

缺点

  • 部署和维护复杂:需要自己管理 Java 应用、服务器环境,问题排查相对困难。
  • 性能瓶颈:单机性能和可扩展性不如一些商业云服务,如果需要高并发,需要自己进行集群部署和负载均衡。
  • 技术栈较旧:核心是基于 Java 和 ActionScript,对于现代开发者来说可能感觉有些“古老”。
  • 文档相对陈旧:官方文档和社区讨论可能跟不上最新的版本。

替代方案

根据你的需求,可以考虑以下替代方案:

  1. 商业云服务 (最推荐)

    • 腾讯云 TRTC阿里云 RTC声网 Agora网易云信
    • 优点:开箱即用,全球节点,性能稳定可靠,提供完整的 SDK 和 7x24 小时技术支持,有强大的控制台进行管理。
    • 缺点:按量或按并发数收费,成本随用户量增长。
    • 适用场景:对稳定性和性能要求高的商业项目。
  2. 自研媒体服务器

    • 使用 SRS (Simple RTMP Server)Ant Media ServerMediaMTX (原 RTMPSimple) 等现代开源媒体服务器。
    • 优点:比 Red5 更轻量、性能更好、更专注于流媒体,部署简单,社区更活跃。
    • 缺点:扩展性可能不如商业云服务。
    • 适用场景:有一定技术能力,希望自建服务器,但对性能有更高要求的项目。
特性 Android + Red5 Android + 商业云服务 (如腾讯云TRTC)
成本 服务器硬件成本 按使用量付费,有免费额度
部署 复杂,需自管 极简,注册即可用
性能/稳定性 依赖自建环境,需自己优化 高,全球CDN,SLA保障
技术支持 社区支持 专业技术支持团队
功能丰富度 需自己开发 SDK集成,功能丰富(美颜、滤镜等)
适用人群 学习研究、有较强运维能力的团队 几乎所有开发者,尤其是商业项目

对于 初学者或学习目的Android + Red5 是一个非常好的选择,它能让你深入理解 RTMP 协议和流媒体服务器的原理。

对于 商业项目,强烈建议优先考虑 商业云服务,它们能让你专注于应用本身的业务逻辑开发,而不是底层基础设施的维护。

分享:
扫描分享到社交APP
上一篇
下一篇