Runs capture processing on a separate thread

This commit is contained in:
Frank Leon Rose 2020-01-27 15:08:24 -05:00
parent d16a0d260c
commit e0444fdcae
4 changed files with 182 additions and 6 deletions

View file

@ -40,6 +40,7 @@
#include <SDL2/SDL_events.h>
#include "events.h"
#include "util/lock.h"
#include "util/log.h"
static const char *string_error(int err) {
@ -200,6 +201,25 @@ bool capture_init(struct capture *capture, const char *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) {
@ -245,10 +265,123 @@ void capture_destroy(struct capture *capture) {
avio_close(capture->file_context);
}
bool capture_push(struct capture *capture, const AVPacket *packet) {
if (decode_packet_to_png(capture->file_context, capture->context, packet, capture->working_frame)) {
LOGI("Parsed PNG from incoming packets.");
return true;
static bool capture_process(struct capture *capture, const AVPacket *packet) {
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;
}
}
return false;
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) {
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) {
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) {
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;
}

View file

@ -4,18 +4,47 @@
#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

@ -96,6 +96,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

@ -228,10 +228,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()!
@ -261,6 +268,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);