安卓群控项目
实时录屏
Android 5.0以上, 系统提供了一个可用于截(录)屏的API, 主要涉及到的类有MediaProjectionManager, MediaProjection, VirtualDisplay等;
- 获取
MediaProjectionManager对象, 该对象是一个系统服务, 可通过以下代码获取
MediaProjectionManager mMediaProjectionManager = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE));- 通过
MediaProjectionManager对象,创建MediaProjection, 该对象需要授权,需要一个活动(Activity)为载体的回调onActivityResult(int requestCode, int resultCode, Intent data)
//在活动(Activity) 调用该方法申请授权.
startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
//在回调中 onActivityResult()方法中获取到授权成功的回执即Intent数据,
//需要注意的是MediaProjection的创建需要该Intent, 如果不希望在onActivityResult中直接创建MediaProjection对象, 那么需要将该Intent保存起来.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
MediaProjection mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
}- 创建一个虚拟投影
VirtualDisplay对象, 并将屏幕数据与surface进行关联.
VirtualDisplay mVirtualDisplay = mMediaProjection.createVirtualDisplay("test-display",
CAPTURE_WITDH, CAPTURE_HIGHT, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface, null, null);
(https://blog.csdn.net/SpringIOC/article/details/78568438) 参考这个项目
安卓编解码 MediaCodec
介绍
MediaCodec 类可以用于使用一些基本的多媒体编解码器 (音视频编解码组件) , 它是Android基本的多媒体支持基础架构的一部分通常 和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack 一起使用.
一个编解码器可以处理输入的数据来产生输出的数据, 编解码器使用一组输入和输出缓冲器来异步处理数据. 你可以创建一个空的输入缓冲区, 填充数据后发送到编解码器进行处理.编解码器使用输入的数据进行转换, 然后输出到一个空的输出缓冲区. 最后你获取到输出缓冲区的数据, 消耗掉里面的数据, 释放回编解码器.如果后续还有数据需要继续处理, 编解码器就会重复这些操作
状态
- 当你使用任意一种工厂方法 (factory methods) 创建了一个编解码器, 此时编解码器处于未初始化状态 (Uninitialized).
- 首先, 你需要使用configure(…)方法对编解码器进行配置, 这将使编解码器转为配置状态 (Configured).
- 然后调用start()方法使其转入执行状态 (Executing).在这种状态下你可以通过上述的缓存队列操作处理数据.
-**执行状态 (Executing)**包含三个子状态:刷新 (Flushed)运行 ( Running) 以及流结束 (End-of-Stream).
- 在调用start()方法后编解码器立即进入刷新子状态 (Flushed), 此时编解码器会拥有所有的缓存.一旦第一个输入缓存 (input buffer) 被移出队列,
- 编解码器就转入运行子状态 (Running) , 编解码器的大部分生命周期会在此状态下度过.
- 当你将一个带有end-of-stream 标记的输入缓存入队列时, 编解码器将转入流结束子状态 (End-of-Stream).在这种状态下, 编解码器不再接收新的输入缓存, 但它仍然产生输出缓存 (output buffers) 直到end-of- stream标记到达输出端.
- 你可以在执行状态 (Executing) 下的任何时候通过调用flush()方法使编解码器重新返回到刷新子状态 (Flushed) .
- 通过调用
stop()方法使编解码器返回到未初始化状态 (Uninitialized), 此时这个编解码器可以再次重新配置 .当你使用完编解码器后, 你必须调用release()方法释放其资源. - 在极少情况下编解码器会遇到错误并进入错误状态 (Error).这个错误可能是在队列操作时返回一个错误的值或者有时候产生了一个异常导致的.通过调用
reset()方法使编解码器再次可用. - 你可以在任何状态调用
reset()方法使编解码器返回到未初始化状态 (Uninitialized) .否则, 调用 release()方法进入最终的Released状态.
数据处理 (Data Processing)
每一个编解码器都包含一组输入和输出缓存 (input and output buffers) , 这些缓存在API调用中通过buffer-id进行引用.当成功调用start()方法后客户端将不会”拥有”输入或输出buffers.
在同步模式下, 通过调用dequeue Input/OutputBuffer(…) 方法从编解码器获得一个输入或输出buffer的所有权.在异步模式下, 你可以通过MediaCodec.Callback.onInput/OutputBufferAvailable(…)的回调方法自动地获得可用的buffers. 在获得一个输入buffer后, 向其中填充数据, 并利用queueInputBuffer方法将其提交给编解码器, 若使用解密, 则利用queueSecureInputBuffer方法提交.不要提交多个具有相同时间戳的输入buffers (除非它是也被同样标记的codec-specific data) .
在异步模式下通过onOutputBufferAvailable方法的回调或者在同步模式下响应dequeueOutputBuffer的调用, 编解码器返回一个只读的output buffer.在这个output buffer被处理后, 调用一个releaseOutputBuffer方法将这个buffer返回给编解码器. 当你不需要立即向编解码器重新提交或释放buffers时, 保持对输入或输出buffers的所有权可使编解码器停止工作, 当然这些行为依赖于设备情况.特别地, 编解码器可能延迟产生输出buffers直到输出的buffers被释放或重新提交.因此, 尽可能短时间地持有可用的buffers.根据API版本情况, 你有三种处理相关数据的方式:
简而言之 本质上就是一个生产者与消费者的模型
配置码率
//创建
MediaCodec mediaCodec = MediaCodec.createEncoderByType("video/avc");
//编码格式
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", MainActivity.CAPTURE_WITDH, MainActivity.CAPTURE_HIGHT);//封装格式, 宽 高 这个尺寸不能超过视频采集时采集到的尺寸, 否则会直接crash
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, MainActivity.CAPTURE_BITRATE);//码率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//帧率
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);//OLOR_FormatSurface这里表明数据将是一个graphicbuffer元数据
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//帧预览间隔, 它指的是, 关键帧的间隔时间.比如你设置成10, 那就是10秒一个关键帧.
mediaFormat.setInteger(MediaFormat.BITRATE_MODE_VBR);
/**
这一步非常关键, 它设置的, 是MediaCodec的编码源, 也就是说, 我要告诉mEncoder, 你给我解码哪些流.
很出乎大家的意料, MediaCodec并没有要求我们传一个流文件进去, 而是要求我们指定一个surface
而这个surface, 其实就是我们在上一讲MediaProjection中用来展示屏幕采集数据的surface
mSurface = mediaCodec.createInputSurface();//或者由它创建Surface
mediaCodec.setInputSurface(mSurface);//或者创建好Surface, 再设置
mediaCodec.configure(mediaFormat, mSurface, null, MediaCodec.CONFIGURE_FLAG_ENCODE); //再或者, 设置配置状态的时候指定 Surface, 如下
*/
mediaCodec.configure(mediaFormat, mSurface, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
参考: https://www.jianshu.com/p/f116b6f81ab3
参考: https://blog.csdn.net/qq_24554061/article/details/52220295
码流/PC端
使用 libstreaming 库封装rtp流 在PC端使用 vlcj 流媒体播放 已弃画面延迟太高, 质量也太差了, 艹
封装rtp流
参考git项目 (https://github.com/fish58bf/libstreaming) 它还实现了 rtsp rtcp 等等
VLC & vlcj 流媒体播放
使用 VLC media player VLC (一个开源解码框架)和 vlcj (一个基于VLC解码的java开源框架)
VLC 介绍:
VLC is a free and open source cross-platform multimedia player and framework that plays most multimedia files as well as DVDs, Audio CDs, VCDs, and various streaming protocols.
vlcj 介绍:
The vlcj project provides a Java framework to allow an instance of a native VLC media player to be embedded in a Java application.
最终方案 JavaCV 解帧
ffmpeg 解帧 参考: N_QT.md→ 解码(Javacv)
最最终方案
安卓端: 封装h264 UDP包; 服务端: ffmpeg 解帧
源码归档
adb install E:\content-for-work\2024-12农机智驾APP\云卓遥控器适配\MT2.17.3.apk
安卓对驱动设备模拟按键
- android界面点击事件流程. 有必要先说下android界面捕获事件的流程.用户在屏幕上点击一下后, 程序里面的OnClickListener是怎样收到这个事件的.大致流程如下:
用户点击-(硬件驱动部分)硬件产生一个中断, 往/dev/input/event*写入一个相应的信号;jni部分, android循环读取/dev/input/event的事件, 再分发给WindowManagerServer, 最后再发到相应的ViewGroup和View.
这里可以通过往/dev/input/event写信号的方式, 来达到模拟事件的目的, 接下来关心的就是信号的协议了.
- 按键协议分析
连接手机,
adb shell, 输入getevent, 按一下手机的menu键, 会看到类似如下输出.
D:\Android\android-platform-tools>adb shell
gemini:/ $ getevent
add device 1: /dev/input/event8
name: "uinput-fpc"
add device 2: /dev/input/event7
name: "msm8996-tasha-mtp-snd-card Button Jack"
///....
/dev/input/event4: 0001 0066 00000001
/dev/input/event4: 0000 0000 00000000
/dev/input/event8: 0001 0060 00000001
/dev/input/event8: 0000 0000 00000000
/dev/input/event4: 0001 0066 00000000
/dev/input/event4: 0000 0000 00000000
/dev/input/event8: 0001 0060 00000000
/dev/input/event8: 0000 0000 00000000试着在adb shell里面输入, 可以模拟类似的效果 sendevent /dev/input/event2 0 139 1 sendevent /dev/input/event2 0 139 2
- 注意问题
一是root, getevent和sendevent需要/dev/input/event的权限.一般应用是没有这个权限的, 需要在程序里面获取su后, 执行chmod 666 /dev/input/event.
二是设备名称.因为你不知道触摸屏或者按键到底对应的event是多少.需要有一个初始化的过程, 大致思路是往event0-event9分别写入按键和触摸信号, 同时监听activity里的onkeydown和view的onclick, 这样来侦测设备.
三是厂商的实现不一样, 这个没办法, 只能一个一个适配了, 一般来说都还是标准的, 有些厂商会有单独的实现.
root后 shell 命令模拟按键(更好)
root 无敌=.=
Process process = Runtime.getRuntime().exec("su");
os = process.getOutputStream();
os.write(cmd.getBytes());//命令
os.write("\n".getBytes());
os.flush();参考 shell 的 input命令
//输入文本123456
input text 123456
//使用keycode num输入, keycode表可百度查询
input keyevent 7
//使用keycode name输入1
input keyevent KEYCODE_1
//使用keycode name按空格键
input keyevent KEYCODE_HOME
//点击坐标367 1277
input tap 367 1277
//从 (1024, 945) 滑动到 (134, 968) 200毫秒内
input swipe 1024 945 134 968 200
//高级
input text //输入文字 (中文不支持)
input keyevent //keyevent按键
input [touchscreen|touchpad|touchnavigation] tap <x> <y>//点击屏幕
input [touchscreen|touchpad|touchnavigation] swipe <x1> <y1> <x2> <y2> //屏幕滑动
input trackball press //滚球已经不用了
input trackball roll //滚球已经不用了
input rotationevent 0 1->90 2->180 3->270> //顺时针旋转ADB forward socket转发
4G无线网络太差, 想用usb + adb forward通信
adb forward的功能是建立一个转发, adb forward tcp:11111 tcp:22222的意思是, 将PC端的11111端口收到的数据, 转发给到手机中22222端口.
//多台使用 -s 指定?
adb -s 9dd759fc forward tcp:11111 tcp:22222
一些模拟器的端口
adb connect 127.0.0.1:21503
adb connect 192.168.26.14
- 海马玩模拟器的端口号是26944.
- 逍遥安卓模拟器的端口号是 21503.
- 夜神模拟器的端口号是 62001.
音频/多媒体 MediaPlayer
Android 多媒体框架支持播放各种常见媒体类型, 以便您轻松地将音频 视频和图片集成到应用中.您可以使用 MediaPlayer API, 播放存储在应用资源 (原始资源) 内的媒体文件 文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频.
以下示例展示了如何播放作为本地原始资源 (保存在应用的 res/raw/ 目录中) 提供的音频:
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you释放 MediaPlayer
MediaPlayer 会占用宝贵的系统资源.因此, 您应该始终采取额外的预防措施, 确保 MediaPlayer 实例保留的时间不会过长.
完成该操作后, 您应始终调用 release() 以确保分配给它的所有系统资源均已正确释放.
例如, 如果您使用 MediaPlayer, 并且您的 Activity 接收到对 onStop() 的调用, 则您必须释放该 MediaPlayer, 因为当 Activity 未与用户互动时, 保留该 MediaPlayer 并没有什么意义 (除非您在后台播放媒体内容, 这将在下一部分中介绍) .当然, 当 Activity 恢复或重启时, 您需要先创建一个新的 MediaPlayer 并再次完成准备工作, 然后才能恢复播放.
mediaPlayer.release();
mediaPlayer = null;