commit
7fad611dfb
@ -1,28 +1,33 @@
|
|||||||
#ifndef BUFFER_UTIL_H
|
#ifndef BUFFER_UTIL_H
|
||||||
#define BUFFER_UTIL_H
|
#define BUFFER_UTIL_H
|
||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
static inline void buffer_write16be(Uint8 *buf, Uint16 value) {
|
static inline void
|
||||||
|
buffer_write16be(uint8_t *buf, uint16_t value) {
|
||||||
buf[0] = value >> 8;
|
buf[0] = value >> 8;
|
||||||
buf[1] = value;
|
buf[1] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void buffer_write32be(Uint8 *buf, Uint32 value) {
|
static inline void
|
||||||
|
buffer_write32be(uint8_t *buf, uint32_t value) {
|
||||||
buf[0] = value >> 24;
|
buf[0] = value >> 24;
|
||||||
buf[1] = value >> 16;
|
buf[1] = value >> 16;
|
||||||
buf[2] = value >> 8;
|
buf[2] = value >> 8;
|
||||||
buf[3] = value;
|
buf[3] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Uint32 buffer_read32be(Uint8 *buf) {
|
static inline uint32_t
|
||||||
|
buffer_read32be(uint8_t *buf) {
|
||||||
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Uint64 buffer_read64be(Uint8 *buf) {
|
static inline
|
||||||
Uint32 msb = buffer_read32be(buf);
|
uint64_t buffer_read64be(uint8_t *buf) {
|
||||||
Uint32 lsb = buffer_read32be(&buf[4]);
|
uint32_t msb = buffer_read32be(buf);
|
||||||
return ((Uint64) msb << 32) | lsb;
|
uint32_t lsb = buffer_read32be(&buf[4]);
|
||||||
|
return ((uint64_t) msb << 32) | lsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,36 +1,29 @@
|
|||||||
#ifndef DECODER_H
|
#ifndef DECODER_H
|
||||||
#define DECODER_H
|
#define DECODER_H
|
||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "common.h"
|
struct video_buffer;
|
||||||
#include "net.h"
|
|
||||||
|
|
||||||
struct frames;
|
|
||||||
|
|
||||||
struct frame_meta {
|
|
||||||
uint64_t pts;
|
|
||||||
struct frame_meta *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct decoder {
|
struct decoder {
|
||||||
struct frames *frames;
|
struct video_buffer *video_buffer;
|
||||||
socket_t video_socket;
|
AVCodecContext *codec_ctx;
|
||||||
SDL_Thread *thread;
|
|
||||||
SDL_mutex *mutex;
|
|
||||||
struct recorder *recorder;
|
|
||||||
struct receiver_state {
|
|
||||||
// meta (in order) for frames not consumed yet
|
|
||||||
struct frame_meta *frame_meta_queue;
|
|
||||||
size_t remaining; // remaining bytes to receive for the current frame
|
|
||||||
} receiver_state;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void decoder_init(struct decoder *decoder, struct frames *frames,
|
void
|
||||||
socket_t video_socket, struct recorder *recoder);
|
decoder_init(struct decoder *decoder, struct video_buffer *vb);
|
||||||
SDL_bool decoder_start(struct decoder *decoder);
|
|
||||||
void decoder_stop(struct decoder *decoder);
|
bool
|
||||||
void decoder_join(struct decoder *decoder);
|
decoder_open(struct decoder *decoder, const AVCodec *codec);
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_close(struct decoder *decoder);
|
||||||
|
|
||||||
|
bool
|
||||||
|
decoder_push(struct decoder *decoder, const AVPacket *packet);
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_interrupt(struct decoder *decoder);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
bool
|
||||||
|
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
||||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
int r = net_recv_all(device_socket, buf, sizeof(buf));
|
int r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||||
LOGE("Could not retrieve device information");
|
LOGE("Could not retrieve device information");
|
||||||
return SDL_FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
|
// in case the client sends garbage
|
||||||
// strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
|
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||||
// and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
// strcpy is safe here, since name contains at least
|
||||||
|
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||||
strcpy(device_name, (char *) buf);
|
strcpy(device_name, (char *) buf);
|
||||||
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||||
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||||
return SDL_TRUE;
|
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||||
|
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
#define EVENT_NEW_SESSION SDL_USEREVENT
|
#define EVENT_NEW_SESSION SDL_USEREVENT
|
||||||
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
|
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
|
||||||
#define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
|
||||||
|
|||||||
@ -1,26 +1,35 @@
|
|||||||
#ifndef FPSCOUNTER_H
|
#ifndef FPSCOUNTER_H
|
||||||
#define FPSCOUNTER_H
|
#define FPSCOUNTER_H
|
||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
struct fps_counter {
|
struct fps_counter {
|
||||||
SDL_bool started;
|
bool started;
|
||||||
Uint32 slice_start; // initialized by SDL_GetTicks()
|
uint32_t slice_start; // initialized by SDL_GetTicks()
|
||||||
int nr_rendered;
|
int nr_rendered;
|
||||||
#ifdef SKIP_FRAMES
|
#ifdef SKIP_FRAMES
|
||||||
int nr_skipped;
|
int nr_skipped;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
void fps_counter_init(struct fps_counter *counter);
|
void
|
||||||
void fps_counter_start(struct fps_counter *counter);
|
fps_counter_init(struct fps_counter *counter);
|
||||||
void fps_counter_stop(struct fps_counter *counter);
|
|
||||||
|
void
|
||||||
|
fps_counter_start(struct fps_counter *counter);
|
||||||
|
|
||||||
|
void
|
||||||
|
fps_counter_stop(struct fps_counter *counter);
|
||||||
|
|
||||||
|
void
|
||||||
|
fps_counter_add_rendered_frame(struct fps_counter *counter);
|
||||||
|
|
||||||
void fps_counter_add_rendered_frame(struct fps_counter *counter);
|
|
||||||
#ifdef SKIP_FRAMES
|
#ifdef SKIP_FRAMES
|
||||||
void fps_counter_add_skipped_frame(struct fps_counter *counter);
|
void
|
||||||
|
fps_counter_add_skipped_frame(struct fps_counter *counter);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,110 +0,0 @@
|
|||||||
#include "frames.h"
|
|
||||||
|
|
||||||
#include <SDL2/SDL_assert.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <libavutil/avutil.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "lock_util.h"
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
SDL_bool frames_init(struct frames *frames) {
|
|
||||||
if (!(frames->decoding_frame = av_frame_alloc())) {
|
|
||||||
goto error_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(frames->rendering_frame = av_frame_alloc())) {
|
|
||||||
goto error_1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(frames->mutex = SDL_CreateMutex())) {
|
|
||||||
goto error_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef SKIP_FRAMES
|
|
||||||
if (!(frames->rendering_frame_consumed_cond = SDL_CreateCond())) {
|
|
||||||
SDL_DestroyMutex(frames->mutex);
|
|
||||||
goto error_2;
|
|
||||||
}
|
|
||||||
frames->stopped = SDL_FALSE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// there is initially no rendering frame, so consider it has already been
|
|
||||||
// consumed
|
|
||||||
frames->rendering_frame_consumed = SDL_TRUE;
|
|
||||||
fps_counter_init(&frames->fps_counter);
|
|
||||||
|
|
||||||
return SDL_TRUE;
|
|
||||||
|
|
||||||
error_2:
|
|
||||||
av_frame_free(&frames->rendering_frame);
|
|
||||||
error_1:
|
|
||||||
av_frame_free(&frames->decoding_frame);
|
|
||||||
error_0:
|
|
||||||
return SDL_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void frames_destroy(struct frames *frames) {
|
|
||||||
#ifndef SKIP_FRAMES
|
|
||||||
SDL_DestroyCond(frames->rendering_frame_consumed_cond);
|
|
||||||
#endif
|
|
||||||
SDL_DestroyMutex(frames->mutex);
|
|
||||||
av_frame_free(&frames->rendering_frame);
|
|
||||||
av_frame_free(&frames->decoding_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void frames_swap(struct frames *frames) {
|
|
||||||
AVFrame *tmp = frames->decoding_frame;
|
|
||||||
frames->decoding_frame = frames->rendering_frame;
|
|
||||||
frames->rendering_frame = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_bool frames_offer_decoded_frame(struct frames *frames) {
|
|
||||||
mutex_lock(frames->mutex);
|
|
||||||
#ifndef SKIP_FRAMES
|
|
||||||
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
|
|
||||||
// frame to be consumed
|
|
||||||
while (!frames->rendering_frame_consumed && !frames->stopped) {
|
|
||||||
cond_wait(frames->rendering_frame_consumed_cond, frames->mutex);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (frames->fps_counter.started && !frames->rendering_frame_consumed) {
|
|
||||||
fps_counter_add_skipped_frame(&frames->fps_counter);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
frames_swap(frames);
|
|
||||||
|
|
||||||
SDL_bool previous_frame_consumed = frames->rendering_frame_consumed;
|
|
||||||
frames->rendering_frame_consumed = SDL_FALSE;
|
|
||||||
|
|
||||||
mutex_unlock(frames->mutex);
|
|
||||||
return previous_frame_consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVFrame *frames_consume_rendered_frame(struct frames *frames) {
|
|
||||||
SDL_assert(!frames->rendering_frame_consumed);
|
|
||||||
frames->rendering_frame_consumed = SDL_TRUE;
|
|
||||||
if (frames->fps_counter.started) {
|
|
||||||
fps_counter_add_rendered_frame(&frames->fps_counter);
|
|
||||||
}
|
|
||||||
#ifndef SKIP_FRAMES
|
|
||||||
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
|
|
||||||
// consumed, so that it may push a new one
|
|
||||||
cond_signal(frames->rendering_frame_consumed_cond);
|
|
||||||
#endif
|
|
||||||
return frames->rendering_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
void frames_stop(struct frames *frames) {
|
|
||||||
#ifdef SKIP_FRAMES
|
|
||||||
(void) frames; // unused
|
|
||||||
#else
|
|
||||||
mutex_lock(frames->mutex);
|
|
||||||
frames->stopped = SDL_TRUE;
|
|
||||||
mutex_unlock(frames->mutex);
|
|
||||||
// wake up blocking wait
|
|
||||||
cond_signal(frames->rendering_frame_consumed_cond);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@ -1,27 +1,40 @@
|
|||||||
#ifndef INPUTMANAGER_H
|
#ifndef INPUTMANAGER_H
|
||||||
#define INPUTMANAGER_H
|
#define INPUTMANAGER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "frames.h"
|
#include "video_buffer.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
struct input_manager {
|
struct input_manager {
|
||||||
struct controller *controller;
|
struct controller *controller;
|
||||||
struct frames *frames;
|
struct video_buffer *video_buffer;
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
};
|
};
|
||||||
|
|
||||||
void input_manager_process_text_input(struct input_manager *input_manager,
|
void
|
||||||
|
input_manager_process_text_input(struct input_manager *input_manager,
|
||||||
const SDL_TextInputEvent *event);
|
const SDL_TextInputEvent *event);
|
||||||
void input_manager_process_key(struct input_manager *input_manager,
|
|
||||||
const SDL_KeyboardEvent *event);
|
void
|
||||||
void input_manager_process_mouse_motion(struct input_manager *input_manager,
|
input_manager_process_key(struct input_manager *input_manager,
|
||||||
|
const SDL_KeyboardEvent *event,
|
||||||
|
bool control);
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_process_mouse_motion(struct input_manager *input_manager,
|
||||||
const SDL_MouseMotionEvent *event);
|
const SDL_MouseMotionEvent *event);
|
||||||
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
|
||||||
const SDL_MouseButtonEvent *event);
|
void
|
||||||
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||||
|
const SDL_MouseButtonEvent *event,
|
||||||
|
bool control);
|
||||||
|
|
||||||
|
void
|
||||||
|
input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
||||||
const SDL_MouseWheelEvent *event);
|
const SDL_MouseWheelEvent *event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -0,0 +1,286 @@
|
|||||||
|
#include "stream.h"
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavutil/time.h>
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
|
#include <SDL2/SDL_events.h>
|
||||||
|
#include <SDL2/SDL_mutex.h>
|
||||||
|
#include <SDL2/SDL_thread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "compat.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "buffer_util.h"
|
||||||
|
#include "decoder.h"
|
||||||
|
#include "events.h"
|
||||||
|
#include "lock_util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "recorder.h"
|
||||||
|
|
||||||
|
#define BUFSIZE 0x10000
|
||||||
|
|
||||||
|
#define HEADER_SIZE 12
|
||||||
|
#define NO_PTS UINT64_C(-1)
|
||||||
|
|
||||||
|
static struct frame_meta *
|
||||||
|
frame_meta_new(uint64_t pts) {
|
||||||
|
struct frame_meta *meta = malloc(sizeof(*meta));
|
||||||
|
if (!meta) {
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
meta->pts = pts;
|
||||||
|
meta->next = NULL;
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
frame_meta_delete(struct frame_meta *frame_meta) {
|
||||||
|
free(frame_meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
receiver_state_push_meta(struct receiver_state *state, uint64_t pts) {
|
||||||
|
struct frame_meta *frame_meta = frame_meta_new(pts);
|
||||||
|
if (!frame_meta) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append to the list
|
||||||
|
// (iterate to find the last item, in practice the list should be tiny)
|
||||||
|
struct frame_meta **p = &state->frame_meta_queue;
|
||||||
|
while (*p) {
|
||||||
|
p = &(*p)->next;
|
||||||
|
}
|
||||||
|
*p = frame_meta;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t
|
||||||
|
receiver_state_take_meta(struct receiver_state *state) {
|
||||||
|
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
|
||||||
|
SDL_assert(frame_meta); // must not be empty
|
||||||
|
uint64_t pts = frame_meta->pts;
|
||||||
|
state->frame_meta_queue = frame_meta->next; // remove the item
|
||||||
|
frame_meta_delete(frame_meta);
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
|
||||||
|
struct stream *stream = opaque;
|
||||||
|
struct receiver_state *state = &stream->receiver_state;
|
||||||
|
|
||||||
|
// The video stream contains raw packets, without time information. When we
|
||||||
|
// record, we retrieve the timestamps separately, from a "meta" header
|
||||||
|
// added by the server before each raw packet.
|
||||||
|
//
|
||||||
|
// The "meta" header length is 12 bytes:
|
||||||
|
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||||
|
// <-------------> <-----> <-----------------------------...
|
||||||
|
// PTS packet raw packet
|
||||||
|
// size
|
||||||
|
//
|
||||||
|
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||||
|
|
||||||
|
if (!state->remaining) {
|
||||||
|
#define HEADER_SIZE 12
|
||||||
|
uint8_t header[HEADER_SIZE];
|
||||||
|
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
|
||||||
|
if (r == -1) {
|
||||||
|
return AVERROR(errno);
|
||||||
|
}
|
||||||
|
if (r == 0) {
|
||||||
|
return AVERROR_EOF;
|
||||||
|
}
|
||||||
|
// no partial read (net_recv_all())
|
||||||
|
SDL_assert_release(r == HEADER_SIZE);
|
||||||
|
|
||||||
|
uint64_t pts = buffer_read64be(header);
|
||||||
|
state->remaining = buffer_read32be(&header[8]);
|
||||||
|
|
||||||
|
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
|
||||||
|
LOGE("Could not store PTS for recording");
|
||||||
|
// we cannot save the PTS, the recording would be broken
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert(state->remaining);
|
||||||
|
|
||||||
|
if (buf_size > state->remaining) {
|
||||||
|
buf_size = state->remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t r = net_recv(stream->socket, buf, buf_size);
|
||||||
|
if (r == -1) {
|
||||||
|
return AVERROR(errno);
|
||||||
|
}
|
||||||
|
if (r == 0) {
|
||||||
|
return AVERROR_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert(state->remaining >= r);
|
||||||
|
state->remaining -= r;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
|
||||||
|
struct stream *stream = opaque;
|
||||||
|
ssize_t r = net_recv(stream->socket, buf, buf_size);
|
||||||
|
if (r == -1) {
|
||||||
|
return AVERROR(errno);
|
||||||
|
}
|
||||||
|
if (r == 0) {
|
||||||
|
return AVERROR_EOF;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
notify_stopped(void) {
|
||||||
|
SDL_Event stop_event;
|
||||||
|
stop_event.type = EVENT_STREAM_STOPPED;
|
||||||
|
SDL_PushEvent(&stop_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_stream(void *data) {
|
||||||
|
struct stream *stream = data;
|
||||||
|
|
||||||
|
AVFormatContext *format_ctx = avformat_alloc_context();
|
||||||
|
if (!format_ctx) {
|
||||||
|
LOGC("Could not allocate format context");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *buffer = av_malloc(BUFSIZE);
|
||||||
|
if (!buffer) {
|
||||||
|
LOGC("Could not allocate buffer");
|
||||||
|
goto finally_free_format_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the receiver state
|
||||||
|
stream->receiver_state.frame_meta_queue = NULL;
|
||||||
|
stream->receiver_state.remaining = 0;
|
||||||
|
|
||||||
|
// if recording is enabled, a "header" is sent between raw packets
|
||||||
|
int (*read_packet)(void *, uint8_t *, int) =
|
||||||
|
stream->recorder ? read_packet_with_meta : read_raw_packet;
|
||||||
|
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, stream,
|
||||||
|
read_packet, NULL, NULL);
|
||||||
|
if (!avio_ctx) {
|
||||||
|
LOGC("Could not allocate avio context");
|
||||||
|
// avformat_open_input takes ownership of 'buffer'
|
||||||
|
// so only free the buffer before avformat_open_input()
|
||||||
|
av_free(buffer);
|
||||||
|
goto finally_free_format_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
format_ctx->pb = avio_ctx;
|
||||||
|
|
||||||
|
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
|
||||||
|
LOGE("Could not open video stream");
|
||||||
|
goto finally_free_avio_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
|
if (!codec) {
|
||||||
|
LOGE("H.264 decoder not found");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
|
||||||
|
LOGE("Could not open decoder");
|
||||||
|
goto finally_close_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
|
||||||
|
LOGE("Could not open recorder");
|
||||||
|
goto finally_close_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVPacket packet;
|
||||||
|
av_init_packet(&packet);
|
||||||
|
packet.data = NULL;
|
||||||
|
packet.size = 0;
|
||||||
|
|
||||||
|
while (!av_read_frame(format_ctx, &packet)) {
|
||||||
|
if (stream->decoder && !decoder_push(stream->decoder, &packet)) {
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
goto quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->recorder) {
|
||||||
|
// we retrieve the PTS in order they were received, so they will
|
||||||
|
// be assigned to the correct frame
|
||||||
|
uint64_t pts = receiver_state_take_meta(&stream->receiver_state);
|
||||||
|
packet.pts = pts;
|
||||||
|
packet.dts = pts;
|
||||||
|
|
||||||
|
// no need to rescale with av_packet_rescale_ts(), the timestamps
|
||||||
|
// are in microseconds both in input and output
|
||||||
|
if (!recorder_write(stream->recorder, &packet)) {
|
||||||
|
LOGE("Could not write frame to output file");
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
goto quit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
|
||||||
|
if (avio_ctx->eof_reached) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("End of frames");
|
||||||
|
|
||||||
|
quit:
|
||||||
|
if (stream->recorder) {
|
||||||
|
recorder_close(stream->recorder);
|
||||||
|
}
|
||||||
|
finally_close_input:
|
||||||
|
avformat_close_input(&format_ctx);
|
||||||
|
finally_free_avio_ctx:
|
||||||
|
av_free(avio_ctx->buffer);
|
||||||
|
av_free(avio_ctx);
|
||||||
|
finally_free_format_ctx:
|
||||||
|
avformat_free_context(format_ctx);
|
||||||
|
end:
|
||||||
|
notify_stopped();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
|
struct decoder *decoder, struct recorder *recorder) {
|
||||||
|
stream->socket = socket;
|
||||||
|
stream->decoder = decoder,
|
||||||
|
stream->recorder = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
stream_start(struct stream *stream) {
|
||||||
|
LOGD("Starting stream thread");
|
||||||
|
|
||||||
|
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
|
||||||
|
if (!stream->thread) {
|
||||||
|
LOGC("Could not start stream thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_stop(struct stream *stream) {
|
||||||
|
if (stream->decoder) {
|
||||||
|
decoder_interrupt(stream->decoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_join(struct stream *stream) {
|
||||||
|
SDL_WaitThread(stream->thread, NULL);
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef STREAM_H
|
||||||
|
#define STREAM_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
struct video_buffer;
|
||||||
|
|
||||||
|
struct frame_meta {
|
||||||
|
uint64_t pts;
|
||||||
|
struct frame_meta *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stream {
|
||||||
|
socket_t socket;
|
||||||
|
struct video_buffer *video_buffer;
|
||||||
|
SDL_Thread *thread;
|
||||||
|
struct decoder *decoder;
|
||||||
|
struct recorder *recorder;
|
||||||
|
struct receiver_state {
|
||||||
|
// meta (in order) for frames not consumed yet
|
||||||
|
struct frame_meta *frame_meta_queue;
|
||||||
|
size_t remaining; // remaining bytes to receive for the current frame
|
||||||
|
} receiver_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
|
struct decoder *decoder, struct recorder *recorder);
|
||||||
|
|
||||||
|
bool
|
||||||
|
stream_start(struct stream *stream);
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_stop(struct stream *stream);
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_join(struct stream *stream);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
#include "video_buffer.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL_assert.h>
|
||||||
|
#include <SDL2/SDL_mutex.h>
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "lock_util.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
bool
|
||||||
|
video_buffer_init(struct video_buffer *vb) {
|
||||||
|
if (!(vb->decoding_frame = av_frame_alloc())) {
|
||||||
|
goto error_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(vb->rendering_frame = av_frame_alloc())) {
|
||||||
|
goto error_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(vb->mutex = SDL_CreateMutex())) {
|
||||||
|
goto error_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SKIP_FRAMES
|
||||||
|
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
|
||||||
|
SDL_DestroyMutex(vb->mutex);
|
||||||
|
goto error_2;
|
||||||
|
}
|
||||||
|
vb->interrupted = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// there is initially no rendering frame, so consider it has already been
|
||||||
|
// consumed
|
||||||
|
vb->rendering_frame_consumed = true;
|
||||||
|
fps_counter_init(&vb->fps_counter);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_2:
|
||||||
|
av_frame_free(&vb->rendering_frame);
|
||||||
|
error_1:
|
||||||
|
av_frame_free(&vb->decoding_frame);
|
||||||
|
error_0:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
video_buffer_destroy(struct video_buffer *vb) {
|
||||||
|
#ifndef SKIP_FRAMES
|
||||||
|
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
|
||||||
|
#endif
|
||||||
|
SDL_DestroyMutex(vb->mutex);
|
||||||
|
av_frame_free(&vb->rendering_frame);
|
||||||
|
av_frame_free(&vb->decoding_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
video_buffer_swap_frames(struct video_buffer *vb) {
|
||||||
|
AVFrame *tmp = vb->decoding_frame;
|
||||||
|
vb->decoding_frame = vb->rendering_frame;
|
||||||
|
vb->rendering_frame = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||||
|
bool *previous_frame_skipped) {
|
||||||
|
mutex_lock(vb->mutex);
|
||||||
|
#ifndef SKIP_FRAMES
|
||||||
|
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
|
||||||
|
// frame to be consumed
|
||||||
|
while (!vb->rendering_frame_consumed && !vb->interrupted) {
|
||||||
|
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
|
||||||
|
fps_counter_add_skipped_frame(&vb->fps_counter);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
video_buffer_swap_frames(vb);
|
||||||
|
|
||||||
|
*previous_frame_skipped = !vb->rendering_frame_consumed;
|
||||||
|
vb->rendering_frame_consumed = false;
|
||||||
|
|
||||||
|
mutex_unlock(vb->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVFrame *
|
||||||
|
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
|
||||||
|
SDL_assert(!vb->rendering_frame_consumed);
|
||||||
|
vb->rendering_frame_consumed = true;
|
||||||
|
if (vb->fps_counter.started) {
|
||||||
|
fps_counter_add_rendered_frame(&vb->fps_counter);
|
||||||
|
}
|
||||||
|
#ifndef SKIP_FRAMES
|
||||||
|
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
|
||||||
|
// consumed, so that it may push a new one
|
||||||
|
cond_signal(vb->rendering_frame_consumed_cond);
|
||||||
|
#endif
|
||||||
|
return vb->rendering_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
video_buffer_interrupt(struct video_buffer *vb) {
|
||||||
|
#ifdef SKIP_FRAMES
|
||||||
|
(void) vb; // unused
|
||||||
|
#else
|
||||||
|
mutex_lock(vb->mutex);
|
||||||
|
vb->interrupted = true;
|
||||||
|
mutex_unlock(vb->mutex);
|
||||||
|
// wake up blocking wait
|
||||||
|
cond_signal(vb->rendering_frame_consumed_cond);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.view.InputEvent;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class StatusBarManager {
|
||||||
|
|
||||||
|
private final IInterface manager;
|
||||||
|
private final Method expandNotificationsPanelMethod;
|
||||||
|
private final Method collapsePanelsMethod;
|
||||||
|
|
||||||
|
public StatusBarManager(IInterface manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
try {
|
||||||
|
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
|
||||||
|
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expandNotificationsPanel() {
|
||||||
|
try {
|
||||||
|
expandNotificationsPanelMethod.invoke(manager);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collapsePanels() {
|
||||||
|
try {
|
||||||
|
collapsePanelsMethod.invoke(manager);
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue