群控系统是市场上比拟风行的营销工具,很多人在很好的利用这一工具后掘到了一桶桶金。其利用的行业也是非常宽泛,只有是须要网络营销推广,须要获取大量粉丝的行业,短视频须要上人气,闲鱼主动上货等等,都能用到该工具。自己率领技术团队也开发了一套残缺的群控系统,上面就群控系统的实现原理,以及局部外围代码供大家参考。不同于市场上的其余群控系统,咱们研发的群控系统是能够运行在任何电脑上的,不必再购买另外一台服务器,对群控系统的操作便捷性以及实用性都做了极大的优化。该零碎的实现次要分为几个局部,一是 pc 端操作软件,二是 adb 底层传输,三是手机管制端 (蕴含脚本引擎和实时画面传输模块)。如果是想管制受权客户,还有一个受权零碎。pc 端操作软件,次要是把所有手机的界面投射到电脑上,实现手机和电脑的同步操作,以及一些与手机互动操作的性能,例如:一键锁屏、一键解锁、上传文件、装置 APK、卸载 APK、一键截图、手机音量调节等等。其中比拟外围的问题是手机界面实时投射到电脑上,咱们在安卓端采纳了 H264 视频录屏模式和截图模式,采纳 Socket 传输,在进行了图像压缩,品质压缩等优化后,目前传输根本零提早,无任何卡顿景象。手机端次要应用的是 Uiautomator2.0,应用该计划比其余应用 Uiautomator 的群控获取界面信息更快,即便是动静界面,一样能够疾速获取,这是其余群控锁不能比较的,而且咱们在手机端也退出了 opencv 图像识别引擎,保障了一些无奈通过自动化测试工具获取界面元素的状况下也能够实现自动化脚本。以下是手机端的截图模式的外围实现代码,外围代码为:
public final void onImageAvailable(ImageReader imageReader) {
try {Image acquireLatestImage = imageReader.acquireLatestImage();
try {
int bufferLen = 0;
if (acquireLatestImage != null) {long currentTimeMillis = System.currentTimeMillis();
Image.Plane[] planes = acquireLatestImage.getPlanes();
Buffer buffer = planes[0].getBuffer();
this.aa.pixelStride = planes[0].getPixelStride();
this.aa.rowStride = planes[0].getRowStride();
acquireLatestImage.close();
int x;
Bitmap createBitmap;
byte[] tmpBuffer;
if (this.aa.isNormalSpeed) {if (System.currentTimeMillis() - this.aa.lastSendTimestamps > 200) {this.aa.lock.lock();
this.aa.imageBuffer = null;
this.aa.lock.unlock();
x = this.aa.rowStride - (this.aa.pixelStride * this.aa.widthImg);
createBitmap = Bitmap.createBitmap((x / this.aa.pixelStride) + this.aa.widthImg, this.aa.heighImg, Config.ARGB_8888);
createBitmap.copyPixelsFromBuffer(buffer);
tmpBuffer = a.compressBitmap(this.aa.shrinkBitmap(createBitmap, 0.4f), 75);
bufferLen = tmpBuffer.length;
this.aa.lastSendTimestamps = System.currentTimeMillis();
this.aa.sendBackToPc(tmpBuffer, (byte) 1);
}
else {this.aa.lock.lock();
this.aa.imageBuffer = buffer;
this.aa.lock.unlock();}
Log.i(TAG, new StringBuilder("发送一帧 大小:").append(bufferLen).append("耗时:").append(System.currentTimeMillis() - currentTimeMillis).toString());
}
else if (this.aa.isImageModel) {
// 高速模式,并且是图像模式
//2019.12.25 东东批改,应用大图像处理线程发送图像
if(System.currentTimeMillis() - this.aa.lastMaxImgSendTimestamps > 200)
{
try {this.aa.lock.lock();
this.aa.imageBuffer = null;
this.aa.lock.unlock();
x = this.aa.rowStride - (this.aa.pixelStride * this.aa.widthImg);
createBitmap = Bitmap.createBitmap((x / this.aa.pixelStride) + this.aa.widthImg, this.aa.heighImg, Config.ARGB_8888);
createBitmap.copyPixelsFromBuffer(buffer);
tmpBuffer = a.compressBitmap(this.aa.shrinkBitmap(createBitmap, 1.0f), this.aa.quality);
bufferLen = tmpBuffer.length;
this.aa.sendBackToPc(tmpBuffer, (byte) 2);
}
catch (Throwable th) {th.printStackTrace();
}
}
else
{this.aa.lock2.lock();
this.aa.imageMaxBuffer = buffer;
this.aa.lock2.unlock();}
Log.i(TAG, new StringBuilder("发送一帧 大小:").append(bufferLen).append("耗时:").append(System.currentTimeMillis() - currentTimeMillis).toString());
}
}
} catch (Throwable th2) {Log.e(TAG, "onImageAvailable:" + th2.getLocalizedMessage());
}
finally {
try {if (acquireLatestImage != null) {acquireLatestImage.close();
}
} catch (Exception th3) {}}
}
catch (Exception th4) {}
上面是 H264 录屏模式的代码:
// 启动屏幕录制
private synchronized String startScreenRecorderH264(int width, int height, int bitrate) {
try {if (this.mediaCodec != null) {this.mediaCodec.stop();
this.mediaCodec.release();
this.mediaCodec = null;
System.gc();}
MediaFormat createVideoFormat = MediaFormat.createVideoFormat("video/avc", this.widthImg, this.heighImg);
if (createVideoFormat == null) {Log.e(TAG,this.heighImg + "开始录制时出错!createVideoFormat 为 null" + this.widthImg);
return "开始录制时出错 createVideoFormat 无奈启动" ;
}
createVideoFormat.setInteger("color-format", 2130708361);
createVideoFormat.setInteger("sample-rate", 0);
createVideoFormat.setInteger("bitrate", bitrate);
createVideoFormat.setInteger("frame-rate", 20);
createVideoFormat.setInteger("i-frame-interval", 60);
createVideoFormat.setInteger("width", width);
createVideoFormat.setInteger("height", height);
Log.e(TAG,"created video format:" + createVideoFormat);
this.mediaCodec = MediaCodec.createEncoderByType("video/avc");
if (this.mediaCodec == null) {Log.e(TAG,"开始录制时出错!mediaCodec 为 null");
return "开始录制时出错 mediaCodec 为 null" ;
}
this.mediaCodec.configure(createVideoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
this.surfaceVideo = this.mediaCodec.createInputSurface();
System.out.print("created input surface:" + this.surfaceVideo + "\r\n");
this.mediaCodec.start();
Log.i(TAG,"编码器启动胜利!");
this.iBinder = 驶 ("video2", this.surfaceVideo,this.widthImg,this.heighImg,this.width,this.heigh);
return null;
}
catch (Throwable th) {th.printStackTrace();
String s = new StringBuilder("开始录制时出错:").append(th.getCause()).append(th.getStackTrace()[0].getLineNumber()).toString();
Log.e(TAG, s);
return s;
}
}
/**
*===========H264 屏幕录制线程函数 ==========
*
**/
static void changeScreenH264(a aVar) {while (true) {
// 如果是 NormalSpeed,则空循环, 由 image 格局去刷新 pc 端小屏幕
if (aVar.isNormalSpeed) {
try {Thread.sleep(100);
}
catch (Throwable th) {th.printStackTrace();
}
}
else {
try {if (aVar.mediaCodec == null) {Log.e(TAG, "mediaCodec 是 null");
try {Thread.sleep(2000);
}
catch (Exception e) { }
continue;
}
int dequeueOutputBuffer = aVar.mediaCodec.dequeueOutputBuffer(aVar.bufferInfo, -1);
if (dequeueOutputBuffer == -2) {Log.i(TAG, new StringBuilder("New format").append(aVar.mediaCodec.getOutputFormat()).toString());
}
else if (dequeueOutputBuffer == -1) {
try {Thread.sleep(10);
}
catch (Exception e) {}}
else if (dequeueOutputBuffer >= 0) {ByteBuffer byteBuffer = aVar.mediaCodec.getOutputBuffers()[dequeueOutputBuffer];
if (aVar.bufferInfo.size == 0) {byteBuffer = null;}
if (byteBuffer != null) {
int size = aVar.bufferInfo.size;
long j = aVar.bufferInfo.presentationTimeUs;
aVar.sendH264ToPc(byteBuffer, size, aVar.bufferInfo.flags);
byteBuffer.clear();}
aVar.mediaCodec.releaseOutputBuffer(dequeueOutputBuffer, false);
}
}
catch (Exception e2)
{
try
{Thread.sleep(2000);
}
catch (Exception e)
{ }
Log.e(TAG, new StringBuilder("录制失败:").append(e2.getLocalizedMessage()).append(getStackMsg(e2)).toString());
}
}
}
}
整个群控系统的界面截图: