├── res ├── template.jpg └── scrcpy-server ├── .vscode └── settings.json ├── ui_project_backup └── server_window │ ├── main.cpp │ ├── server_window.h │ ├── server_window.cpp │ ├── CMakeLists.txt │ ├── server_window.ui │ └── CMakeLists.txt.user ├── .gitignore ├── code ├── server │ ├── server_interface.h │ ├── server_interface.cpp │ ├── server_window.h │ ├── server_window.cpp │ ├── server.h │ ├── server.cpp │ └── server_window.ui ├── recipe │ ├── zhidao.h │ └── zhidao.cpp ├── main.cpp └── stream │ ├── frame_buffer.h │ ├── stream.h │ ├── frame_buffer.cpp │ └── stream.cpp ├── README.md ├── CMakeLists.txt └── LICENSE /res/template.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fb029ed/scrcpy-opencv-SQ/HEAD/res/template.jpg -------------------------------------------------------------------------------- /res/scrcpy-server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fb029ed/scrcpy-opencv-SQ/HEAD/res/scrcpy-server -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "string_view": "cpp", 4 | "regex": "cpp", 5 | "tuple": "cpp" 6 | } 7 | } -------------------------------------------------------------------------------- /ui_project_backup/server_window/main.cpp: -------------------------------------------------------------------------------- 1 | #include "server_window.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | ServerWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /code/server/server_interface.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_INTERFACE_H 2 | #define SERVER_INTERFACE_H 3 | #include "server_window.h" 4 | #include 5 | 6 | class ServerInterface 7 | { 8 | public: 9 | ServerInterface(); 10 | ~ServerInterface(); 11 | bool init(); 12 | int get_socket(); 13 | private: 14 | int _socket_id; 15 | ServerWindow* _p_server_window; 16 | }; 17 | 18 | #endif // !SERVER_INTERFACE_H -------------------------------------------------------------------------------- /code/recipe/zhidao.h: -------------------------------------------------------------------------------- 1 | #ifndef ZHIDAO_H 2 | #define ZHIDAO_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | using namespace cv; 9 | //简单实现,类未设计 10 | class ZhiDao{ 11 | public: 12 | ZhiDao(); 13 | ~ZhiDao(); 14 | bool detect(Mat& inmat); 15 | bool action(); 16 | void init(string template_path); 17 | private: 18 | Mat _template; 19 | std::chrono::high_resolution_clock::time_point _last_time; 20 | }; 21 | #endif // !ZHIDAO_H -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scrcpy-opencv-SQ 2 | ## 项目介绍 3 | https://github.com/Genymobile/scrcpy 4 | 实现了安卓手机的投屏和触控功能 5 | 本项目使用c++对scrcpy进行重构,提供了opencv中mat结构的图像便于后续处理 6 | SQ(simple and quick) 7 | * 因为只实现了一部分,所以项目比较简单清晰,容易二次开发 8 | * 提供opencv Mat图像,可以对获取到的图像进行图像处理 9 | * 对解码和图像处理进行了并行优化,实时性较好 10 | * recipe中提供了一些已经实现好的自动化脚本 11 | > 2021.3.27针对智慧树网课实现了自动刷课功能 12 | 13 | ## 使用方式 14 | ```shell 15 | mkdir build 16 | cd build 17 | cmake .. 18 | make -j2 19 | 执行可执行文件 20 | ``` 21 | 22 | ## TODO 23 | * recipe写的还比较粗糙,改成工厂模式 24 | * 游戏键盘映射还没做 25 | * 变量的配置方式和相对路径 26 | * 只实现了usb数据传输方式,wifi方式待实现 27 | * 提供可直接运行的文件 28 | * 私有仓库时期的一些命名和现在还不一致 29 | 30 | -------------------------------------------------------------------------------- /code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "server_interface.h" 2 | #include "stream.h" 3 | #include "zhidao.h" 4 | int main(int argc, char *argv[]) 5 | { 6 | ZhiDao zhidao; 7 | zhidao.init("/home/y/github/scrcpy-GamePro/res/template.jpg"); 8 | ServerInterface server; 9 | server.init(); 10 | Stream video_stream; 11 | video_stream.stream_init(server.get_socket()); 12 | 13 | Mat src; 14 | while(1){ 15 | if(!video_stream.get_img(src)) 16 | break; 17 | imshow("origin",src); 18 | if(!zhidao.detect(src)){ 19 | zhidao.action(); 20 | cout << "action" << endl; 21 | } 22 | waitKey(1); 23 | } 24 | return 0; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /code/server/server_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "server_interface.h" 2 | 3 | ServerInterface::ServerInterface(){ 4 | } 5 | 6 | ServerInterface::~ServerInterface(){ 7 | if(_p_server_window != nullptr) 8 | delete _p_server_window; 9 | } 10 | 11 | bool ServerInterface::init(){ 12 | int argc = 0; 13 | char* argv[1]; 14 | QApplication* p_app = new QApplication(argc,argv); 15 | _p_server_window = new ServerWindow; 16 | _p_server_window->set_app(p_app); 17 | //传入引用,在槽函数实现赋值 18 | _p_server_window->get_socket(&_socket_id); 19 | _p_server_window->show(); 20 | p_app->exec(); 21 | delete p_app; 22 | return true; 23 | } 24 | 25 | int ServerInterface::get_socket(){ 26 | return _socket_id; 27 | } 28 | -------------------------------------------------------------------------------- /ui_project_backup/server_window/server_window.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVERWINDOW_H 2 | #define SERVERWINDOW_H 3 | 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class ServerWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class ServerWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | ServerWindow(QWidget *parent = nullptr); 16 | ~ServerWindow(); 17 | 18 | private slots: 19 | void on_ComboBox_bitrate_currentIndexChanged(const QString &arg1); 20 | 21 | void on_ComboBox_resolution_currentIndexChanged(const QString &arg1); 22 | 23 | void on_pushButton_start_clicked(); 24 | 25 | void on_pushButton_stop_clicked(); 26 | 27 | void on_pushButton_refresh_clicked(); 28 | 29 | void on_pushButton_find_config_clicked(); 30 | 31 | void on_pushButton_apply_clicked(); 32 | 33 | void on_comboBox_game_currentIndexChanged(const QString &arg1); 34 | 35 | private: 36 | Ui::ServerWindow *ui; 37 | }; 38 | #endif // SERVERWINDOW_H 39 | -------------------------------------------------------------------------------- /ui_project_backup/server_window/server_window.cpp: -------------------------------------------------------------------------------- 1 | #include "server_window.h" 2 | #include "./ui_server_window.h" 3 | 4 | ServerWindow::ServerWindow(QWidget *parent) 5 | : QMainWindow(parent) 6 | , ui(new Ui::ServerWindow) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | ServerWindow::~ServerWindow() 12 | { 13 | delete ui; 14 | } 15 | 16 | 17 | void ServerWindow::on_ComboBox_bitrate_currentIndexChanged(const QString &arg1) 18 | { 19 | 20 | } 21 | 22 | void ServerWindow::on_ComboBox_resolution_currentIndexChanged(const QString &arg1) 23 | { 24 | 25 | } 26 | 27 | void ServerWindow::on_pushButton_start_clicked() 28 | { 29 | 30 | } 31 | 32 | void ServerWindow::on_pushButton_stop_clicked() 33 | { 34 | 35 | } 36 | 37 | void ServerWindow::on_pushButton_refresh_clicked() 38 | { 39 | 40 | } 41 | 42 | void ServerWindow::on_pushButton_find_config_clicked() 43 | { 44 | 45 | } 46 | 47 | void ServerWindow::on_pushButton_apply_clicked() 48 | { 49 | 50 | } 51 | 52 | void ServerWindow::on_comboBox_game_currentIndexChanged(const QString &arg1) 53 | { 54 | 55 | } 56 | -------------------------------------------------------------------------------- /code/server/server_window.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVERWINDOW_H 2 | #define SERVERWINDOW_H 3 | 4 | #include 5 | #include "server.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class ServerWindow; } 9 | QT_END_NAMESPACE 10 | 11 | class ServerWindow : public QMainWindow 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | ServerWindow(QWidget *parent = nullptr); 17 | ~ServerWindow(); 18 | void set_app(QApplication* p_app); 19 | int get_socket(int* socket); 20 | 21 | private slots: 22 | void on_ComboBox_bitrate_currentIndexChanged(const QString &arg1); 23 | 24 | void on_ComboBox_resolution_currentIndexChanged(const QString &arg1); 25 | 26 | void on_pushButton_start_clicked(); 27 | 28 | void on_pushButton_stop_clicked(); 29 | 30 | void on_pushButton_refresh_clicked(); 31 | 32 | void on_pushButton_find_config_clicked(); 33 | 34 | void on_pushButton_apply_clicked(); 35 | 36 | void on_comboBox_game_currentIndexChanged(const QString &arg1); 37 | 38 | 39 | private: 40 | Ui::ServerWindow *ui; 41 | Server _server; 42 | //用于界面控制 43 | QApplication* _p_app; 44 | int* _psocket; 45 | }; 46 | #endif // SERVERWINDOW_H 47 | -------------------------------------------------------------------------------- /ui_project_backup/server_window/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(server_window LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | # QtCreator supports the following variables for Android, which are identical to qmake Android variables. 15 | # Check http://doc.qt.io/qt-5/deployment-android.html for more information. 16 | # They need to be set before the find_package(Qt5 ...) call. 17 | 18 | #if(ANDROID) 19 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 20 | # if (ANDROID_ABI STREQUAL "armeabi-v7a") 21 | # set(ANDROID_EXTRA_LIBS 22 | # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so 23 | # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) 24 | # endif() 25 | #endif() 26 | 27 | find_package(Qt5 COMPONENTS Widgets REQUIRED) 28 | 29 | if(ANDROID) 30 | add_library(server_window SHARED 31 | main.cpp 32 | server_window.cpp 33 | server_window.h 34 | server_window.ui 35 | ) 36 | else() 37 | add_executable(server_window 38 | main.cpp 39 | server_window.cpp 40 | server_window.h 41 | server_window.ui 42 | ) 43 | endif() 44 | 45 | target_link_libraries(server_window PRIVATE Qt5::Widgets) 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(scrcpy-gamepro) 3 | add_compile_options(-std=c++11) 4 | add_compile_options(-fPIC) 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | set(CMAKE_AUTOUIC ON) 7 | set(CMAKE_AUTOMOC ON) 8 | set(CMAKE_AUTORCC ON) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | 11 | find_package(Qt5 COMPONENTS Widgets REQUIRED) 12 | find_package(OpenCV 4.2 REQUIRED) 13 | 14 | include_directories(code/server) 15 | include_directories(code/stream) 16 | include_directories(code/recipe) 17 | #qt头文件路径 18 | include_directories(${QT_INCLUDES}) 19 | include_directories(${Qt5Widgets_INCLUDE_DIRS}) 20 | 21 | #生成server库 22 | AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR}/code/server DIR_SRCS) 23 | add_library(server ${DIR_SRCS}) 24 | target_link_libraries(server 25 | PRIVATE Qt5::Widgets 26 | pthread 27 | ) 28 | 29 | #生成stream库 30 | AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR}/code/stream DIR_SRCS) 31 | add_library(stream ${DIR_SRCS}) 32 | target_link_libraries(stream 33 | ${OpenCV_LIBS} 34 | avcodec 35 | avformat 36 | avutil 37 | swscale 38 | ) 39 | 40 | #生成recipe库 41 | AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR}/code/recipe DIR_SRCS) 42 | add_library(recipe ${DIR_SRCS}) 43 | target_link_libraries(recipe 44 | ${OpenCV_LIBS} 45 | ) 46 | 47 | add_executable(scrcpy-gamepro 48 | code/main.cpp 49 | ) 50 | 51 | target_link_libraries(scrcpy-gamepro 52 | recipe 53 | server 54 | stream 55 | ${OpenCV_LIBS} 56 | ) -------------------------------------------------------------------------------- /code/server/server_window.cpp: -------------------------------------------------------------------------------- 1 | #include "server_window.h" 2 | #include "./ui_server_window.h" 3 | ServerWindow::ServerWindow(QWidget *parent) 4 | : QMainWindow(parent) 5 | , ui(new Ui::ServerWindow) 6 | { 7 | ui->setupUi(this); 8 | } 9 | 10 | ServerWindow::~ServerWindow() 11 | { 12 | delete ui; 13 | } 14 | 15 | void ServerWindow::on_ComboBox_bitrate_currentIndexChanged(const QString &arg1) 16 | { 17 | 18 | } 19 | 20 | void ServerWindow::on_ComboBox_resolution_currentIndexChanged(const QString &arg1) 21 | { 22 | 23 | } 24 | 25 | void ServerWindow::on_pushButton_start_clicked() 26 | { 27 | _server.start_by_step(); 28 | //完成参数设置则退出设置界面 29 | *_psocket = _server.get_socket(); 30 | _p_app->exit(0); 31 | } 32 | 33 | void ServerWindow::on_pushButton_stop_clicked() 34 | { 35 | _server.stop_server(); 36 | _p_app->exit(0); //退出当前界面 37 | } 38 | 39 | void ServerWindow::on_pushButton_refresh_clicked() 40 | { 41 | //获取器件名称 42 | string device_name; 43 | _server.get_device_name(device_name); 44 | QString show = QString::fromStdString(device_name); 45 | ui -> lineEdit_device -> setText(show); 46 | } 47 | 48 | void ServerWindow::on_pushButton_find_config_clicked() 49 | { 50 | 51 | } 52 | 53 | void ServerWindow::on_pushButton_apply_clicked() 54 | { 55 | 56 | } 57 | 58 | void ServerWindow::on_comboBox_game_currentIndexChanged(const QString &arg1) 59 | { 60 | 61 | } 62 | 63 | void ServerWindow::set_app(QApplication* p_app){ 64 | _p_app = p_app; 65 | } 66 | 67 | int ServerWindow::get_socket(int* socket){ 68 | _psocket = socket; 69 | } -------------------------------------------------------------------------------- /code/recipe/zhidao.cpp: -------------------------------------------------------------------------------- 1 | #include "zhidao.h" 2 | ZhiDao::ZhiDao(){ 3 | } 4 | 5 | ZhiDao::~ZhiDao(){ 6 | } 7 | 8 | void ZhiDao::init(string template_path){ 9 | _template = imread(template_path); 10 | _last_time = chrono::high_resolution_clock::now(); 11 | } 12 | 13 | //检测是否有提问框 14 | bool ZhiDao::detect(Mat& inmat){ 15 | int width = inmat.cols; 16 | int length = inmat.rows; 17 | double x_rate = 0.84 , y_rate=0.13; 18 | Mat roi(inmat, Rect( width * x_rate , length * y_rate , 34 , 34)); 19 | Mat sub=roi-_template; 20 | imshow("af", sub); 21 | //用减法匹配模板 22 | Scalar mean; //均值 23 | Scalar stddev; //标准差 24 | meanStdDev( sub, mean, stddev ); //计算均值和标准差 25 | double mean_pxl = mean.val[0]; 26 | double stddev_pxl = stddev.val[0]; 27 | cout << mean_pxl << "\t" << stddev_pxl << endl; 28 | if(mean_pxl > 0.5 && mean_pxl < 1 && stddev_pxl > 1 && stddev_pxl < 3) return false; 29 | return true; 30 | } 31 | 32 | //处理提问框,继续播放视频 33 | //两次触发时间间隔至少大于5分钟 34 | bool ZhiDao::action(){ 35 | auto now = chrono::high_resolution_clock::now(); 36 | std::chrono::duration diff = now - _last_time; 37 | cout<<"use "< 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace std; 10 | using namespace cv; 11 | /* 12 | 需求分析 13 | 图像获取解码的典型时间消耗为5ms以下,图像处理时间最短也在30ms以上 14 | */ 15 | #define BUFFER_LEN 10 16 | class FrameBuffer{ 17 | public: 18 | FrameBuffer(); 19 | ~FrameBuffer(); 20 | 21 | //初始化内存区域 22 | bool init(int height,int width); 23 | //返回已经分配好的,当前应使用的内存的指针 24 | unsigned char* get_buffer(); 25 | 26 | enum ReadMode{ 27 | IN_ORDER,//顺序读取 28 | REAL_TIME//实时性优先,读取最晚产生的,并移动指针忽略较早产生的内容 29 | }; 30 | //图像处理线程获取图像内容.默认读取策略为按照顺序读取 31 | bool get_mat(Mat& mat_out,ReadMode read_mode = IN_ORDER); 32 | //将解码完成的帧在缓冲区里进行标记,有分辨率信息是为了处理横竖屏转换 33 | bool push_frame(int height,int width); 34 | private: 35 | //获取当前可用帧的数量 36 | int get_useful_frame_number(); 37 | //用于存储缓冲区信息 38 | vector _buffer; 39 | //用于记录每个缓冲区的状态 40 | //0代表无效数据,1代表原始分频率数据,2代表原始分辨率旋转90之后的数据 41 | enum BufferStat{ 42 | EMPTY, //不可用 43 | ORIGIN, //原始分辨率 44 | ROTATED //图像经过旋转 45 | }; 46 | vector _buffer_stat; 47 | int _origin_height,_origin_width; 48 | 49 | //用于控制缓冲区为空的时候,等待生产者生成数据 50 | condition_variable _not_empty; 51 | //用于防止缓冲区信息被同时更改 52 | mutex _buffer_info_lock; 53 | //消费者读取位置和生产者写入位置 54 | int _read_point = -1,_write_point=0; 55 | //计时工具 56 | chrono::high_resolution_clock::time_point _this_time,_last_time; 57 | }; 58 | 59 | #endif // !FRAME_BUFFER_H -------------------------------------------------------------------------------- /code/server/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | using namespace std; 12 | 13 | //提供功能实现供外部调用 14 | class Server{ 15 | public: 16 | Server(); 17 | ~Server(); 18 | //波特率,分辨率设置,负责合理性检查 19 | bool update_bitrate(string bitrate); 20 | bool update_resolution(string resolution); 21 | //获取手机id 22 | bool get_device_name(string& device_name); 23 | bool start_by_step(); 24 | bool stop_server(); 25 | int get_socket(); 26 | 27 | private: 28 | //推送安卓端到手机 29 | bool push_server_to_device(string server_path); 30 | bool remove_server_from_device(string server_path); 31 | //启动反向代理 32 | bool reverse_config(string domain_socket_name,string local_port); 33 | bool remove_reverse(string domain); 34 | //在安卓端运行 35 | bool start_run_in_device(); 36 | bool stop_run_in_device(); 37 | bool aftermath(); 38 | //建立图像传输socket连接 39 | bool video_socket_init(string id,string port); 40 | 41 | //运行shell指令并获取输出结果 42 | string run_shell(string origin_cmd); 43 | 44 | enum ServerStat{ 45 | INIT_STAT, //初始状态 46 | PUSH_SUCCESS, //成功推送服务端到手机 47 | REVERSE_SUCCESS, //成功 启动反向代理 48 | RUN_SUCCESS //服务端程序已经在手机上运行 49 | }; 50 | ServerStat _server_stat = INIT_STAT; 51 | string _device_name; 52 | 53 | //未人工设置情况下采用默认数值 54 | //TODO:完成参数的选取建议 55 | int _bitrate = 200000000; 56 | int _resolution = 720; 57 | thread _device_server_thread; 58 | int _video_socket; //连接套接字 59 | int _socket_fd; //监听套接字 60 | }; 61 | #endif // !SERVER_H 62 | -------------------------------------------------------------------------------- /code/stream/stream.h: -------------------------------------------------------------------------------- 1 | #ifndef STREAM_H 2 | #define STREAM_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "frame_buffer.h" 12 | 13 | extern "C"{ 14 | #include 15 | #include 16 | #include 17 | } 18 | #define NO_PTS UINT64_MAX 19 | #define HEADER_SIZE 12 20 | #define DEVICE_NAME_FIELD_LENGTH 64 21 | using namespace std; 22 | 23 | //数据流处理类负责从socket原始数据到原始数据帧生成的部分 24 | class Stream{ 25 | public: 26 | Stream(); 27 | ~Stream(); 28 | //初始化socket和相关解码器 29 | bool stream_init(int video_socket); 30 | //运行yuv帧生成线程 31 | void frame_creat_run(); 32 | static void run_recv(Stream* video_stream); 33 | //关停原始帧获取线程,释放相关资源 34 | bool stop_frame_creat(); 35 | //供外部图像处理线程获取图像 36 | bool get_img(Mat& mat_out); 37 | 38 | private: 39 | bool ffmpeg_init(); 40 | bool decoder_init(); 41 | bool get_device_info(); 42 | bool recv_packet(AVPacket* packet); 43 | //从数据包中获取视频帧 44 | bool packet_manager(AVPacket* packet); 45 | //从数据包中解析出图像帧 46 | bool parse_packet_to_frame(AVPacket* packet,AVFrame* decoding_frame); 47 | 48 | //单包数据头解析工具函数 49 | uint64_t buffer_read_64byte(uint8_t* buf); 50 | uint32_t buffer_read_32byte(uint8_t* buf); 51 | 52 | //针对ctrl c退出的情况,释放资源 53 | static void interrupt_handle(int sig); 54 | 55 | int _video_socket; 56 | string _device_name; 57 | int _resolution_width,_resolution_height; 58 | 59 | AVCodec* _codec = nullptr; //解码器 60 | AVCodecContext* _codec_context= nullptr; //解码器上下文 61 | AVCodecParserContext* _parser = nullptr; //用于数据拼接,获取单帧数据 62 | bool _need_stop; 63 | //单包数据的头部信息,会被频繁调用,为避免内存分配时间浪费提前分配 64 | unsigned char _header[HEADER_SIZE]; 65 | 66 | //h264数据包需要拼接,使用该信息记录是否需要拼接 67 | bool _has_pending = false; 68 | //需要拼接情况下使用的缓冲区 69 | AVPacket _pending; 70 | //yuv整帧信息 71 | AVFrame* _yuv_frame = nullptr; 72 | 73 | FrameBuffer _frame_buffer; 74 | 75 | //数据接收线程 76 | thread _recv_thread; 77 | 78 | }; 79 | 80 | #endif // !STREAM_H -------------------------------------------------------------------------------- /code/stream/frame_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "frame_buffer.h" 2 | 3 | //缓冲区的处理逻辑 4 | //解码耗时很短,图像处理耗时较长 5 | //仅设置了缓冲区为空的条件变量,在无图像时阻塞等待另一线程生成图像 6 | //针对生产者,循环写入缓冲区,不设置缓冲区满的条件 7 | //特殊处理,在生产者写入位置和消费者读取位置相同时,生产者跳过当前缓冲区写入下一个缓冲区 8 | 9 | FrameBuffer::FrameBuffer(){ 10 | } 11 | FrameBuffer::~FrameBuffer(){ 12 | } 13 | 14 | //初始化内存区域 15 | bool FrameBuffer::init(int height,int width){ 16 | int size = height*width *3;//rgb 17 | _buffer.resize(BUFFER_LEN,nullptr); 18 | _buffer_stat.resize(BUFFER_LEN,EMPTY); 19 | for(int i =0;i lock(_buffer_info_lock); 30 | //调用该函数之后,外部解码线程会将解码内容写入缓冲区,此时该内存不可用于图像处理 31 | assert(_buffer[_write_point] != nullptr); 32 | //将写入缓冲区和读者正在使用的缓冲区重合则写入下一个缓冲区 33 | if(_write_point == _read_point){ 34 | _write_point = (_write_point+1)%BUFFER_LEN; 35 | } 36 | return _buffer[_write_point]; 37 | } 38 | 39 | //TODO::考虑该模块是否需要加互斥锁 40 | //条件变量 41 | int FrameBuffer::get_useful_frame_number(){ 42 | int len = 0; 43 | for(int i=0;i lock(_buffer_info_lock); 54 | while(get_useful_frame_number() == 0){ 55 | _not_empty.wait(lock); 56 | } 57 | 58 | //下一位有可能为空,此时可以确定有可用数据 59 | //按照生产者填充数据方向搜寻可用数据帧数 60 | do{ 61 | _read_point = (_read_point + 1) % BUFFER_LEN; 62 | }while(_buffer_stat[_read_point] == EMPTY); 63 | 64 | //有数据可以获取了 65 | if(_buffer_stat[_read_point] == ORIGIN){ 66 | Mat t(_origin_height,_origin_width,CV_8UC3); 67 | t.data = _buffer[_read_point]; 68 | mat_out = t; 69 | } 70 | else if(_buffer_stat[_read_point] == ROTATED){ 71 | Mat t(_origin_width,_origin_height,CV_8UC3); 72 | t.data = _buffer[_read_point]; 73 | mat_out = t; 74 | } 75 | else{ 76 | cout << "线程同步异常" << endl; 77 | return false; 78 | } 79 | _buffer_stat[_read_point] = EMPTY; 80 | return true; 81 | } 82 | 83 | //生产者使用 84 | //将解码完成的帧在缓冲区里进行标记,有分辨率信息是为了处理横竖屏转换 85 | //循环使用缓冲区,无缓冲区满的情况 86 | bool FrameBuffer::push_frame(int height,int width){ 87 | _this_time = chrono::high_resolution_clock::now(); 88 | chrono::duration tm = _this_time - _last_time; // 毫秒 89 | _last_time = _this_time; 90 | //cout << "单帧传输加解码时间" << tm.count() << "ms" << std::endl; 91 | { 92 | unique_lock lock(_buffer_info_lock); 93 | if(height == _origin_height && width == _origin_width){ 94 | //未旋转且图像可用 95 | _buffer_stat[_write_point] = ORIGIN; 96 | } 97 | else{ 98 | //经过旋转图像可用 99 | _buffer_stat[_write_point] = ROTATED; 100 | } 101 | _write_point = (_write_point+1)%BUFFER_LEN; 102 | } 103 | //先解锁再通知,防止通知之后获取不到互斥锁再次休眠 104 | _not_empty.notify_all(); 105 | return true; 106 | } 107 | -------------------------------------------------------------------------------- /code/server/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | Server::Server(){ 4 | } 5 | 6 | Server::~Server(){ 7 | } 8 | 9 | bool Server::get_device_name(string& device_name){ 10 | device_name = "no available devices"; 11 | string terminal_out = run_shell("adb devices"); 12 | //TODO:检验器件名称的正则表达式正确性 13 | regex devices_name_rule("\\n[a-z0-9]+\\s+"); 14 | cmatch m; 15 | vector devices_name_list; 16 | while(regex_search(terminal_out.c_str(), m, devices_name_rule)){ 17 | devices_name_list.push_back(m.str()); 18 | terminal_out = m.suffix().first; 19 | } 20 | if(devices_name_list.size() > 1){ 21 | device_name = "more than 1 devices"; 22 | } 23 | else if(devices_name_list.size() == 1){ 24 | //去除id前后的不可见字符 25 | string blanks("\f\v\r\t\n "); 26 | devices_name_list[0].erase(0,devices_name_list[0].find_first_not_of(blanks)); 27 | devices_name_list[0].erase(devices_name_list[0].find_last_not_of(blanks) + 1); 28 | device_name = devices_name_list[0]; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | bool Server::aftermath(){ 35 | bool ret = false; 36 | switch(_server_stat){ 37 | case INIT_STAT: 38 | break; 39 | case PUSH_SUCCESS:{ 40 | remove_server_from_device("/data/local/tmp/scrcpy-server"); 41 | break; 42 | } 43 | case REVERSE_SUCCESS:{ 44 | remove_server_from_device("/data/local/tmp/scrcpy-server"); 45 | remove_reverse("localabstract:scrcpy"); 46 | break; 47 | } 48 | case RUN_SUCCESS:{ 49 | /* 50 | remove_server_from_device("/data/local/tmp/scrcpy-server"); 51 | remove_reverse("localabstract:scrcpy"); 52 | */ 53 | ret = true; 54 | break; 55 | } 56 | } 57 | return ret; 58 | } 59 | 60 | bool Server::start_by_step(){ 61 | switch (_server_stat){ 62 | case INIT_STAT:{ 63 | if(!get_device_name(_device_name)) 64 | break; 65 | //仅对在build下构建有效 66 | if(!push_server_to_device("../res/scrcpy-server")) 67 | break; 68 | _server_stat = PUSH_SUCCESS; 69 | } 70 | case PUSH_SUCCESS:{ 71 | if(!reverse_config("localabstract:scrcpy","tcp:27183")) 72 | break; 73 | _server_stat = REVERSE_SUCCESS; 74 | } 75 | case REVERSE_SUCCESS:{ 76 | if(!start_run_in_device()) 77 | break; 78 | _server_stat = RUN_SUCCESS; 79 | } 80 | } 81 | return aftermath(); 82 | } 83 | 84 | bool Server::stop_server(){ 85 | return stop_run_in_device(); 86 | } 87 | 88 | bool Server::update_bitrate(string bitrate){ 89 | 90 | } 91 | 92 | bool Server::update_resolution(string resolution){ 93 | 94 | } 95 | 96 | string Server::run_shell(string origin_cmd){ 97 | string temp_path = " > temp.txt"; 98 | system((origin_cmd + temp_path).c_str()); 99 | ifstream terminal_out("temp.txt"); 100 | string terminal_result; 101 | string tmp; 102 | while(getline(terminal_out,tmp)){ 103 | terminal_result += "\n" + tmp; 104 | } 105 | system("rm -rf temp.txt"); 106 | return terminal_result; 107 | } 108 | 109 | bool Server::push_server_to_device(string server_path){ 110 | string device_path = "/data/local/tmp/scrcpy-server"; //在手机上的推送地址使用定值 111 | string cmd = "adb -s " + _device_name + " push " + server_path + " " + device_path; 112 | string result = run_shell(cmd); 113 | regex result_rule("pushed"); 114 | cmatch m; 115 | if(!regex_search(result.c_str(), m, result_rule)){ 116 | return false; 117 | } 118 | return true; 119 | } 120 | 121 | bool Server::remove_server_from_device(string server_path){ 122 | string cmd = "adb -s " + _device_name + " shell rm -rf " + server_path; 123 | system(cmd.c_str()); 124 | return true; 125 | } 126 | 127 | bool Server::reverse_config(string domain_socket_name,string local_port){ 128 | string cmd ="adb -s " + _device_name + " reverse " 129 | + domain_socket_name + " " + local_port; 130 | system(cmd.c_str()); 131 | //判断套接字是否建立成功 132 | string result = run_shell("adb -s " + _device_name + " reverse --list"); 133 | regex result_rule(local_port); 134 | cmatch m; 135 | if(!regex_search(result.c_str(), m, result_rule)){ 136 | return false; 137 | } 138 | return true; 139 | } 140 | 141 | bool Server::remove_reverse(string domain){ 142 | string cmd = "adb -s " + _device_name + " reverse --remove domain"; 143 | system(cmd.c_str()); 144 | return true; 145 | } 146 | 147 | bool Server::start_run_in_device(){ 148 | string cmd = "adb -s "+ _device_name 149 | + " shell CLASSPATH=/data/local/tmp/scrcpy-server app_process \ 150 | / com.genymobile.scrcpy.Server 1.14 info " + to_string(_resolution) + " " + to_string(_bitrate) + 151 | " 120 -1 false - true true 0 false false profile=1,level=1" + " > temp.txt"; 152 | auto tmp = [](string cmd_use){ 153 | //this_thread::sleep_for(chrono::milliseconds(5)); 154 | system(cmd_use.c_str()); 155 | }; 156 | _device_server_thread = thread(tmp,cmd); 157 | 158 | //初始化本地socket 159 | string id ="127.0.0.1";//本地回环 160 | string port = "27183"; 161 | if(!video_socket_init(id,port)){ 162 | return false; 163 | } 164 | ifstream terminal_out("temp.txt"); 165 | string terminal_result; 166 | string tmps; 167 | while(getline(terminal_out,tmps)){ 168 | terminal_result += "\n" + tmps; 169 | } 170 | system("rm -rf temp.txt"); 171 | cout << terminal_result << endl; 172 | //判断是否启动成功 173 | //有ERROR出现说明启动失败 174 | regex result_rule("ERROR"); 175 | cmatch m; 176 | if(regex_search(terminal_result.c_str(), m, result_rule)){ 177 | return false; 178 | } 179 | return true; 180 | } 181 | 182 | //TODO:验证有效性 183 | bool Server::stop_run_in_device(){ 184 | //kill安卓端进程 185 | string cmd = "adb -s "+ _device_name + " shell ps | grep app_process"; 186 | string t_out = run_shell(cmd); 187 | //获取pid 188 | regex pid_rule("[0-9]+"); 189 | cmatch m; 190 | vector pid_list; 191 | regex_search(t_out.c_str(), m, pid_rule); 192 | string pid = m.str(); 193 | cmd = "adb -s "+ _device_name + " shell kill -9 " + pid; 194 | system(cmd.c_str()); 195 | //释放socket端口 196 | shutdown(_video_socket,SHUT_RDWR); 197 | shutdown(_socket_fd,SHUT_RDWR); 198 | return true; 199 | } 200 | 201 | bool Server::video_socket_init(string id,string port){ 202 | assert(! (id.empty() || port.empty())); 203 | //申请套接字描述符 204 | _socket_fd = socket(PF_INET, SOCK_STREAM, 0);//监听套接字 205 | if(_socket_fd < 0){ 206 | cout << "socket创建失败" << endl; 207 | return false; 208 | } 209 | //将套接字描述符和端口进行绑定 210 | struct sockaddr_in serv_addr; 211 | serv_addr.sin_family = AF_INET; 212 | serv_addr.sin_port = htons(atoi(port.c_str())); 213 | serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 214 | const int on=1; 215 | //通过设置SO_REUSEADDR,可以在tcp连接断开之后立即重新连接 216 | setsockopt(_socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 217 | 218 | if(bind(_socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0){ 219 | cout << "绑定失败" << endl; 220 | return false; 221 | } 222 | 223 | //监听对应端口信息 224 | if(listen(_socket_fd, 10) < 0){ 225 | cout << "监听失败" << endl; 226 | return false; 227 | } 228 | 229 | //获取套接字 230 | struct sockaddr_in client_addr; 231 | socklen_t client_addrlength = sizeof(client_addr); 232 | _video_socket = accept(_socket_fd, (struct sockaddr*)&client_addr, &client_addrlength); //连接套接字 233 | if(_video_socket < 0){ 234 | cout << "套接字获取失败" << endl; 235 | return false; 236 | } 237 | cout << "video socket初始化成功"<< endl << "连接客户端id为:" << inet_ntoa(client_addr.sin_addr) << endl; 238 | return true; 239 | } 240 | 241 | int Server::get_socket(){ 242 | return _video_socket; 243 | } 244 | 245 | 246 | -------------------------------------------------------------------------------- /code/server/server_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ServerWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 211 10 | 481 11 | 12 | 13 | 14 | ServerWindow 15 | 16 | 17 | 18 | 19 | 20 | 10 21 | 10 22 | 191 23 | 91 24 | 25 | 26 | 27 | border-color: rgb(238, 238, 236); 28 | 29 | 30 | start config 31 | 32 | 33 | 34 | 35 | 10 36 | 30 37 | 180 38 | 58 39 | 40 | 41 | 42 | 43 | 44 | 45 | bit rate 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 2000000 54 | 55 | 56 | 57 | 58 | 6000000 59 | 60 | 61 | 62 | 63 | 8000000 64 | 65 | 66 | 67 | 68 | 10000000 69 | 70 | 71 | 72 | 73 | 20000000 74 | 75 | 76 | 77 | 78 | 50000000 79 | 80 | 81 | 82 | 83 | 100000000 84 | 85 | 86 | 87 | 88 | 200000000 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | resolution 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 640 110 | 111 | 112 | 113 | 114 | 720 115 | 116 | 117 | 118 | 119 | 1080 120 | 121 | 122 | 123 | 124 | 1280 125 | 126 | 127 | 128 | 129 | 1920 130 | 131 | 132 | 133 | 134 | original 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 10 146 | 340 147 | 67 148 | 17 149 | 150 | 151 | 152 | run result 153 | 154 | 155 | 156 | 157 | 158 | 10 159 | 360 160 | 191 161 | 71 162 | 163 | 164 | 165 | 166 | 167 | 168 | 10 169 | 110 170 | 191 171 | 121 172 | 173 | 174 | 175 | control 176 | 177 | 178 | 179 | 180 | 10 181 | 30 182 | 81 183 | 21 184 | 185 | 186 | 187 | start 188 | 189 | 190 | 191 | 192 | 193 | 100 194 | 30 195 | 81 196 | 21 197 | 198 | 199 | 200 | stop 201 | 202 | 203 | 204 | 205 | 206 | 80 207 | 60 208 | 101 209 | 21 210 | 211 | 212 | 213 | refresh device 214 | 215 | 216 | 217 | 218 | 219 | 10 220 | 60 221 | 67 222 | 17 223 | 224 | 225 | 226 | device id 227 | 228 | 229 | 230 | 231 | 232 | 10 233 | 90 234 | 171 235 | 21 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 10 244 | 240 245 | 191 246 | 91 247 | 248 | 249 | 250 | game config 251 | 252 | 253 | 254 | 255 | 50 256 | 60 257 | 131 258 | 21 259 | 260 | 261 | 262 | 263 | 264 | 265 | 10 266 | 30 267 | 81 268 | 21 269 | 270 | 271 | 272 | find config 273 | 274 | 275 | 276 | 277 | 278 | 100 279 | 30 280 | 81 281 | 21 282 | 283 | 284 | 285 | apply 286 | 287 | 288 | 289 | 290 | 291 | 10 292 | 60 293 | 41 294 | 17 295 | 296 | 297 | 298 | game 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 0 307 | 0 308 | 211 309 | 28 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /ui_project_backup/server_window/server_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ServerWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 211 10 | 481 11 | 12 | 13 | 14 | ServerWindow 15 | 16 | 17 | 18 | 19 | 20 | 10 21 | 10 22 | 191 23 | 91 24 | 25 | 26 | 27 | border-color: rgb(238, 238, 236); 28 | 29 | 30 | start config 31 | 32 | 33 | 34 | 35 | 10 36 | 30 37 | 180 38 | 58 39 | 40 | 41 | 42 | 43 | 44 | 45 | bit rate 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 2000000 54 | 55 | 56 | 57 | 58 | 6000000 59 | 60 | 61 | 62 | 63 | 8000000 64 | 65 | 66 | 67 | 68 | 10000000 69 | 70 | 71 | 72 | 73 | 20000000 74 | 75 | 76 | 77 | 78 | 50000000 79 | 80 | 81 | 82 | 83 | 100000000 84 | 85 | 86 | 87 | 88 | 200000000 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | resolution 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 640 110 | 111 | 112 | 113 | 114 | 720 115 | 116 | 117 | 118 | 119 | 1080 120 | 121 | 122 | 123 | 124 | 1280 125 | 126 | 127 | 128 | 129 | 1920 130 | 131 | 132 | 133 | 134 | original 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 10 146 | 340 147 | 67 148 | 17 149 | 150 | 151 | 152 | run result 153 | 154 | 155 | 156 | 157 | 158 | 10 159 | 360 160 | 191 161 | 71 162 | 163 | 164 | 165 | 166 | 167 | 168 | 10 169 | 110 170 | 191 171 | 121 172 | 173 | 174 | 175 | control 176 | 177 | 178 | 179 | 180 | 10 181 | 30 182 | 81 183 | 21 184 | 185 | 186 | 187 | start 188 | 189 | 190 | 191 | 192 | 193 | 100 194 | 30 195 | 81 196 | 21 197 | 198 | 199 | 200 | stop 201 | 202 | 203 | 204 | 205 | 206 | 80 207 | 60 208 | 101 209 | 21 210 | 211 | 212 | 213 | refresh device 214 | 215 | 216 | 217 | 218 | 219 | 10 220 | 60 221 | 67 222 | 17 223 | 224 | 225 | 226 | device id 227 | 228 | 229 | 230 | 231 | 232 | 10 233 | 90 234 | 171 235 | 21 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 10 244 | 240 245 | 191 246 | 91 247 | 248 | 249 | 250 | game config 251 | 252 | 253 | 254 | 255 | 50 256 | 60 257 | 131 258 | 21 259 | 260 | 261 | 262 | 263 | 264 | 265 | 10 266 | 30 267 | 81 268 | 21 269 | 270 | 271 | 272 | find config 273 | 274 | 275 | 276 | 277 | 278 | 100 279 | 30 280 | 81 281 | 21 282 | 283 | 284 | 285 | apply 286 | 287 | 288 | 289 | 290 | 291 | 10 292 | 60 293 | 41 294 | 17 295 | 296 | 297 | 298 | game 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 0 307 | 0 308 | 211 309 | 28 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /code/stream/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "stream.h" 2 | Stream::Stream(){ 3 | } 4 | 5 | Stream::~Stream(){ 6 | } 7 | 8 | //全局变量,signal无法传参数进去只能使用全局变量代替 9 | int socket_fd_g = -1; 10 | void Stream::interrupt_handle(int sig){ 11 | //释放网络资源 12 | if(socket_fd_g != -1) 13 | shutdown(socket_fd_g,SHUT_RDWR); 14 | exit(0); 15 | } 16 | 17 | //初始化socket和相关解码器 18 | bool Stream::stream_init(int video_socket){ 19 | if(_video_socket < 0){ 20 | cout << "无效套接字" << endl; 21 | return false; 22 | } 23 | _video_socket = video_socket; 24 | if(!get_device_info()){ 25 | cout << "获取器件信息失败" << endl; 26 | return false; 27 | } 28 | //初始化ffmpeg 29 | if(!ffmpeg_init()){ 30 | return false; 31 | } 32 | //初始化解码器 33 | if(!decoder_init()){ 34 | cout << "解码器初始化失败" << endl; 35 | return false; 36 | } 37 | //初始化帧缓冲区 38 | _frame_buffer.init(_resolution_height,_resolution_width); 39 | _need_stop = false; 40 | _yuv_frame = av_frame_alloc(); 41 | _recv_thread = thread(run_recv,this); 42 | return true; 43 | } 44 | 45 | //socket建立只初获取设备信息 46 | bool Stream::get_device_info(){ 47 | unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4]; 48 | int n = recv(_video_socket, buf, DEVICE_NAME_FIELD_LENGTH + 4, 0); 49 | if(n != DEVICE_NAME_FIELD_LENGTH + 4){ 50 | cout << "获取器件信息失败" << endl; 51 | return false; 52 | } 53 | buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage 54 | // strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes 55 | // and strlen(buf) < DEVICE_NAME_FIELD_LENGTH 56 | _device_name = (char *)buf; 57 | _resolution_width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1]; 58 | _resolution_height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3]; 59 | cout << "器件名称:"<< _device_name << endl; 60 | cout << "分辨率 :" << _resolution_width << " * " << _resolution_height << endl; 61 | return true; 62 | } 63 | 64 | //初始化解码器 65 | bool Stream::decoder_init(){ 66 | //初始化解码器 67 | _codec = avcodec_find_decoder(AV_CODEC_ID_H264); 68 | if(!_codec){ 69 | cout << "解码器初始化失败" <flags |= PARSER_FLAG_COMPLETE_FRAMES; 92 | return true; 93 | } 94 | 95 | bool Stream::ffmpeg_init(){ 96 | av_register_all(); 97 | if(avformat_network_init()){ //返回0为失败 98 | cout << "ffmpeg初始化失败" << endl; 99 | return false; 100 | } 101 | return true; 102 | } 103 | //以上程序为初始化相关程序 104 | 105 | //运行yuv帧生成线程 106 | void Stream::frame_creat_run(){ 107 | while(!_need_stop){ 108 | //读取原始h264码流包 109 | AVPacket packet; 110 | bool ok = recv_packet(&packet); 111 | if (!ok) { 112 | cout << "单帧数据获取失败" << endl; 113 | return; 114 | } 115 | ok = packet_manager(&packet); 116 | av_packet_unref(&packet); 117 | if(!ok){ 118 | cout << "视频帧获取失败" << endl; 119 | return; 120 | } 121 | } 122 | //TODO:退出时通知读者退出 123 | } 124 | 125 | void Stream::run_recv(Stream* video_stream){ 126 | signal(SIGINT, interrupt_handle); 127 | video_stream->frame_creat_run(); 128 | } 129 | 130 | bool Stream::get_img(Mat& mat_out){ 131 | return _frame_buffer.get_mat(mat_out); 132 | } 133 | 134 | //关停原始帧获取线程,释放相关资源 135 | bool Stream::stop_frame_creat(){ 136 | _need_stop = true; 137 | } 138 | 139 | //从socket中读取h264原始码流 140 | bool Stream::recv_packet(AVPacket* packet){ 141 | // The video stream contains raw packets, without time information. When we 142 | // record, we retrieve the timestamps separately, from a "meta" header 143 | // added by the server before each raw packet. 144 | // 145 | // The "meta" header length is 12 bytes: 146 | // [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ... 147 | // <-------------> <-----> <-----------------------------... 148 | // PTS packet raw packet 149 | // size 150 | // 151 | // It is followed by bytes containing the packet/frame. 152 | int r = recv(_video_socket,_header, HEADER_SIZE,0); 153 | if(r < HEADER_SIZE){ 154 | cout << "单帧头部信息获取失败" << endl; 155 | return false; 156 | } 157 | 158 | //获取时间戳 159 | uint64_t pts = buffer_read_64byte(_header); 160 | //获取帧长度信息 161 | uint32_t len = buffer_read_32byte((uint8_t*)&_header[8]); 162 | assert(pts == NO_PTS || (pts & 0x8000000000000000) == 0); 163 | assert(len); 164 | 165 | //为新接收的数据包分配空间 166 | if (av_new_packet(packet, static_cast(len))) { 167 | cout << "包分配空间失败" << endl; 168 | return false; 169 | } 170 | //根据数据包长度读取数据 171 | int recv_now = 0; 172 | int len_all = static_cast(len); 173 | while(recv_now < len_all){ 174 | int need_len = len_all - recv_now; 175 | r = recv(_video_socket, packet->data + recv_now, need_len,0); 176 | recv_now += r; 177 | } 178 | packet->pts = pts != NO_PTS ? static_cast(pts) : static_cast(AV_NOPTS_VALUE); 179 | return true; 180 | } 181 | 182 | //工具函数,用于头部解码 183 | uint32_t Stream::buffer_read_32byte(uint8_t *buf) 184 | { 185 | return static_cast((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]); 186 | } 187 | 188 | uint64_t Stream::buffer_read_64byte(uint8_t *buf) 189 | { 190 | uint32_t msb = buffer_read_32byte(buf); 191 | uint32_t lsb = buffer_read_32byte(&buf[4]); 192 | return (static_cast(msb) << 32) | lsb; 193 | } 194 | 195 | 196 | bool Stream::packet_manager(AVPacket* packet){ 197 | bool is_config = packet->pts == AV_NOPTS_VALUE; 198 | //config数据包不能立即解码,不包含完整数据信息 199 | //需要配合之后的帧信息才能进行解码 200 | if(_has_pending || is_config){ 201 | int offset; 202 | //有推迟的情况,续接之前的缓冲帧 203 | if(_has_pending){ 204 | offset = _pending.size; 205 | //只进行了内存空间的扩充未进行数据的填充 206 | if(av_grow_packet(&_pending,packet->size)){ 207 | cout << "续接包失败" << endl; 208 | return false; 209 | } 210 | } 211 | //之前没有缓存帧的情况下,新建包 212 | else{ 213 | offset = 0; 214 | if(av_new_packet(&_pending,packet->size)){ 215 | cout << "新建包失败" << endl; 216 | return false; 217 | } 218 | _has_pending = true; 219 | } 220 | 221 | //将新读取的内容加到pending后面 222 | memcpy(_pending.data + offset,packet->data,static_cast(packet->size)); 223 | 224 | //非配置帧,续接完成后进行信息维护 225 | if(!is_config){ 226 | _pending.pts = packet->pts; 227 | _pending.dts = packet -> dts; 228 | _pending.flags = packet->flags; 229 | packet = &_pending; 230 | } 231 | } 232 | 233 | if(!is_config){ 234 | //有可用于包解析的数据产生 235 | bool ok = parse_packet_to_frame(packet,_yuv_frame); 236 | if(_has_pending){ 237 | //此时还有pend非正常情况,出错释放资源 238 | _has_pending = false; 239 | av_packet_unref(&_pending); 240 | } 241 | if(!ok){ 242 | cout << "从包解析图像帧错误" << endl; 243 | return false; 244 | } 245 | } 246 | return true; 247 | } 248 | 249 | //从数据包解析出视频帧数 250 | bool Stream::parse_packet_to_frame(AVPacket* packet,AVFrame* decoding_frame){ 251 | unsigned char* out_data = nullptr; 252 | int out_len = 0; 253 | //从原始数据中分割成一帧帧的数据 254 | int r = av_parser_parse2(_parser,_codec_context,&out_data,&out_len,packet->data,packet->size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1); 255 | if(r != packet->size || packet->size != out_len){ 256 | cout << "解析错误" << endl; 257 | return false; 258 | } 259 | if(_parser->key_frame == 1){ 260 | packet->flags |= AV_PKT_FLAG_KEY; 261 | } 262 | 263 | //对提取出的单帧数据进行处理 264 | if(!_codec_context || !decoding_frame){ 265 | cout << "不具备解码条件" << endl; 266 | return false; 267 | } 268 | //从h264解码到yuv和yuv转换为rgb,处理时间都较短不超过5ms 269 | //外部的图像处理时间消耗较多,因此解码过程全在该线程进行 270 | //h264 to yuv 271 | 272 | int ret = -1; 273 | ret = avcodec_send_packet(_codec_context,packet); 274 | if(ret < 0) return false; 275 | ret = avcodec_receive_frame(_codec_context,decoding_frame); 276 | if(ret < 0) return false; 277 | 278 | //yuv to rgb 279 | //TODO:运行时间优化 280 | AVFrame* rgb_frame = nullptr; 281 | rgb_frame = av_frame_alloc(); 282 | uint8_t* output_buffer = (uint8_t*)_frame_buffer.get_buffer(); 283 | avpicture_fill((AVPicture*)rgb_frame,output_buffer,AV_PIX_FMT_BGR24,_codec_context->width,_codec_context->height); 284 | SwsContext * img_convert_ctx = sws_getContext(_codec_context->width,_codec_context->height,_codec_context->pix_fmt, _codec_context->width, _codec_context->height,AV_PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL); 285 | sws_scale(img_convert_ctx, decoding_frame->data, decoding_frame->linesize, 0, _codec_context->height, rgb_frame->data, rgb_frame->linesize); 286 | _frame_buffer.push_frame(_codec_context->height,_codec_context->width); 287 | av_free(rgb_frame); 288 | return true; 289 | } 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ui_project_backup/server_window/CMakeLists.txt.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {bdbb388c-df20-42b2-b56d-67db7de91f25} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | true 60 | Builtin.Questionable 61 | 62 | true 63 | Builtin.DefaultTidyAndClazy 64 | 4 65 | 66 | 67 | 68 | true 69 | 70 | 71 | 72 | 73 | ProjectExplorer.Project.Target.0 74 | 75 | 桌面 76 | 桌面 77 | {03a46b04-4c48-48cb-b364-4cfdcaa07cc8} 78 | 0 79 | 0 80 | 0 81 | 82 | 83 | CMAKE_BUILD_TYPE:STRING=Debug 84 | CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} 85 | CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} 86 | CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} 87 | QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} 88 | 89 | 2 90 | /home/y/build-server_window-unknown-Debug 91 | 92 | 93 | 94 | 95 | all 96 | 97 | true 98 | CMakeProjectManager.MakeStep 99 | 100 | 1 101 | Build 102 | Build 103 | ProjectExplorer.BuildSteps.Build 104 | 105 | 106 | 107 | 108 | 109 | clean 110 | 111 | true 112 | CMakeProjectManager.MakeStep 113 | 114 | 1 115 | Clean 116 | Clean 117 | ProjectExplorer.BuildSteps.Clean 118 | 119 | 2 120 | false 121 | 122 | Debug 123 | CMakeProjectManager.CMakeBuildConfiguration 124 | 125 | 126 | 127 | CMAKE_BUILD_TYPE:STRING=Release 128 | CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} 129 | CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} 130 | CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} 131 | QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} 132 | 133 | 2 134 | /home/y/build-server_window-unknown-Release 135 | 136 | 137 | 138 | 139 | all 140 | 141 | true 142 | CMakeProjectManager.MakeStep 143 | 144 | 1 145 | Build 146 | Build 147 | ProjectExplorer.BuildSteps.Build 148 | 149 | 150 | 151 | 152 | 153 | clean 154 | 155 | true 156 | CMakeProjectManager.MakeStep 157 | 158 | 1 159 | Clean 160 | Clean 161 | ProjectExplorer.BuildSteps.Clean 162 | 163 | 2 164 | false 165 | 166 | Release 167 | CMakeProjectManager.CMakeBuildConfiguration 168 | 169 | 170 | 171 | CMAKE_BUILD_TYPE:STRING=RelWithDebInfo 172 | CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} 173 | CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} 174 | CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} 175 | QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} 176 | 177 | 2 178 | /home/y/build-server_window-unknown-RelWithDebInfo 179 | 180 | 181 | 182 | 183 | all 184 | 185 | true 186 | CMakeProjectManager.MakeStep 187 | 188 | 1 189 | Build 190 | Build 191 | ProjectExplorer.BuildSteps.Build 192 | 193 | 194 | 195 | 196 | 197 | clean 198 | 199 | true 200 | CMakeProjectManager.MakeStep 201 | 202 | 1 203 | Clean 204 | Clean 205 | ProjectExplorer.BuildSteps.Clean 206 | 207 | 2 208 | false 209 | 210 | Release with Debug Information 211 | CMakeProjectManager.CMakeBuildConfiguration 212 | 213 | 214 | 215 | CMAKE_BUILD_TYPE:STRING=MinSizeRel 216 | CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} 217 | CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} 218 | CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} 219 | QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} 220 | 221 | 2 222 | /home/y/build-server_window-unknown-MinSizeRel 223 | 224 | 225 | 226 | 227 | all 228 | 229 | true 230 | CMakeProjectManager.MakeStep 231 | 232 | 1 233 | Build 234 | Build 235 | ProjectExplorer.BuildSteps.Build 236 | 237 | 238 | 239 | 240 | 241 | clean 242 | 243 | true 244 | CMakeProjectManager.MakeStep 245 | 246 | 1 247 | Clean 248 | Clean 249 | ProjectExplorer.BuildSteps.Clean 250 | 251 | 2 252 | false 253 | 254 | Minimum Size Release 255 | CMakeProjectManager.CMakeBuildConfiguration 256 | 257 | 4 258 | 259 | 260 | 0 261 | Deploy 262 | Deploy 263 | ProjectExplorer.BuildSteps.Deploy 264 | 265 | 1 266 | 267 | false 268 | ProjectExplorer.DefaultDeployConfiguration 269 | 270 | 1 271 | 272 | 273 | dwarf 274 | 275 | cpu-cycles 276 | 277 | 278 | 250 279 | 280 | -e 281 | cpu-cycles 282 | --call-graph 283 | dwarf,4096 284 | -F 285 | 250 286 | 287 | -F 288 | true 289 | 4096 290 | false 291 | false 292 | 1000 293 | 294 | true 295 | 296 | false 297 | false 298 | false 299 | false 300 | true 301 | 0.01 302 | 10 303 | true 304 | kcachegrind 305 | 1 306 | 25 307 | 308 | 1 309 | true 310 | false 311 | true 312 | valgrind 313 | 314 | 0 315 | 1 316 | 2 317 | 3 318 | 4 319 | 5 320 | 6 321 | 7 322 | 8 323 | 9 324 | 10 325 | 11 326 | 12 327 | 13 328 | 14 329 | 330 | 2 331 | 332 | server_window 333 | CMakeProjectManager.CMakeRunConfiguration.server_window 334 | server_window 335 | 336 | false 337 | 338 | false 339 | true 340 | true 341 | false 342 | false 343 | true 344 | 345 | /home/y/build-server_window-unknown-Debug 346 | 347 | 1 348 | 349 | 350 | 351 | ProjectExplorer.Project.TargetCount 352 | 1 353 | 354 | 355 | ProjectExplorer.Project.Updater.FileVersion 356 | 22 357 | 358 | 359 | Version 360 | 22 361 | 362 | 363 | --------------------------------------------------------------------------------