在前面的三篇文章中我们学习了投屏软件开发过程中需要的基础知识,包括adb基本命令,ffmpeg基本的编解码,qt多线程和socket编程等。
本节开始我们正式进入投屏软件的开发,首先看一下软件流程图
下面我们逐步分析下的流程图:
- 启动adb服务,获取已连接设备详细信息的列表
adb devices -l
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
80QBDNQ228H9 device product:meizu_PRO6 model:PRO_6 device:PRO6 transport_id:1
在本例中获取到了一台已连接设备,SN为80QBDNQ228H9,在后面命令的执行中都要带上设备的SN。
2. 将要在手机上运行的java服务,通过adb push到手机上。
adb -s 80QBDNQ228H9 push scrcpy-server /data/local/tmp/scrcpy-server.jar
scrcpy-server: 1 file pushed, 0 skipped. 5.1 MB/s (64363 bytes in 0.012s)
-s 参数用于指定设备的SN,
push 是adb命令,用于给手机上传文件
scrcpy-server 本地文件
/data/local/tmp/scrcpy-server.jar 上传到手机后保存的目录及文件名
- 在Android设备与PC之间设置端口转发
adb -s 80QBDNQ228H9 reverse localabstract:scrcpy_158852df tcp:21873
-s 参数用于指定设备的SN
reverse 是adb命令,设置端口转发
localabstract PC端口映射到了手机侧的Unix域套接字上
scrcpy_158852df 自定义的手机侧套接字id
tcp:21873 PC端口
- 执行Android设备上的java服务
adb -s 80QBDNQ228H9 shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server v2.2 scid=158852df
[server] INFO: Device: [Meizu] Meizu PRO 6 (Android 7.1.1)
通过app_process启动的程序可以常驻内存,并且可以获取root权限,这也录屏、反控的关键。
- Android设备的服务启动后,PC端启动socket监听,依次接收到video、audio、control三个socket链路,为每个链路创建对应的thread。
下面是adb相关的代码:
#ifndef ADBPROCESS_H
#define ADBPROCESS_H
#include <QObject>
#include <QProcess>
#include <vector>
#include "common.h"
enum AdbStep
{
NONE,
START,
PUSH,
REVERSE,
EXECUTE,
CONNECT,
DONE,
};
class AdbProcess : public QProcess
{
Q_OBJECT
public:
explicit AdbProcess(QObject *parent = nullptr);
virtual ~AdbProcess();
bool startServer();
bool push(const std::string &serial);
bool install(const std::string &serial);
bool killServer();
bool deviceList();
bool isRunning();
bool reverse(const std::string &serial, const std::string &deviceSocketName, uint16_t local_port);
bool reverseRemove(const std::string &serial, const std::string &deviceSocketName);
bool execute(QStringList &args);
bool executeDeviceService(const DeviceParams ¶ms);
signals:
void updateDeviceVector(std::vector<DeviceInfo> &vec);
void adbState(AdbState state);
private:
bool parserDevicesFromOutput();
private:
QString standardOutput;
QString errorOutput;
std::string adbPath;
std::vector<DeviceInfo> deviceVector;
QProcess process;
AdbStep adbStep;
};
#endif // ADBPROCESS_H
#include "adbprocess.h"
#include <QDebug>
#include "utils.h"
AdbProcess::AdbProcess(QObject *parent)
: QProcess(parent)
{
adbPath = "adb";
connect(this,QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [=](int exitCode, QProcess::ExitStatus exitStatus){
if (QProcess::NormalExit == exitStatus && 0 == exitCode) {
emit adbState(AdbState::SUCCESS_EXEC);
} else {
emit adbState(AdbState::ERROR_AUTH);
}
qDebug() << "adb return " << exitCode << ",exit status " << exitStatus;
});
connect(this, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error){
if (QProcess::FailedToStart == error) {
emit adbState(AdbState::ERROR_MISSING_BINARY);
} else {
emit adbState(AdbState::ERROR_START);
qDebug()<<QString("qprocess start error:%1 %2").arg(program(),arguments().join(" "));
}
qDebug()<<"process error:"<<error;
});
connect(this, &QProcess::readyReadStandardError, this, [this]() {
this->errorOutput = QString::fromUtf8(readAllStandardError()).trimmed();
qDebug()<<this->errorOutput;
emit adbState(AdbState::ERROR_EXEC);
});
connect(this, &QProcess::readyReadStandardOutput, this, [this]() {
this->standardOutput = QString::fromUtf8(readAllStandardOutput()).trimmed();
qDebug()<<this->standardOutput;
if(this->arguments().contains("devices"))
{
this->parserDevicesFromOutput();
emit updateDeviceVector(this->deviceVector);
}
});
connect(this, &QProcess::started, this, [this](){
qDebug()<<"adb process start";
});
}
AdbProcess::~AdbProcess()
{
this->killServer();
}
bool AdbProcess::startServer()
{
QStringList args;
args << "start-server";
return execute(args);
}
bool AdbProcess::push(const std::string &serial)
{
if(serial.empty())
return false;
QStringList args;
args<<"-s";
args<<serial.c_str();
args<<"push";
args<<LOCAL_SERVER_PATH;
args<<DEVICE_SERVER_PATH;
return execute(args);
}
bool AdbProcess::executeDeviceService(const struct DeviceParams ¶ms)
{
QStringList args;
args << "-s";
args << params.serial.c_str();
args << "shell";
args << QString("CLASSPATH=%1").arg(DEVICE_SERVER_PATH);
args << "app_process";
#ifdef SERVER_DEBUGGER
#define SERVER_DEBUGGER_PORT "5005"
args <<
#ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
#else
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
#endif
SERVER_DEBUGGER_PORT,
#endif
args << "/";
args << "com.genymobile.scrcpy.Server";
args << DEVICE_SERVER_VERSION;
args << QString("scid=%1").arg(QString::asprintf("%08x",params.id));
if (!params.video) {
args << "video=false";
}
if (params.videoBitRate) {
args << QString("video_bit_rate=%1").arg(QString::number(params.videoBitRate));
}
if (!params.audio) {
args << "audio=false";
}
if (params.audioBitRate) {
args << QString("audio_bit_rate=%1").arg(QString::number(params.audioBitRate));
}
if (!params.control) {
args << "control=false";
}
if (params.videoCodec != CODEC_H264) {
args <<"video_codec=" <<Utils::getVideoAudioCodec(params.videoCodec).c_str();
}
if (params.audioCodec != CODEC_OPUS) {
args <<"audio_codec=" <<Utils::getVideoAudioCodec(params.audioCodec).c_str();
}
if (params.videoSource != VIDEO_SOURCE_DISPLAY) {
args << "video_source=camera";
}
if (params.audioSource == AUDIO_SOURCE_MIC) {
args << "audio_source=mic";
}
/*if (params.max_size) {
args << QString("max_size=%1").arg(QString::number(params.max_size));
}*/
if (params.maxFps) {
args << QString("max_fps=%1").arg(QString::number(params.maxFps));
}
if (params.lockVideoOrientation != LOCK_VIDEO_ORIENTATION_UNLOCKED) {
args << QString("lock_video_orientation=%1").arg(QString::number(params.lockVideoOrientation));
}
if (params.showTouches) {
args << "show_touches=true";
}
if (params.stayAwake) {
args << "stay_awake=true";
}
if (!params.videoCodecOptions.empty()) {
args << QString("video_codec_options=%1").arg(params.videoCodecOptions.c_str());
}
if (params.powerOffOnClose) {
args << "power_off_on_close=true";
}
if (!params.clipboardAutosync) {
args << "clipboard_autosync=false";
}
// 这条adb命令是阻塞运行的,进程不会退出了
return execute(args);
}
bool AdbProcess::killServer()
{
QStringList args;
args << "kill-server";
return execute(args);
}
bool AdbProcess::deviceList()
{
QStringList args;
args<<"devices"<<"-l";
return execute(args);
}
bool AdbProcess::isRunning()
{
if(QProcess::NotRunning == state())
{
return false;
}
else
{
return true;
}
}
bool AdbProcess::reverse(const std::string &serial, const std::string &device_socket_name, uint16_t local_port)
{
if(serial.empty() || device_socket_name.empty())
return false;
QStringList args;
args <<"-s" << serial.c_str();
args << "reverse";
args << QString("localabstract:%1").arg(device_socket_name.c_str());
args << QString("tcp:%1").arg(local_port);
return execute(args);
}
bool AdbProcess::reverseRemove(const std::string &serial, const std::string &device_socket_name)
{
if(serial.empty() || device_socket_name.empty())
return false;
QStringList args;
args <<"-s";
args << serial.c_str();
args << "reverse";
args << "--remove";
args << QString("localabstract:%1").arg(device_socket_name.c_str());
return execute(args);
}
bool AdbProcess::execute(QStringList &args)
{
if(args.empty())
return false;
standardOutput = "";
errorOutput = "";
qDebug()<<"execute : adb "<<args;
start(this->adbPath.c_str(),args);
return true;
}
bool AdbProcess::parserDevicesFromOutput()
{
deviceVector.clear();
QStringList device_info_list = this->standardOutput.split(QRegExp("\r\n|\n"), Qt::SkipEmptyParts);
if(device_info_list.size() <= 1)
{
qDebug()<<"none devices!";
return false;
}
for (int i = 1;i<device_info_list.size();++i) {
DeviceInfo device;
QStringList device_it = device_info_list[i].split(" ");
device_it.removeAll("");
device.serial = device_it[0].trimmed().toStdString();
device.state = device_it[1].toStdString();
if(device.state == "offline")
{
deviceVector.push_back(device);
continue;
}
device.product = device_it[2].split(':')[1].toStdString();
device.model = device_it[3].split(':')[1].toStdString();
device.device = device_it[4].split(':')[1].toStdString();
deviceVector.push_back(device);
}
return true;
}
下一篇继续讲解video demuxer thread相关的内容。
GitHub: linkedbyte
微信:linkedbyte
QQ:2276769057