技术背景
我们在做无纸化同屏的时候,好多开发者采集到屏幕、麦克风|扬声器数据,除了需要推RTMP出去,或者启动个轻量级RTSP服务,对外提供个拉流的RTSP URL,别的终端过来拉流(小并发场景),还有个技术需求,就是需要本地实时录像。本文主要介绍屏幕采集的过程中,如何实现推送端录像。
技术实现
实际上,Android同屏,需要录像的话,和采集摄像头数据录像一样,只是数据源不同而已,鉴于不管什么格式的video数据,我们都是投递到模块底层做转换编码,所以本质上没啥差别。
本地录像,我们界面上没有做展示,如果实现,很简单,就是加个开始录像|停止录像按钮即可。
对外提供了二次封装设计如下:
/* * NTStreamMediaEngine.java * Author: daniusdk.com * WeChat: xinsheng120 * * Copyright © 2014~2024 DaniuSDK. All rights reserved. */ 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(); .... /* * 启动本地录像 */ boolean start_stream_record(String record_directory, int file_max_size); boolean is_stream_recording(); void stop_stream_record(); boolean is_stream_running(); }
开始录像实现如下:
/* * NTStreamMediaProjectionEngineImpl.java * Author: daniusdk.com * WeChat: xinsheng120 * * Copyright © 2014~2024 DaniuSDK. All rights reserved. */ public boolean start_stream_record(String record_directory, int file_max_size) { if (stream_publisher_.is_recording()) { Log.e(TAG, "start_stream_record already recording"); return false; } if (!is_video_capture_running()) { Log.e(TAG, "start_stream_record please start_video_capture first"); return false; } if (is_null_or_empty(record_directory)) { Log.e(TAG, "start_stream_record record_directory is null"); return false; } if (file_max_size < 5) { Log.e(TAG, "start_stream_record file_max_size:" + file_max_size + " error"); return false; } Runnable r = new Runnable() { private String record_directory_; private int file_max_size_; public void run() { if (!start_record_internal(this.record_directory_, this.file_max_size_)) { // notify ..... } } Runnable set(String record_directory, int file_max_size) { this.record_directory_ = record_directory; this.file_max_size_ = file_max_size; return this; } }.set(record_directory, file_max_size); post_or_execute(r); Log.i(TAG, "start_stream_record record_directory:" + record_directory + ", file_max_size:" + file_max_size); return true; } public boolean is_stream_recording() { return stream_publisher_.is_recording(); }
start_record_internal()实现如下:
private boolean start_record_internal(String record_directory, int file_max_size) { if (stream_publisher_.is_recording()) { Log.e(TAG, "start_record_internal already recording"); return false; } if (!test_and_create_sdk_instance()) { Log.e(TAG, "start_record_internal create sdk instance failed"); return false; } if (!config_record(record_directory, file_max_size)) { Log.e(TAG, "start_record_internal config_record failed"); stream_publisher_.try_release(); return false; } if (!stream_publisher_.StartRecorder()) { Log.e(TAG, "start_record_internal call sdk start failed"); stream_publisher_.try_release(); return false; } switch_audio_output_type(audio_output_type_); return true; }
这里调用的录像设置config_record()实现如下:
private boolean config_record(String record_directory, int file_max_size) { if (is_null_or_empty(record_directory)) return false; if (file_max_size < 5) return false; if (null == this.lib_publisher_) return false; String directory = record_directory; int ret = lib_publisher_.SmartPublisherCreateFileDirectory(directory); if (ret != 0) { Log.e(TAG, "try create record directory failed, dir:" + directory); return false; } if (!stream_publisher_.SetRecorderDirectory(directory)) { Log.e(TAG, "set record directory failed, dir:" + directory); return false; } if (!stream_publisher_.SetRecorderFileMaxSize(file_max_size)) { Log.e(TAG, "set record file max size failed, size:" + file_max_size); return false; } return true; }
停止录像:
public void stop_stream_record() { if (!stream_publisher_.is_recording()) return; Runnable r = new Runnable() { public void run() { stream_publisher_.StopRecorder(); stream_publisher_.try_release(); test_and_disable_post_audio(); } }; post_or_execute(r); }
总结
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。以上是Android同屏录像设计,感兴趣的开发者,可以跟我单独沟通交流。