diff options
-rw-r--r-- | decode.c | 47 | ||||
-rw-r--r-- | decode.h | 10 | ||||
-rw-r--r-- | main.c | 283 | ||||
-rw-r--r-- | proto.h | 41 |
4 files changed, 224 insertions, 157 deletions
@@ -30,7 +30,6 @@ #include <va/va_drmcommon.h> #include "frame.h" -#include "toolbox/buffer.h" #include "toolbox/utils.h" #include "window.h" @@ -48,11 +47,7 @@ struct DecodeContext { int drm_fd; VADisplay va_display; mfxSession mfx_session; - - struct Buffer buffer; struct Surface** surfaces; - - size_t bitrate; }; static const char* VaStatusString(VAStatus status) { @@ -411,35 +406,14 @@ static size_t UnlockAllSurfaces(struct DecodeContext* decode_context, return result; } -bool DecodeContextDecode(struct DecodeContext* decode_context, int fd) { - switch (BufferAppendFrom(&decode_context->buffer, fd)) { - case -1: - LOG("Failed to append packet data to buffer (%s)", strerror(errno)); - return false; - case 0: - LOG("Server closed connection"); - return false; - default: - break; - } - -again: - if (decode_context->buffer.size < sizeof(uint32_t)) { - // mburakov: Packet size is not yet available. - return true; - } - uint32_t packet_size = *(uint32_t*)decode_context->buffer.data; - if (decode_context->buffer.size < sizeof(uint32_t) + packet_size) { - // mburakov: Full packet is not yet available. - return true; - } - +bool DecodeContextDecode(struct DecodeContext* decode_context, + const void* buffer, size_t size) { mfxBitstream bitstream = { .DecodeTimeStamp = MFX_TIMESTAMP_UNKNOWN, .TimeStamp = (mfxU64)MFX_TIMESTAMP_UNKNOWN, - .Data = (mfxU8*)decode_context->buffer.data + sizeof(uint32_t), - .DataLength = packet_size, - .MaxLength = packet_size, + .Data = (void*)(ptrdiff_t)buffer, + .DataLength = (mfxU32)size, + .MaxLength = (mfxU32)size, .DataFlag = MFX_BITSTREAM_COMPLETE_FRAME, }; @@ -493,20 +467,11 @@ again: return false; } - BufferDiscard(&decode_context->buffer, sizeof(uint32_t) + packet_size); - decode_context->bitrate += (sizeof(uint32_t) + packet_size) * 8; - goto again; + return true; } } -void DecodeContextGetStats(struct DecodeContext* decode_context, - struct DecodeStats* decode_stats) { - decode_stats->bitrate = decode_context->bitrate; - decode_context->bitrate = 0; -} - void DecodeContextDestroy(struct DecodeContext* decode_context) { - BufferDestroy(&decode_context->buffer); MFXClose(decode_context->mfx_session); vaTerminate(decode_context->va_display); close(decode_context->drm_fd); @@ -22,17 +22,11 @@ #include <stddef.h> struct DecodeContext; -struct Frame; struct Window; -struct DecodeStats { - size_t bitrate; -}; - struct DecodeContext* DecodeContextCreate(struct Window* window); -bool DecodeContextDecode(struct DecodeContext* decode_context, int fd); -void DecodeContextGetStats(struct DecodeContext* decode_context, - struct DecodeStats* decode_stats); +bool DecodeContextDecode(struct DecodeContext* decode_context, + const void* buffer, size_t size); void DecodeContextDestroy(struct DecodeContext* decode_context); #endif // RECEIVER_DECODE_H_ @@ -31,20 +31,28 @@ #include "decode.h" #include "input.h" +#include "proto.h" #include "pui/font.h" +#include "toolbox/buffer.h" #include "toolbox/perf.h" #include "toolbox/utils.h" #include "window.h" static volatile sig_atomic_t g_signal; static void OnSignal(int status) { g_signal = status; } -static size_t overlay_width, overlay_height; -static void SocketDtor(int* sock) { - if (*sock == -1) return; - close(*sock); - *sock = -1; -} +struct Context { + struct InputStream* input_stream; + struct Window* window; + size_t overlay_width; + size_t overlay_height; + struct Overlay* overlay; + struct DecodeContext* decode_context; + struct Buffer buffer; + + size_t video_bitstream; + uint64_t timestamp; +}; static int ConnectSocket(const char* arg) { uint16_t port; @@ -122,30 +130,6 @@ static void OnWindowWheel(void* user, int delta) { } } -static void InputStreamDtor(struct InputStream** input_stream) { - if (!*input_stream) return; - InputStreamDestroy(*input_stream); - *input_stream = NULL; -} - -static void WindowDtor(struct Window** window) { - if (!*window) return; - WindowDestroy(*window); - *window = NULL; -} - -static void OverlayDtor(struct Overlay** overlay) { - if (!*overlay) return; - OverlayDestroy(*overlay); - *overlay = NULL; -} - -static void DecodeContextDtor(struct DecodeContext** decode_context) { - if (!*decode_context) return; - DecodeContextDestroy(*decode_context); - *decode_context = NULL; -} - static void GetMaxOverlaySize(size_t* width, size_t* height) { char str[64]; snprintf(str, sizeof(str), "Bitrate: %zu.000 Mbps", SIZE_MAX / 1000); @@ -153,29 +137,153 @@ static void GetMaxOverlaySize(size_t* width, size_t* height) { *height = 20; } -static void RenderOverlay(struct Overlay* overlay, uint64_t clock_delta, - const struct DecodeStats* decode_stats) { - uint32_t* buffer = OverlayLock(overlay); +static struct Context* ContextCreate(int sock, bool no_input, bool stats) { + struct Context* context = calloc(1, sizeof(struct Context)); + if (!context) { + LOG("Failed to allocate context (%s)", strerror(errno)); + return NULL; + } + + const struct WindowEventHandlers* maybe_window_event_handlers = NULL; + if (!no_input) { + context->input_stream = InputStreamCreate(sock); + if (!context->input_stream) { + LOG("Failed to create input stream"); + goto rollback_context; + } + static const struct WindowEventHandlers window_event_handlers = { + .OnClose = OnWindowClose, + .OnFocus = OnWindowFocus, + .OnKey = OnWindowKey, + .OnMove = OnWindowMove, + .OnButton = OnWindowButton, + .OnWheel = OnWindowWheel, + }; + maybe_window_event_handlers = &window_event_handlers; + } + + context->window = + WindowCreate(maybe_window_event_handlers, context->input_stream); + if (!context->window) { + LOG("Failed to create window"); + goto rollback_input_stream; + } + + if (stats) { + GetMaxOverlaySize(&context->overlay_width, &context->overlay_height); + context->overlay = + OverlayCreate(context->window, 4, 4, (int)context->overlay_width, + (int)context->overlay_height); + if (!context->overlay) { + LOG("Failed to create stats overlay"); + goto rollback_window; + } + } + + context->decode_context = DecodeContextCreate(context->window); + if (!context->decode_context) { + LOG("Failed to create decode context"); + goto rollback_overlay; + } + return context; + +rollback_overlay: + if (context->overlay) OverlayDestroy(context->overlay); +rollback_window: + WindowDestroy(context->window); +rollback_input_stream: + if (context->input_stream) InputStreamDestroy(context->input_stream); +rollback_context: + free(context); + return NULL; +} + +static bool RenderOverlay(struct Context* context, uint64_t timestamp) { + uint32_t* buffer = OverlayLock(context->overlay); if (!buffer) { LOG("Failed to lock overlay"); - return; + return false; } char str[64]; - size_t bitrate = decode_stats->bitrate * 1000000 / clock_delta / 1024; + uint64_t clock_delta = timestamp - context->timestamp; + size_t bitrate = context->video_bitstream * 1000000 / clock_delta / 1024; snprintf(str, sizeof(str), "Bitrate: %zu.%03zu Mbps", bitrate / 1000, bitrate % 1000); - size_t width = PuiStringWidth(str) + 8; - memset(buffer, 0, overlay_width * overlay_height * 4); - for (size_t y = 0; y < overlay_height; y++) { - for (size_t x = 0; x < width; x++) - buffer[x + y * overlay_width] = 0x40000000; + size_t overlay_width = PuiStringWidth(str) + 8; + memset(buffer, 0, context->overlay_width * context->overlay_height * 4); + for (size_t y = 0; y < context->overlay_height; y++) { + for (size_t x = 0; x < overlay_width; x++) + buffer[x + y * context->overlay_width] = 0x40000000; } - size_t voffset = overlay_width * 4; - PuiStringRender(str, buffer + voffset + 4, overlay_width, 0xffffffff); - OverlayUnlock(overlay); + size_t voffset = context->overlay_width * 4; + PuiStringRender(str, buffer + voffset + 4, context->overlay_width, + 0xffffffff); + OverlayUnlock(context->overlay); + return true; +} + +static bool HandleVideoStream(struct Context* context) { + const struct Proto* proto = context->buffer.data; + if (!DecodeContextDecode(context->decode_context, proto->data, proto->size)) { + LOG("Failed to decode incoming video data"); + return false; + } + + if (!context->overlay) return true; + if (!context->timestamp) { + context->timestamp = MicrosNow(); + return true; + } + + context->video_bitstream += proto->size; + if (!(proto->flags & PROTO_FLAG_KEYFRAME)) return true; + + uint64_t timestamp = MicrosNow(); + if (!RenderOverlay(context, timestamp)) LOG("Failed to render overlay"); + context->video_bitstream = 0; + context->timestamp = timestamp; + return true; +} + +static bool DemuxProtoStream(int sock, struct Context* context) { + switch (BufferAppendFrom(&context->buffer, sock)) { + case -1: + LOG("Failed to append packet data to buffer (%s)", strerror(errno)); + return false; + case 0: + LOG("Server closed connection"); + return false; + default: + break; + } + +again: + if (context->buffer.size < sizeof(struct Proto)) return true; + const struct Proto* proto = context->buffer.data; + if (context->buffer.size < sizeof(struct Proto) + proto->size) return true; + + switch (proto->type) { + case PROTO_TYPE_VIDEO: + if (!HandleVideoStream(context)) { + LOG("Failed to handle video stream"); + return false; + } + break; + } + + BufferDiscard(&context->buffer, sizeof(struct Proto) + proto->size); + goto again; +} + +static void ContextDestroy(struct Context* context) { + BufferDestroy(&context->buffer); + DecodeContextDestroy(context->decode_context); + if (context->overlay) OverlayDestroy(context->overlay); + WindowDestroy(context->window); + if (context->input_stream) InputStreamDestroy(context->input_stream); } int main(int argc, char* argv[]) { @@ -183,8 +291,12 @@ int main(int argc, char* argv[]) { LOG("Usage: %s <ip>:<port> [--no-input] [--stats]", argv[0]); return EXIT_FAILURE; } - int __attribute__((cleanup(SocketDtor))) sock = ConnectSocket(argv[1]); - if (sock == -1) return EXIT_FAILURE; + + int sock = ConnectSocket(argv[1]); + if (sock == -1) { + LOG("Failed to connect socket"); + return EXIT_FAILURE; + } bool no_input = false; bool stats = false; @@ -195,55 +307,17 @@ int main(int argc, char* argv[]) { stats = true; } } - struct InputStream - __attribute__((cleanup(InputStreamDtor)))* maybe_input_stream = NULL; - const struct WindowEventHandlers* maybe_window_event_handlers = NULL; - if (!no_input) { - maybe_input_stream = InputStreamCreate(sock); - if (!maybe_input_stream) { - LOG("Failed to create input stream"); - return EXIT_FAILURE; - } - static const struct WindowEventHandlers window_event_handlers = { - .OnClose = OnWindowClose, - .OnFocus = OnWindowFocus, - .OnKey = OnWindowKey, - .OnMove = OnWindowMove, - .OnButton = OnWindowButton, - .OnWheel = OnWindowWheel, - }; - maybe_window_event_handlers = &window_event_handlers; - } - struct Window __attribute__((cleanup(WindowDtor)))* window = - WindowCreate(maybe_window_event_handlers, maybe_input_stream); - if (!window) { - LOG("Failed to create window"); - return EXIT_FAILURE; - } - struct Overlay __attribute__((cleanup(OverlayDtor)))* overlay = NULL; - if (stats) { - GetMaxOverlaySize(&overlay_width, &overlay_height); - overlay = - OverlayCreate(window, 4, 4, (int)overlay_width, (int)overlay_height); - if (!overlay) { - LOG("Failed to create overlay"); - return EXIT_FAILURE; - } - } - - struct DecodeContext - __attribute__((cleanup(DecodeContextDtor)))* decode_context = - DecodeContextCreate(window); - if (!decode_context) { - LOG("Failed to create decode context"); - return EXIT_FAILURE; + struct Context* context = ContextCreate(sock, no_input, stats); + if (!context) { + LOG("Failed to create context"); + goto rollback_socket; } - int events_fd = WindowGetEventsFd(window); + int events_fd = WindowGetEventsFd(context->window); if (events_fd == -1) { LOG("Failed to get events fd"); - return EXIT_FAILURE; + goto rollback_context; } if (signal(SIGINT, OnSignal) == SIG_ERR || @@ -252,7 +326,6 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } - uint64_t before = MicrosNow(); while (!g_signal) { struct pollfd pfds[] = { {.fd = sock, .events = POLLIN}, @@ -262,7 +335,7 @@ int main(int argc, char* argv[]) { case -1: if (errno != EINTR) { LOG("Failed to poll (%s)", strerror(errno)); - return EXIT_FAILURE; + goto rollback_context; } __attribute__((fallthrough)); case 0: @@ -270,26 +343,20 @@ int main(int argc, char* argv[]) { default: break; } - if (overlay) { - uint64_t after = MicrosNow(); - static const uint64_t timeout = 5 * 1000 * 1000; - uint64_t time_delta = after - before; - if (time_delta > timeout) { - struct DecodeStats decode_stats; - DecodeContextGetStats(decode_context, &decode_stats); - RenderOverlay(overlay, time_delta, &decode_stats); - before = after; - } + if (pfds[0].revents && !DemuxProtoStream(sock, context)) { + LOG("Failed to demux proto stream"); + goto rollback_context; } - if (pfds[0].revents && !DecodeContextDecode(decode_context, sock)) { - LOG("Failed to decode incoming data"); - return EXIT_FAILURE; - } - if (pfds[1].revents && !WindowProcessEvents(window)) { + if (pfds[1].revents && !WindowProcessEvents(context->window)) { LOG("Failed to process window events"); - return EXIT_FAILURE; + goto rollback_context; } } - return EXIT_SUCCESS; +rollback_context: + ContextDestroy(context); +rollback_socket: + close(sock); + bool result = g_signal == SIGINT || g_signal == SIGTERM; + return result ? EXIT_SUCCESS : EXIT_FAILURE; } @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Mikhail Burakov. This file is part of receiver. + * + * receiver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * receiver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with receiver. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef RECEIVER_PROTO_H_ +#define RECEIVER_PROTO_H_ + +#include <assert.h> +#include <stdint.h> + +#define PROTO_TYPE_MISC 0 +#define PROTO_TYPE_VIDEO 1 +#define PROTO_TYPE_AUDIO 2 + +#define PROTO_FLAG_KEYFRAME 1 + +struct Proto { + uint32_t size; + uint8_t type; + uint8_t flags; + uint16_t latency; + uint8_t data[]; +}; + +static_assert(sizeof(struct Proto) == 8 * sizeof(uint8_t), + "Suspicious proto struct size"); + +#endif // RECEIVER_PROTO_H_ |