Merge pull request #2 from team-mobot/ENG-2301-add-png-screen-capture

ENG-2301 add png screen capture
This commit is contained in:
Frank Leon Rose 2021-04-05 11:55:33 -04:00 committed by GitHub
commit eaaa9b53cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 597 additions and 10 deletions

View file

@ -1,5 +1,6 @@
src = [
'src/main.c',
'src/capture.c',
'src/cli.c',
'src/command.c',
'src/control_msg.c',
@ -31,6 +32,8 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('libpng'),
dependency('sdl2'),
]
@ -167,7 +170,8 @@ if get_option('buildtype') == 'debug'
'tests/test_queue.c',
]],
['test_strutil', [
'tests/test_strutil.c',
'tests/test_s
til.c',
'src/util/str_util.c',
]],
]

410
app/src/capture.c Normal file
View file

@ -0,0 +1,410 @@
/*
* Copyright (c) 2001 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file
* video decoding with libavcodec API example
*
* @example decode_video.c
*/
#include "capture.h"
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL2/SDL_events.h>
#include "events.h"
#include "util/lock.h"
#include "util/log.h"
static const char *string_error(int err) {
switch (err) {
case AVERROR_EOF: return "End of File";
case AVERROR_BUG: return "Bug";
case AVERROR_BUG2: return "Bug2";
case AVERROR_BUFFER_TOO_SMALL: return "Buffer Too Small";
case AVERROR_EXIT: return "Exit Requested";
case AVERROR_DECODER_NOT_FOUND: return "Decoder Not Found";
case AVERROR_DEMUXER_NOT_FOUND: return "Demuxer Not Found";
case AVERROR_ENCODER_NOT_FOUND: return "Encoder Not Found";
case AVERROR_EXTERNAL: return "External Error";
case AVERROR_FILTER_NOT_FOUND: return "Filter Not Found";
case AVERROR_MUXER_NOT_FOUND: return "Muxer Not Found";
case AVERROR_OPTION_NOT_FOUND: return "Option Not Found";
case AVERROR_PATCHWELCOME: return "Patch Welcome";
case AVERROR_PROTOCOL_NOT_FOUND: return "Protocol Not Found";
case AVERROR_STREAM_NOT_FOUND: return "Stream Not Found";
case AVERROR_INVALIDDATA: return "Invalid Data (waiting for index frame?)";
case AVERROR(EINVAL): return "Invalid";
case AVERROR(ENOMEM): return "No Memory";
case AVERROR(EAGAIN): return "Try Again";
default: return "Unknown";
}
}
static void notify_complete() {
static SDL_Event new_frame_event = {
.type = EVENT_SCREEN_CAPTURE_COMPLETE,
};
SDL_PushEvent(&new_frame_event);
}
// Thanks, https://stackoverflow.com/a/18313791
// Also, https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/doc/examples/decode_video.c
static bool in_frame_to_png(
AVIOContext *output_context,
AVCodecContext *codecCtx, AVFrame *inframe) {
uint64_t start = get_timestamp();
int targetHeight = codecCtx->height;
int targetWidth = codecCtx->width;
struct SwsContext * swCtx = sws_getContext(codecCtx->width,
codecCtx->height,
codecCtx->pix_fmt,
targetWidth,
targetHeight,
AV_PIX_FMT_RGB24,
// NOTE(frankleonrose): These options offer better picture
// quality in case of scaling, according to https://stackoverflow.com/a/46169884
// SWS_BILINEAR | SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND,
SWS_FAST_BILINEAR,
0, 0, 0);
AVFrame * rgbFrame = av_frame_alloc();
LOGV("Image frame width: %d height: %d scaling to %d x %d", *inframe->linesize, inframe->height, targetWidth, targetHeight);
rgbFrame->width = targetWidth;
rgbFrame->height = targetHeight;
rgbFrame->format = AV_PIX_FMT_RGB24;
av_image_alloc(
rgbFrame->data, rgbFrame->linesize, targetWidth, targetHeight, AV_PIX_FMT_RGB24, 1);
sws_scale(
swCtx,
// const_cast<const uint8_t * const*>(inframe->data)
(const uint8_t * const*)(inframe->data),
inframe->linesize, 0,
inframe->height, rgbFrame->data, rgbFrame->linesize);
LOGV("Scaling image: %llu", get_timestamp() - start);
AVCodec *outCodec = avcodec_find_encoder(AV_CODEC_ID_PNG);
if (!outCodec) {
LOGE("Failed to find PNG codec");
return false;
}
AVCodecContext *outCodecCtx = avcodec_alloc_context3(outCodec);
if (!outCodecCtx) {
LOGE("Unable to allocate PNG codec context");
return false;
}
outCodecCtx->width = targetWidth;
outCodecCtx->height = targetHeight;
outCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
outCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
if (codecCtx->time_base.num && codecCtx->time_base.num) {
outCodecCtx->time_base.num = codecCtx->time_base.num;
outCodecCtx->time_base.den = codecCtx->time_base.den;
} else {
outCodecCtx->time_base.num = 1;
outCodecCtx->time_base.den = 10; // FPS
}
int ret = avcodec_open2(outCodecCtx, outCodec, NULL);
if (ret < 0) {
LOGE("Failed to open PNG codec: %s", string_error(ret));
return false;
}
ret = avcodec_send_frame(outCodecCtx, rgbFrame);
av_frame_free(&rgbFrame);
if (ret < 0) {
LOGE("Failed to send frame to output codex");
return false;
}
AVPacket outPacket;
av_init_packet(&outPacket);
outPacket.size = 0;
outPacket.data = NULL;
ret = avcodec_receive_packet(outCodecCtx, &outPacket);
avcodec_close(outCodecCtx);
av_free(outCodecCtx);
LOGV("Converted to PNG: %llu", get_timestamp() - start);
if (ret >= 0) {
// Dump packet
avio_write(output_context, outPacket.data, outPacket.size);
notify_complete();
av_packet_unref(&outPacket);
LOGV("Wrote file: %llu", get_timestamp() - start);
log_timestamp("Capture written");
return true;
} else {
LOGE("Failed to receive packet");
return false;
}
}
// Decodes a video packet into PNG image, if possible.
// Sends a new video packet `packet` into the active decoding
// context `dec_ctx`.
// Returns `true` when the decoding yielded a PNG.
static bool decode_packet_to_png(
AVIOContext *output_context,
AVCodecContext *dec_ctx,
const AVPacket *packet,
AVFrame *working_frame) {
LOGV("Sending video packet: %p Size: %d", packet, packet ? packet->size : 0);
int ret = avcodec_send_packet(dec_ctx, packet);
if (ret < 0) {
LOGW( "Error sending a packet for decoding: %s", string_error(ret));
return false;
}
bool found = false;
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, working_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
LOGV("No frame received: %s", string_error(ret));
break; // We're done
} else if (ret < 0) {
LOGW("Error during decoding: %s", string_error(ret));
}
LOGI("Found frame: %d", dec_ctx->frame_number);
if (in_frame_to_png(output_context, dec_ctx, working_frame)) {
found = true;
break;
} else {
LOGE("Failed to generate PNG");
}
}
return found;
}
bool capture_init(struct capture *capture, const char *filename) {
capture->filename = SDL_strdup(filename);
if (!capture->filename) {
LOGE("Could not strdup filename");
return false;
}
capture->mutex = SDL_CreateMutex();
if (!capture->mutex) {
LOGC("Could not create mutex");
SDL_free(capture->filename);
return false;
}
capture->queue_cond = SDL_CreateCond();
if (!capture->queue_cond) {
LOGC("Could not create capture cond");
SDL_DestroyMutex(capture->mutex);
SDL_free(capture->filename);
return false;
}
queue_init(&capture->queue);
capture->stopped = false;
capture->finished = false;
int ret = avio_open(&capture->file_context, capture->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", capture->filename);
return false;
}
/* find the MPEG-1 video decoder */
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("Codec not found: %d", AV_CODEC_ID_H264);
return false;
}
capture->context = avcodec_alloc_context3(codec);
if (!capture->context) {
LOGE("Could not allocate video codec context");
return false;
}
/* For some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because this information is not
available in the bitstream. */
/* open it */
if (avcodec_open2(capture->context, codec, NULL) < 0) {
LOGE("Could not open codec");
return false;
}
// Create a reusable frame buffer.
capture->working_frame = av_frame_alloc();
if (!capture->working_frame) {
LOGE("Could not allocate video frame");
return false;
}
return true;
}
void capture_destroy(struct capture *capture) {
av_frame_free(&capture->working_frame);
avcodec_free_context(&capture->context);
avio_close(capture->file_context);
}
static bool capture_process(struct capture *capture, const AVPacket *packet) {
log_timestamp("Processing packet");
static uint64_t total = 0;
uint64_t start = get_timestamp();
if (capture->finished) {
LOGV("Skipping redundant call to capture_push");
} else {
bool found_png = decode_packet_to_png(capture->file_context, capture->context, packet, capture->working_frame);
if (found_png) {
LOGI("Parsed PNG from incoming packets.");
capture->finished = found_png;
}
}
uint64_t duration = get_timestamp() - start;
total += duration;
LOGV("Capture step microseconds: %llu total: %llu", duration, total);
return capture->finished;
}
static struct capture_packet *
capture_packet_new(const AVPacket *packet) {
struct capture_packet *rec = SDL_malloc(sizeof(*rec));
if (!rec) {
return NULL;
}
// av_packet_ref() does not initialize all fields in old FFmpeg versions
// See <https://github.com/Genymobile/scrcpy/issues/707>
av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) {
SDL_free(rec);
return NULL;
}
return rec;
}
static void
capture_packet_delete(struct capture_packet *rec) {
av_packet_unref(&rec->packet);
SDL_free(rec);
}
static int
run_capture(void *data) {
log_timestamp("Running capture thread");
struct capture *capture = data;
for (;;) {
mutex_lock(capture->mutex);
while (!capture->stopped && queue_is_empty(&capture->queue)) {
cond_wait(capture->queue_cond, capture->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the capture) before actually stopping
if (capture->stopped && queue_is_empty(&capture->queue)) {
mutex_unlock(capture->mutex);
break;
}
struct capture_packet *rec;
queue_take(&capture->queue, next, &rec);
mutex_unlock(capture->mutex);
bool ok = capture_process(capture, &rec->packet);
capture_packet_delete(rec);
if (ok) {
break;
}
}
LOGD("capture thread ended");
return 0;
}
bool
capture_start(struct capture *capture) {
log_timestamp("Starting capture thread");
capture->thread = SDL_CreateThread(run_capture, "capture", capture);
if (!capture->thread) {
LOGC("Could not start capture thread");
return false;
}
return true;
}
void
capture_stop(struct capture *capture) {
mutex_lock(capture->mutex);
capture->stopped = true;
cond_signal(capture->queue_cond);
mutex_unlock(capture->mutex);
}
void
capture_join(struct capture *capture) {
SDL_WaitThread(capture->thread, NULL);
}
bool
capture_push(struct capture *capture, const AVPacket *packet) {
log_timestamp("Received packet");
mutex_lock(capture->mutex);
assert(!capture->stopped);
if (capture->finished) {
// reject any new packet (this will stop the stream)
return false;
}
struct capture_packet *rec = capture_packet_new(packet);
if (!rec) {
LOGC("Could not allocate capture packet");
return false;
}
queue_push(&capture->queue, next, rec);
cond_signal(capture->queue_cond);
mutex_unlock(capture->mutex);
return true;
}

50
app/src/capture.h Normal file
View file

@ -0,0 +1,50 @@
#ifndef CAPTURE_H
#define CAPTURE_H
#include <stdbool.h>
#include <libavformat/avio.h>
#include <libavcodec/avcodec.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "common.h"
#include "util/queue.h"
struct capture_packet {
AVPacket packet;
struct capture_packet *next;
};
struct capture_queue QUEUE(struct capture_packet);
struct capture {
char *filename;
AVIOContext *file_context;
AVCodecContext *context;
AVFrame *working_frame;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *queue_cond;
bool finished; // PNG has been captured
bool stopped; // set on recorder_stop() by the stream reader
struct capture_queue queue;
};
bool capture_init(struct capture *capture, const char *filename);
void capture_destroy(struct capture *capture);
bool
capture_start(struct capture *capture);
void
capture_stop(struct capture *capture);
void
capture_join(struct capture *capture);
bool capture_push(struct capture *capture, const AVPacket *packet);
#endif // CAPTURE_H

View file

@ -622,6 +622,10 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
*format = SC_RECORD_FORMAT_MKV;
return true;
}
if (!strcmp(optarg, "h264")) {
*format = SC_RECORD_FORMAT_H264;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
@ -704,6 +708,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"screen-capture", required_argument, NULL, 'C'},
{"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'},
@ -726,7 +731,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
optind = 0; // reset to start from the first argument in tests
int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w",
while ((c = getopt_long(argc, argv, "b:cC:fF:hm:nNp:r:s:StTvV:w",
long_options, NULL)) != -1) {
switch (c) {
case 'b':
@ -734,6 +739,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false;
}
break;
case 'C':
opts->capture_filename = optarg;
break;
case 'c':
LOGW("Deprecated option -c. Use --crop instead.");
// fall through
@ -892,8 +900,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
}
}
if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
if (!opts->display && !opts->record_filename && !opts->capture_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record) or screen capture (-C/--screen-capture)");
return false;
}

View file

@ -120,6 +120,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd, &process);
if (r != PROCESS_SUCCESS) {
LOGE("adb process execution returned an error.");
show_adb_err_msg(r, cmd);
return PROCESS_NONE;
}

View file

@ -1,3 +1,4 @@
#define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
#define EVENT_SCREEN_CAPTURE_COMPLETE (SDL_USEREVENT + 3)

View file

@ -4,6 +4,7 @@
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>
@ -14,7 +15,7 @@
static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "scrcpy %s (with Mobot extensions)\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
@ -28,6 +29,9 @@ print_version(void) {
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
fprintf(stderr, " - libswscale %d.%d.%d\n", LIBSWSCALE_VERSION_MAJOR,
LIBSWSCALE_VERSION_MINOR,
LIBSWSCALE_VERSION_MICRO);
}
static SDL_LogPriority
@ -57,6 +61,11 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL);
#endif
#ifndef NDEBUG
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.help = false,

View file

@ -107,6 +107,7 @@ recorder_destroy(struct recorder *recorder) {
static const char *
recorder_get_format_name(enum sc_record_format format) {
switch (format) {
case SC_RECORD_FORMAT_H264: return "h264";
case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska";
default: return NULL;

View file

@ -24,6 +24,7 @@
#include "file_handler.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "capture.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
@ -41,6 +42,7 @@ static struct video_buffer video_buffer;
static struct stream stream;
static struct decoder decoder;
static struct recorder recorder;
static struct capture capture;
static struct controller controller;
static struct file_handler file_handler;
@ -175,6 +177,9 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case EVENT_SCREEN_CAPTURE_COMPLETE:
LOGD("Screen capture completed");
return EVENT_RESULT_STOPPED_BY_USER;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
@ -304,6 +309,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
bool
scrcpy(const struct scrcpy_options *options) {
log_timestamp("scrcpy Started");
if (!server_init(&server)) {
return false;
}
@ -315,11 +321,13 @@ scrcpy(const struct scrcpy_options *options) {
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
bool capture_initialized = false;
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
bool record = !!options->record_filename;
bool captures = !!options->capture_filename;
struct server_params params = {
.log_level = options->log_level,
.crop = options->crop,
@ -336,17 +344,20 @@ scrcpy(const struct scrcpy_options *options) {
.encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward,
};
log_timestamp("server_start");
if (!server_start(&server, options->serial, &params)) {
goto end;
}
server_started = true;
log_timestamp("sdl_init_and_configure");
if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) {
goto end;
}
log_timestamp("server_connect_to");
if (!server_connect_to(&server)) {
goto end;
}
@ -357,12 +368,14 @@ scrcpy(const struct scrcpy_options *options) {
// screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately
log_timestamp("device_read_info");
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
goto end;
}
struct decoder *dec = NULL;
if (options->display) {
log_timestamp("Display and fps_counter_init");
if (!fps_counter_init(&fps_counter)) {
goto end;
}
@ -398,9 +411,20 @@ scrcpy(const struct scrcpy_options *options) {
recorder_initialized = true;
}
struct capture *cap = NULL;
if (captures) {
if (!capture_init(&capture,
options->capture_filename)) {
goto end;
}
cap = &capture;
capture_initialized = true;
}
log_timestamp("stream_init");
av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec);
stream_init(&stream, server.video_socket, dec, rec, cap);
// now we consumed the header values, the socket receives the video stream
// start the stream
@ -451,9 +475,12 @@ scrcpy(const struct scrcpy_options *options) {
input_manager_init(&input_manager, options);
log_timestamp("Starting event_loop");
ret = event_loop(options);
log_timestamp("Terminated event_loop");
LOGD("quit...");
log_timestamp("screen_destroy");
screen_destroy(&screen);
end:
@ -493,6 +520,9 @@ end:
recorder_destroy(&recorder);
}
if (capture_initialized) {
capture_destroy(&capture);
}
if (file_handler_initialized) {
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);

View file

@ -18,6 +18,7 @@ enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
SC_RECORD_FORMAT_H264,
};
#define SC_MAX_SHORTCUT_MODS 8
@ -46,6 +47,7 @@ struct sc_port_range {
struct scrcpy_options {
const char *serial;
const char *crop;
const char *capture_filename;
const char *record_filename;
const char *window_title;
const char *push_target;
@ -87,6 +89,7 @@ struct scrcpy_options {
#define SCRCPY_OPTIONS_DEFAULT { \
.serial = NULL, \
.crop = NULL, \
.capture_filename = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \

View file

@ -254,6 +254,7 @@ log_level_to_server_string(enum sc_log_level level) {
static process_t
execute_server(struct server *server, const struct server_params *params) {
log_timestamp("Starting device server");
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
@ -308,6 +309,7 @@ execute_server(struct server *server, const struct server_params *params) {
// Port: 5005
// Then click on "Debug"
#endif
LOGV("Executing ADB command");
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@ -424,14 +426,17 @@ server_start(struct server *server, const char *serial,
}
if (!push_server(serial)) {
LOGE("Failed to push server to device.");
goto error1;
}
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
LOGE("Failed to enable tunnel.");
goto error1;
}
LOGV("Executing server");
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
@ -473,12 +478,15 @@ error1:
bool
server_connect_to(struct server *server) {
log_timestamp("Starting server_connect_to");
if (!server->tunnel_forward) {
log_timestamp("Accepting video socket");
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
log_timestamp("Accepting control socket");
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be cleaned up on destroy
@ -494,6 +502,7 @@ server_connect_to(struct server *server) {
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
log_timestamp("Connecting video socket");
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
@ -501,6 +510,7 @@ server_connect_to(struct server *server) {
}
// we know that the device is listening, we don't need several attempts
log_timestamp("Connecting control socket");
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
@ -512,6 +522,7 @@ server_connect_to(struct server *server) {
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
log_timestamp("Finished server_connect_to");
return true;
}

View file

@ -12,6 +12,7 @@
#include "compat.h"
#include "decoder.h"
#include "events.h"
#include "capture.h"
#include "recorder.h"
#include "util/buffer_util.h"
#include "util/log.h"
@ -59,6 +60,8 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
packet->pts = pts != NO_PTS ? (int64_t) pts : AV_NOPTS_VALUE;
log_timestamp("Received packet via socket stream");
return true;
}
@ -71,6 +74,10 @@ notify_stopped(void) {
static bool
process_config_packet(struct stream *stream, AVPacket *packet) {
if (stream->capture && !capture_push(stream->capture, packet)) {
LOGW("Could not send config packet to capture");
return true; // We're Ok. Keep sending packets.
}
if (stream->recorder && !recorder_push(stream->recorder, packet)) {
LOGE("Could not send config packet to recorder");
return false;
@ -93,6 +100,15 @@ process_frame(struct stream *stream, AVPacket *packet) {
}
}
if (stream->capture) {
packet->dts = packet->pts;
if (!capture_push(stream->capture, packet)) {
LOGE("Could not send packet to capture");
return false;
}
}
return true;
}
@ -214,10 +230,17 @@ run_stream(void *data) {
}
}
if (stream->capture) {
if (!capture_start(stream->capture)) {
LOGE("Could not start capture");
goto finally_stop_and_join_recorder;
}
}
stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->parser) {
LOGE("Could not initialize parser");
goto finally_stop_and_join_recorder;
goto finally_stop_and_join_capture;
}
// We must only pass complete frames to av_parser_parse2()!
@ -247,6 +270,12 @@ run_stream(void *data) {
}
av_parser_close(stream->parser);
finally_stop_and_join_capture:
if (stream->capture) {
capture_stop(stream->capture);
LOGI("Finishing capture...");
capture_join(stream->capture);
}
finally_stop_and_join_recorder:
if (stream->recorder) {
recorder_stop(stream->recorder);
@ -270,16 +299,18 @@ end:
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
struct decoder *decoder, struct recorder *recorder,
struct capture *capture) {
stream->socket = socket;
stream->decoder = decoder,
stream->capture = capture;
stream->recorder = recorder;
stream->has_pending = false;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
log_timestamp("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {

View file

@ -17,6 +17,7 @@ struct stream {
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;
struct capture *capture;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
@ -27,7 +28,8 @@ struct stream {
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
struct decoder *decoder, struct recorder *recorder,
struct capture *capture);
bool
stream_start(struct stream *stream);

View file

@ -18,6 +18,8 @@
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/gmon.h>
#include <sys/time.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
@ -26,6 +28,21 @@
#include "util/log.h"
uint64_t get_timestamp() {
struct timeval tv;
gettimeofday(&tv,NULL);
return tv.tv_sec*(uint64_t)1000000+tv.tv_usec;
}
static uint64_t StartTime = 0;
void log_timestamp(const char *tag) {
uint64_t now = get_timestamp();
if (StartTime==0) {
StartTime = now;
}
LOGV("Timestamp: %llu ms %s", (now-StartTime) / 1000, tag);
}
bool
cmd_search(const char *file) {
char *path = getenv("PATH");
@ -66,6 +83,8 @@ enum process_result
cmd_execute(const char *const argv[], pid_t *pid) {
int fd[2];
uint64_t start = get_timestamp();
if (pipe(fd) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
@ -73,6 +92,7 @@ cmd_execute(const char *const argv[], pid_t *pid) {
enum process_result ret = PROCESS_SUCCESS;
log_timestamp("Running command");
*pid = fork();
if (*pid == -1) {
perror("fork");
@ -90,6 +110,7 @@ cmd_execute(const char *const argv[], pid_t *pid) {
ret = PROCESS_ERROR_GENERIC;
goto end;
}
LOGV("Command microseconds: %s %llu ms", argv[0], (get_timestamp()-start)/1000);
} else if (*pid == 0) {
// child close read side
close(fd[0]);

View file

@ -10,4 +10,7 @@
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#include <time.h>
uint64_t get_timestamp();
void log_timestamp(const char *tag);
#endif

View file

@ -58,6 +58,7 @@ static void test_options(void) {
"--record", "file",
"--record-format", "mkv",
"--render-expired-frames",
"--screen-capture", "filename.png",
"--serial", "0123456789abcdef",
"--show-touches",
"--turn-screen-off",
@ -87,6 +88,7 @@ static void test_options(void) {
assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames);
assert(!strcmp(opts->capture_filename, "filename.png"));
assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches);
assert(opts->turn_screen_off);