智慧教室无纸化方案是一种基于现代信息技术,旨在通过数字化手段实现教学过程的无纸化、智能化和高效化的解决方案。该方案以学生为中心,强调互动化的数字教学服务,旨在提升教学质量和学习效率,同时减少对传统纸张的依赖,实现绿色环保。以下是对智慧教室无纸化方案的详细阐述:
智慧教室无纸化方案通过整合物联网、大数据、人工智能等先进技术,构建了一个集智能管理、智慧教学、环境便捷调节及资源制作于一体的新型现代化智慧教室。该方案不仅实现了教学资源的数字化、网络化,还通过智能设备和平台实现了教学过程的自动化、智能化,为师生提供了更加便捷、高效、互动的学习和教学环境。
智慧教室无纸化方案的实施可以带来以下应用效果:
智慧教室RTMP在智慧教室场景下的应用,以实现实时音视频流传输、屏幕共享、互动教学等功能。以下是一个基于RTMP技术的智慧教室技术方案概述:
本文以大牛直播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推送按钮即可。
下面从代码逻辑实现角度,介绍下同屏的具体流程:
启动媒体服务,进入系统后,我们会自动启动媒体服务,对应的实现逻辑如下:
/*
* 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权限:
/*
* 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;
}
}
启动、停止媒体投影:
/*
* 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");
}
}
启动媒体投影后,选择“采集音频播放”,如果需要采集麦克风,可以点击“采集麦克风”:
/*
* 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推送:
/*
* 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调用这里的接口逻辑即可,非常方便:
/*
* 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文件,查看或修改相关的技术实现:
/*
* 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推送模块为例,我们主要实现了如下功能:
智慧教室无纸化方案是一种具有广泛应用前景和发展潜力的教学解决方案。它不仅能够提升教学质量和学习效率,还能够实现绿色环保和可持续发展目标。随着信息技术的不断发展和普及,智慧教室无纸化方案将会在未来的教育领域中发挥更加重要的作用。
智慧教室RTMP技术方案通过利用RTMP协议的实时性和低延迟特性,结合适当的组网、服务器部署、编码转码、横竖屏适配、补帧策略以及网络稳定性保障措施,为智慧教室场景下的实时授课、屏幕共享、互动教学等功能提供了强有力的技术支持。以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。