智慧教室无纸化同屏方案是否适用RTMP?

2024-09-18 11:39:04 浏览数 (3)

智慧教室无纸化方案技术背景

智慧教室无纸化方案是一种基于现代信息技术,旨在通过数字化手段实现教学过程的无纸化、智能化和高效化的解决方案。该方案以学生为中心,强调互动化的数字教学服务,旨在提升教学质量和学习效率,同时减少对传统纸张的依赖,实现绿色环保。以下是对智慧教室无纸化方案的详细阐述:

一、方案概述

智慧教室无纸化方案通过整合物联网、大数据、人工智能等先进技术,构建了一个集智能管理、智慧教学、环境便捷调节及资源制作于一体的新型现代化智慧教室。该方案不仅实现了教学资源的数字化、网络化,还通过智能设备和平台实现了教学过程的自动化、智能化,为师生提供了更加便捷、高效、互动的学习和教学环境。

二、主要特点

  1. 数字化教学资源:智慧教室无纸化方案提供了丰富海量的数字化教学资源,包括电子教材、多媒体课件、在线题库等,师生可以随时随地进行访问和学习,打破了传统纸质教材的束缚。
  2. 在线学习互动:通过智慧教室平台,学生可以随时收听老师的讲课内容,参与在线讨论、提问和答疑,实现了师生之间的即时互动。同时,学生还可以根据自己的学习进度和需求进行个性化学习,提高了学习的针对性和自主性。
  3. 智能管理:智慧教室无纸化方案支持对教室内的多种终端设备进行无缝连接和智能化管理,如智能白板、互动电子白板、环境监测设备等。这些设备能够自动采集和分析数据,为教学和管理提供有力支持。
  4. 环境便捷调节:智慧教室通过温度、湿度和光照感应器等设备,实现了对教室内环境的自动调节,为师生提供了更加舒适的学习和教学环境。

三、实施步骤

  1. 需求分析:根据学校的实际情况和需求,对智慧教室无纸化方案进行需求分析,明确建设目标和功能需求。
  2. 方案设计:根据需求分析结果,设计智慧教室无纸化方案,包括系统架构、设备选型、平台开发等方面。
  3. 设备采购与安装:按照方案要求采购相关设备,并进行安装调试,确保设备能够正常运行。
  4. 平台开发与部署:开发智慧教室平台,并进行部署和测试,确保平台能够稳定运行并满足教学需求。
  5. 培训与推广:对教师和学生进行培训和推广,帮助他们掌握智慧教室无纸化方案的使用方法和技巧。

四、应用效果

智慧教室无纸化方案的实施可以带来以下应用效果:

  1. 提升教学质量:通过数字化教学资源和在线学习互动功能,教师可以更加生动、直观地传授知识,激发学生的学习兴趣和积极性;学生可以更加自主、灵活地进行学习,提高学习效果和质量。
  2. 提高教学效率:智慧教室无纸化方案可以大大简化教学流程和管理流程,减少教师和管理人员的工作量;同时,通过智能设备和平台的支持,可以实现教学过程的自动化和智能化,提高教学效率。
  3. 实现绿色环保:通过减少对传统纸张的依赖和使用数字化教学资源等方式,智慧教室无纸化方案有助于实现绿色环保和可持续发展。

智慧教室RTMP方案探究

智慧教室RTMP在智慧教室场景下的应用,以实现实时音视频流传输、屏幕共享、互动教学等功能。以下是一个基于RTMP技术的智慧教室技术方案概述:

技术方案架构

1. 组网与服务器部署
  • 组网方式:建议采用无线组网方式,并配置高性能的AP模块以支持大并发流量。推送端(如教师端设备)到AP的连接最好是有线网络,以确保稳定性。
  • 服务器部署:选择SRS或NGINX作为RTMP服务器,可以根据实际情况与Windows平台的教师机部署在同一台机器上,或部署在独立的服务器上。
2. 教师端设备配置
  • 如果教师有移动PAD或其他Android设备,可以直接将音视频流推送到RTMP服务器,实现实时授课。
  • 对于高分屏设备,建议适当缩放视频分辨率以减轻编码和上行压力,如将宽高缩放至2/3,并保持等比例缩放,缩放宽高建议做好字节对齐。
3. 学生端设备配置
  • 学生端设备可以通过RTMP客户端接收来自服务器的音视频流,进行实时观看和学习。
  • 如果需要,学生端也可以作为示范案例,将屏幕数据共享给其他同学,只需请求同屏,数据反推到RTMP服务器,其他学生即可查看。
4. 互动功能实现
  • 通过RTMP协议,可以实现教师与学生的实时互动,如问答、投票等。
  • 如果需要更进一步的监控功能,如教师端监控学生端的屏幕情况,可以通过学生端直接推送RTMP流或启动内置RTSP服务的方式实现。

关键技术点

1. 编码与转码
  • 使用适当的视频编码器(如H.264)和音频编码器(如AAC)对音视频流进行编码,以确保高质量的传输效果。
  • 如果需要适应不同带宽或设备要求,可以使用FFmpeg等工具进行流的转码操作。
2. 横竖屏适配
  • 在Android设备上,横竖屏切换时需要考虑屏幕宽高的变化,确保推拉流两端可以自动适配。
  • 编码器需要重启以适应新的分辨率设置,拉流端也需要能够自动播放适应变化后的视频流。
3. 补帧策略
  • 当屏幕静止不动时,为了避免播放端因帧间距过大而长时间收不到数据,可以采用补帧策略。
  • 保存最后一帧数据,并设定一定的补帧间隔,以确保数据的连续性和流畅性。
4. 网络稳定性与重连机制
  • 在网络抖动或其他网络异常情况下,需要有良好的重连机制和状态回馈机制以确保传输的稳定性。
  • 通过设置心跳包、超时重连等策略来提高系统的健壮性和用户体验。

智慧教室方案选型

本文以大牛直播SDK的Android的SmartServicePublisherV2的同屏demo为例,Android采集计时器,编码打包分别启动RTMP推送和轻量级RTSP服务,Windows过来分别拉取RTMP和RTSP的流,整体延迟毫秒级:

启动APP后,先选择需要采集的分辨率(如果选原始分辨率,系统不做缩放),然后选择“启动媒体投影”,并分别启动音频播放采集、采集麦克风。如果音频播放采集和采集麦克风都打开,可以通过右侧下拉框,推送过程中,音频播放采集和麦克风采集实时切换。需要注意的是,Android采集音频播放的audio,音频播放采集是依赖屏幕投影的,屏幕投影关闭后,音频播放也就采不到了。

编码的话,考虑到屏幕分辨率一般不会太低,我们可以缩放后再推送,默认我们开启了原始分辨率、标准分辨率、低分辨率选项设置。一般建议标准分辨率即可。如果对画质和分辨率要求比较高,可以选择原始分辨率。设备支持硬编码,优先选择H.264硬编,如果是H.265硬编,需要RTMP服务器支持扩展H.265(或Enhanced RTMP)。都选择好后,设置RTMP推送的URL,点开始RTMP推送按钮即可。

下面从代码逻辑实现角度,介绍下同屏的具体流程:

启动媒体服务,进入系统后,我们会自动启动媒体服务,对应的实现逻辑如下:

代码语言:java复制
/*
 * MainActivity.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
private void start_media_service() {
	Intent intent = new Intent(getApplicationContext(), StreamMediaDemoService.class);
	if (Build.VERSION.SDK_INT >= 26) {
		Log.i(TAG, "startForegroundService");
		startForegroundService(intent);
	} else
		startService(intent);

	bindService(intent, service_connection_, Context.BIND_AUTO_CREATE);
	button_stop_media_service_.setText("停止媒体服务");
}

private void stop_media_service() {
	if (media_engine_callback_ != null)
		media_engine_callback_.reset(null);

	if (media_engine_ != null) {
		media_engine_.unregister_callback(media_engine_callback_);
		media_engine_ = null;
	}

	media_engine_callback_ = null;

	if (media_binder_ != null) {
		media_binder_ = null;
		unbindService(service_connection_);
	}

	Intent intent = new Intent(getApplicationContext(), StreamMediaDemoService.class);
	stopService(intent);
	button_stop_media_service_.setText("启动媒体服务");
}

需要注意的是,Android 6.0及以上版本,动态获取Audio权限:

代码语言:java复制
/*
 * MainActivity.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
private boolean check_record_audio_permission() {
	//6.0及以上版本,动态获取Audio权限
	if (PackageManager.PERMISSION_GRANTED == checkPermission(android.Manifest.permission.RECORD_AUDIO, Process.myPid(), Process.myUid()))
		return true;

	return false;
}

private void request_audio_permission() {
	if (Build.VERSION.SDK_INT < 23)
		return;

	Log.i(TAG, "requestPermissions RECORD_AUDIO");
	ActivityCompat.requestPermissions(this, new String[] {android.Manifest.permission.RECORD_AUDIO}, REQUEST_AUDIO_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
	switch(requestCode){
		case REQUEST_AUDIO_CODE:
			if (grantResults != null && grantResults.length > 0 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
				Log.i(TAG, "RECORD_AUDIO permission has been granted");
			}else {
				Toast.makeText(this, "请开启录音权限!", Toast.LENGTH_SHORT).show();
			}
			break;
	}
}

启动、停止媒体投影:

代码语言:java复制
/*
 * MainActivity.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
private class ButtonStartMediaProjectionListener implements OnClickListener {
	public void onClick(View v) {
		if (null == media_engine_)
			return;

		if (media_engine_.is_video_capture_running()) {
			media_engine_.stop_audio_playback_capture();
			media_engine_.stop_video_capture();
			resolution_selector_.setEnabled(true);
			button_capture_audio_playback_.setText("采集音频播放");
			button_start_media_projection_.setText("启动媒体投影");
			return;
		}

		Intent capture_intent;
		capture_intent = media_projection_manager_.createScreenCaptureIntent();

		startActivityForResult(capture_intent, REQUEST_MEDIA_PROJECTION);
		Log.i(TAG, "startActivityForResult request media projection");
	}
}

启动媒体投影后,选择“采集音频播放”,如果需要采集麦克风,可以点击“采集麦克风”:

代码语言:java复制
/*
 * MainActivity.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
private class ButtonCaptureAudioPlaybackListener implements OnClickListener {
	public void onClick(View v) {
		if (null == media_engine_)
			return;

		if (media_engine_.is_audio_playback_capture_running()) {
			media_engine_.stop_audio_playback_capture();
			button_capture_audio_playback_.setText("采集音频播放");
			return;
		}

		if (!media_engine_.start_audio_playback_capture(44100, 1))
			Log.e(TAG, "start_audio_playback_capture failed");
		else
			button_capture_audio_playback_.setText("停止音频播放采集");
	}
}

private class ButtonStartAudioRecordListener implements OnClickListener {
	public void onClick(View v) {
		if (null == media_engine_)
			return;

		if (media_engine_.is_audio_record_running()) {
			media_engine_.stop_audio_record();
			button_start_audio_record_.setText("采集麦克风");
			return;
		}

		if (!media_engine_.start_audio_record(44100, 1))
			Log.e(TAG, "start_audio_record failed");
		else
			button_start_audio_record_.setText("停止麦克风");
	}
}

启动、停止RTMP推送:

代码语言:java复制
/*
 * MainActivity.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
private class ButtonRTMPPublisherListener implements OnClickListener {
	@Override
	public void onClick(View v) {
		if (null == media_engine_)
			return;

		if (media_engine_.is_rtmp_stream_running()) {
			media_engine_.stop_rtmp_stream();
			button_rtmp_publisher_.setText("开始RTMP推送");
			text_view_rtmp_url_.setText("RTMP URL: ");
			Log.i(TAG, "stop rtmp stream");
			return;
		}

		if (!media_engine_.is_video_capture_running())
			return;

		String rtmp_url;
		if (input_rtmp_url_ != null && input_rtmp_url_.length() > 1) {
			rtmp_url = input_rtmp_url_;
			Log.i(TAG, "start, input rtmp url:"   rtmp_url);
		} else {
			rtmp_url = baseURL   String.valueOf((int) (System.currentTimeMillis() % 1000000));
			Log.i(TAG, "start, generate random url:"   rtmp_url);
		}

		media_engine_.set_fps(fps_);
		media_engine_.set_gop(gop_);
		media_engine_.set_video_encoder_type(video_encoder_type);

		if (!media_engine_.start_rtmp_stream(rtmp_url))
			return;

		button_rtmp_publisher_.setText("停止RTMP推送");
		text_view_rtmp_url_.setText("RTMP URL:"   rtmp_url);
		Log.i(TAG, "RTMP URL:"   rtmp_url);
	}
}

可以看到,上述操作,都是在MainActivity.java调用的,如果是需要做demo版本集成,只需要关注MainActivity.java的业务逻辑即可,为了便于开发者对接,我们做了接口的二次封装,除了常规的RTMP推送、轻量级RTSP服务设计外,如果需要录像,只要在MainActivity.java调用这里的接口逻辑即可,非常方便:

代码语言:java复制
/*
 * NTStreamMediaEngine.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
package com.daniulive.smartpublisher;

public interface NTStreamMediaEngine {
    void register_callback(Callback callback);

    void unregister_callback(Callback callback);

    void set_resolution_level(int level);

    int get_resolution_level();

    /*
    * 启动媒体投影
     */
    boolean start_video_capture(int token_code, android.content.Intent token_data);

    boolean is_video_capture_running();

    void stop_video_capture();

    /*
    * 启动麦克风
     */
    boolean start_audio_record(int sample_rate, int channels);

    boolean is_audio_record_running();

    void stop_audio_record();

    /*
     *  Android 10及以上支持, Android10以下设备调用直接返回false
     *  需要有RECORD_AUDIO权限
     *  要开启媒体投影
     */
    boolean start_audio_playback_capture(int sample_rate, int channels);

    boolean is_audio_playback_capture_running();

    void stop_audio_playback_capture();

    /*
     * 输出的音频类型
     *  0: 不输出音频
     *  1: 输出麦克风
     *  2: 输出audio playback(Android 10及以上支持)
     */
    boolean set_audio_output_type(int type);

    int get_audio_output_type();

    void set_fps(int fps);

    void set_gop(int gop);

    boolean set_video_encoder_type(int video_encoder_type);

    int get_video_encoder_type();

    /*
    * 推送RTMP
     */
    boolean start_rtmp_stream(String url);

    boolean is_rtmp_stream_running();

    String get_rtmp_stream_url();

    void stop_rtmp_stream();

    /*
    * 启动RTSP Server, 需要设置端口,用户名和密码可选
     */
    boolean start_rtsp_server(int port, String user_name, String password);

    boolean is_rtsp_server_running();

    void stop_rtsp_server();

    /*
    * 发布RTSP流
     */
    boolean start_rtsp_stream(String stream_name);

    boolean is_rtsp_stream_running();

    String get_rtsp_stream_url();

    void stop_rtsp_stream();

    /*
    * 启动本地录像
     */
    boolean start_stream_record(String record_directory, int file_max_size);

    boolean is_stream_recording();

    void stop_stream_record();

    boolean is_stream_running();

    interface Callback {
        void on_nt_video_capture_stop();
        void on_nt_rtsp_stream_url(String url);
    }
}

如果对音视频这块相对了解的开发者,可以继续到NTStreamMediaProjectionEngineImpl.java文件,查看或修改相关的技术实现:

代码语言:java复制
/*
 * NTStreamMediaProjectionEngineImpl.java
 * Created by daniusdk.com on 2017/04/19.
 * WeChat: xinsheng120
 */
package com.daniulive.smartpublisher;

import android.app.Activity;
import android.app.Application;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.media.Image;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.WindowManager;
import android.view.WindowMetrics;

import com.eventhandle.NTSmartEventCallbackV2;
import com.eventhandle.NTSmartEventID;
import com.voiceengine.NTAudioRecordV2;
import com.voiceengine.NTAudioRecordV2Callback;
import com.videoengine.NTMediaProjectionCapture;
import com.voiceengine.NTAudioPlaybackCapture;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

public class NTStreamMediaProjectionEngineImpl implements AutoCloseable, NTStreamMediaEngine,
        NTVirtualDisplaySurfaceSinker.Callback, NTMediaProjectionCapture.Callback {
    private static final String TAG = "NTLogProjectionEngine";

    private static final Size DEFAULT_SIZE = new Size(1920, 1080);

    public static final int RESOLUTION_LOW = 0;
    public static final int RESOLUTION_MEDIUM = 1;
    public static final int RESOLUTION_HIGH = 2;

    private final Application application_;

    private final long image_thread_id_;
    private final long running_thread_id_;

    private final Handler image_handler_;
    private final Handler running_handler_;

    private final WindowManager window_manager_;
    private final MediaProjectionManager projection_manager_;
    private int screen_density_dpi_ = android.util.DisplayMetrics.DENSITY_DEFAULT;

    private final SmartPublisherJniV2 lib_publisher_;
    private final LibPublisherWrapper.RTSPServer rtsp_server_;
    private final LibPublisherWrapper stream_publisher_;

    private final CopyOnWriteArrayList<NTStreamMediaEngine.Callback> callbacks_ = new CopyOnWriteArrayList<>();

    private final AtomicReference<VideoSinkerCapturePair> video_capture_pair_ = new AtomicReference<>();

    private final AudioRecordCallbackImpl audio_record_callback_;
    private final AudioPlaybackCaptureCallbackImpl audio_playback_capture_callback_;

    private final AtomicReference<NTAudioRecordV2> audio_record_ = new AtomicReference<>();
    private final AtomicReference<NTAudioPlaybackCapture> audio_playback_capture_ = new AtomicReference<>();
	
	...
}

以Android平台RTMP推送模块为例,我们主要实现了如下功能:

  • 音频编码:AAC/SPEEX;
  • 视频编码:H.264、H.265;
  • 推流协议:RTMP;
  • [音视频]支持纯音频/纯视频/音视频推送;
  • [摄像头]支持采集过程中,前后摄像头实时切换;
  • 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
  • 支持RTMP推送 live|record模式设置;
  • 支持前置摄像头镜像设置;
  • 支持软编码、特定机型硬编码;
  • 支持横屏、竖屏推送;
  • 支持Android屏幕采集推送;
  • 支持自建标准RTMP服务器或CDN;
  • 支持断网自动重连、网络状态回调;
  • 支持实时动态水印;
  • 支持实时快照;
  • 支持降噪处理、自动增益控制;
  • 支持外部编码前音视频数据对接;
  • 支持外部编码后音视频数据对接;
  • 支持RTMP扩展H.265(需设备支持H.265特定机型硬编码)和Enhanced RTMP;
  • 支持实时音量调节;
  • 支持扩展录像模块;
  • 支持Unity接口;
  • 支持H.264扩展SEI发送模块;
  • 支持Android 5.1及以上版本。

总结

智慧教室无纸化方案是一种具有广泛应用前景和发展潜力的教学解决方案。它不仅能够提升教学质量和学习效率,还能够实现绿色环保和可持续发展目标。随着信息技术的不断发展和普及,智慧教室无纸化方案将会在未来的教育领域中发挥更加重要的作用。

智慧教室RTMP技术方案通过利用RTMP协议的实时性和低延迟特性,结合适当的组网、服务器部署、编码转码、横竖屏适配、补帧策略以及网络稳定性保障措施,为智慧教室场景下的实时授课、屏幕共享、互动教学等功能提供了强有力的技术支持。以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。

0 人点赞