summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--audio.c367
-rw-r--r--audio.h45
-rw-r--r--audio_context.c220
-rw-r--r--audio_context.h (renamed from capture_kms.h)23
-rw-r--r--bitstream.c77
-rw-r--r--buffer_queue.c100
-rw-r--r--buffer_queue.h42
-rw-r--r--capture.c77
-rw-r--r--capture.h38
-rw-r--r--capture_kms.c225
-rw-r--r--capture_wlr.c329
-rw-r--r--capture_wlr.h39
-rw-r--r--encode.c942
-rw-r--r--encode.h40
-rw-r--r--gpu.c802
-rw-r--r--gpu.h54
-rw-r--r--hevc.c708
-rw-r--r--hevc.h97
-rw-r--r--input.c165
-rw-r--r--io_context.c304
-rw-r--r--io_context.h (renamed from input.h)21
-rw-r--r--main.c428
-rw-r--r--makefile30
-rw-r--r--proto.c58
-rw-r--r--proto.h35
-rw-r--r--queue.c60
-rw-r--r--queue.h (renamed from bitstream.h)28
m---------toolbox0
-rw-r--r--util.h (renamed from colorspace.h)25
30 files changed, 723 insertions, 4659 deletions
diff --git a/.gitmodules b/.gitmodules
index 92e6233..0930b7f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "toolbox"]
- path = toolbox
- url = https://burakov.eu/toolbox.git
[submodule "wlr-protocols"]
path = wlr-protocols
url = https://gitlab.freedesktop.org/wlroots/wlr-protocols.git
diff --git a/audio.c b/audio.c
deleted file mode 100644
index fa5b23e..0000000
--- a/audio.c
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifdef USE_PIPEWIRE
-
-#include "audio.h"
-
-#include <errno.h>
-#include <pipewire/pipewire.h>
-#include <spa/param/audio/raw-utils.h>
-#include <spa/utils/result.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "buffer_queue.h"
-#include "toolbox/utils.h"
-
-#define STATUS_OK 0
-#define STATUS_ERR 1
-
-struct AudioContext {
- size_t one_second_size;
- const struct AudioContextCallbacks* callbacks;
- void* user;
-
- int waker[2];
- struct BufferQueue* buffer_queue;
- struct pw_thread_loop* pw_thread_loop;
- struct pw_stream* pw_stream;
-};
-
-static bool LookupChannel(const char* name, uint32_t* value) {
- struct {
- const char* name;
- enum spa_audio_channel value;
- } static const kChannelMap[] = {
-#define _(op) {.name = #op, .value = SPA_AUDIO_CHANNEL_##op}
- _(FL), _(FR), _(FC), _(LFE), _(SL), _(SR), _(FLC),
- _(FRC), _(RC), _(RL), _(RR), _(TC), _(TFL), _(TFC),
- _(TFR), _(TRL), _(TRC), _(TRR), _(RLC), _(RRC), _(FLW),
- _(FRW), _(LFE2), _(FLH), _(FCH), _(FRH), _(TFLC), _(TFRC),
- _(TSL), _(TSR), _(LLFE), _(RLFE), _(BC), _(BLC), _(BRC),
-#undef _
- };
- for (size_t i = 0; i < LENGTH(kChannelMap); i++) {
- if (!strcmp(kChannelMap[i].name, name)) {
- if (value) *value = kChannelMap[i].value;
- return true;
- }
- }
- return false;
-}
-
-static size_t ParseChannelMap(
- const char* channel_map,
- uint32_t channel_positions[SPA_AUDIO_MAX_CHANNELS]) {
- char minibuf[5];
- size_t channels_counter = 0;
- for (size_t i = 0, j = 0;; i++) {
- switch (channel_map[i]) {
- case 0:
- case ',':
- minibuf[j] = 0;
- if (channels_counter == SPA_AUDIO_MAX_CHANNELS ||
- !LookupChannel(minibuf, &channel_positions[channels_counter++]))
- return 0;
- if (!channel_map[i]) return channels_counter;
- j = 0;
- break;
- default:
- if (j == 4) return 0;
- minibuf[j++] = channel_map[i];
- break;
- }
- }
-}
-
-static bool ParseAudioConfig(const char* audio_config,
- const char** out_channel_map,
- struct spa_audio_info_raw* out_audio_info) {
- int sample_rate = atoi(audio_config);
- if (sample_rate != 44100 && sample_rate != 48000) {
- LOG("Invalid sample rate requested");
- return false;
- }
- const char* channel_map = strchr(audio_config, ':');
- if (!channel_map) {
- LOG("Invalid audio config requested");
- return false;
- }
-
- channel_map++;
- struct spa_audio_info_raw audio_info = {
- .format = SPA_AUDIO_FORMAT_S16_LE,
- .rate = (uint32_t)sample_rate,
- };
- audio_info.channels =
- (uint32_t)ParseChannelMap(channel_map, audio_info.position);
- if (!audio_info.channels) {
- LOG("Invalid channel map requested");
- return false;
- }
-
- *out_channel_map = channel_map;
- *out_audio_info = audio_info;
- return true;
-}
-
-static void WakeClient(const struct AudioContext* audio_context, char status) {
- if (write(audio_context->waker[1], &status, sizeof(status)) !=
- sizeof(status)) {
- // TODO(mburakov): Then what?..
- abort();
- }
-}
-
-static void OnStreamStateChanged(void* data, enum pw_stream_state old,
- enum pw_stream_state state,
- const char* error) {
- (void)data;
- LOG("Stream state change %s->%s, error is %s", pw_stream_state_as_string(old),
- pw_stream_state_as_string(state), error);
-}
-
-static void OnStreamParamChanged(void* data, uint32_t id,
- const struct spa_pod* param) {
- struct AudioContext* audio_context = data;
- if (param == NULL || id != SPA_PARAM_Format) return;
-
- struct spa_audio_info audio_info;
- if (spa_format_parse(param, &audio_info.media_type,
- &audio_info.media_subtype) < 0) {
- LOG("Failed to parse stream format");
- goto failure;
- }
- if (audio_info.media_type != SPA_MEDIA_TYPE_audio ||
- audio_info.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
- LOG("Unexpected stream format");
- goto failure;
- }
- if (spa_format_audio_raw_parse(param, &audio_info.info.raw) < 0) {
- LOG("Faield to parse audio stream format");
- goto failure;
- }
-
- LOG("Capturing rate: %u, channels: %u", audio_info.info.raw.rate,
- audio_info.info.raw.channels);
- return;
-
-failure:
- pw_thread_loop_stop(audio_context->pw_thread_loop);
- WakeClient(audio_context, STATUS_ERR);
-}
-
-static void OnStreamProcess(void* data) {
- struct AudioContext* audio_context = data;
- struct pw_buffer* pw_buffer =
- pw_stream_dequeue_buffer(audio_context->pw_stream);
- if (!pw_buffer) {
- LOG("Failed to dequeue stream buffer");
- goto failure;
- }
-
- for (uint32_t i = 0; i < pw_buffer->buffer->n_datas; i++) {
- const struct spa_data* spa_data = pw_buffer->buffer->datas + i;
- const void* buffer = (const uint8_t*)spa_data->data +
- spa_data->chunk->offset % spa_data->maxsize;
- uint32_t size = MIN(spa_data->chunk->size, spa_data->maxsize);
- struct BufferQueueItem* buffer_queue_item =
- BufferQueueItemCreate(buffer, size);
- if (!buffer_queue_item) {
- LOG("Failed to copy stream buffer");
- goto failure;
- }
- if (!BufferQueueQueue(audio_context->buffer_queue, buffer_queue_item)) {
- LOG("Failed to queue stream buffer copy");
- BufferQueueItemDestroy(buffer_queue_item);
- goto failure;
- }
- }
-
- pw_stream_queue_buffer(audio_context->pw_stream, pw_buffer);
- WakeClient(audio_context, STATUS_OK);
- return;
-
-failure:
- pw_thread_loop_stop(audio_context->pw_thread_loop);
- WakeClient(audio_context, STATUS_ERR);
-}
-
-struct AudioContext* AudioContextCreate(
- const char* audio_config, const struct AudioContextCallbacks* callbacks,
- void* user) {
- const char* channel_map;
- struct spa_audio_info_raw audio_info;
- if (!ParseAudioConfig(audio_config, &channel_map, &audio_info)) {
- LOG("Failed to parse audio config argument");
- return NULL;
- }
-
- pw_init(0, NULL);
- struct AudioContext* audio_context = malloc(sizeof(struct AudioContext));
- if (!audio_context) {
- LOG("Failed to allocate audio context (%s)", strerror(errno));
- return NULL;
- }
- *audio_context = (struct AudioContext){
- .one_second_size =
- audio_info.channels * audio_info.rate * sizeof(int16_t),
- .callbacks = callbacks,
- .user = user,
- };
-
- if (pipe(audio_context->waker)) {
- LOG("Failed to create pipe (%s)", strerror(errno));
- goto rollback_audio_context;
- }
-
- audio_context->buffer_queue = BufferQueueCreate();
- if (!audio_context->buffer_queue) {
- LOG("Failed to create buffer queue (%s)", strerror(errno));
- goto rollback_waker;
- }
-
- audio_context->pw_thread_loop = pw_thread_loop_new("audio-capture", NULL);
- if (!audio_context->pw_thread_loop) {
- LOG("Failed to create pipewire thread loop");
- goto rollback_buffer_queue;
- }
-
- pw_thread_loop_lock(audio_context->pw_thread_loop);
- int err = pw_thread_loop_start(audio_context->pw_thread_loop);
- if (err) {
- LOG("Failed to start pipewire thread loop (%s)", spa_strerror(err));
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- goto rollback_thread_loop;
- }
-
- struct pw_properties* pw_properties = pw_properties_new(
-#define _(...) __VA_ARGS__
- _(PW_KEY_AUDIO_FORMAT, "S16LE"), _(SPA_KEY_AUDIO_POSITION, channel_map),
- _(PW_KEY_NODE_NAME, "streamer-sink"), _(PW_KEY_NODE_VIRTUAL, "true"),
- _(PW_KEY_MEDIA_CLASS, "Audio/Sink"), NULL
-#undef _
- );
- if (!pw_properties) {
- LOG("Failed to create pipewire properties");
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- goto rollback_thread_loop;
- }
-
- pw_properties_setf(pw_properties, PW_KEY_AUDIO_RATE, "%du", audio_info.rate);
- pw_properties_setf(pw_properties, PW_KEY_AUDIO_CHANNELS, "%du",
- audio_info.channels);
- static const struct pw_stream_events kPwStreamEvents = {
- .version = PW_VERSION_STREAM_EVENTS,
- .state_changed = OnStreamStateChanged,
- .param_changed = OnStreamParamChanged,
- .process = OnStreamProcess,
- };
- audio_context->pw_stream = pw_stream_new_simple(
- pw_thread_loop_get_loop(audio_context->pw_thread_loop), "audio-capture",
- pw_properties, &kPwStreamEvents, audio_context);
- if (!audio_context->pw_stream) {
- LOG("Failed to create pipewire stream");
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- goto rollback_thread_loop;
- }
-
- uint8_t buffer[1024];
- struct spa_pod_builder spa_pod_builder =
- SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
- const struct spa_pod* params[] = {spa_format_audio_raw_build(
- &spa_pod_builder, SPA_PARAM_EnumFormat, &audio_info)};
- static const enum pw_stream_flags kPwStreamFlags =
- PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
- PW_STREAM_FLAG_RT_PROCESS;
- if (pw_stream_connect(audio_context->pw_stream, PW_DIRECTION_INPUT, PW_ID_ANY,
- kPwStreamFlags, params, LENGTH(params))) {
- LOG("Failed to connect pipewire stream");
- pw_stream_destroy(audio_context->pw_stream);
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- goto rollback_thread_loop;
- }
-
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- return audio_context;
-
-rollback_thread_loop:
- pw_thread_loop_destroy(audio_context->pw_thread_loop);
-rollback_buffer_queue:
- BufferQueueDestroy(audio_context->buffer_queue);
-rollback_waker:
- close(audio_context->waker[1]);
- close(audio_context->waker[0]);
-rollback_audio_context:
- free(audio_context);
- pw_deinit();
- return NULL;
-}
-
-int AudioContextGetEventsFd(struct AudioContext* audio_context) {
- return audio_context->waker[0];
-}
-
-bool AudioContextProcessEvents(struct AudioContext* audio_context) {
- char status;
- if (read(audio_context->waker[0], &status, sizeof(status)) !=
- sizeof(status)) {
- // TODO(mburakov): Then what?..
- abort();
- }
-
- switch (status) {
- case STATUS_OK:
- break;
- case STATUS_ERR:
- LOG("Error reported from audio thread");
- return false;
- default:
- __builtin_unreachable();
- }
-
- for (;;) {
- struct BufferQueueItem* buffer_queue_item;
- if (!BufferQueueDequeue(audio_context->buffer_queue, &buffer_queue_item)) {
- LOG("Failed to dequeue stream buffer copy");
- return false;
- }
- if (!buffer_queue_item) return true;
- audio_context->callbacks->OnAudioReady(
- audio_context->user, buffer_queue_item->data, buffer_queue_item->size,
- buffer_queue_item->size * 1000000 / audio_context->one_second_size);
- BufferQueueItemDestroy(buffer_queue_item);
- }
-}
-
-void AudioContextDestroy(struct AudioContext* audio_context) {
- pw_thread_loop_lock(audio_context->pw_thread_loop);
- pw_stream_destroy(audio_context->pw_stream);
- pw_thread_loop_unlock(audio_context->pw_thread_loop);
- pw_thread_loop_destroy(audio_context->pw_thread_loop);
- BufferQueueDestroy(audio_context->buffer_queue);
- close(audio_context->waker[1]);
- close(audio_context->waker[0]);
- free(audio_context);
- pw_deinit();
-}
-
-#endif // USE_PIPEWIRE
diff --git a/audio.h b/audio.h
deleted file mode 100644
index 1fb71d0..0000000
--- a/audio.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_AUDIO_H_
-#define STREAMER_AUDIO_H_
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct AudioContext;
-
-struct AudioContextCallbacks {
- void (*OnAudioReady)(void* user, const void* buffer, size_t size,
- size_t latency);
-};
-
-#ifdef USE_PIPEWIRE
-struct AudioContext* AudioContextCreate(
- const char* audio_config, const struct AudioContextCallbacks* callbacks,
- void* user);
-int AudioContextGetEventsFd(struct AudioContext* audio_context);
-bool AudioContextProcessEvents(struct AudioContext* audio_context);
-void AudioContextDestroy(struct AudioContext* audio_context);
-#else // USE_PIPEWIRE
-#define AudioContextCreate(...) ((void*)~0)
-#define AudioContextGetEventsFd(...) -1
-#define AudioContextProcessEvents(...) false
-#define AudioContextDestroy(...)
-#endif
-
-#endif // STREAMER_AUDIO_H_
diff --git a/audio_context.c b/audio_context.c
new file mode 100644
index 0000000..ba0202d
--- /dev/null
+++ b/audio_context.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
+ *
+ * streamer 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.
+ *
+ * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "audio_context.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "io_context.h"
+#include "proto.h"
+#include "util.h"
+
+struct AudioContext {
+ struct IoContext* io_context;
+ struct pw_thread_loop* thread_loop;
+ struct pw_stream* stream;
+};
+
+struct ProtoImpl {
+ struct Proto proto;
+ struct ProtoHeader header;
+ uint8_t data[];
+};
+
+static void ProtoDestroy(struct Proto* proto) { free(proto); }
+
+static void OnStreamStateChanged(void* arg, enum pw_stream_state old,
+ enum pw_stream_state state,
+ const char* error) {
+ (void)arg;
+ LOG("Stream state change %s->%s, error is %s", pw_stream_state_as_string(old),
+ pw_stream_state_as_string(state), error);
+}
+
+static void OnStreamParamChanged(void* arg, uint32_t id,
+ const struct spa_pod* param) {
+ (void)arg;
+ if (!param || id != SPA_PARAM_Format) return;
+
+ struct spa_audio_info audio_info;
+ if (spa_format_parse(param, &audio_info.media_type,
+ &audio_info.media_subtype) < 0) {
+ LOG("Failed to parse stream format");
+ return;
+ }
+
+ if (audio_info.media_type != SPA_MEDIA_TYPE_audio ||
+ audio_info.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
+ LOG("Unexpected stream format");
+ return;
+ }
+
+ if (spa_format_audio_raw_parse(param, &audio_info.info.raw) < 0) {
+ LOG("Failed to parse stream raw format");
+ return;
+ }
+
+ LOG("Params changed to format=%u, rate=%u, channels=%u",
+ audio_info.info.raw.format, audio_info.info.raw.rate,
+ audio_info.info.raw.channels);
+}
+
+static void OnStreamProcess(void* arg) {
+ struct AudioContext* audio_context = arg;
+ struct pw_buffer* buffer = pw_stream_dequeue_buffer(audio_context->stream);
+ if (!buffer) {
+ LOG("Failed to dequeue stream buffer");
+ return;
+ }
+
+ struct ProtoHeader header = {
+ .type = kProtoTypeAudio,
+ .timestamp = buffer->time,
+ };
+ for (size_t index = 0; index < buffer->buffer->n_datas; index++)
+ header.size += buffer->buffer->datas[index].chunk->size;
+
+ struct ProtoImpl* proto_impl = malloc(sizeof(struct ProtoImpl) + header.size);
+ if (!proto_impl) {
+ LOG("Failed to allocate proto (%s)", strerror(errno));
+ goto rollback_buffer;
+ }
+
+ proto_impl->header = header;
+ const struct Proto proto = {
+ .Destroy = ProtoDestroy,
+ .header = &proto_impl->header,
+ .data = proto_impl->data,
+ };
+ memcpy(proto_impl, &proto, sizeof(proto));
+ uint8_t* target = proto_impl->data;
+ for (size_t index = 0; index < buffer->buffer->n_datas; index++) {
+ const void* source = buffer->buffer->datas[index].data;
+ struct spa_chunk* chunk = buffer->buffer->datas[index].chunk;
+ memcpy(target, source + chunk->offset, chunk->size);
+ target += chunk->size;
+ }
+ if (!IoContextWrite(audio_context->io_context, &proto_impl->proto)) {
+ LOG("Failed to write audio proto");
+ goto rollback_buffer;
+ }
+
+rollback_buffer:
+ assert(!pw_stream_queue_buffer(audio_context->stream, buffer));
+}
+
+struct AudioContext* AudioContextCreate(struct IoContext* io_context,
+ struct Proto* proto_hello) {
+ assert(proto_hello->header->type == kProtoTypeHello);
+ struct AudioContext* audio_context = malloc(sizeof(struct AudioContext));
+ if (!audio_context) {
+ LOG("Failed to allocate audio context (%s)", strerror(errno));
+ goto rollback_proto_hello;
+ }
+
+ *audio_context = (struct AudioContext){
+ .io_context = io_context,
+ .thread_loop = pw_thread_loop_new("audio-capture", NULL),
+ };
+ if (!audio_context->thread_loop) {
+ LOG("Failed to create thread loop");
+ goto rollback_audio_context;
+ }
+
+ pw_thread_loop_lock(audio_context->thread_loop);
+ if (pw_thread_loop_start(audio_context->thread_loop)) {
+ LOG("Failed to start thread loop");
+ goto rollback_thread_loop;
+ }
+
+ struct pw_properties* properties = pw_properties_new(
+#define _(...) __VA_ARGS__
+ _(PW_KEY_NODE_NAME, "streamer-sink"), _(PW_KEY_NODE_VIRTUAL, "true"),
+ _(PW_KEY_MEDIA_CLASS, "Audio/Sink"), NULL
+#undef _
+ );
+ if (!properties) {
+ LOG("Failed to create properties");
+ goto rollback_thread_loop;
+ }
+
+ static const struct pw_stream_events kStreamEvents = {
+ .version = PW_VERSION_STREAM_EVENTS,
+ .state_changed = OnStreamStateChanged,
+ .param_changed = OnStreamParamChanged,
+ .process = OnStreamProcess,
+ };
+ audio_context->stream = pw_stream_new_simple(
+ pw_thread_loop_get_loop(audio_context->thread_loop), "audio-capture",
+ properties, &kStreamEvents, audio_context);
+ if (!audio_context->stream) {
+ LOG("Failed to create stream");
+ goto rollback_thread_loop;
+ }
+
+ uint8_t buffer[1024];
+ struct spa_pod_builder builder = {
+ .data = buffer,
+ .size = sizeof(buffer),
+ };
+ if (proto_hello->header->size != sizeof(struct spa_audio_info_raw)) {
+ LOG("Invalid hello proto");
+ goto rollback_stream;
+ }
+
+ const struct spa_pod* params[] = {
+ spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat,
+ proto_hello->data),
+ };
+ static const enum pw_stream_flags kStreamFlags = (enum pw_stream_flags)(
+ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS);
+ if (pw_stream_connect(audio_context->stream, PW_DIRECTION_INPUT, PW_ID_ANY,
+ kStreamFlags, params, LENGTH(params))) {
+ LOG("Failed to connect stream");
+ goto rollback_stream;
+ }
+
+ pw_thread_loop_unlock(audio_context->thread_loop);
+ proto_hello->Destroy(proto_hello);
+ return audio_context;
+
+rollback_stream:
+ pw_stream_destroy(audio_context->stream);
+rollback_thread_loop:
+ pw_thread_loop_unlock(audio_context->thread_loop);
+ pw_thread_loop_destroy(audio_context->thread_loop);
+rollback_audio_context:
+ free(audio_context);
+rollback_proto_hello:
+ proto_hello->Destroy(proto_hello);
+ return NULL;
+}
+
+void AudioContextDestroy(struct AudioContext* audio_context) {
+ pw_thread_loop_lock(audio_context->thread_loop);
+ pw_stream_destroy(audio_context->stream);
+ pw_thread_loop_unlock(audio_context->thread_loop);
+ pw_thread_loop_destroy(audio_context->thread_loop);
+ free(audio_context);
+}
diff --git a/capture_kms.h b/audio_context.h
index 52531d2..57d8110 100644
--- a/capture_kms.h
+++ b/audio_context.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,18 +15,15 @@
* along with streamer. If not, see <https://www.gnu.org/licenses/>.
*/
-#ifndef STREAMER_CAPTURE_KMS_H_
-#define STREAMER_CAPTURE_KMS_H_
+#ifndef STREAMER_AUDIO_CONTEXT_H_
+#define STREAMER_AUDIO_CONTEXT_H_
-#include "capture.h"
+struct AudioContext;
+struct IoContext;
+struct Proto;
-struct CaptureContextKms;
+struct AudioContext* AudioContextCreate(struct IoContext* io_context,
+ struct Proto* proto_hello);
+void AudioContextDestroy(struct AudioContext* audio_context);
-struct CaptureContextKms* CaptureContextKmsCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user);
-int CaptureContextKmsGetEventsFd(struct CaptureContextKms* capture_context);
-bool CaptureContextKmsProcessEvents(struct CaptureContextKms* capture_context);
-void CaptureContextKmsDestroy(struct CaptureContextKms* capture_context);
-
-#endif // STREAMER_CAPTURE_KMS_H_
+#endif // STREAMER_AUDIO_CONTEXT_H_
diff --git a/bitstream.c b/bitstream.c
deleted file mode 100644
index 43d6d2c..0000000
--- a/bitstream.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "bitstream.h"
-
-void BitstreamAppend(struct Bitstream* bitstream, size_t size, uint32_t bits) {
- uint8_t* ptr = (uint8_t*)bitstream->data + bitstream->size / 8;
- size_t vacant_bits = 8 - bitstream->size % 8;
- *ptr &= ~0 << vacant_bits;
-
- if (vacant_bits >= size) {
- *ptr |= bits << (vacant_bits - size);
- bitstream->size += size;
- return;
- }
-
- *ptr |= bits >> (size - vacant_bits);
- bitstream->size += vacant_bits;
- BitstreamAppend(bitstream, size - vacant_bits, bits);
-}
-
-void BitstreamAppendUE(struct Bitstream* bitstream, uint32_t bits) {
- size_t size = 0;
- uint32_t dummy = ++bits;
- while (dummy) {
- dummy >>= 1;
- size++;
- }
- BitstreamAppend(bitstream, size - 1, 0);
- BitstreamAppend(bitstream, size, bits);
-}
-
-void BitstreamAppendSE(struct Bitstream* bitstream, int32_t bits) {
- BitstreamAppendUE(
- bitstream, bits <= 0 ? (uint32_t)(-2 * bits) : (uint32_t)(2 * bits - 1));
-}
-
-void BitstreamByteAlign(struct Bitstream* bitstream) {
- uint8_t* ptr = (uint8_t*)bitstream->data + bitstream->size / 8;
- size_t vacant_bits = 8 - bitstream->size % 8;
- if (vacant_bits == 8) return;
-
- *ptr &= ~0 << vacant_bits;
- bitstream->size += vacant_bits;
-}
-
-void BitstreamInflate(struct Bitstream* bitstream,
- const struct Bitstream* source) {
- uint8_t* dst_data = (uint8_t*)bitstream->data + (bitstream->size + 7) / 8;
- uint8_t* src_data = source->data;
- size_t src_size = (source->size + 7) / 8;
-
- if (src_size > 0) *dst_data++ = *src_data++;
- if (src_size > 1) *dst_data++ = *src_data++;
-
- for (size_t i = 2; i < src_size; i++) {
- // mburakov: emulation_prevention_three_byte
- if (!dst_data[-2] && !dst_data[-1] && !src_data[0]) *dst_data++ = 3;
- *dst_data++ = *src_data++;
- }
-
- bitstream->size = (size_t)(dst_data - (uint8_t*)bitstream->data) * 8;
-}
diff --git a/buffer_queue.c b/buffer_queue.c
deleted file mode 100644
index bd29b14..0000000
--- a/buffer_queue.c
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "buffer_queue.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <threads.h>
-
-struct BufferQueue {
- mtx_t mutex;
- struct BufferQueueItem** items;
- size_t size;
- size_t alloc;
-};
-
-struct BufferQueueItem* BufferQueueItemCreate(const void* data, size_t size) {
- struct BufferQueueItem* buffer_queue_item =
- malloc(sizeof(struct BufferQueueItem) + size);
- if (!buffer_queue_item) return NULL;
- buffer_queue_item->size = size;
- memcpy(buffer_queue_item->data, data, size);
- return buffer_queue_item;
-}
-
-void BufferQueueItemDestroy(struct BufferQueueItem* buffer_queue_item) {
- free(buffer_queue_item);
-}
-
-struct BufferQueue* BufferQueueCreate(void) {
- struct BufferQueue* buffer_queue = calloc(1, sizeof(struct BufferQueue));
- if (!buffer_queue) return false;
- if (mtx_init(&buffer_queue->mutex, mtx_plain) != thrd_success)
- goto rollback_buffer_queue;
- return buffer_queue;
-
-rollback_buffer_queue:
- free(buffer_queue);
- return NULL;
-}
-
-bool BufferQueueQueue(struct BufferQueue* buffer_queue,
- struct BufferQueueItem* buffer_queue_item) {
- if (!buffer_queue_item || mtx_lock(&buffer_queue->mutex) != thrd_success)
- return false;
-
- if (buffer_queue->size == buffer_queue->alloc) {
- size_t alloc = buffer_queue->alloc + 1;
- struct BufferQueueItem** items =
- realloc(buffer_queue->items, sizeof(struct BufferQueueItem*) * alloc);
- if (!items) {
- mtx_unlock(&buffer_queue->mutex);
- return false;
- }
- buffer_queue->items = items;
- buffer_queue->alloc = alloc;
- }
-
- buffer_queue->items[buffer_queue->size] = buffer_queue_item;
- buffer_queue->size++;
- mtx_unlock(&buffer_queue->mutex);
- return true;
-}
-
-bool BufferQueueDequeue(struct BufferQueue* buffer_queue,
- struct BufferQueueItem** buffer_queue_item) {
- if (mtx_lock(&buffer_queue->mutex) != thrd_success) return false;
- if (!buffer_queue->size) {
- *buffer_queue_item = NULL;
- } else {
- buffer_queue->size--;
- *buffer_queue_item = buffer_queue->items[0];
- memmove(buffer_queue->items, buffer_queue->items + 1,
- sizeof(struct BufferQueueItem*) * buffer_queue->size);
- }
- mtx_unlock(&buffer_queue->mutex);
- return true;
-}
-
-void BufferQueueDestroy(struct BufferQueue* buffer_queue) {
- for (size_t i = 0; i < buffer_queue->size; i++)
- BufferQueueItemDestroy(buffer_queue->items[i]);
- free(buffer_queue->items);
- mtx_destroy(&buffer_queue->mutex);
- free(buffer_queue);
-}
diff --git a/buffer_queue.h b/buffer_queue.h
deleted file mode 100644
index 4597fc4..0000000
--- a/buffer_queue.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_BUFFER_QUEUE_H_
-#define STREAMER_BUFFER_QUEUE_H_
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-struct BufferQueue;
-
-struct BufferQueueItem {
- size_t size;
- uint8_t data[];
-};
-
-struct BufferQueueItem* BufferQueueItemCreate(const void* data, size_t size);
-void BufferQueueItemDestroy(struct BufferQueueItem* buffer_queue_item);
-
-struct BufferQueue* BufferQueueCreate(void);
-bool BufferQueueQueue(struct BufferQueue* buffer_queue,
- struct BufferQueueItem* buffer_queue_item);
-bool BufferQueueDequeue(struct BufferQueue* buffer_queue,
- struct BufferQueueItem** buffer_queue_item);
-void BufferQueueDestroy(struct BufferQueue* buffer_queue);
-
-#endif // STREAMER_BUFFER_QUEUE_H_
diff --git a/capture.c b/capture.c
deleted file mode 100644
index c0ea634..0000000
--- a/capture.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "capture.h"
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "capture_kms.h"
-#include "capture_wlr.h"
-#include "toolbox/utils.h"
-
-struct CaptureContext {
- struct CaptureContextKms* kms;
- struct CaptureContextWlr* wlr;
-};
-
-struct CaptureContext* CaptureContextCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user) {
- struct CaptureContext* capture_context =
- calloc(1, sizeof(struct CaptureContext));
- if (!capture_context) {
- LOG("Failed to allocate capture context (%s)", strerror(errno));
- return NULL;
- }
-
- capture_context->kms = CaptureContextKmsCreate(gpu_context, callbacks, user);
- if (capture_context->kms) return capture_context;
-
- LOG("Failed to create kms capture context");
- capture_context->wlr = CaptureContextWlrCreate(gpu_context, callbacks, user);
- if (capture_context->wlr) return capture_context;
-
- LOG("Failed to create wlr capture context");
- free(capture_context);
- return NULL;
-}
-
-int CaptureContextGetEventsFd(struct CaptureContext* capture_context) {
- if (capture_context->kms)
- return CaptureContextKmsGetEventsFd(capture_context->kms);
- if (capture_context->wlr)
- return CaptureContextWlrGetEventsFd(capture_context->wlr);
- return -1;
-}
-
-bool CaptureContextProcessEvents(struct CaptureContext* capture_context) {
- if (capture_context->kms)
- return CaptureContextKmsProcessEvents(capture_context->kms);
- if (capture_context->wlr)
- return CaptureContextWlrProcessEvents(capture_context->wlr);
- return false;
-}
-
-void CaptureContextDestroy(struct CaptureContext* capture_context) {
- if (capture_context->wlr) CaptureContextWlrDestroy(capture_context->wlr);
- if (capture_context->kms) CaptureContextKmsDestroy(capture_context->kms);
- free(capture_context);
-}
diff --git a/capture.h b/capture.h
deleted file mode 100644
index a56a5d2..0000000
--- a/capture.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_CAPTURE_H_
-#define STREAMER_CAPTURE_H_
-
-#include <stdbool.h>
-
-struct CaptureContext;
-struct GpuContext;
-struct GpuFrame;
-
-struct CaptureContextCallbacks {
- void (*OnFrameReady)(void* user, const struct GpuFrame* gpu_frame);
-};
-
-struct CaptureContext* CaptureContextCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user);
-int CaptureContextGetEventsFd(struct CaptureContext* capture_context);
-bool CaptureContextProcessEvents(struct CaptureContext* capture_context);
-void CaptureContextDestroy(struct CaptureContext* capture_context);
-
-#endif // STREAMER_CAPTURE_H_
diff --git a/capture_kms.c b/capture_kms.c
deleted file mode 100644
index fa46bd3..0000000
--- a/capture_kms.c
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "capture_kms.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/timerfd.h>
-#include <time.h>
-#include <unistd.h>
-#include <xf86drm.h>
-
-#include "gpu.h"
-#include "toolbox/utils.h"
-
-static const int kCapturePeriod = 1000000000 / 60;
-
-struct CaptureContextKms {
- struct GpuContext* gpu_context;
- const struct CaptureContextCallbacks* callbacks;
- void* user;
-
- int drm_fd;
- uint32_t crtc_id;
- int timer_fd;
-};
-
-static int OpenAnyModule(void) {
- static const char* const modules[] = {
- "i915",
- "amdgpu",
- };
- for (size_t i = 0; i < LENGTH(modules); i++) {
- int drm_fd = drmOpen(modules[i], NULL);
- if (drm_fd >= 0) return drm_fd;
- LOG("Failed to open %s (%s)", modules[i], strerror(errno));
- }
- return -1;
-}
-
-static bool GetCrtcFb(int drm_fd, uint32_t crtc_id,
- struct drm_mode_fb_cmd2* drm_mode_fb_cmd2) {
- struct drm_mode_crtc drm_mode_crtc = {
- .crtc_id = crtc_id,
- };
- if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETCRTC, &drm_mode_crtc)) {
- LOG("Failed to get crtc %u (%s)", crtc_id, strerror(errno));
- return false;
- }
- if (!drm_mode_crtc.fb_id) {
- LOG("Crtc %u has no framebuffer", crtc_id);
- return false;
- }
-
- struct drm_mode_fb_cmd2 result = {
- .fb_id = drm_mode_crtc.fb_id,
- };
- if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETFB2, &result)) {
- LOG("Failed to get framebuffer %u (%s)", drm_mode_crtc.fb_id,
- strerror(errno));
- return false;
- }
- if (!result.handles[0]) {
- LOG("Framebuffer %u has no handles", drm_mode_crtc.fb_id);
- return false;
- }
-
- if (drm_mode_fb_cmd2) *drm_mode_fb_cmd2 = result;
- return true;
-}
-
-struct CaptureContextKms* CaptureContextKmsCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user) {
- struct CaptureContextKms* capture_context =
- malloc(sizeof(struct CaptureContextKms));
- if (!capture_context) {
- LOG("Failed to allocate capture context (%s)", strerror(errno));
- return NULL;
- }
- *capture_context = (struct CaptureContextKms){
- .gpu_context = gpu_context,
- .callbacks = callbacks,
- .user = user,
- .drm_fd = -1,
- .timer_fd = -1,
- };
-
- capture_context->drm_fd = OpenAnyModule();
- if (capture_context->drm_fd == -1) {
- LOG("Failed to open any module");
- goto rollback_capture_context;
- }
-
- uint32_t crtc_ids[16];
- struct drm_mode_card_res drm_mode_card_res = {
- .crtc_id_ptr = (uintptr_t)crtc_ids,
- .count_crtcs = LENGTH(crtc_ids),
- };
- if (drmIoctl(capture_context->drm_fd, DRM_IOCTL_MODE_GETRESOURCES,
- &drm_mode_card_res)) {
- LOG("Failed to get drm mode resources (%s)", strerror(errno));
- goto rollback_drm_fd;
- }
- for (size_t i = 0; i < drm_mode_card_res.count_crtcs; i++) {
- if (GetCrtcFb(capture_context->drm_fd, crtc_ids[i], NULL)) {
- LOG("Capturing crtc %u", crtc_ids[i]);
- capture_context->crtc_id = crtc_ids[i];
- break;
- }
- }
- if (!capture_context->crtc_id) {
- LOG("Nothing to capture");
- goto rollback_drm_fd;
- }
-
- capture_context->timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);
- if (capture_context->timer_fd == -1) {
- LOG("Failed to create timer (%s)", strerror(errno));
- goto rollback_drm_fd;
- }
- static const struct itimerspec kTimerSpec = {
- .it_interval.tv_nsec = kCapturePeriod,
- .it_value.tv_nsec = kCapturePeriod,
- };
- if (timerfd_settime(capture_context->timer_fd, 0, &kTimerSpec, NULL)) {
- LOG("Failed to arm timer (%s)", strerror(errno));
- goto rollback_timer_fd;
- }
- return capture_context;
-
-rollback_timer_fd:
- close(capture_context->timer_fd);
-rollback_drm_fd:
- drmClose(capture_context->drm_fd);
-rollback_capture_context:
- free(capture_context);
- return NULL;
-}
-
-int CaptureContextKmsGetEventsFd(struct CaptureContextKms* capture_context) {
- return capture_context->timer_fd;
-}
-
-bool CaptureContextKmsProcessEvents(struct CaptureContextKms* capture_context) {
- uint64_t expirations;
- if (read(capture_context->timer_fd, &expirations, sizeof(expirations)) !=
- sizeof(expirations)) {
- LOG("Failed to read timer expirations (%s)", strerror(errno));
- return false;
- }
-
- struct drm_mode_fb_cmd2 drm_mode_fb_cmd2;
- if (!GetCrtcFb(capture_context->drm_fd, capture_context->crtc_id,
- &drm_mode_fb_cmd2)) {
- return false;
- }
-
- struct GpuFramePlane planes[] = {
- {.dmabuf_fd = -1},
- {.dmabuf_fd = -1},
- {.dmabuf_fd = -1},
- {.dmabuf_fd = -1},
- };
- static_assert(LENGTH(planes) == LENGTH(drm_mode_fb_cmd2.handles),
- "Suspicious drm_mode_fb_cmd2 structure");
-
- size_t nplanes = 0;
- for (; nplanes < LENGTH(planes); nplanes++) {
- if (!drm_mode_fb_cmd2.handles[nplanes]) break;
- int status = drmPrimeHandleToFD(capture_context->drm_fd,
- drm_mode_fb_cmd2.handles[nplanes], 0,
- &planes[nplanes].dmabuf_fd);
- if (status) {
- LOG("Failed to get dmabuf fd (%d)", status);
- goto release_planes;
- }
- planes[nplanes].offset = drm_mode_fb_cmd2.offsets[nplanes];
- planes[nplanes].pitch = drm_mode_fb_cmd2.pitches[nplanes];
- planes[nplanes].modifier = drm_mode_fb_cmd2.modifier[nplanes];
- }
-
- struct GpuFrame* gpu_frame = GpuContextCreateFrame(
- capture_context->gpu_context, drm_mode_fb_cmd2.width,
- drm_mode_fb_cmd2.height, drm_mode_fb_cmd2.pixel_format, nplanes, planes);
- if (!gpu_frame) {
- LOG("Failed to create gpu frame");
- goto release_planes;
- }
-
- // mburakov: Capture context might get destroyed in callback.
- struct GpuContext* gpu_context = capture_context->gpu_context;
- capture_context->callbacks->OnFrameReady(capture_context->user, gpu_frame);
- GpuContextDestroyFrame(gpu_context, gpu_frame);
- return true;
-
-release_planes:
- CloseUniqueFds((int[]){planes[0].dmabuf_fd, planes[1].dmabuf_fd,
- planes[2].dmabuf_fd, planes[3].dmabuf_fd});
- return false;
-}
-
-void CaptureContextKmsDestroy(struct CaptureContextKms* capture_context) {
- close(capture_context->timer_fd);
- drmClose(capture_context->drm_fd);
- free(capture_context);
-}
diff --git a/capture_wlr.c b/capture_wlr.c
deleted file mode 100644
index 0e5b56d..0000000
--- a/capture_wlr.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifdef USE_WAYLAND
-
-#include "capture_wlr.h"
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <wayland-client.h>
-
-#include "gpu.h"
-#include "toolbox/utils.h"
-#include "wlr-export-dmabuf-unstable-v1.h"
-
-struct CaptureContextWlr {
- struct GpuContext* gpu_context;
- const struct CaptureContextCallbacks* callbacks;
- void* user;
-
- // Wayland globals
- struct wl_display* wl_display;
- struct wl_registry* wl_registry;
- struct wl_output* wl_output;
- struct zwlr_export_dmabuf_manager_v1* zwlr_export_dmabuf_manager_v1;
-
- // Wayland dynamics
- struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1;
-
- // Volatile states
- uint32_t gpu_frame_width;
- uint32_t gpu_frame_height;
- uint32_t gpu_frame_fourcc;
- size_t gpu_frame_nplanes;
- struct GpuFramePlane gpu_frame_planes[4];
-};
-
-static bool CaptureOutput(struct CaptureContextWlr* capture_context);
-
-static void OnWlRegistryGlobal(void* data, struct wl_registry* wl_registry,
- uint32_t name, const char* interface,
- uint32_t version) {
-#define MAYBE_BIND(what) \
- if (!strcmp(interface, what##_interface.name)) do { \
- capture_context->what = \
- wl_registry_bind(wl_registry, name, &what##_interface, version); \
- if (!capture_context->what) \
- LOG("Failed to bind " #what " (%s)", strerror(errno)); \
- return; \
- } while (false)
- struct CaptureContextWlr* capture_context = data;
- if (!capture_context->wl_output) MAYBE_BIND(wl_output);
- MAYBE_BIND(zwlr_export_dmabuf_manager_v1);
-#undef MAYBE_BIND
-}
-
-static void OnWlRegistryGlobalRemove(void* data, struct wl_registry* registry,
- uint32_t name) {
- (void)data;
- (void)registry;
- (void)name;
-}
-
-static bool InitWaylandGlobals(struct CaptureContextWlr* capture_context) {
- capture_context->wl_display = wl_display_connect(NULL);
- if (!capture_context->wl_display) {
- LOG("Failed to connect wl_display (%s)", strerror(errno));
- return false;
- }
-
- capture_context->wl_registry =
- wl_display_get_registry(capture_context->wl_display);
- if (!capture_context->wl_registry) {
- LOG("Failed to get wl_registry (%s)", strerror(errno));
- goto rollback_wl_display;
- }
-
- static const struct wl_registry_listener wl_registry_listener = {
- .global = OnWlRegistryGlobal,
- .global_remove = OnWlRegistryGlobalRemove,
- };
- if (wl_registry_add_listener(capture_context->wl_registry,
- &wl_registry_listener, capture_context)) {
- LOG("Failed to add wl_registry listener (%s)", strerror(errno));
- goto rollback_wl_registry;
- }
- if (wl_display_roundtrip(capture_context->wl_display) == -1) {
- LOG("Failed to roundtrip wl_display (%s)", strerror(errno));
- goto rollback_wl_registry;
- }
-
- if (!capture_context->wl_output ||
- !capture_context->zwlr_export_dmabuf_manager_v1) {
- LOG("Some required wayland globals are missing");
- goto rollback_wayland_globals;
- }
- return true;
-
-rollback_wayland_globals:
- if (capture_context->zwlr_export_dmabuf_manager_v1)
- zwlr_export_dmabuf_manager_v1_destroy(
- capture_context->zwlr_export_dmabuf_manager_v1);
- if (capture_context->wl_output) wl_output_destroy(capture_context->wl_output);
-rollback_wl_registry:
- wl_registry_destroy(capture_context->wl_registry);
-rollback_wl_display:
- wl_display_disconnect(capture_context->wl_display);
- return false;
-}
-
-static void DeinitWaylandGlobals(struct CaptureContextWlr* capture_context) {
- zwlr_export_dmabuf_manager_v1_destroy(
- capture_context->zwlr_export_dmabuf_manager_v1);
- wl_output_destroy(capture_context->wl_output);
- wl_registry_destroy(capture_context->wl_registry);
- wl_display_disconnect(capture_context->wl_display);
-}
-
-static void OnExportDmabufFrameFrame(
- void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
- uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
- uint32_t buffer_flags, uint32_t flags, uint32_t format, uint32_t mod_high,
- uint32_t mod_low, uint32_t num_objects) {
- (void)zwlr_export_dmabuf_frame_v1;
- (void)offset_x;
- (void)offset_y;
- (void)buffer_flags;
- (void)flags;
- struct CaptureContextWlr* capture_context = data;
- capture_context->gpu_frame_width = width;
- capture_context->gpu_frame_height = height;
- capture_context->gpu_frame_fourcc = format;
- capture_context->gpu_frame_nplanes = num_objects;
- for (size_t i = 0; i < LENGTH(capture_context->gpu_frame_planes); i++) {
- capture_context->gpu_frame_planes[i].dmabuf_fd = -1;
- capture_context->gpu_frame_planes[i].modifier =
- (uint64_t)mod_high << 32 | mod_low;
- }
-}
-
-static void OnExportDmabufFrameObject(
- void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
- uint32_t index, int32_t fd, uint32_t size, uint32_t offset, uint32_t stride,
- uint32_t plane_index) {
- (void)zwlr_export_dmabuf_frame_v1;
- (void)size;
- (void)index;
- struct CaptureContextWlr* capture_context = data;
- capture_context->gpu_frame_planes[plane_index].dmabuf_fd = fd;
- capture_context->gpu_frame_planes[plane_index].pitch = stride;
- capture_context->gpu_frame_planes[plane_index].offset = offset;
-}
-
-static void OnExportDmabufFrameReady(
- void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
- uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
- (void)zwlr_export_dmabuf_frame_v1;
- (void)tv_sec_hi;
- (void)tv_sec_lo;
- (void)tv_nsec;
- struct CaptureContextWlr* capture_context = data;
-
- struct GpuFrame* gpu_frame = GpuContextCreateFrame(
- capture_context->gpu_context, capture_context->gpu_frame_width,
- capture_context->gpu_frame_height, capture_context->gpu_frame_fourcc,
- capture_context->gpu_frame_nplanes, capture_context->gpu_frame_planes);
- if (!gpu_frame) {
- // TODO(mburakov): ... then what?
- abort();
- }
-
- // TODO(mburakov): Callee could theoretically drop client, and then below code
- // in this function would probably fail miserably. Do something about this!
- capture_context->callbacks->OnFrameReady(capture_context->user, gpu_frame);
-
- CloseUniqueFds((int[4]){capture_context->gpu_frame_planes[0].dmabuf_fd,
- capture_context->gpu_frame_planes[1].dmabuf_fd,
- capture_context->gpu_frame_planes[2].dmabuf_fd,
- capture_context->gpu_frame_planes[3].dmabuf_fd});
- zwlr_export_dmabuf_frame_v1_destroy(
- capture_context->zwlr_export_dmabuf_frame_v1);
- capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
-
- if (!CaptureOutput(capture_context)) {
- // TODO(mburakov): ... then what?
- abort();
- }
-}
-
-static void OnExportDmabufFrameCancel(
- void* data, struct zwlr_export_dmabuf_frame_v1* zwlr_export_dmabuf_frame_v1,
- uint32_t reason) {
- (void)zwlr_export_dmabuf_frame_v1;
- struct CaptureContextWlr* capture_context = data;
- CloseUniqueFds((int[4]){capture_context->gpu_frame_planes[0].dmabuf_fd,
- capture_context->gpu_frame_planes[1].dmabuf_fd,
- capture_context->gpu_frame_planes[2].dmabuf_fd,
- capture_context->gpu_frame_planes[3].dmabuf_fd});
- zwlr_export_dmabuf_frame_v1_destroy(
- capture_context->zwlr_export_dmabuf_frame_v1);
- capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
- bool result;
- switch (reason) {
- case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY:
- result = CaptureOutput(capture_context);
- break;
- case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT:
- result = false;
- break;
- case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_RESIZING:
- result = CaptureOutput(capture_context);
- break;
- default:
- __builtin_unreachable();
- }
- if (!result) {
- // TODO(mburakov): ... then what?
- abort();
- }
-}
-
-static bool CaptureOutput(struct CaptureContextWlr* capture_context) {
- capture_context->zwlr_export_dmabuf_frame_v1 =
- zwlr_export_dmabuf_manager_v1_capture_output(
- capture_context->zwlr_export_dmabuf_manager_v1, 1,
- capture_context->wl_output);
- if (!capture_context->zwlr_export_dmabuf_frame_v1) {
- LOG("Failed to capture zwlr_export_dmabuf_manager_v1 (%s)",
- strerror(errno));
- return false;
- }
-
- static const struct zwlr_export_dmabuf_frame_v1_listener
- zwlr_export_dmabuf_frame_v1_listener = {
- .frame = OnExportDmabufFrameFrame,
- .object = OnExportDmabufFrameObject,
- .ready = OnExportDmabufFrameReady,
- .cancel = OnExportDmabufFrameCancel,
- };
- if (zwlr_export_dmabuf_frame_v1_add_listener(
- capture_context->zwlr_export_dmabuf_frame_v1,
- &zwlr_export_dmabuf_frame_v1_listener, capture_context)) {
- LOG("Failed to add zwlr_export_dmabuf_frame_v1 listener (%s)",
- strerror(errno));
- goto rollback_frame;
- }
-
- if (wl_display_flush(capture_context->wl_display) == -1) {
- LOG("Failed to flush wl_display (%s)", strerror(errno));
- goto rollback_frame;
- }
- return true;
-
-rollback_frame:
- zwlr_export_dmabuf_frame_v1_destroy(
- capture_context->zwlr_export_dmabuf_frame_v1);
- capture_context->zwlr_export_dmabuf_frame_v1 = NULL;
- return false;
-}
-
-struct CaptureContextWlr* CaptureContextWlrCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user) {
- struct CaptureContextWlr* capture_context =
- malloc(sizeof(struct CaptureContextWlr));
- if (!capture_context) {
- LOG("Failed to allocate capture context (%s)", strerror(errno));
- return NULL;
- }
- *capture_context = (struct CaptureContextWlr){
- .gpu_context = gpu_context,
- .callbacks = callbacks,
- .user = user,
- };
-
- if (!InitWaylandGlobals(capture_context)) {
- LOG("Failed to initialize wayland globals");
- goto rollback_capture_context;
- }
- if (!CaptureOutput(capture_context)) {
- LOG("Failed to capture output");
- goto rollback_wayland_globals;
- }
- return capture_context;
-
-rollback_wayland_globals:
- DeinitWaylandGlobals(capture_context);
-rollback_capture_context:
- free(capture_context);
- return NULL;
-}
-
-int CaptureContextWlrGetEventsFd(struct CaptureContextWlr* capture_context) {
- int events_fd = wl_display_get_fd(capture_context->wl_display);
- if (events_fd == -1) LOG("Failed to get wl_display fd (%s)", strerror(errno));
- return events_fd;
-}
-
-bool CaptureContextWlrProcessEvents(struct CaptureContextWlr* capture_context) {
- bool result = wl_display_dispatch(capture_context->wl_display) != -1;
- if (!result) LOG("Failed to dispatch wl_display (%s)", strerror(errno));
- return result;
-}
-
-void CaptureContextWlrDestroy(struct CaptureContextWlr* capture_context) {
- if (capture_context->zwlr_export_dmabuf_frame_v1)
- zwlr_export_dmabuf_frame_v1_destroy(
- capture_context->zwlr_export_dmabuf_frame_v1);
- DeinitWaylandGlobals(capture_context);
- free(capture_context);
-}
-
-#endif // USE_WAYLAND
diff --git a/capture_wlr.h b/capture_wlr.h
deleted file mode 100644
index 8c732a3..0000000
--- a/capture_wlr.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_CAPTURE_WLR_H_
-#define STREAMER_CAPTURE_WLR_H_
-
-#include "capture.h"
-
-struct CaptureContextWlr;
-
-#ifdef USE_WAYLAND
-struct CaptureContextWlr* CaptureContextWlrCreate(
- struct GpuContext* gpu_context,
- const struct CaptureContextCallbacks* callbacks, void* user);
-int CaptureContextWlrGetEventsFd(struct CaptureContextWlr* capture_context);
-bool CaptureContextWlrProcessEvents(struct CaptureContextWlr* capture_context);
-void CaptureContextWlrDestroy(struct CaptureContextWlr* capture_context);
-#else // USE_WAYLAND
-#define CaptureContextWlrCreate(...) NULL
-#define CaptureContextWlrGetEventsFd(...) -1
-#define CaptureContextWlrProcessEvents(...) false
-#define CaptureContextWlrDestroy(...)
-#endif // USE_WAYLAND
-
-#endif // STREAMER_CAPTURE_WLR_H_
diff --git a/encode.c b/encode.c
deleted file mode 100644
index 317922a..0000000
--- a/encode.c
+++ /dev/null
@@ -1,942 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "encode.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <va/va.h>
-#include <va/va_drm.h>
-#include <va/va_drmcommon.h>
-
-#include "bitstream.h"
-#include "gpu.h"
-#include "hevc.h"
-#include "proto.h"
-#include "toolbox/perf.h"
-#include "toolbox/utils.h"
-
-struct EncodeContext {
- struct GpuContext* gpu_context;
- uint32_t width;
- uint32_t height;
- enum YuvColorspace colorspace;
- enum YuvRange range;
-
- int render_node;
- VADisplay va_display;
- VAConfigID va_config_id;
-
- uint32_t va_packed_headers;
- VAConfigAttribValEncHEVCFeatures va_hevc_features;
- VAConfigAttribValEncHEVCBlockSizes va_hevc_block_sizes;
-
- VAContextID va_context_id;
- VASurfaceID input_surface_id;
- struct GpuFrame* gpu_frame;
-
- VASurfaceID recon_surface_ids[2];
- VABufferID output_buffer_id;
-
- VAEncSequenceParameterBufferHEVC seq;
- VAEncPictureParameterBufferHEVC pic;
- VAEncSliceParameterBufferHEVC slice;
- size_t frame_counter;
-};
-
-static const char* VaErrorString(VAStatus error) {
- static const char* va_error_strings[] = {
- "VA_STATUS_SUCCESS",
- "VA_STATUS_ERROR_OPERATION_FAILED",
- "VA_STATUS_ERROR_ALLOCATION_FAILED",
- "VA_STATUS_ERROR_INVALID_DISPLAY",
- "VA_STATUS_ERROR_INVALID_CONFIG",
- "VA_STATUS_ERROR_INVALID_CONTEXT",
- "VA_STATUS_ERROR_INVALID_SURFACE",
- "VA_STATUS_ERROR_INVALID_BUFFER",
- "VA_STATUS_ERROR_INVALID_IMAGE",
- "VA_STATUS_ERROR_INVALID_SUBPICTURE",
- "VA_STATUS_ERROR_ATTR_NOT_SUPPORTED",
- "VA_STATUS_ERROR_MAX_NUM_EXCEEDED",
- "VA_STATUS_ERROR_UNSUPPORTED_PROFILE",
- "VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT",
- "VA_STATUS_ERROR_UNSUPPORTED_RT_FORMAT",
- "VA_STATUS_ERROR_UNSUPPORTED_BUFFERTYPE",
- "VA_STATUS_ERROR_SURFACE_BUSY",
- "VA_STATUS_ERROR_FLAG_NOT_SUPPORTED",
- "VA_STATUS_ERROR_INVALID_PARAMETER",
- "VA_STATUS_ERROR_RESOLUTION_NOT_SUPPORTED",
- "VA_STATUS_ERROR_UNIMPLEMENTED",
- "VA_STATUS_ERROR_SURFACE_IN_DISPLAYING",
- "VA_STATUS_ERROR_INVALID_IMAGE_FORMAT",
- "VA_STATUS_ERROR_DECODING_ERROR",
- "VA_STATUS_ERROR_ENCODING_ERROR",
- "VA_STATUS_ERROR_INVALID_VALUE",
- "???",
- "???",
- "???",
- "???",
- "???",
- "???",
- "VA_STATUS_ERROR_UNSUPPORTED_FILTER",
- "VA_STATUS_ERROR_INVALID_FILTER_CHAIN",
- "VA_STATUS_ERROR_HW_BUSY",
- "???",
- "VA_STATUS_ERROR_UNSUPPORTED_MEMORY_TYPE",
- "VA_STATUS_ERROR_NOT_ENOUGH_BUFFER",
- "VA_STATUS_ERROR_TIMEDOUT",
- };
- return VA_STATUS_SUCCESS <= error && error <= VA_STATUS_ERROR_TIMEDOUT
- ? va_error_strings[error - VA_STATUS_SUCCESS]
- : "???";
-}
-
-static void OnVaLogMessage(void* context, const char* message) {
- (void)context;
- size_t len = strlen(message);
- while (message[len - 1] == '\n') len--;
- LOG("%.*s", (int)len, message);
-}
-
-static bool InitializeCodecCaps(struct EncodeContext* encode_context) {
- VAConfigAttrib attrib_list[] = {
- {.type = VAConfigAttribEncPackedHeaders},
- {.type = VAConfigAttribEncHEVCFeatures},
- {.type = VAConfigAttribEncHEVCBlockSizes},
- };
- VAStatus status = vaGetConfigAttributes(
- encode_context->va_display, VAProfileHEVCMain, VAEntrypointEncSlice,
- attrib_list, LENGTH(attrib_list));
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to get va config attributes (%s)", VaErrorString(status));
- return false;
- }
-
- if (attrib_list[0].value == VA_ATTRIB_NOT_SUPPORTED) {
- LOG("VAConfigAttribEncPackedHeaders is not supported");
- } else {
- LOG("VAConfigAttribEncPackedHeaders is 0x%08x", attrib_list[0].value);
- encode_context->va_packed_headers = attrib_list[0].value;
- }
-
- if (attrib_list[1].value == VA_ATTRIB_NOT_SUPPORTED) {
- LOG("VAConfigAttribEncHEVCFeatures is not supported");
- encode_context->va_hevc_features = (VAConfigAttribValEncHEVCFeatures){
- .bits =
- {
- // mburakov: ffmpeg hardcodes these for i965 Skylake driver.
- .separate_colour_planes = 0, // Table 6-1
- .scaling_lists = 0, // No scaling lists
- .amp = 1, // hardcoded
- .sao = 0, // hardcoded
- .pcm = 0, // hardcoded
- .temporal_mvp = 0, // hardcoded
- .strong_intra_smoothing = 0, // TODO
- .dependent_slices = 0, // No slice segments
- .sign_data_hiding = 0, // TODO
- .constrained_intra_pred = 0, // TODO
- .transform_skip = 0, // defaulted
- .cu_qp_delta = 0, // Fixed quality
- .weighted_prediction = 0, // TODO
- .transquant_bypass = 0, // TODO
- .deblocking_filter_disable = 0, // TODO
- },
- };
- } else {
- LOG("VAConfigAttribEncHEVCFeatures is 0x%08x", attrib_list[1].value);
- encode_context->va_hevc_features.value = attrib_list[1].value;
- }
-
- if (attrib_list[2].value == VA_ATTRIB_NOT_SUPPORTED) {
- LOG("VAConfigAttribEncHEVCBlockSizes is not supported");
- encode_context->va_hevc_block_sizes = (VAConfigAttribValEncHEVCBlockSizes){
- .bits =
- {
- // mburakov: ffmpeg hardcodes these for i965 Skylake driver.
- .log2_max_coding_tree_block_size_minus3 = 2, // hardcoded
- .log2_min_coding_tree_block_size_minus3 = 0, // TODO
- .log2_min_luma_coding_block_size_minus3 = 0, // hardcoded
- .log2_max_luma_transform_block_size_minus2 = 3, // hardcoded
- .log2_min_luma_transform_block_size_minus2 = 0, // hardcoded
- .max_max_transform_hierarchy_depth_inter = 3, // hardcoded
- .min_max_transform_hierarchy_depth_inter = 0, // defaulted
- .max_max_transform_hierarchy_depth_intra = 3, // hardcoded
- .min_max_transform_hierarchy_depth_intra = 0, // defaulted
- .log2_max_pcm_coding_block_size_minus3 = 0, // TODO
- .log2_min_pcm_coding_block_size_minus3 = 0, // TODO
- },
- };
- } else {
- LOG("VAConfigAttribEncHEVCBlockSizes is 0x%08x", attrib_list[2].value);
- encode_context->va_hevc_block_sizes.value = attrib_list[2].value;
- }
-
-#ifndef NDEBUG
- const typeof(encode_context->va_hevc_features.bits)* features_bits =
- &encode_context->va_hevc_features.bits;
- const typeof(encode_context->va_hevc_block_sizes.bits)* block_sizes_bits =
- &encode_context->va_hevc_block_sizes.bits;
- LOG("VAConfigAttribEncHEVCFeatures dump:"
- "\n\tseparate_colour_planes = %u"
- "\n\tscaling_lists = %u"
- "\n\tamp = %u"
- "\n\tsao = %u"
- "\n\tpcm = %u"
- "\n\ttemporal_mvp = %u"
- "\n\tstrong_intra_smoothing = %u"
- "\n\tdependent_slices = %u"
- "\n\tsign_data_hiding = %u"
- "\n\tconstrained_intra_pred = %u"
- "\n\ttransform_skip = %u"
- "\n\tcu_qp_delta = %u"
- "\n\tweighted_prediction = %u"
- "\n\ttransquant_bypass = %u"
- "\n\tdeblocking_filter_disable = %u",
- features_bits->separate_colour_planes, features_bits->scaling_lists,
- features_bits->amp, features_bits->sao, features_bits->pcm,
- features_bits->temporal_mvp, features_bits->strong_intra_smoothing,
- features_bits->dependent_slices, features_bits->sign_data_hiding,
- features_bits->constrained_intra_pred, features_bits->transform_skip,
- features_bits->cu_qp_delta, features_bits->weighted_prediction,
- features_bits->transquant_bypass,
- features_bits->deblocking_filter_disable);
- LOG("VAConfigAttribEncHEVCBlockSizes dump:"
- "\n\tlog2_max_coding_tree_block_size_minus3 = %u"
- "\n\tlog2_min_coding_tree_block_size_minus3 = %u"
- "\n\tlog2_min_luma_coding_block_size_minus3 = %u"
- "\n\tlog2_max_luma_transform_block_size_minus2 = %u"
- "\n\tlog2_min_luma_transform_block_size_minus2 = %u"
- "\n\tmax_max_transform_hierarchy_depth_inter = %u"
- "\n\tmin_max_transform_hierarchy_depth_inter = %u"
- "\n\tmax_max_transform_hierarchy_depth_intra = %u"
- "\n\tmin_max_transform_hierarchy_depth_intra = %u"
- "\n\tlog2_max_pcm_coding_block_size_minus3 = %u"
- "\n\tlog2_min_pcm_coding_block_size_minus3 = %u",
- block_sizes_bits->log2_max_coding_tree_block_size_minus3,
- block_sizes_bits->log2_min_coding_tree_block_size_minus3,
- block_sizes_bits->log2_min_luma_coding_block_size_minus3,
- block_sizes_bits->log2_max_luma_transform_block_size_minus2,
- block_sizes_bits->log2_min_luma_transform_block_size_minus2,
- block_sizes_bits->max_max_transform_hierarchy_depth_inter,
- block_sizes_bits->min_max_transform_hierarchy_depth_inter,
- block_sizes_bits->max_max_transform_hierarchy_depth_intra,
- block_sizes_bits->min_max_transform_hierarchy_depth_intra,
- block_sizes_bits->log2_max_pcm_coding_block_size_minus3,
- block_sizes_bits->log2_min_pcm_coding_block_size_minus3);
-#endif
-
- return true;
-}
-
-static struct GpuFrame* VaSurfaceToGpuFrame(VADisplay va_display,
- VASurfaceID va_surface_id,
- struct GpuContext* gpu_context) {
- VADRMPRIMESurfaceDescriptor prime;
- VAStatus status = vaExportSurfaceHandle(
- va_display, va_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
- VA_EXPORT_SURFACE_WRITE_ONLY | VA_EXPORT_SURFACE_COMPOSED_LAYERS, &prime);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to export va surface (%s)", VaErrorString(status));
- return NULL;
- }
-
- struct GpuFramePlane planes[] = {{.dmabuf_fd = -1},
- {.dmabuf_fd = -1},
- {.dmabuf_fd = -1},
- {.dmabuf_fd = -1}};
- static_assert(LENGTH(planes) == LENGTH(prime.layers[0].object_index),
- "Suspicious VADRMPRIMESurfaceDescriptor structure");
-
- for (size_t i = 0; i < prime.layers[0].num_planes; i++) {
- uint32_t object_index = prime.layers[0].object_index[i];
- planes[i] = (struct GpuFramePlane){
- .dmabuf_fd = prime.objects[object_index].fd,
- .pitch = prime.layers[0].pitch[i],
- .offset = prime.layers[0].offset[i],
- .modifier = prime.objects[object_index].drm_format_modifier,
- };
- }
-
- struct GpuFrame* gpu_frame =
- GpuContextCreateFrame(gpu_context, prime.width, prime.height,
- prime.fourcc, prime.layers[0].num_planes, planes);
- if (!gpu_frame) {
- LOG("Failed to create gpu frame");
- goto release_planes;
- }
- return gpu_frame;
-
-release_planes:
- CloseUniqueFds((int[]){planes[0].dmabuf_fd, planes[1].dmabuf_fd,
- planes[2].dmabuf_fd, planes[3].dmabuf_fd});
- return NULL;
-}
-
-static void InitializeSeqHeader(struct EncodeContext* encode_context,
- uint16_t pic_width_in_luma_samples,
- uint16_t pic_height_in_luma_samples) {
- const typeof(encode_context->va_hevc_features.bits)* features_bits =
- &encode_context->va_hevc_features.bits;
- const typeof(encode_context->va_hevc_block_sizes.bits)* block_sizes_bits =
- &encode_context->va_hevc_block_sizes.bits;
-
- uint8_t log2_diff_max_min_luma_coding_block_size =
- block_sizes_bits->log2_max_coding_tree_block_size_minus3 -
- block_sizes_bits->log2_min_luma_coding_block_size_minus3;
- uint8_t log2_diff_max_min_transform_block_size =
- block_sizes_bits->log2_max_luma_transform_block_size_minus2 -
- block_sizes_bits->log2_min_luma_transform_block_size_minus2;
-
- encode_context->seq = (VAEncSequenceParameterBufferHEVC){
- .general_profile_idc = 1, // Main profile
- .general_level_idc = 120, // Level 4
- .general_tier_flag = 0, // Main tier
-
- .intra_period = 120, // Where this one comes from?
- .intra_idr_period = 120, // Each I frame is an IDR frame
- .ip_period = 1, // No B-frames
- .bits_per_second = 0, // TODO
-
- .pic_width_in_luma_samples = pic_width_in_luma_samples,
- .pic_height_in_luma_samples = pic_height_in_luma_samples,
-
- .seq_fields.bits =
- {
- .chroma_format_idc = 1, // 4:2:0
- .separate_colour_plane_flag = 0, // Table 6-1
- .bit_depth_luma_minus8 = 0, // 8 bpp luma
- .bit_depth_chroma_minus8 = 0, // 8 bpp chroma
- .scaling_list_enabled_flag = 0, // No scaling lists
- .strong_intra_smoothing_enabled_flag = 0, // defaulted
-
- .amp_enabled_flag = features_bits->amp != 0,
- .sample_adaptive_offset_enabled_flag = features_bits->sao,
- .pcm_enabled_flag = features_bits->pcm,
- .pcm_loop_filter_disabled_flag = 0, // defaulted
- .sps_temporal_mvp_enabled_flag = features_bits->temporal_mvp,
-
- .low_delay_seq = 1, // No B-frames
- .hierachical_flag = 0, // defaulted
- },
-
- .log2_min_luma_coding_block_size_minus3 =
- block_sizes_bits->log2_min_luma_coding_block_size_minus3,
- .log2_diff_max_min_luma_coding_block_size =
- log2_diff_max_min_luma_coding_block_size,
- .log2_min_transform_block_size_minus2 =
- block_sizes_bits->log2_min_luma_transform_block_size_minus2,
- .log2_diff_max_min_transform_block_size =
- log2_diff_max_min_transform_block_size,
- .max_transform_hierarchy_depth_inter =
- block_sizes_bits->max_max_transform_hierarchy_depth_inter,
- .max_transform_hierarchy_depth_intra =
- block_sizes_bits->max_max_transform_hierarchy_depth_intra,
-
- .pcm_sample_bit_depth_luma_minus1 = 0, // defaulted
- .pcm_sample_bit_depth_chroma_minus1 = 0, // defaulted
- .log2_min_pcm_luma_coding_block_size_minus3 = 0, // defaulted
- .log2_max_pcm_luma_coding_block_size_minus3 = 0, // defaulted
-
- .vui_parameters_present_flag = 1,
- .vui_fields.bits =
- {
- .aspect_ratio_info_present_flag = 0, // defaulted
- .neutral_chroma_indication_flag = 0, // defaulted
- .field_seq_flag = 0, // defaulted
- .vui_timing_info_present_flag = 0, // No timing
- .bitstream_restriction_flag = 1, // hardcoded
- .tiles_fixed_structure_flag = 0, // defaulted
- .motion_vectors_over_pic_boundaries_flag = 1, // hardcoded
- .restricted_ref_pic_lists_flag = 1, // hardcoded
- .log2_max_mv_length_horizontal = 15, // hardcoded
- .log2_max_mv_length_vertical = 15, // hardcoded
- },
-
- .vui_num_units_in_tick = 0, // No timing
- .vui_time_scale = 0, // No timing
- .min_spatial_segmentation_idc = 0, // defaulted
- .max_bytes_per_pic_denom = 0, // hardcoded
- .max_bits_per_min_cu_denom = 0, // hardcoded
-
- .scc_fields.bits =
- {
- .palette_mode_enabled_flag = 0, // defaulted
- },
- };
-}
-
-static void InitializePicHeader(struct EncodeContext* encode_context) {
- const typeof(encode_context->seq.seq_fields.bits)* seq_bits =
- &encode_context->seq.seq_fields.bits;
- const typeof(encode_context->va_hevc_features.bits)* features_bits =
- &encode_context->va_hevc_features.bits;
-
- uint8_t collocated_ref_pic_index =
- seq_bits->sps_temporal_mvp_enabled_flag ? 0 : 0xff;
-
- encode_context->pic = (VAEncPictureParameterBufferHEVC){
- .decoded_curr_pic =
- {
- .picture_id = VA_INVALID_ID, // dynamic
- .flags = VA_PICTURE_HEVC_INVALID, // dynamic
- },
-
- // .reference_frames[15],
-
- .coded_buf = encode_context->output_buffer_id,
- .collocated_ref_pic_index = collocated_ref_pic_index,
- .last_picture = 0, // hardcoded
-
- .pic_init_qp = 30, // Fixed quality
- .diff_cu_qp_delta_depth = 0, // Fixed quality
- .pps_cb_qp_offset = 0, // hardcoded
- .pps_cr_qp_offset = 0, // hardcoded
-
- .num_tile_columns_minus1 = 0, // No tiles
- .num_tile_rows_minus1 = 0, // No tiles
- .column_width_minus1 = {0}, // No tiles
- .row_height_minus1 = {0}, // No tiles
-
- .log2_parallel_merge_level_minus2 = 0, // defaulted
- .ctu_max_bitsize_allowed = 0, // hardcoded
- .num_ref_idx_l0_default_active_minus1 = 0, // hardcoded
- .num_ref_idx_l1_default_active_minus1 = 0, // hardcoded
- .slice_pic_parameter_set_id = 0, // hardcoded
- .nal_unit_type = 0, // dynamic
-
- .pic_fields.bits =
- {
- .idr_pic_flag = 0, // dynamic
- .coding_type = 0, // dynamic
- .reference_pic_flag = 1, // No B-frames
-
- .dependent_slice_segments_enabled_flag = 0, // defaulted
- .sign_data_hiding_enabled_flag = 0, // defaulted
- .constrained_intra_pred_flag = 0, // defaulted
- .transform_skip_enabled_flag = features_bits->transform_skip,
- .cu_qp_delta_enabled_flag = 0, // Fixed quality
- .weighted_pred_flag = 0, // defaulted
- .weighted_bipred_flag = 0, // defaulted
- .transquant_bypass_enabled_flag = 0, // defaulted
- .tiles_enabled_flag = 0, // No tiles
- .entropy_coding_sync_enabled_flag = 0, // defaulted
- .loop_filter_across_tiles_enabled_flag = 0, // No tiles
-
- .pps_loop_filter_across_slices_enabled_flag = 1, // hardcoded
- .scaling_list_data_present_flag = 0, // No scaling lists
-
- .screen_content_flag = 0, // TODO
- .enable_gpu_weighted_prediction = 0, // hardcoded
- .no_output_of_prior_pics_flag = 0, // hardcoded
- },
-
- .hierarchical_level_plus1 = 0, // defaulted
- .scc_fields.bits =
- {
- .pps_curr_pic_ref_enabled_flag = 0, // defaulted
- },
- };
-
- for (size_t i = 0; i < LENGTH(encode_context->pic.reference_frames); i++) {
- encode_context->pic.reference_frames[i] = (VAPictureHEVC){
- .picture_id = VA_INVALID_ID,
- .flags = VA_PICTURE_HEVC_INVALID,
- };
- }
-}
-
-static void InitializeSliceHeader(struct EncodeContext* encode_context) {
- const typeof(encode_context->seq.seq_fields.bits)* seq_bits =
- &encode_context->seq.seq_fields.bits;
- const typeof(encode_context->va_hevc_block_sizes.bits)* block_sizes_bits =
- &encode_context->va_hevc_block_sizes.bits;
-
- uint32_t ctu_size =
- 1 << (block_sizes_bits->log2_max_coding_tree_block_size_minus3 + 3);
- uint32_t slice_block_rows =
- (encode_context->height + ctu_size - 1) / ctu_size;
- uint32_t slice_block_cols = (encode_context->width + ctu_size - 1) / ctu_size;
- uint32_t num_ctu_in_slice = slice_block_rows * slice_block_cols;
-
- encode_context->slice = (VAEncSliceParameterBufferHEVC){
- .slice_segment_address = 0, // No slice segments
- .num_ctu_in_slice = num_ctu_in_slice,
-
- .slice_type = 0, // dynamic
- .slice_pic_parameter_set_id =
- encode_context->pic.slice_pic_parameter_set_id,
-
- .num_ref_idx_l0_active_minus1 =
- encode_context->pic.num_ref_idx_l0_default_active_minus1,
- .num_ref_idx_l1_active_minus1 =
- encode_context->pic.num_ref_idx_l1_default_active_minus1,
-
- .luma_log2_weight_denom = 0, // defaulted
- .delta_chroma_log2_weight_denom = 0, // defaulted
-
- // .delta_luma_weight_l0[15],
- // .luma_offset_l0[15],
- // .delta_chroma_weight_l0[15][2],
- // .chroma_offset_l0[15][2],
- // .delta_luma_weight_l1[15],
- // .luma_offset_l1[15],
- // .delta_chroma_weight_l1[15][2],
- // .chroma_offset_l1[15][2],
-
- .max_num_merge_cand = 5, // defaulted
- .slice_qp_delta = 0, // Fixed quality
- .slice_cb_qp_offset = 0, // defaulted
- .slice_cr_qp_offset = 0, // defaulted
-
- .slice_beta_offset_div2 = 0, // defaulted
- .slice_tc_offset_div2 = 0, // defaulted
-
- .slice_fields.bits =
- {
- .last_slice_of_pic_flag = 1, // No slice segments
- .dependent_slice_segment_flag = 0, // No slice segments
- .colour_plane_id = 0, // defaulted
- .slice_temporal_mvp_enabled_flag =
- seq_bits->sps_temporal_mvp_enabled_flag,
- .slice_sao_luma_flag =
- seq_bits->sample_adaptive_offset_enabled_flag,
- .slice_sao_chroma_flag =
- seq_bits->sample_adaptive_offset_enabled_flag,
- .num_ref_idx_active_override_flag = 0, // hardcoded
- .mvd_l1_zero_flag = 0, // defaulted
- .cabac_init_flag = 0, // defaulted
- .slice_deblocking_filter_disabled_flag = 0, // defaulted
- .slice_loop_filter_across_slices_enabled_flag = 0, // defaulted
- .collocated_from_l0_flag = 0, // No B-frames
- },
-
- .pred_weight_table_bit_offset = 0, // defaulted
- .pred_weight_table_bit_length = 0, // defaulted
- };
-
- for (size_t i = 0; i < LENGTH(encode_context->slice.ref_pic_list0); i++) {
- encode_context->slice.ref_pic_list0[i] = (VAPictureHEVC){
- .picture_id = VA_INVALID_ID,
- .flags = VA_PICTURE_HEVC_INVALID,
- };
- }
-
- for (size_t i = 0; i < LENGTH(encode_context->slice.ref_pic_list1); i++) {
- encode_context->slice.ref_pic_list1[i] = (VAPictureHEVC){
- .picture_id = VA_INVALID_ID,
- .flags = VA_PICTURE_HEVC_INVALID,
- };
- }
-}
-
-struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context,
- uint32_t width, uint32_t height,
- enum YuvColorspace colorspace,
- enum YuvRange range) {
- struct EncodeContext* encode_context = malloc(sizeof(struct EncodeContext));
- if (!encode_context) {
- LOG("Faield to allocate encode context (%s)", strerror(errno));
- return NULL;
- }
-
- *encode_context = (struct EncodeContext){
- .gpu_context = gpu_context,
- .width = width,
- .height = height,
- .colorspace = colorspace,
- .range = range,
- };
-
- encode_context->render_node = open("/dev/dri/renderD128", O_RDWR);
- if (encode_context->render_node == -1) {
- LOG("Failed to open render node (%s)", strerror(errno));
- goto rollback_encode_context;
- }
-
- encode_context->va_display = vaGetDisplayDRM(encode_context->render_node);
- if (!encode_context->va_display) {
- LOG("Failed to get va display (%s)", strerror(errno));
- goto rollback_render_node;
- }
-
- vaSetErrorCallback(encode_context->va_display, OnVaLogMessage, NULL);
-#ifndef NDEBUG
- vaSetInfoCallback(encode_context->va_display, OnVaLogMessage, NULL);
-#endif // NDEBUG
-
- int major, minor;
- VAStatus status = vaInitialize(encode_context->va_display, &major, &minor);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to initialize va (%s)", VaErrorString(status));
- goto rollback_va_display;
- }
-
- LOG("Initialized VA %d.%d", major, minor);
- // TODO(mburakov): Check entry points?
-
- VAConfigAttrib attrib_list[] = {
- {.type = VAConfigAttribRTFormat, .value = VA_RT_FORMAT_YUV420},
- };
- status = vaCreateConfig(encode_context->va_display, VAProfileHEVCMain,
- VAEntrypointEncSlice, attrib_list,
- LENGTH(attrib_list), &encode_context->va_config_id);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create va config (%s)", VaErrorString(status));
- goto rollback_va_display;
- }
-
- if (!InitializeCodecCaps(encode_context)) {
- LOG("Failed to initialize codec caps");
- goto rollback_va_config_id;
- }
-
- // mburakov: Intel fails badly when min_cb_size value is not set to 16 and
- // log2_min_luma_coding_block_size_minus3 is not set to zero. Judging from
- // ffmpeg code, calculating one from another should work on other platforms,
- // but I hardcoded it instead since AMD is fine with alignment on 16 anyway.
- static const uint32_t min_cb_size = 16;
- uint32_t aligned_width =
- (encode_context->width + min_cb_size - 1) & ~(min_cb_size - 1);
- uint32_t aligned_height =
- (encode_context->height + min_cb_size - 1) & ~(min_cb_size - 1);
-
- status =
- vaCreateContext(encode_context->va_display, encode_context->va_config_id,
- (int)aligned_width, (int)aligned_height, VA_PROGRESSIVE,
- NULL, 0, &encode_context->va_context_id);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create va context (%s)", VaErrorString(status));
- goto rollback_va_config_id;
- }
-
- status =
- vaCreateSurfaces(encode_context->va_display, VA_RT_FORMAT_YUV420, width,
- height, &encode_context->input_surface_id, 1, NULL, 0);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create va input surface (%s)", VaErrorString(status));
- goto rollback_va_context_id;
- }
-
- encode_context->gpu_frame = VaSurfaceToGpuFrame(
- encode_context->va_display, encode_context->input_surface_id,
- encode_context->gpu_context);
- if (!encode_context->gpu_frame) {
- LOG("Failed to convert va surface to gpu frame");
- goto rollback_input_surface_id;
- }
-
- status = vaCreateSurfaces(encode_context->va_display, VA_RT_FORMAT_YUV420,
- aligned_width, aligned_height,
- encode_context->recon_surface_ids,
- LENGTH(encode_context->recon_surface_ids), NULL, 0);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create va recon surfaces (%s)", VaErrorString(status));
- goto rollback_gpu_frame;
- }
-
- unsigned int max_encoded_size =
- encode_context->width * encode_context->height * 3 / 2;
- status =
- vaCreateBuffer(encode_context->va_display, encode_context->va_context_id,
- VAEncCodedBufferType, max_encoded_size, 1, NULL,
- &encode_context->output_buffer_id);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create va output buffer (%s)", VaErrorString(status));
- goto rollback_recon_surface_ids;
- }
-
- InitializeSeqHeader(encode_context, (uint16_t)aligned_width,
- (uint16_t)aligned_height);
- InitializePicHeader(encode_context);
- InitializeSliceHeader(encode_context);
- return encode_context;
-
-rollback_recon_surface_ids:
- vaDestroySurfaces(encode_context->va_display,
- encode_context->recon_surface_ids,
- LENGTH(encode_context->recon_surface_ids));
-rollback_gpu_frame:
- GpuContextDestroyFrame(encode_context->gpu_context,
- encode_context->gpu_frame);
-rollback_input_surface_id:
- vaDestroySurfaces(encode_context->va_display,
- &encode_context->input_surface_id, 1);
-rollback_va_context_id:
- vaDestroyContext(encode_context->va_display, encode_context->va_config_id);
-rollback_va_config_id:
- vaDestroyConfig(encode_context->va_display, encode_context->va_config_id);
-rollback_va_display:
- vaTerminate(encode_context->va_display);
-rollback_render_node:
- close(encode_context->render_node);
-rollback_encode_context:
- free(encode_context);
- return NULL;
-}
-
-const struct GpuFrame* EncodeContextGetFrame(
- struct EncodeContext* encode_context) {
- return encode_context->gpu_frame;
-}
-
-static bool UploadBuffer(const struct EncodeContext* encode_context,
- VABufferType va_buffer_type, unsigned int size,
- void* data, VABufferID** presult) {
- VAStatus status =
- vaCreateBuffer(encode_context->va_display, encode_context->va_context_id,
- va_buffer_type, size, 1, data, *presult);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to create buffer (%s)", VaErrorString(status));
- return false;
- }
- (*presult)++;
- return true;
-}
-
-static bool UploadPackedBuffer(const struct EncodeContext* encode_context,
- VAEncPackedHeaderType packed_header_type,
- unsigned int bit_length, void* data,
- VABufferID** presult) {
- VAEncPackedHeaderParameterBuffer packed_header = {
- .type = packed_header_type,
- .bit_length = bit_length,
- .has_emulation_bytes = 1,
- };
- return UploadBuffer(encode_context, VAEncPackedHeaderParameterBufferType,
- sizeof(packed_header), &packed_header, presult) &&
- UploadBuffer(encode_context, VAEncPackedHeaderDataBufferType,
- (bit_length + 7) / 8, data, presult);
-}
-
-static void UpdatePicHeader(struct EncodeContext* encode_context, bool idr) {
- encode_context->pic.decoded_curr_pic = (VAPictureHEVC){
- .picture_id =
- encode_context
- ->recon_surface_ids[encode_context->frame_counter %
- LENGTH(encode_context->recon_surface_ids)],
- .pic_order_cnt = (int32_t)(encode_context->frame_counter %
- encode_context->seq.intra_idr_period),
- };
-
- if (idr) {
- encode_context->pic.reference_frames[0] = (VAPictureHEVC){
- .picture_id = VA_INVALID_ID,
- .flags = VA_PICTURE_HEVC_INVALID,
- };
- encode_context->pic.nal_unit_type = IDR_W_RADL;
- encode_context->pic.pic_fields.bits.idr_pic_flag = 1;
- encode_context->pic.pic_fields.bits.coding_type = 1;
- } else {
- encode_context->pic.reference_frames[0] = (VAPictureHEVC){
- .picture_id =
- encode_context
- ->recon_surface_ids[(encode_context->frame_counter - 1) %
- LENGTH(encode_context->recon_surface_ids)],
- .pic_order_cnt = (int32_t)((encode_context->frame_counter - 1) %
- encode_context->seq.intra_idr_period),
- };
- encode_context->pic.nal_unit_type = TRAIL_R;
- encode_context->pic.pic_fields.bits.idr_pic_flag = 0;
- encode_context->pic.pic_fields.bits.coding_type = 2;
- }
-}
-
-bool EncodeContextEncodeFrame(struct EncodeContext* encode_context, int fd,
- unsigned long long timestamp) {
- bool result = false;
- VABufferID buffers[8];
- VABufferID* buffer_ptr = buffers;
-
- bool idr =
- !(encode_context->frame_counter % encode_context->seq.intra_idr_period);
- if (idr && !UploadBuffer(encode_context, VAEncSequenceParameterBufferType,
- sizeof(encode_context->seq), &encode_context->seq,
- &buffer_ptr)) {
- LOG("Failed to upload sequence parameter buffer");
- goto rollback_buffers;
- }
-
- if (idr &&
- (encode_context->va_packed_headers & VA_ENC_PACKED_HEADER_SEQUENCE)) {
- char buffer[256];
- struct Bitstream bitstream = {
- .data = buffer,
- .size = 0,
- };
-
- static const struct MoreVideoParameters mvp = {
- .vps_max_dec_pic_buffering_minus1 = 1, // No B-frames
- .vps_max_num_reorder_pics = 0, // No B-frames
- };
- uint32_t conf_win_right_offset_luma =
- encode_context->seq.pic_width_in_luma_samples - encode_context->width;
- uint32_t conf_win_bottom_offset_luma =
- encode_context->seq.pic_height_in_luma_samples - encode_context->height;
- const struct MoreSeqParameters msp = {
- .conf_win_left_offset = 0,
- .conf_win_right_offset = conf_win_right_offset_luma / 2,
- .conf_win_top_offset = 0,
- .conf_win_bottom_offset = conf_win_bottom_offset_luma / 2,
- .sps_max_dec_pic_buffering_minus1 = 1, // No B-frames
- .sps_max_num_reorder_pics = 0, // No B-frames
- .video_signal_type_present_flag = 1,
- .video_full_range_flag = encode_context->range == kFullRange,
- .colour_description_present_flag = 1,
- .colour_primaries = 2, // Unsepcified
- .transfer_characteristics = 2, // Unspecified
- .matrix_coeffs =
- encode_context->colorspace == kItuRec601 ? 6 : 1, // Table E.5
- };
-
- PackVideoParameterSetNalUnit(&bitstream, &encode_context->seq, &mvp);
- PackSeqParameterSetNalUnit(&bitstream, &encode_context->seq, &msp);
- PackPicParameterSetNalUnit(&bitstream, &encode_context->pic);
- if (!UploadPackedBuffer(encode_context, VAEncPackedHeaderSequence,
- (unsigned int)bitstream.size, bitstream.data,
- &buffer_ptr)) {
- LOG("Failed to upload packed sequence header");
- goto rollback_buffers;
- }
- }
-
- UpdatePicHeader(encode_context, idr);
- if (!UploadBuffer(encode_context, VAEncPictureParameterBufferType,
- sizeof(encode_context->pic), &encode_context->pic,
- &buffer_ptr)) {
- LOG("Failed to upload picture parameter buffer");
- goto rollback_buffers;
- }
-
- encode_context->slice.slice_type = idr ? I : P;
- encode_context->slice.ref_pic_list0[0] =
- encode_context->pic.reference_frames[0];
- if (encode_context->va_packed_headers & VA_ENC_PACKED_HEADER_SLICE) {
- char buffer[256];
- struct Bitstream bitstream = {
- .data = buffer,
- .size = 0,
- };
- static const struct NegativePics negative_pics[] = {
- {
- .delta_poc_s0_minus1 = 0,
- .used_by_curr_pic_s0_flag = true,
- },
- };
- const struct MoreSliceParamerters msp = {
- .first_slice_segment_in_pic_flag = 1,
- .num_negative_pics = idr ? 0 : LENGTH(negative_pics),
- .negative_pics = idr ? NULL : negative_pics,
- };
- PackSliceSegmentHeaderNalUnit(&bitstream, &encode_context->seq,
- &encode_context->pic, &encode_context->slice,
- &msp);
- if (!UploadPackedBuffer(encode_context, VAEncPackedHeaderSlice,
- (unsigned int)bitstream.size, bitstream.data,
- &buffer_ptr)) {
- LOG("Failed to upload packed sequence header");
- goto rollback_buffers;
- }
- }
-
- if (!UploadBuffer(encode_context, VAEncSliceParameterBufferType,
- sizeof(encode_context->slice), &encode_context->slice,
- &buffer_ptr)) {
- LOG("Failed to upload slice parameter buffer");
- goto rollback_buffers;
- }
-
- VAStatus status =
- vaBeginPicture(encode_context->va_display, encode_context->va_context_id,
- encode_context->input_surface_id);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to begin va picture (%s)", VaErrorString(status));
- goto rollback_buffers;
- }
-
- int num_buffers = (int)(buffer_ptr - buffers);
- status = vaRenderPicture(encode_context->va_display,
- encode_context->va_context_id, buffers, num_buffers);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to render va picture (%s)", VaErrorString(status));
- goto rollback_buffers;
- }
-
- status =
- vaEndPicture(encode_context->va_display, encode_context->va_context_id);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to end va picture (%s)", VaErrorString(status));
- goto rollback_buffers;
- }
-
- status = vaSyncBuffer(encode_context->va_display,
- encode_context->output_buffer_id, VA_TIMEOUT_INFINITE);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to sync va buffer (%s)", VaErrorString(status));
- goto rollback_buffers;
- }
-
- VACodedBufferSegment* segment;
- status = vaMapBuffer(encode_context->va_display,
- encode_context->output_buffer_id, (void**)&segment);
- if (status != VA_STATUS_SUCCESS) {
- LOG("Failed to map va buffer (%s)", VaErrorString(status));
- goto rollback_buffers;
- }
- if (segment->next != NULL) {
- LOG("Next segment non-null!");
- abort();
- }
-
- struct Proto proto = {
- .size = segment->size,
- .type = PROTO_TYPE_VIDEO,
- .flags = idr ? PROTO_FLAG_KEYFRAME : 0,
- .latency = (uint16_t)(MicrosNow() - timestamp),
- };
- if (!WriteProto(fd, &proto, segment->buf)) {
- LOG("Failed to write encoded frame");
- goto rollback_segment;
- }
-
- encode_context->frame_counter++;
- result = true;
-
-rollback_segment:
- vaUnmapBuffer(encode_context->va_display, encode_context->output_buffer_id);
-rollback_buffers:
- while (buffer_ptr-- > buffers)
- vaDestroyBuffer(encode_context->va_display, *buffer_ptr);
- return result;
-}
-
-void EncodeContextDestroy(struct EncodeContext* encode_context) {
- vaDestroyBuffer(encode_context->va_display, encode_context->output_buffer_id);
- GpuContextDestroyFrame(encode_context->gpu_context,
- encode_context->gpu_frame);
- vaDestroySurfaces(encode_context->va_display,
- &encode_context->input_surface_id, 1);
- vaDestroyContext(encode_context->va_display, encode_context->va_context_id);
- vaDestroyConfig(encode_context->va_display, encode_context->va_config_id);
- vaTerminate(encode_context->va_display);
- close(encode_context->render_node);
- free(encode_context);
-}
diff --git a/encode.h b/encode.h
deleted file mode 100644
index 1612fc2..0000000
--- a/encode.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_ENCODE_H_
-#define STREAMER_ENCODE_H_
-
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "colorspace.h"
-
-struct EncodeContext;
-struct GpuContext;
-struct GpuFrame;
-
-struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context,
- uint32_t width, uint32_t height,
- enum YuvColorspace colorspace,
- enum YuvRange range);
-const struct GpuFrame* EncodeContextGetFrame(
- struct EncodeContext* encode_context);
-bool EncodeContextEncodeFrame(struct EncodeContext* encode_context, int fd,
- unsigned long long timestamp);
-void EncodeContextDestroy(struct EncodeContext* encode_context);
-
-#endif // STREAMER_ENCODE_H_
diff --git a/gpu.c b/gpu.c
deleted file mode 100644
index ea455e8..0000000
--- a/gpu.c
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "gpu.h"
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl32.h>
-#include <drm_fourcc.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
-#include <fcntl.h>
-#include <gbm.h>
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
-
-#include "toolbox/utils.h"
-
-#define _(...) __VA_ARGS__
-#define LOOKUP_FUNCTION(a, b, c) \
- gpu_context->b = (a)eglGetProcAddress(#b); \
- if (!gpu_context->b) { \
- LOG("Failed to look up " #b " function"); \
- goto c; \
- }
-
-// TODO(mburakov): It should be theoretically possible to do everything in a
-// single pass using a compute shader and GLES3. Unfortunately my test machine
-// reports the primary framebuffer as multiplane. This is probably the reason
-// why texture created from it can not be sampled using imageLoad in a compute
-// shader even though it's still RGB. Fallback to GLES2 and per-plane textures
-// for now, and figure out details later.
-
-extern const char _binary_vertex_glsl_start[];
-extern const char _binary_vertex_glsl_end[];
-extern const char _binary_luma_glsl_start[];
-extern const char _binary_luma_glsl_end[];
-extern const char _binary_chroma_glsl_start[];
-extern const char _binary_chroma_glsl_end[];
-
-struct GpuContext {
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
- int render_node;
- struct gbm_device* device;
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
- EGLDisplay display;
- EGLContext context;
- PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT;
- PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT;
- PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
- GLuint program_luma;
- GLuint program_chroma;
- GLint sample_offsets;
- GLuint framebuffer;
- GLuint vertices;
-};
-
-struct GpuFrameImpl {
- struct GpuFrame size;
- int dmabuf_fds[4];
- EGLImage images[2];
- GLuint textures[2];
-};
-
-static const char* EglErrorString(EGLint error) {
- static const char* const egl_error_strings[] = {
- "EGL_SUCCESS", "EGL_NOT_INITIALIZED", "EGL_BAD_ACCESS",
- "EGL_BAD_ALLOC", "EGL_BAD_ATTRIBUTE", "EGL_BAD_CONFIG",
- "EGL_BAD_CONTEXT", "EGL_BAD_CURRENT_SURFACE", "EGL_BAD_DISPLAY",
- "EGL_BAD_MATCH", "EGL_BAD_NATIVE_PIXMAP", "EGL_BAD_NATIVE_WINDOW",
- "EGL_BAD_PARAMETER", "EGL_BAD_SURFACE", "EGL_CONTEXT_LOST",
- };
- return EGL_SUCCESS <= error && error <= EGL_CONTEXT_LOST
- ? egl_error_strings[error - EGL_SUCCESS]
- : "???";
-}
-
-static const char* GlErrorString(GLenum error) {
- static const char* const gl_error_strings[] = {
- "GL_INVALID_ENUM",
- "GL_INVALID_VALUE",
- "GL_INVALID_OPERATION",
- "GL_STACK_OVERFLOW",
- "GL_STACK_UNDERFLOW",
- "GL_OUT_OF_MEMORY",
- "GL_INVALID_FRAMEBUFFER_OPERATION",
- "GL_CONTEXT_LOST",
- };
- if (error == GL_NO_ERROR) return "GL_NO_ERROR";
- return GL_INVALID_ENUM <= error && error <= GL_CONTEXT_LOST
- ? gl_error_strings[error - GL_INVALID_ENUM]
- : "???";
-}
-
-#define DEFINE_CHECK_BUILDABLE_FUNCTION(postfix, err_msg, getter_fn, \
- status_enum, logger_fn) \
- static bool CheckBuildable##postfix(GLuint buildable) { \
- GLenum error = glGetError(); \
- if (error != GL_NO_ERROR) { \
- LOG(err_msg " (%s)", GlErrorString(error)); \
- return false; \
- } \
- GLint status; \
- getter_fn(buildable, status_enum, &status); \
- if (status != GL_TRUE) { \
- GLint log_length; \
- getter_fn(buildable, GL_INFO_LOG_LENGTH, &log_length); \
- char message[log_length]; \
- memset(message, 0, sizeof(message)); \
- logger_fn(buildable, log_length, NULL, message); \
- LOG("%s", message); \
- return false; \
- } \
- return true; \
- }
-
-DEFINE_CHECK_BUILDABLE_FUNCTION(Shader, "Failed to compile shader",
- glGetShaderiv, GL_COMPILE_STATUS,
- glGetShaderInfoLog)
-DEFINE_CHECK_BUILDABLE_FUNCTION(Program, "Failed to link program",
- glGetProgramiv, GL_LINK_STATUS,
- glGetProgramInfoLog)
-
-static bool HasExtension(const char* haystack, const char* needle) {
- bool result = !!strstr(haystack, needle);
- if (!result) LOG("Unsupported extension %s", needle);
- return result;
-}
-
-static GLuint CreateGlProgram(const char* vs_begin, const char* vs_end,
- const char* fs_begin, const char* fs_end) {
- GLuint program = 0;
- GLuint vertex = glCreateShader(GL_VERTEX_SHADER);
- if (!vertex) {
- LOG("Failed to create vertex shader (%s)", GlErrorString(glGetError()));
- goto bail_out;
- }
- GLsizei size = (GLsizei)(vs_end - vs_begin);
- glShaderSource(vertex, 1, &vs_begin, &size);
- glCompileShader(vertex);
- if (!CheckBuildableShader(vertex)) goto delete_vs;
-
- GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER);
- if (!fragment) {
- LOG("Failed to create fragment shader (%s)", GlErrorString(glGetError()));
- goto delete_vs;
- }
- size = (GLsizei)(fs_end - fs_begin);
- glShaderSource(fragment, 1, &fs_begin, &size);
- glCompileShader(fragment);
- if (!CheckBuildableShader(fragment)) goto delete_fs;
-
- program = glCreateProgram();
- if (!program) {
- LOG("Failed to create shader program (%s)", GlErrorString(glGetError()));
- goto delete_fs;
- }
- glAttachShader(program, vertex);
- glAttachShader(program, fragment);
- glLinkProgram(program);
- if (!CheckBuildableProgram(program)) {
- glDeleteProgram(program);
- program = 0;
- goto delete_fs;
- }
-
-delete_fs:
- glDeleteShader(fragment);
-delete_vs:
- glDeleteShader(vertex);
-bail_out:
- return program;
-}
-
-static const GLfloat* GetColorspaceMatrix(enum YuvColorspace colorspace) {
- static const GLfloat rec601[] = {
- _(0.299f, 0.587f, 0.114f),
- _(-0.168736f, -0.331264f, 0.5f),
- _(0.5f, -0.418688f, -0.081312f),
- };
- static const GLfloat rec709[] = {
- _(0.2126f, 0.7152f, 0.0722f),
- _(-0.1146f, -0.3854f, 0.5f),
- _(0.5f, -0.4542f, -0.0458f),
- };
- switch (colorspace) {
- case kItuRec601:
- return rec601;
- case kItuRec709:
- return rec709;
- default:
- __builtin_unreachable();
- }
-}
-
-static const GLfloat* GetRangeVectors(enum YuvRange range) {
- static const GLfloat narrow[] = {
- _(16.f / 255.f, 16.f / 255.f, 16.f / 255.f),
- _((235.f - 16.f) / 255.f, (240.f - 16.f) / 255.f, (240.f - 16.f) / 255.f),
- };
- static const GLfloat full[] = {
- _(0.f, 0.f, 0.f),
- _(1.f, 1.f, 1.f),
- };
- switch (range) {
- case kNarrowRange:
- return narrow;
- case kFullRange:
- return full;
- default:
- __builtin_unreachable();
- }
-}
-
-static bool SetupCommonUniforms(GLuint program, enum YuvColorspace colorspace,
- enum YuvRange range) {
- struct {
- const char* name;
- GLint location;
- } uniforms[] = {
- {.name = "img_input"},
- {.name = "colorspace"},
- {.name = "ranges"},
- };
-
- for (size_t i = 0; i < LENGTH(uniforms); i++) {
- uniforms[i].location = glGetUniformLocation(program, uniforms[i].name);
- if (uniforms[i].location == -1) {
- LOG("Failed to locate %s uniform (%s)", uniforms[i].name,
- GlErrorString(glGetError()));
- return false;
- }
- }
-
- glUseProgram(program);
- glUniform1i(uniforms[0].location, 0);
- glUniformMatrix3fv(uniforms[1].location, 1, GL_TRUE,
- GetColorspaceMatrix(colorspace));
- glUniform3fv(uniforms[2].location, 2, GetRangeVectors(range));
- GLenum error = glGetError();
- if (error != GL_NO_ERROR) {
- LOG("Failed to set img_input uniform (%s)", GlErrorString(glGetError()));
- return false;
- }
- return true;
-}
-
-struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace,
- enum YuvRange range) {
- struct GpuContext* gpu_context = malloc(sizeof(struct GpuContext));
- if (!gpu_context) {
- LOG("Failed to allocate gpu context (%s)", strerror(errno));
- return NULL;
- }
- *gpu_context = (struct GpuContext){
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
- .render_node = -1,
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
- .display = EGL_NO_DISPLAY,
- .context = EGL_NO_CONTEXT,
- .sample_offsets = -1,
- };
-
- const char* egl_ext = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
- if (!egl_ext) {
- LOG("Failed to query platformless egl extensions (%s)",
- EglErrorString(eglGetError()));
- goto rollback_gpu_context;
- }
-
- LOG("EGL_EXTENSIONS: %s", egl_ext);
- // TODO(mburakov): Quite surprisingly EGL_MESA_platform_surfaceless does not
- // provide support for AMD_FMT_MOD_TILE_VER_GFX11 dmabuf modifier on my AMD
- // system. For comparison, on my Intel system all the modifiers ever found on
- // framebuffers are reported as supported by EGL. Maybe that's because the
- // support for RDNA3 is still quite young in MESA.
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
- if (!HasExtension(egl_ext, "EGL_MESA_platform_gbm"))
- goto rollback_gpu_context;
- gpu_context->render_node = open("/dev/dri/renderD128", O_RDWR);
- if (gpu_context->render_node == -1) {
- LOG("Failed to open render node (%s)", strerror(errno));
- goto rollback_gpu_context;
- }
- gpu_context->device = gbm_create_device(gpu_context->render_node);
- if (!gpu_context->device) {
- LOG("Failed to create gbm device (%s)", strerror(errno));
- goto rollback_render_node;
- }
- gpu_context->display =
- eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gpu_context->device, NULL);
-#else // USE_EGL_MESA_PLATFORM_SURFACELESS
- if (!HasExtension(egl_ext, "EGL_MESA_platform_surfaceless"))
- goto rollback_gpu_context;
- gpu_context->display =
- eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL);
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
- if (gpu_context->display == EGL_NO_DISPLAY) {
- LOG("Failed to get egl display (%s)", EglErrorString(eglGetError()));
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
- goto rollback_device;
-#else // USE_EGL_MESA_PLATFORM_SURFACELESS
- goto rollback_gpu_context;
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
- }
-
- EGLint major, minor;
- if (!eglInitialize(gpu_context->display, &major, &minor)) {
- LOG("Failed to initialize egl display (%s)", EglErrorString(eglGetError()));
- goto rollback_display;
- }
-
- LOG("Initialized EGL %d.%d", major, minor);
- egl_ext = eglQueryString(gpu_context->display, EGL_EXTENSIONS);
- if (!egl_ext) {
- LOG("Failed to query egl extensions (%s)", EglErrorString(eglGetError()));
- goto rollback_display;
- }
-
- LOG("EGL_EXTENSIONS: %s", egl_ext);
- if (!HasExtension(egl_ext, "EGL_KHR_surfaceless_context") ||
- !HasExtension(egl_ext, "EGL_KHR_no_config_context") ||
- !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import") ||
- !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import_modifiers"))
- goto rollback_display;
- LOOKUP_FUNCTION(PFNEGLQUERYDMABUFFORMATSEXTPROC, eglQueryDmaBufFormatsEXT,
- rollback_display)
- LOOKUP_FUNCTION(PFNEGLQUERYDMABUFMODIFIERSEXTPROC, eglQueryDmaBufModifiersEXT,
- rollback_display)
-
- if (!eglBindAPI(EGL_OPENGL_ES_API)) {
- LOG("Failed to bind egl api (%s)", EglErrorString(eglGetError()));
- goto rollback_display;
- }
-
- static const EGLint context_attribs[] = {
- _(EGL_CONTEXT_MAJOR_VERSION, 3),
- _(EGL_CONTEXT_MINOR_VERSION, 1),
- EGL_NONE,
- };
- gpu_context->context = eglCreateContext(
- gpu_context->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs);
- if (gpu_context->context == EGL_NO_CONTEXT) {
- LOG("Failed to create egl context (%s)", EglErrorString(eglGetError()));
- goto rollback_display;
- }
-
- if (!eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- gpu_context->context)) {
- LOG("Failed to make egl context current (%s)",
- EglErrorString(eglGetError()));
- goto rollback_context;
- }
-
- const char* gl_ext = (const char*)glGetString(GL_EXTENSIONS);
- if (!gl_ext) {
- LOG("Failed to get gl extensions (%s)", GlErrorString(glGetError()));
- goto rollback_context;
- }
-
- LOG("GL_EXTENSIONS: %s", gl_ext);
- if (!HasExtension(gl_ext, "GL_OES_EGL_image")) goto rollback_context;
- LOOKUP_FUNCTION(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC,
- glEGLImageTargetTexture2DOES, rollback_context)
-
- gpu_context->program_luma =
- CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end,
- _binary_luma_glsl_start, _binary_luma_glsl_end);
- if (!gpu_context->program_luma ||
- !SetupCommonUniforms(gpu_context->program_luma, colorspace, range)) {
- LOG("Failed to create luma program");
- goto rollback_context;
- }
-
- gpu_context->program_chroma =
- CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end,
- _binary_chroma_glsl_start, _binary_chroma_glsl_end);
- if (!gpu_context->program_chroma ||
- !SetupCommonUniforms(gpu_context->program_chroma, colorspace, range)) {
- LOG("Failed to create chroma program");
- goto rollback_program_luma;
- }
- gpu_context->sample_offsets =
- glGetUniformLocation(gpu_context->program_chroma, "sample_offsets");
- if (gpu_context->sample_offsets == -1) {
- LOG("Failed to find sample_offsets uniform (%s)",
- GlErrorString(glGetError()));
- goto rollback_program_chroma;
- }
-
- glGenFramebuffers(1, &gpu_context->framebuffer);
- glBindFramebuffer(GL_FRAMEBUFFER, gpu_context->framebuffer);
- glGenBuffers(1, &gpu_context->vertices);
- glBindBuffer(GL_ARRAY_BUFFER, gpu_context->vertices);
- static const GLfloat vertices[] = {0, 0, 1, 0, 1, 1, 0, 1};
- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
- glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
- glEnableVertexAttribArray(0);
- GLenum error = glGetError();
- if (error != GL_NO_ERROR) {
- LOG("Failed to create gl objects (%s)", GlErrorString(glGetError()));
- goto rollback_buffers;
- }
- return gpu_context;
-
-rollback_buffers:
- if (gpu_context->vertices) glDeleteBuffers(1, &gpu_context->vertices);
- if (gpu_context->framebuffer)
- glDeleteFramebuffers(1, &gpu_context->framebuffer);
-rollback_program_chroma:
- glDeleteProgram(gpu_context->program_chroma);
-rollback_program_luma:
- glDeleteProgram(gpu_context->program_luma);
-rollback_context:
- eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
- eglDestroyContext(gpu_context->display, gpu_context->context);
-rollback_display:
- eglTerminate(gpu_context->display);
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
-rollback_device:
- gbm_device_destroy(gpu_context->device);
-rollback_render_node:
- close(gpu_context->render_node);
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
-rollback_gpu_context:
- free(gpu_context);
- return NULL;
-}
-
-static void DumpEglImageParams(const EGLAttrib* attribs) {
- for (; *attribs != EGL_NONE; attribs += 2) {
- switch (attribs[0]) {
- case EGL_HEIGHT:
- LOG("\tEGL_HEIGHT: %ld", attribs[1]);
- break;
- case EGL_WIDTH:
- LOG("\tEGL_WIDTH: %ld", attribs[1]);
- break;
- case EGL_LINUX_DRM_FOURCC_EXT:
- LOG("\tEGL_LINUX_DRM_FOURCC_EXT: %.4s", (const char*)&attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE0_FD_EXT:
- case EGL_DMA_BUF_PLANE1_FD_EXT:
- case EGL_DMA_BUF_PLANE2_FD_EXT:
- LOG("\tEGL_DMA_BUF_PLANE%ld_FD_EXT: %ld",
- (attribs[0] - EGL_DMA_BUF_PLANE0_FD_EXT) / 3, attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE0_OFFSET_EXT:
- case EGL_DMA_BUF_PLANE1_OFFSET_EXT:
- case EGL_DMA_BUF_PLANE2_OFFSET_EXT:
- LOG("\tEGL_DMA_BUF_PLANE%ld_OFFSET_EXT: %ld",
- (attribs[0] - EGL_DMA_BUF_PLANE0_OFFSET_EXT) / 3, attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE0_PITCH_EXT:
- case EGL_DMA_BUF_PLANE1_PITCH_EXT:
- case EGL_DMA_BUF_PLANE2_PITCH_EXT:
- LOG("\tEGL_DMA_BUF_PLANE%ld_PITCH_EXT: %ld",
- (attribs[0] - EGL_DMA_BUF_PLANE0_PITCH_EXT) / 3, attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE3_FD_EXT:
- LOG("\tEGL_DMA_BUF_PLANE3_FD_EXT: %ld", attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE3_OFFSET_EXT:
- LOG("\tEGL_DMA_BUF_PLANE3_OFFSET_EXT: %ld", attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE3_PITCH_EXT:
- LOG("\tEGL_DMA_BUF_PLANE3_PITCH_EXT: %ld", attribs[1]);
- break;
- case EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT:
- case EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT:
- case EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT:
- case EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT:
- case EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT:
- case EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT:
- case EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT:
- case EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT:
- LOG("\tEGL_DMA_BUF_PLANE%ld_MODIFIER_%s_EXT: 0x%08lx",
- (attribs[0] - EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT) / 2,
- attribs[0] & 1 ? "LO" : "HI", attribs[1]);
- break;
- }
- }
-}
-
-static bool IsFourccSupported(struct GpuContext* gpu_context, uint32_t fourcc) {
- EGLint num_formats;
- if (!gpu_context->eglQueryDmaBufFormatsEXT(gpu_context->display, 0, NULL,
- &num_formats)) {
- LOG("Faield to get number of supported dmabuf formats (%s)",
- EglErrorString(eglGetError()));
- return false;
- }
- EGLint formats[num_formats];
- if (!gpu_context->eglQueryDmaBufFormatsEXT(gpu_context->display, num_formats,
- formats, &num_formats)) {
- LOG("Failed to get supported dmabuf formats (%s)",
- EglErrorString(eglGetError()));
- return false;
- }
- for (int i = 0; i < num_formats; i++) {
- if ((uint32_t)formats[i] == fourcc) return true;
- }
- LOG("Format %.4s is unsupported by egl", (const char*)&fourcc);
- LOG("Supported formats are:");
- for (int i = 0; i < num_formats; i++) {
- LOG("\t%.4s", (const char*)&formats[i]);
- }
- return false;
-}
-
-static bool IsModifierSupported(struct GpuContext* gpu_context, uint32_t fourcc,
- uint64_t modifier) {
- EGLint num_modifiers;
- if (!gpu_context->eglQueryDmaBufModifiersEXT(
- gpu_context->display, (GLint)fourcc, 0, NULL, NULL, &num_modifiers)) {
- LOG("Failed to get number of supported dmabuf modifiers (%s)",
- EglErrorString(eglGetError()));
- return false;
- }
- EGLuint64KHR modifiers[num_modifiers];
- EGLBoolean external_only[num_modifiers];
- if (!gpu_context->eglQueryDmaBufModifiersEXT(
- gpu_context->display, (GLint)fourcc, num_modifiers, modifiers,
- external_only, &num_modifiers)) {
- LOG("Failed to get supported dmabuf modifiers (%s)",
- EglErrorString(eglGetError()));
- return false;
- }
- for (int i = 0; i < num_modifiers; i++) {
- if (modifiers[i] == modifier && !external_only[i]) return true;
- }
- LOG("Modifier 0x%016lx for format %.4s is unsupported by egl", modifier,
- (const char*)&fourcc);
- LOG("Supported modifiers for format %.4s are:", (const char*)&fourcc);
- for (int i = 0; i < num_modifiers; i++) {
- LOG("\t0x%016lx%s", modifiers[i],
- external_only[i] ? " (external only)" : "");
- }
- return false;
-}
-
-static EGLImage CreateEglImage(struct GpuContext* gpu_context, uint32_t width,
- uint32_t height, uint32_t fourcc, size_t nplanes,
- const struct GpuFramePlane* planes) {
- static const EGLAttrib attrib_keys[] = {
- EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT,
- EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
- EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_FD_EXT,
- EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT,
- EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
- EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT,
- EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
- EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_FD_EXT,
- EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT,
- EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT,
- };
-
- EGLAttrib attrib_list[7 + LENGTH(attrib_keys) * 2] = {
- _(EGL_HEIGHT, height),
- _(EGL_WIDTH, width),
- _(EGL_LINUX_DRM_FOURCC_EXT, fourcc),
- };
-
- EGLAttrib* pairs = &attrib_list[6];
- const EGLAttrib* key = &attrib_keys[0];
- for (size_t i = 0; i < nplanes; i++) {
- if (planes[i].dmabuf_fd == -1) break;
- *pairs++ = *key++;
- *pairs++ = planes[i].dmabuf_fd;
- *pairs++ = *key++;
- *pairs++ = planes[i].offset;
- *pairs++ = *key++;
- *pairs++ = planes[i].pitch;
- *pairs++ = *key++;
- *pairs++ = planes[i].modifier & UINT32_MAX;
- *pairs++ = *key++;
- *pairs++ = planes[i].modifier >> 32;
- }
-
- *pairs = EGL_NONE;
- if (!IsFourccSupported(gpu_context, fourcc)) goto failure;
- for (size_t i = 0; i < nplanes; i++) {
- if (!IsModifierSupported(gpu_context, fourcc, planes[i].modifier))
- goto failure;
- }
- EGLImage image = eglCreateImage(gpu_context->display, EGL_NO_CONFIG_KHR,
- EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list);
- if (image == EGL_NO_IMAGE) {
- LOG("Failed to create egl image (%s)", EglErrorString(eglGetError()));
- goto failure;
- }
- return image;
-
-failure:
- LOG("Attributes list for failed egl image:");
- DumpEglImageParams(attrib_list);
- return EGL_NO_IMAGE;
-}
-
-static GLuint CreateTexture(struct GpuContext* gpu_context, EGLImage image) {
- GLuint texture = 0;
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- gpu_context->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
-
- GLenum error = glGetError();
- if (error != GL_NO_ERROR) {
- LOG("Failed to create texture (%s)", GlErrorString(error));
- glDeleteTextures(1, &texture);
- return 0;
- }
- return texture;
-}
-
-struct GpuFrame* GpuContextCreateFrame(struct GpuContext* gpu_context,
- uint32_t width, uint32_t height,
- uint32_t fourcc, size_t nplanes,
- const struct GpuFramePlane* planes) {
- struct GpuFrameImpl* gpu_frame_impl = malloc(sizeof(struct GpuFrameImpl));
- if (!gpu_frame_impl) {
- LOG("Failed to allocate gpu frame (%s)", strerror(errno));
- return NULL;
- }
- *gpu_frame_impl = (struct GpuFrameImpl){
- .size.width = width,
- .size.height = height,
- .dmabuf_fds = {-1, -1, -1, -1},
- .images = {EGL_NO_IMAGE, EGL_NO_IMAGE},
- };
-
- if (fourcc == DRM_FORMAT_NV12) {
- gpu_frame_impl->images[0] = CreateEglImage(gpu_context, width, height,
- DRM_FORMAT_R8, 1, &planes[0]);
- if (gpu_frame_impl->images[0] == EGL_NO_IMAGE) {
- LOG("Failed to create luma plane image");
- goto rollback_gpu_frame;
- }
- gpu_frame_impl->images[1] = CreateEglImage(
- gpu_context, width / 2, height / 2, DRM_FORMAT_GR88, 1, &planes[1]);
- if (gpu_frame_impl->images[1] == EGL_NO_IMAGE) {
- LOG("Failed to create chroma plane image");
- goto rollback_images;
- }
- } else {
- gpu_frame_impl->images[0] =
- CreateEglImage(gpu_context, width, height, fourcc, nplanes, planes);
- if (gpu_frame_impl->images[0] == EGL_NO_IMAGE) {
- LOG("Failed to create multiplanar image");
- goto rollback_gpu_frame;
- }
- }
-
- for (size_t i = 0; i < LENGTH(gpu_frame_impl->images); i++) {
- if (gpu_frame_impl->images[i] == EGL_NO_IMAGE) break;
- gpu_frame_impl->textures[i] =
- CreateTexture(gpu_context, gpu_frame_impl->images[i]);
- if (!gpu_frame_impl->textures[i]) {
- LOG("Failed to create texture");
- goto rollback_textures;
- }
- }
-
- for (size_t i = 0; i < nplanes; i++)
- gpu_frame_impl->dmabuf_fds[i] = planes[i].dmabuf_fd;
- return (struct GpuFrame*)gpu_frame_impl;
-
-rollback_textures:
- for (size_t i = LENGTH(gpu_frame_impl->textures); i; i--) {
- if (gpu_frame_impl->textures[i - 1])
- glDeleteTextures(1, &gpu_frame_impl->textures[i - 1]);
- }
-rollback_images:
- for (size_t i = LENGTH(gpu_frame_impl->images); i; i--) {
- if (gpu_frame_impl->images[i - 1] != EGL_NO_IMAGE)
- eglDestroyImage(gpu_context->device, gpu_frame_impl->images[i - 1]);
- }
-rollback_gpu_frame:
- free(gpu_frame_impl);
- return NULL;
-}
-
-static bool GpuFrameConvertImpl(GLuint from, GLuint to) {
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- to, 0);
- GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
- LOG("Framebuffer is incomplete (0x%x)", framebuffer_status);
- return false;
- }
-
- glBindTexture(GL_TEXTURE_2D, from);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- GLenum error = glGetError();
- if (error != GL_NO_ERROR) {
- LOG("Failed to convert plane (%s)", GlErrorString(error));
- return false;
- }
- return true;
-}
-
-bool GpuContextConvertFrame(struct GpuContext* gpu_context,
- const struct GpuFrame* from,
- const struct GpuFrame* to) {
- const struct GpuFrameImpl* from_impl = (const void*)from;
- const struct GpuFrameImpl* to_impl = (const void*)to;
-
- glUseProgram(gpu_context->program_luma);
- glViewport(0, 0, (GLsizei)to->width, (GLsizei)to->height);
- if (!GpuFrameConvertImpl(from_impl->textures[0], to_impl->textures[0])) {
- LOG("Failed to convert luma plane");
- return false;
- }
-
- const GLfloat sample_offsets[] = {
- _(0.f, 0.f),
- _(1.f / (GLfloat)from->width, 0.f),
- _(0.f, 1.f / (GLfloat)from->height),
- _(1.f / (GLfloat)from->width, 1.f / (GLfloat)from->height),
- };
-
- glUseProgram(gpu_context->program_chroma);
- glUniform2fv(gpu_context->sample_offsets, 4, sample_offsets);
- glViewport(0, 0, (GLsizei)to->width / 2, (GLsizei)to->height / 2);
- if (!GpuFrameConvertImpl(from_impl->textures[0], to_impl->textures[1])) {
- LOG("Failed to convert chroma plane");
- return false;
- }
-
- EGLSync sync = eglCreateSync(gpu_context->display, EGL_SYNC_FENCE, NULL);
- if (sync == EGL_NO_SYNC) {
- LOG("Failed to create egl fence sync (%s)", EglErrorString(eglGetError()));
- return false;
- }
- eglClientWaitSync(gpu_context->display, sync, 0, EGL_FOREVER);
- eglDestroySync(gpu_context->display, sync);
- return true;
-}
-
-void GpuContextDestroyFrame(struct GpuContext* gpu_context,
- struct GpuFrame* gpu_frame) {
- struct GpuFrameImpl* gpu_frame_impl = (void*)gpu_frame;
- for (size_t i = LENGTH(gpu_frame_impl->textures); i; i--) {
- if (gpu_frame_impl->textures[i - 1])
- glDeleteTextures(1, &gpu_frame_impl->textures[i - 1]);
- }
- for (size_t i = LENGTH(gpu_frame_impl->images); i; i--) {
- if (gpu_frame_impl->images[i - 1] != EGL_NO_IMAGE)
- eglDestroyImage(gpu_context->device, gpu_frame_impl->images[i - 1]);
- }
- CloseUniqueFds(gpu_frame_impl->dmabuf_fds);
- free(gpu_frame_impl);
-}
-
-void GpuContextDestroy(struct GpuContext* gpu_context) {
- glDeleteBuffers(1, &gpu_context->vertices);
- glDeleteFramebuffers(1, &gpu_context->framebuffer);
- glDeleteProgram(gpu_context->program_chroma);
- glDeleteProgram(gpu_context->program_luma);
- eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
- eglDestroyContext(gpu_context->display, gpu_context->context);
- eglTerminate(gpu_context->display);
-#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS
- gbm_device_destroy(gpu_context->device);
- close(gpu_context->render_node);
-#endif // USE_EGL_MESA_PLATFORM_SURFACELESS
- free(gpu_context);
-}
-
-void CloseUniqueFds(int fds[4]) {
- // TODO(mburakov): Meh, but still better than looping...
- if (fds[3] != -1 && fds[3] != fds[2] && fds[3] != fds[1] && fds[3] != fds[0])
- close(fds[3]);
- if (fds[2] != -1 && fds[2] != fds[1] && fds[2] != fds[0]) close(fds[2]);
- if (fds[1] != -1 && fds[1] != fds[0]) close(fds[1]);
- if (fds[0] != -1) close(fds[0]);
-}
diff --git a/gpu.h b/gpu.h
deleted file mode 100644
index 6d07366..0000000
--- a/gpu.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_GPU_H_
-#define STREAMER_GPU_H_
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-#include "colorspace.h"
-
-struct GpuFrame {
- uint32_t width;
- uint32_t height;
-};
-
-struct GpuFramePlane {
- int dmabuf_fd;
- uint32_t pitch;
- uint32_t offset;
- uint64_t modifier;
-};
-
-struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace,
- enum YuvRange range);
-struct GpuFrame* GpuContextCreateFrame(struct GpuContext* gpu_context,
- uint32_t width, uint32_t height,
- uint32_t fourcc, size_t nplanes,
- const struct GpuFramePlane* planes);
-bool GpuContextConvertFrame(struct GpuContext* gpu_context,
- const struct GpuFrame* from,
- const struct GpuFrame* to);
-void GpuContextDestroyFrame(struct GpuContext* gpu_context,
- struct GpuFrame* gpu_frame);
-void GpuContextDestroy(struct GpuContext* gpu_context);
-
-void CloseUniqueFds(int fds[4]);
-
-#endif // STREAMER_GPU_H_
diff --git a/hevc.c b/hevc.c
deleted file mode 100644
index 9d65386..0000000
--- a/hevc.c
+++ /dev/null
@@ -1,708 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "hevc.h"
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "bitstream.h"
-
-// mburakov: Below entries are hardcoded by ffmpeg:
-static const uint8_t vps_video_parameter_set_id = 0;
-static const bool vps_base_layer_internal_flag = 1;
-static const bool vps_base_layer_available_flag = 1;
-static const uint8_t vps_max_layers_minus1 = 0;
-static const uint8_t vps_max_sub_layers_minus1 = 0;
-static const bool vps_temporal_id_nesting_flag = 1;
-static const uint8_t general_profile_space = 0;
-static const bool general_progressive_source_flag = 1;
-static const bool general_interlaced_source_flag = 0;
-static const bool general_non_packed_constraint_flag = 1;
-static const bool general_frame_only_constraint_flag = 1;
-static const bool general_one_picture_only_constraint_flag = 0;
-static const bool vps_sub_layer_ordering_info_present_flag = 0;
-static const uint32_t vps_max_latency_increase_plus1 = 0;
-static const uint8_t vps_max_layer_id = 0;
-static const uint32_t vps_num_layer_sets_minus1 = 0;
-static const bool vps_poc_proportional_to_timing_flag = 0;
-static const uint32_t vps_num_hrd_parameters = 0;
-static const uint8_t sps_video_parameter_set_id = vps_video_parameter_set_id;
-static const uint8_t sps_max_sub_layers_minus1 = vps_max_sub_layers_minus1;
-static const bool sps_temporal_id_nesting_flag = vps_temporal_id_nesting_flag;
-static const uint32_t sps_seq_parameter_set_id = 0;
-static const uint32_t log2_max_pic_order_cnt_lsb_minus4 = 8;
-static const bool sps_sub_layer_ordering_info_present_flag =
- vps_sub_layer_ordering_info_present_flag;
-static const uint32_t sps_max_latency_increase_plus1 =
- vps_max_latency_increase_plus1;
-static const uint32_t num_short_term_ref_pic_sets = 0;
-static const bool long_term_ref_pics_present_flag = 0;
-static const uint8_t video_format = 5;
-static const bool vui_poc_proportional_to_timing_flag =
- vps_poc_proportional_to_timing_flag;
-static const bool vui_hrd_parameters_present_flag = 0;
-static const uint32_t pps_pic_parameter_set_id = 0;
-static const uint32_t pps_seq_parameter_set_id = sps_seq_parameter_set_id;
-static const bool short_term_ref_pic_set_sps_flag = 0;
-
-// mburakov: Below entries are defaulted by ffmpeg:
-static const bool general_inbld_flag = 0;
-static const bool vps_extension_flag = 0;
-static const bool overscan_info_present_flag = 0;
-static const bool frame_field_info_present_flag = 0;
-static const bool default_display_window_flag = 0;
-static const bool sps_extension_present_flag = 0;
-static const bool output_flag_present_flag = 0;
-static const uint8_t num_extra_slice_header_bits = 0;
-static const bool cabac_init_present_flag = 0;
-static const bool pps_slice_chroma_qp_offsets_present_flag = 0;
-static const bool deblocking_filter_control_present_flag = 0;
-static const bool lists_modification_present_flag = 0;
-static const bool slice_segment_header_extension_present_flag = 0;
-static const bool pps_extension_present_flag = 0;
-static const uint32_t motion_vector_resolution_control_idc = 0;
-static const bool pps_slice_act_qp_offsets_present_flag = 0;
-static const bool chroma_qp_offset_list_enabled_flag = 0;
-static const bool deblocking_filter_override_enabled_flag = 0;
-static const bool deblocking_filter_override_flag = 0;
-static const uint32_t num_entry_point_offsets = 0;
-static const bool inter_ref_pic_set_prediction_flag = 0;
-
-// 7.3.1.2 NAL unit header syntax
-static void PackNalUnitHeader(struct Bitstream* bitstream,
- uint8_t nal_unit_type) {
- BitstreamAppend(bitstream, 32, 0x00000001);
- BitstreamAppend(bitstream, 1, 0); // forbidden_zero_bit
- BitstreamAppend(bitstream, 6, nal_unit_type);
- BitstreamAppend(bitstream, 6, 0); // nuh_layer_id
- BitstreamAppend(bitstream, 3, 1); // nuh_temporal_id_plus1
-}
-
-// 7.3.3 Profile, tier and level syntax
-static void PackProfileTierLevel(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- bool profilePresentFlag,
- uint8_t maxNumSubLayersMinus1) {
- if (profilePresentFlag) {
- BitstreamAppend(bitstream, 2, general_profile_space);
- BitstreamAppend(bitstream, 1, seq->general_tier_flag);
- BitstreamAppend(bitstream, 5, seq->general_profile_idc);
-
- // mburakov: ffmpeg deduces general_profile_compatibility_flag.
- bool general_profile_compatibility_flag[32] = {0};
- general_profile_compatibility_flag[seq->general_profile_idc] = 1;
- if (general_profile_compatibility_flag[1])
- general_profile_compatibility_flag[2] = 1;
- if (general_profile_compatibility_flag[3]) {
- general_profile_compatibility_flag[1] = 1;
- general_profile_compatibility_flag[2] = 1;
- }
-
- for (uint8_t j = 0; j < 32; j++)
- BitstreamAppend(bitstream, 1, general_profile_compatibility_flag[j]);
-
- BitstreamAppend(bitstream, 1, general_progressive_source_flag);
- BitstreamAppend(bitstream, 1, general_interlaced_source_flag);
- BitstreamAppend(bitstream, 1, general_non_packed_constraint_flag);
- BitstreamAppend(bitstream, 1, general_frame_only_constraint_flag);
- if (seq->general_profile_idc == 4 ||
- general_profile_compatibility_flag[4] ||
- seq->general_profile_idc == 5 ||
- general_profile_compatibility_flag[5] ||
- seq->general_profile_idc == 6 ||
- general_profile_compatibility_flag[6] ||
- seq->general_profile_idc == 7 ||
- general_profile_compatibility_flag[7] ||
- seq->general_profile_idc == 8 ||
- general_profile_compatibility_flag[8] ||
- seq->general_profile_idc == 9 ||
- general_profile_compatibility_flag[9] ||
- seq->general_profile_idc == 10 ||
- general_profile_compatibility_flag[10] ||
- seq->general_profile_idc == 11 ||
- general_profile_compatibility_flag[11]) {
- // TODO(mburakov): Implement this!
- abort();
- } else if (seq->general_profile_idc == 2 ||
- general_profile_compatibility_flag[2]) {
- BitstreamAppend(bitstream, 7, 0); // general_reserved_zero_7bits
- BitstreamAppend(bitstream, 1, general_one_picture_only_constraint_flag);
- BitstreamAppend(bitstream, 24, 0); // general_reserved_zero_35bits
- BitstreamAppend(bitstream, 11, 0); // general_reserved_zero_35bits
- } else {
- BitstreamAppend(bitstream, 24, 0); // general_reserved_zero_43bits
- BitstreamAppend(bitstream, 19, 0); // general_reserved_zero_43bits
- }
-
- if (seq->general_profile_idc == 1 ||
- general_profile_compatibility_flag[1] ||
- seq->general_profile_idc == 2 ||
- general_profile_compatibility_flag[2] ||
- seq->general_profile_idc == 3 ||
- general_profile_compatibility_flag[3] ||
- seq->general_profile_idc == 4 ||
- general_profile_compatibility_flag[4] ||
- seq->general_profile_idc == 5 ||
- general_profile_compatibility_flag[5] ||
- seq->general_profile_idc == 9 ||
- general_profile_compatibility_flag[9] ||
- seq->general_profile_idc == 11 ||
- general_profile_compatibility_flag[11]) {
- BitstreamAppend(bitstream, 1, general_inbld_flag);
- } else {
- BitstreamAppend(bitstream, 1, 0); // general_reserved_zero_bit
- }
- }
-
- BitstreamAppend(bitstream, 8, seq->general_level_idc);
- for (uint8_t i = 0; i < maxNumSubLayersMinus1; i++) {
- // TODO(mburakov): Implement this!
- abort();
- }
- if (maxNumSubLayersMinus1 > 0) {
- for (uint8_t i = maxNumSubLayersMinus1; i < 8; i++)
- BitstreamAppend(bitstream, 2, 0); // reserved_zero_2bits
- }
- for (uint8_t i = 0; i < maxNumSubLayersMinus1; i++) {
- // TODO(mburakov): Implement this!
- abort();
- }
-}
-
-// 7.3.2.11 RBSP trailing bits syntax
-static void PackRbspTrailingBits(struct Bitstream* bitstream) {
- BitstreamAppend(bitstream, 1, 1); // rbsp_stop_one_bit
- BitstreamByteAlign(bitstream); // rbsp_alignment_zero_bit
-}
-
-// 7.3.2.1 Video parameter set RBSP syntax
-void PackVideoParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const struct MoreVideoParameters* mvp) {
- const typeof(seq->vui_fields.bits)* vui_bits = &seq->vui_fields.bits;
-
- PackNalUnitHeader(bitstream, VPS_NUT);
-
- char buffer_on_the_stack[64];
- struct Bitstream vps_rbsp = {
- .data = buffer_on_the_stack,
- .size = 0,
- };
-
- BitstreamAppend(&vps_rbsp, 4, vps_video_parameter_set_id);
- BitstreamAppend(&vps_rbsp, 1, vps_base_layer_internal_flag);
- BitstreamAppend(&vps_rbsp, 1, vps_base_layer_available_flag);
- BitstreamAppend(&vps_rbsp, 6, vps_max_layers_minus1);
- BitstreamAppend(&vps_rbsp, 3, vps_max_sub_layers_minus1);
- BitstreamAppend(&vps_rbsp, 1, vps_temporal_id_nesting_flag);
- BitstreamAppend(&vps_rbsp, 16, 0xffff); // vps_reserved_0xffff_16bits
-
- PackProfileTierLevel(&vps_rbsp, seq, 1, vps_max_sub_layers_minus1);
-
- BitstreamAppend(&vps_rbsp, 1, vps_sub_layer_ordering_info_present_flag);
- for (uint8_t i = (vps_sub_layer_ordering_info_present_flag
- ? 0
- : vps_max_sub_layers_minus1);
- i <= vps_max_sub_layers_minus1; i++) {
- if (i != vps_max_sub_layers_minus1) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppendUE(&vps_rbsp, mvp->vps_max_dec_pic_buffering_minus1);
- BitstreamAppendUE(&vps_rbsp, mvp->vps_max_num_reorder_pics);
- BitstreamAppendUE(&vps_rbsp, vps_max_latency_increase_plus1);
- }
-
- BitstreamAppend(&vps_rbsp, 6, vps_max_layer_id);
- BitstreamAppendUE(&vps_rbsp, vps_num_layer_sets_minus1);
- for (uint8_t i = 1; i <= vps_max_layers_minus1; i++) {
- for (uint8_t j = 0; j <= vps_max_layer_id; j++) {
- // TODO(mburakov): Implement this!
- abort();
- }
- }
-
- BitstreamAppend(&vps_rbsp, 1, vui_bits->vui_timing_info_present_flag);
- if (vui_bits->vui_timing_info_present_flag) {
- BitstreamAppend(&vps_rbsp, 32,
- seq->vui_num_units_in_tick); // vps_num_units_in_tick
- BitstreamAppend(&vps_rbsp, 32, seq->vui_time_scale); // vps_time_scale
- BitstreamAppend(&vps_rbsp, 1, vps_poc_proportional_to_timing_flag);
- if (vps_poc_proportional_to_timing_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppendUE(&vps_rbsp, vps_num_hrd_parameters);
- for (uint32_t i = 0; i < vps_num_hrd_parameters; i++) {
- // TODO(mburakov): Implement this!
- abort();
- }
- }
-
- BitstreamAppend(&vps_rbsp, 1, vps_extension_flag);
- if (vps_extension_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- PackRbspTrailingBits(&vps_rbsp);
- BitstreamInflate(bitstream, &vps_rbsp);
-}
-
-// E.2.1 VUI parameters syntax
-static void PackVuiParameters(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const struct MoreSeqParameters* msp) {
- const typeof(seq->vui_fields.bits)* vui_bits = &seq->vui_fields.bits;
-
- BitstreamAppend(bitstream, 1, vui_bits->aspect_ratio_info_present_flag);
- if (vui_bits->aspect_ratio_info_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(bitstream, 1, overscan_info_present_flag);
- if (overscan_info_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(bitstream, 1, msp->video_signal_type_present_flag);
- if (msp->video_signal_type_present_flag) {
- BitstreamAppend(bitstream, 3, video_format);
- BitstreamAppend(bitstream, 1, msp->video_full_range_flag);
- BitstreamAppend(bitstream, 1, msp->colour_description_present_flag);
- if (msp->colour_description_present_flag) {
- BitstreamAppend(bitstream, 8, msp->colour_primaries);
- BitstreamAppend(bitstream, 8, msp->transfer_characteristics);
- BitstreamAppend(bitstream, 8, msp->matrix_coeffs);
- }
- }
-
- BitstreamAppend(bitstream, 1, msp->chroma_loc_info_present_flag);
- if (msp->chroma_loc_info_present_flag) {
- BitstreamAppendUE(bitstream, msp->chroma_sample_loc_type_top_field);
- BitstreamAppendUE(bitstream, msp->chroma_sample_loc_type_bottom_field);
- }
-
- BitstreamAppend(bitstream, 1, vui_bits->neutral_chroma_indication_flag);
- if (vui_bits->neutral_chroma_indication_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(bitstream, 1, vui_bits->field_seq_flag);
- BitstreamAppend(bitstream, 1, frame_field_info_present_flag);
- BitstreamAppend(bitstream, 1, default_display_window_flag);
- if (default_display_window_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(bitstream, 1, vui_bits->vui_timing_info_present_flag);
- if (vui_bits->vui_timing_info_present_flag) {
- BitstreamAppend(bitstream, 32, seq->vui_num_units_in_tick);
- BitstreamAppend(bitstream, 32, seq->vui_time_scale);
- BitstreamAppend(bitstream, 1, vui_poc_proportional_to_timing_flag);
- if (vui_poc_proportional_to_timing_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(bitstream, 1, vui_hrd_parameters_present_flag);
- if (vui_hrd_parameters_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
- }
-
- BitstreamAppend(bitstream, 1, vui_bits->bitstream_restriction_flag);
- if (vui_bits->bitstream_restriction_flag) {
- BitstreamAppend(bitstream, 1, vui_bits->tiles_fixed_structure_flag);
- BitstreamAppend(bitstream, 1,
- vui_bits->motion_vectors_over_pic_boundaries_flag);
- BitstreamAppend(bitstream, 1, vui_bits->restricted_ref_pic_lists_flag);
- BitstreamAppendUE(bitstream, seq->min_spatial_segmentation_idc);
- BitstreamAppendUE(bitstream, seq->max_bytes_per_pic_denom);
- BitstreamAppendUE(bitstream, seq->max_bits_per_min_cu_denom);
- BitstreamAppendUE(bitstream, vui_bits->log2_max_mv_length_horizontal);
- BitstreamAppendUE(bitstream, vui_bits->log2_max_mv_length_vertical);
- }
-}
-
-void PackSeqParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const struct MoreSeqParameters* msp) {
- const typeof(seq->seq_fields.bits)* seq_bits = &seq->seq_fields.bits;
-
- PackNalUnitHeader(bitstream, SPS_NUT);
-
- char buffer_on_the_stack[64];
- struct Bitstream sps_rbsp = {
- .data = buffer_on_the_stack,
- .size = 0,
- };
-
- BitstreamAppend(&sps_rbsp, 4, sps_video_parameter_set_id);
- BitstreamAppend(&sps_rbsp, 3, sps_max_sub_layers_minus1);
- BitstreamAppend(&sps_rbsp, 1, sps_temporal_id_nesting_flag);
-
- PackProfileTierLevel(&sps_rbsp, seq, 1, sps_max_sub_layers_minus1);
-
- BitstreamAppendUE(&sps_rbsp, sps_seq_parameter_set_id);
- BitstreamAppendUE(&sps_rbsp, seq_bits->chroma_format_idc);
- if (seq_bits->chroma_format_idc == 3) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppendUE(&sps_rbsp, seq->pic_width_in_luma_samples);
- BitstreamAppendUE(&sps_rbsp, seq->pic_height_in_luma_samples);
-
- bool conformance_window_flag =
- msp->conf_win_left_offset || msp->conf_win_right_offset ||
- msp->conf_win_top_offset || msp->conf_win_bottom_offset;
- BitstreamAppend(&sps_rbsp, 1, conformance_window_flag);
- if (conformance_window_flag) {
- BitstreamAppendUE(&sps_rbsp, msp->conf_win_left_offset);
- BitstreamAppendUE(&sps_rbsp, msp->conf_win_right_offset);
- BitstreamAppendUE(&sps_rbsp, msp->conf_win_top_offset);
- BitstreamAppendUE(&sps_rbsp, msp->conf_win_bottom_offset);
- }
-
- BitstreamAppendUE(&sps_rbsp, seq_bits->bit_depth_luma_minus8);
- BitstreamAppendUE(&sps_rbsp, seq_bits->bit_depth_chroma_minus8);
- BitstreamAppendUE(&sps_rbsp, log2_max_pic_order_cnt_lsb_minus4);
-
- BitstreamAppend(&sps_rbsp, 1, sps_sub_layer_ordering_info_present_flag);
- for (uint8_t i = (sps_sub_layer_ordering_info_present_flag
- ? 0
- : sps_max_sub_layers_minus1);
- i <= sps_max_sub_layers_minus1; i++) {
- if (i != sps_max_sub_layers_minus1) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppendUE(&sps_rbsp, msp->sps_max_dec_pic_buffering_minus1);
- BitstreamAppendUE(&sps_rbsp, msp->sps_max_num_reorder_pics);
- BitstreamAppendUE(&sps_rbsp, sps_max_latency_increase_plus1);
- }
-
- BitstreamAppendUE(&sps_rbsp, seq->log2_min_luma_coding_block_size_minus3);
- BitstreamAppendUE(&sps_rbsp, seq->log2_diff_max_min_luma_coding_block_size);
- BitstreamAppendUE(&sps_rbsp, seq->log2_min_transform_block_size_minus2);
- BitstreamAppendUE(&sps_rbsp, seq->log2_diff_max_min_transform_block_size);
- BitstreamAppendUE(&sps_rbsp, seq->max_transform_hierarchy_depth_inter);
- BitstreamAppendUE(&sps_rbsp, seq->max_transform_hierarchy_depth_intra);
-
- BitstreamAppend(&sps_rbsp, 1, seq_bits->scaling_list_enabled_flag);
- if (seq_bits->scaling_list_enabled_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&sps_rbsp, 1, seq_bits->amp_enabled_flag);
- BitstreamAppend(&sps_rbsp, 1, seq_bits->sample_adaptive_offset_enabled_flag);
- BitstreamAppend(&sps_rbsp, 1, seq_bits->pcm_enabled_flag);
- if (seq_bits->pcm_enabled_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppendUE(&sps_rbsp, num_short_term_ref_pic_sets);
- for (uint32_t i = 0; i < num_short_term_ref_pic_sets; i++) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&sps_rbsp, 1, long_term_ref_pics_present_flag);
- if (long_term_ref_pics_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&sps_rbsp, 1, seq_bits->sps_temporal_mvp_enabled_flag);
- BitstreamAppend(&sps_rbsp, 1, seq_bits->strong_intra_smoothing_enabled_flag);
- BitstreamAppend(&sps_rbsp, 1, seq->vui_parameters_present_flag);
- if (seq->vui_parameters_present_flag) PackVuiParameters(&sps_rbsp, seq, msp);
- BitstreamAppend(&sps_rbsp, 1, sps_extension_present_flag);
- if (sps_extension_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- PackRbspTrailingBits(&sps_rbsp);
- BitstreamInflate(bitstream, &sps_rbsp);
-}
-
-// 7.3.2.3.1 General picture parameter set RBSP syntax
-void PackPicParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncPictureParameterBufferHEVC* pic) {
- const typeof(pic->pic_fields.bits)* pic_bits = &pic->pic_fields.bits;
-
- PackNalUnitHeader(bitstream, PPS_NUT);
-
- char buffer_on_the_stack[64];
- struct Bitstream pps_rbsp = {
- .data = buffer_on_the_stack,
- .size = 0,
- };
-
- BitstreamAppendUE(&pps_rbsp, pps_pic_parameter_set_id);
- BitstreamAppendUE(&pps_rbsp, pps_seq_parameter_set_id);
- BitstreamAppend(&pps_rbsp, 1,
- pic_bits->dependent_slice_segments_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, output_flag_present_flag);
- BitstreamAppend(&pps_rbsp, 3, num_extra_slice_header_bits);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->sign_data_hiding_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, cabac_init_present_flag);
- BitstreamAppendUE(&pps_rbsp, pic->num_ref_idx_l0_default_active_minus1);
- BitstreamAppendUE(&pps_rbsp, pic->num_ref_idx_l1_default_active_minus1);
- BitstreamAppendSE(&pps_rbsp,
- pic->pic_init_qp - 26); // init_qp_minus26
- BitstreamAppend(&pps_rbsp, 1, pic_bits->constrained_intra_pred_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->transform_skip_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->cu_qp_delta_enabled_flag);
- if (pic_bits->cu_qp_delta_enabled_flag)
- BitstreamAppendUE(&pps_rbsp, pic->diff_cu_qp_delta_depth);
- BitstreamAppendSE(&pps_rbsp, pic->pps_cb_qp_offset);
- BitstreamAppendSE(&pps_rbsp, pic->pps_cr_qp_offset);
- BitstreamAppend(&pps_rbsp, 1, pps_slice_chroma_qp_offsets_present_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->weighted_pred_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->weighted_bipred_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->transquant_bypass_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->tiles_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, pic_bits->entropy_coding_sync_enabled_flag);
-
- if (pic_bits->tiles_enabled_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&pps_rbsp, 1,
- pic_bits->pps_loop_filter_across_slices_enabled_flag);
- BitstreamAppend(&pps_rbsp, 1, deblocking_filter_control_present_flag);
- if (deblocking_filter_control_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&pps_rbsp, 1, pic_bits->scaling_list_data_present_flag);
- if (pic_bits->scaling_list_data_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- BitstreamAppend(&pps_rbsp, 1, lists_modification_present_flag);
- BitstreamAppendUE(&pps_rbsp, pic->log2_parallel_merge_level_minus2);
- BitstreamAppend(&pps_rbsp, 1, slice_segment_header_extension_present_flag);
- BitstreamAppend(&pps_rbsp, 1, pps_extension_present_flag);
-
- if (pps_extension_present_flag) {
- // TODO(mburakov): Implement this!
- abort();
- }
-
- PackRbspTrailingBits(&pps_rbsp);
- BitstreamInflate(bitstream, &pps_rbsp);
-}
-
-// 7.3.7 Short-term reference picture set syntax
-static void PackStRefPicSet(struct Bitstream* bitstream, uint32_t stRpsIdx,
- const struct MoreSliceParamerters* msp) {
- if (stRpsIdx != 0)
- BitstreamAppend(bitstream, 1, inter_ref_pic_set_prediction_flag);
- if (inter_ref_pic_set_prediction_flag) {
- // TODO(mburakov): Implement this!
- abort();
- } else {
- BitstreamAppendUE(bitstream, msp->num_negative_pics);
- BitstreamAppendUE(bitstream, msp->num_positive_pics);
- for (uint32_t i = 0; i < msp->num_negative_pics; i++) {
- BitstreamAppendUE(bitstream, msp->negative_pics[i].delta_poc_s0_minus1);
- BitstreamAppend(bitstream, 1,
- msp->negative_pics[i].used_by_curr_pic_s0_flag);
- }
- for (uint32_t i = 0; i < msp->num_positive_pics; i++) {
- BitstreamAppendUE(bitstream, msp->positive_pics[i].delta_poc_s1_minus1);
- BitstreamAppend(bitstream, 1,
- msp->positive_pics[i].used_by_curr_pic_s1_flag);
- }
- }
-}
-
-// 7.3.6.1 General slice segment header syntax
-void PackSliceSegmentHeaderNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const VAEncPictureParameterBufferHEVC* pic,
- const VAEncSliceParameterBufferHEVC* slice,
- const struct MoreSliceParamerters* msp) {
- const typeof(seq->seq_fields.bits)* seq_bits = &seq->seq_fields.bits;
- const typeof(pic->pic_fields.bits)* pic_bits = &pic->pic_fields.bits;
- const typeof(slice->slice_fields.bits)* slice_bits =
- &slice->slice_fields.bits;
-
- PackNalUnitHeader(bitstream, pic->nal_unit_type);
-
- char buffer_on_the_stack[64];
- struct Bitstream slice_rbsp = {
- .data = buffer_on_the_stack,
- .size = 0,
- };
-
- BitstreamAppend(&slice_rbsp, 1, msp->first_slice_segment_in_pic_flag);
- if (pic->nal_unit_type >= BLA_W_LP && pic->nal_unit_type <= RSV_IRAP_VCL23)
- BitstreamAppend(&slice_rbsp, 1, pic_bits->no_output_of_prior_pics_flag);
- BitstreamAppendUE(&slice_rbsp, slice->slice_pic_parameter_set_id);
- if (!msp->first_slice_segment_in_pic_flag) {
- if (pic_bits->dependent_slice_segments_enabled_flag)
- BitstreamAppend(&slice_rbsp, 1, slice_bits->dependent_slice_segment_flag);
- // TODO(mburakov): Implement this!!!
- abort();
- }
-
- if (!slice_bits->dependent_slice_segment_flag) {
- for (uint32_t i = 0; i < num_extra_slice_header_bits; i++) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- BitstreamAppendUE(&slice_rbsp, slice->slice_type);
- if (output_flag_present_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (seq_bits->separate_colour_plane_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (pic->nal_unit_type != IDR_W_RADL && pic->nal_unit_type != IDR_N_LP) {
- uint32_t slice_pic_order_cnt_lsb =
- pic->decoded_curr_pic.pic_order_cnt &
- (1 << (log2_max_pic_order_cnt_lsb_minus4 + 4)) - 1;
- BitstreamAppend(&slice_rbsp, log2_max_pic_order_cnt_lsb_minus4 + 4,
- slice_pic_order_cnt_lsb);
- BitstreamAppend(&slice_rbsp, 1, short_term_ref_pic_set_sps_flag);
- if (!short_term_ref_pic_set_sps_flag)
- PackStRefPicSet(&slice_rbsp, num_short_term_ref_pic_sets, msp);
- else if (num_short_term_ref_pic_sets > 1) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (long_term_ref_pics_present_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (seq_bits->sps_temporal_mvp_enabled_flag) {
- BitstreamAppend(&slice_rbsp, 1,
- slice_bits->slice_temporal_mvp_enabled_flag);
- }
- }
- if (seq_bits->sample_adaptive_offset_enabled_flag) {
- BitstreamAppend(&slice_rbsp, 1, slice_bits->slice_sao_luma_flag);
- uint32_t ChromaArrayType = !seq_bits->separate_colour_plane_flag
- ? seq_bits->chroma_format_idc
- : 0;
- if (ChromaArrayType != 0)
- BitstreamAppend(&slice_rbsp, 1, slice_bits->slice_sao_chroma_flag);
- }
- if (slice->slice_type == P || slice->slice_type == B) {
- BitstreamAppend(&slice_rbsp, 1,
- slice_bits->num_ref_idx_active_override_flag);
- if (slice_bits->num_ref_idx_active_override_flag) {
- BitstreamAppendUE(&slice_rbsp, slice->num_ref_idx_l0_active_minus1);
- if (slice->slice_type == B)
- BitstreamAppendUE(&slice_rbsp, slice->num_ref_idx_l1_active_minus1);
- }
- if (lists_modification_present_flag /* && NumPicTotalCurr > 1*/) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (slice->slice_type == B)
- BitstreamAppend(&slice_rbsp, 1, slice_bits->mvd_l1_zero_flag);
- if (cabac_init_present_flag)
- BitstreamAppend(&slice_rbsp, 1, slice_bits->cabac_init_flag);
- if (slice_bits->slice_temporal_mvp_enabled_flag) {
- if (slice->slice_type == B)
- BitstreamAppend(&slice_rbsp, 1, slice_bits->collocated_from_l0_flag);
- if ((slice_bits->collocated_from_l0_flag &&
- slice->num_ref_idx_l0_active_minus1 > 0) ||
- (!slice_bits->collocated_from_l0_flag &&
- slice->num_ref_idx_l1_active_minus1 > 0))
- BitstreamAppendUE(&slice_rbsp, pic->collocated_ref_pic_index);
- }
- if ((pic_bits->weighted_pred_flag && slice->slice_type == P) ||
- (pic_bits->weighted_bipred_flag && slice->slice_type == B)) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- BitstreamAppendUE(
- &slice_rbsp,
- 5 - slice->max_num_merge_cand); // five_minus_max_num_merge_cand
- if (motion_vector_resolution_control_idc == 2) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- }
- BitstreamAppendSE(&slice_rbsp, slice->slice_qp_delta);
- if (pps_slice_chroma_qp_offsets_present_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (pps_slice_act_qp_offsets_present_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (chroma_qp_offset_list_enabled_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (deblocking_filter_override_enabled_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (deblocking_filter_override_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- if (pic_bits->pps_loop_filter_across_slices_enabled_flag &&
- (slice_bits->slice_sao_luma_flag || slice_bits->slice_sao_chroma_flag ||
- !slice_bits->slice_deblocking_filter_disabled_flag)) {
- BitstreamAppend(&slice_rbsp, 1,
- slice_bits->slice_loop_filter_across_slices_enabled_flag);
- }
- }
- if (pic_bits->tiles_enabled_flag ||
- pic_bits->entropy_coding_sync_enabled_flag) {
- BitstreamAppendUE(&slice_rbsp, num_entry_point_offsets);
- if (num_entry_point_offsets > 0) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
- }
- if (slice_segment_header_extension_present_flag) {
- // TODO(mburakov): Implement this!!!
- abort();
- }
-
- PackRbspTrailingBits(&slice_rbsp);
- BitstreamInflate(bitstream, &slice_rbsp);
-}
diff --git a/hevc.h b/hevc.h
deleted file mode 100644
index e82f32c..0000000
--- a/hevc.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#ifndef STREAMER_HEVC_H_
-#define STREAMER_HEVC_H_
-
-#include <stdbool.h>
-#include <va/va.h>
-
-// Table 7-1
-enum NalUnitType {
- TRAIL_R = 1,
- BLA_W_LP = 16,
- IDR_W_RADL = 19,
- IDR_N_LP = 20,
- RSV_IRAP_VCL23 = 23,
- VPS_NUT = 32,
- SPS_NUT = 33,
- PPS_NUT = 34,
-};
-
-// Table 7-7
-enum SliceType {
- B = 0,
- P = 1,
- I = 2,
-};
-
-struct Bitstream;
-
-struct MoreVideoParameters {
- uint32_t vps_max_dec_pic_buffering_minus1;
- uint32_t vps_max_num_reorder_pics;
-};
-
-struct MoreSeqParameters {
- uint32_t conf_win_left_offset;
- uint32_t conf_win_right_offset;
- uint32_t conf_win_top_offset;
- uint32_t conf_win_bottom_offset;
- uint32_t sps_max_dec_pic_buffering_minus1;
- uint32_t sps_max_num_reorder_pics;
- bool video_signal_type_present_flag;
- bool video_full_range_flag;
- bool colour_description_present_flag;
- uint8_t colour_primaries;
- uint8_t transfer_characteristics;
- uint8_t matrix_coeffs;
- bool chroma_loc_info_present_flag;
- uint32_t chroma_sample_loc_type_top_field;
- uint32_t chroma_sample_loc_type_bottom_field;
-};
-
-struct MoreSliceParamerters {
- bool first_slice_segment_in_pic_flag;
- // TODO(mburakov): Deduce from picture parameter buffer?
- uint32_t num_negative_pics;
- uint32_t num_positive_pics;
- struct NegativePics {
- uint32_t delta_poc_s0_minus1;
- bool used_by_curr_pic_s0_flag;
- } const* negative_pics;
- struct PositivePics {
- uint32_t delta_poc_s1_minus1;
- bool used_by_curr_pic_s1_flag;
- } const* positive_pics;
-};
-
-void PackVideoParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const struct MoreVideoParameters* mvp);
-void PackSeqParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const struct MoreSeqParameters* msp);
-void PackPicParameterSetNalUnit(struct Bitstream* bitstream,
- const VAEncPictureParameterBufferHEVC* pic);
-void PackSliceSegmentHeaderNalUnit(struct Bitstream* bitstream,
- const VAEncSequenceParameterBufferHEVC* seq,
- const VAEncPictureParameterBufferHEVC* pic,
- const VAEncSliceParameterBufferHEVC* slice,
- const struct MoreSliceParamerters* msp);
-
-#endif // STREAMER_HEVC_H_
diff --git a/input.c b/input.c
deleted file mode 100644
index 5a279cc..0000000
--- a/input.c
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "input.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/uhid.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "proto.h"
-#include "toolbox/buffer.h"
-#include "toolbox/utils.h"
-
-struct InputHandler {
- struct Buffer buffer;
- int uhid_fd;
-};
-
-struct InputHandler* InputHandlerCreate(bool disable_uhid) {
- struct InputHandler* input_handler = malloc(sizeof(struct InputHandler));
- if (!input_handler) {
- LOG("Failed to allocate input handler (%s)", strerror(errno));
- return NULL;
- }
- *input_handler = (struct InputHandler){
- .uhid_fd = -1,
- };
-
- BufferCreate(&input_handler->buffer);
- input_handler->uhid_fd =
- open(disable_uhid ? "/dev/null" : "/dev/uhid", O_RDWR);
- if (input_handler->uhid_fd == -1) {
- LOG("Failed to open uhid device (%s)", strerror(errno));
- goto rollback_input_handler;
- }
- return input_handler;
-
-rollback_input_handler:
- free(input_handler);
- return NULL;
-}
-
-int InputHandlerGetEventsFd(struct InputHandler* input_handler) {
- return input_handler->uhid_fd;
-}
-
-bool InputHandlerProcessEvents(struct InputHandler* input_handler) {
- struct uhid_event uhid_event;
- if (read(input_handler->uhid_fd, &uhid_event, sizeof(uhid_event)) < 0) {
- LOG("Failed to process uhid events (%s)", strerror(errno));
- return false;
- }
- // TODO(mburakov): Add logging?
- return true;
-}
-
-bool InputHandlerHandle(struct InputHandler* input_handler, int fd) {
- switch (BufferAppendFrom(&input_handler->buffer, fd)) {
- case -1:
- LOG("Failed to append input data to buffer (%s)", strerror(errno));
- return false;
- case 0:
- LOG("Client closed connection");
- return false;
- default:
- break;
- }
-
- for (;;) {
- struct uhid_event* event = input_handler->buffer.data;
- if (input_handler->buffer.size < sizeof(event->type)) {
- // mburakov: Packet type is not yet available.
- return true;
- }
-
- if (event->type == ~0u) {
- // mburakov: Special case, a ping message.
- size_t size = sizeof(event->type) + sizeof(uint64_t);
- if (input_handler->buffer.size < size) {
- // mburakov: Payload of ping message is not yet available.
- return true;
- }
- struct Proto proto = {
- .size = sizeof(uint64_t),
- .type = PROTO_TYPE_MISC,
- };
- if (!WriteProto(fd, &proto, &event->u)) {
- LOG("Failed to write pong message (%s)", strerror(errno));
- return false;
- }
- BufferDiscard(&input_handler->buffer, size);
- continue;
- }
-
- size_t size;
- switch (event->type) {
- case UHID_CREATE2:
- if (input_handler->buffer.size <
- offsetof(struct uhid_event, u.create2.rd_size) +
- sizeof(event->u.create2.rd_size)) {
- // mburakov: Report descriptor size is not yet available.
- return true;
- }
- size = offsetof(struct uhid_event, u.create2.rd_data) +
- event->u.create2.rd_size;
- if (input_handler->buffer.size < size) {
- // mburakov: Report descriptor data is not yet available.
- return true;
- }
- break;
- case UHID_INPUT2:
- if (input_handler->buffer.size <
- offsetof(struct uhid_event, u.input2.size) +
- sizeof(event->u.input2.size)) {
- // mburakov: Report size is not yet available.
- return true;
- }
- size =
- offsetof(struct uhid_event, u.input2.data) + event->u.input2.size;
- if (input_handler->buffer.size < size) {
- // mburakov: Report data is not yet available.
- return true;
- }
- break;
- case UHID_DESTROY:
- size = sizeof(event->type);
- break;
- default:
- __builtin_unreachable();
- }
-
- // mburakov: This write has to be atomic.
- if (write(input_handler->uhid_fd, event, size) != (ssize_t)size) {
- LOG("Failed to write uhid event (%s)", strerror(errno));
- return false;
- }
- BufferDiscard(&input_handler->buffer, size);
- }
-}
-
-void InputHandlerDestroy(struct InputHandler* input_handler) {
- close(input_handler->uhid_fd);
- BufferDestroy(&input_handler->buffer);
- free(input_handler);
-}
diff --git a/io_context.c b/io_context.c
new file mode 100644
index 0000000..38a7d13
--- /dev/null
+++ b/io_context.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
+ *
+ * streamer 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.
+ *
+ * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "io_context.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <threads.h>
+#include <unistd.h>
+
+#include "proto.h"
+#include "queue.h"
+#include "util.h"
+
+struct IoContext {
+ int fd;
+ atomic_bool running;
+ struct Queue prio;
+ struct Queue queue;
+
+ mtx_t mutex;
+ cnd_t cond;
+ thrd_t thread;
+};
+
+struct ProtoImpl {
+ struct Proto proto;
+ struct ProtoHeader header;
+ uint8_t data[];
+};
+
+static bool IsPrioProto(const struct Proto* proto) {
+ return proto->header->type == kProtoTypePing ||
+ proto->header->type == kProtoTypePong;
+}
+
+static void ProtoDestroy(struct Proto* proto) { free(proto); }
+
+static bool ReadAll(int fd, void* buffer, size_t size) {
+ for (uint8_t* ptr = buffer; size;) {
+ ssize_t result = read(fd, ptr, size);
+ if (result <= 0) {
+ LOG("Failed to read socket (%s)", strerror(errno));
+ return false;
+ }
+ size -= (size_t)result;
+ ptr += result;
+ }
+ return true;
+}
+
+static bool WriteAll(int fd, struct iovec* iov, size_t count) {
+ while (count) {
+ int max_count = (int)MIN(count, UIO_MAXIOV);
+ ssize_t result = writev(fd, iov, max_count);
+ if (result <= 0) {
+ LOG("Failed to write socket (%s)", strerror(errno));
+ return false;
+ }
+
+ for (;;) {
+ if ((size_t)result < iov->iov_len) {
+ iov->iov_len -= (size_t)result;
+ iov->iov_base = (uint8_t*)iov->iov_base + result;
+ break;
+ }
+ result -= (ssize_t)iov->iov_len;
+ count--;
+ iov++;
+ }
+ }
+ return true;
+}
+
+static bool IoContextDequeue(struct IoContext* io_context,
+ struct Proto** pproto) {
+ if (mtx_lock(&io_context->mutex) != thrd_success) {
+ LOG("Failed to lock mutex (%s)", strerror(errno));
+ return false;
+ }
+
+ void* item = NULL;
+ while (!QueuePop(&io_context->prio, &item) &&
+ !QueuePop(&io_context->queue, &item) &&
+ atomic_load_explicit(&io_context->running, memory_order_relaxed)) {
+ assert(cnd_wait(&io_context->cond, &io_context->mutex) == thrd_success);
+ }
+
+ assert(mtx_unlock(&io_context->mutex) == thrd_success);
+ *pproto = item;
+ return true;
+}
+
+static int IoContextThreadProc(void* arg) {
+ struct IoContext* io_context = arg;
+ for (;;) {
+ struct Proto* proto;
+ if (!IoContextDequeue(io_context, &proto)) {
+ LOG("Failed to dequeue proto");
+ goto leave;
+ }
+
+ if (!proto) {
+ // mburakov: running was set to false externally.
+ return 0;
+ }
+
+ struct iovec iov[] = {
+ {.iov_base = (void*)(uintptr_t)(proto->header),
+ .iov_len = sizeof(struct ProtoHeader)},
+ {.iov_base = (void*)(uintptr_t)(proto->data),
+ .iov_len = proto->header->size},
+ };
+ bool result = WriteAll(io_context->fd, iov, LENGTH(iov));
+ proto->Destroy(proto);
+ if (!result) {
+ LOG("Failed to write proto");
+ goto leave;
+ }
+ }
+
+leave:
+ atomic_store_explicit(&io_context->running, false, memory_order_relaxed);
+ return 0;
+}
+
+struct IoContext* IoContextCreate(uint16_t port) {
+ int sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ LOG("Failed to create socket (%s)", strerror(errno));
+ return NULL;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int))) {
+ LOG("Failed to reuse socket address (%s)", strerror(errno));
+ goto rollback_sock;
+ }
+
+ const struct sockaddr_in sa = {
+ .sin_family = AF_INET,
+ .sin_port = htons(port),
+ .sin_addr = INADDR_ANY,
+ };
+ if (bind(sock, (const struct sockaddr*)&sa, sizeof(sa))) {
+ LOG("Failed to bind socket (%s)", strerror(errno));
+ goto rollback_sock;
+ }
+
+ if (listen(sock, SOMAXCONN)) {
+ LOG("Failed to listen socket (%s)", strerror(errno));
+ goto rollback_sock;
+ }
+
+ struct IoContext* io_context = malloc(sizeof(struct IoContext));
+ if (!io_context) {
+ LOG("Failed to allocate io context (%s)", strerror(errno));
+ goto rollback_sock;
+ }
+
+ io_context->fd = accept(sock, NULL, NULL);
+ if (io_context->fd == -1) {
+ LOG("Failed to accept socket (%s)", strerror(errno));
+ goto rollback_io_context;
+ }
+
+ if (setsockopt(io_context->fd, IPPROTO_TCP, TCP_NODELAY, &(int){1},
+ sizeof(int))) {
+ LOG("Failed to set TCP_NODELAY (%s)", strerror(errno));
+ goto rollback_fd;
+ }
+
+ atomic_init(&io_context->running, true);
+ QueueCreate(&io_context->prio);
+ QueueCreate(&io_context->queue);
+ if (mtx_init(&io_context->mutex, mtx_plain) != thrd_success) {
+ LOG("Failed to init mutex (%s)", strerror(errno));
+ goto rollback_fd;
+ }
+
+ if (cnd_init(&io_context->cond) != thrd_success) {
+ LOG("Failed to init condition variable (%s)", strerror(errno));
+ goto rollback_mutex;
+ }
+
+ if (thrd_create(&io_context->thread, &IoContextThreadProc, io_context) !=
+ thrd_success) {
+ LOG("Failed to create thread (%s)", strerror(errno));
+ goto rollback_cond;
+ }
+
+ assert(!close(sock));
+ return io_context;
+
+rollback_cond:
+ cnd_destroy(&io_context->cond);
+rollback_mutex:
+ mtx_destroy(&io_context->mutex);
+rollback_fd:
+ assert(!close(io_context->fd));
+rollback_io_context:
+ free(io_context);
+rollback_sock:
+ assert(!close(sock));
+ return NULL;
+}
+
+struct Proto* IoContextRead(struct IoContext* io_context) {
+ struct ProtoHeader header;
+ if (!ReadAll(io_context->fd, &header, sizeof(header))) {
+ LOG("Failed to read proto header");
+ return NULL;
+ }
+
+ struct ProtoImpl* proto_impl = malloc(sizeof(struct ProtoImpl) + header.size);
+ if (!proto_impl) {
+ LOG("Failed to allocate proto (%s)", strerror(errno));
+ return NULL;
+ }
+
+ if (!ReadAll(io_context->fd, &proto_impl->data, header.size)) {
+ LOG("Failed to read proto body");
+ goto rollback_proto_impl;
+ }
+
+ proto_impl->header = header;
+ const struct Proto proto = {
+ .Destroy = ProtoDestroy,
+ .header = &proto_impl->header,
+ .data = proto_impl->data,
+ };
+ memcpy(proto_impl, &proto, sizeof(proto));
+ return &proto_impl->proto;
+
+rollback_proto_impl:
+ free(proto_impl);
+ return NULL;
+}
+
+bool IoContextWrite(struct IoContext* io_context, struct Proto* proto) {
+ if (!atomic_load_explicit(&io_context->running, memory_order_relaxed)) {
+ LOG("Io context is not running");
+ goto rollback_proto;
+ }
+
+ struct Queue* queue =
+ IsPrioProto(proto) ? &io_context->prio : &io_context->queue;
+ if (mtx_lock(&io_context->mutex) != thrd_success) {
+ LOG("Failed to lock mutex (%s)", strerror(errno));
+ goto rollback_proto;
+ }
+
+ if (!QueuePush(queue, proto)) {
+ LOG("Failed to queue proto");
+ goto rollback_lock;
+ }
+
+ assert(cnd_broadcast(&io_context->cond) == thrd_success);
+ assert(mtx_unlock(&io_context->mutex) == thrd_success);
+ return true;
+
+rollback_lock:
+ assert(mtx_unlock(&io_context->mutex) == thrd_success);
+rollback_proto:
+ proto->Destroy(proto);
+ return false;
+}
+
+void IoContextDestroy(struct IoContext* io_context) {
+ atomic_store_explicit(&io_context->running, false, memory_order_relaxed);
+ assert(cnd_broadcast(&io_context->cond) == thrd_success);
+ assert(thrd_join(io_context->thread, NULL) == thrd_success);
+ cnd_destroy(&io_context->cond);
+ mtx_destroy(&io_context->mutex);
+ for (void* item; QueuePop(&io_context->prio, &item); free(item));
+ QueueDestroy(&io_context->prio);
+ for (void* item; QueuePop(&io_context->queue, &item); free(item));
+ QueueDestroy(&io_context->queue);
+ assert(!close(io_context->fd));
+ free(io_context);
+}
diff --git a/input.h b/io_context.h
index a3ed01e..9e880fd 100644
--- a/input.h
+++ b/io_context.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,17 +15,18 @@
* along with streamer. If not, see <https://www.gnu.org/licenses/>.
*/
-#ifndef STREAMER_INPUT_H_
-#define STREAMER_INPUT_H_
+#ifndef STREAMER_IO_CONTEXT_H_
+#define STREAMER_IO_CONTEXT_H_
+#include <stdint.h>
#include <stdbool.h>
-struct InputHandler;
+struct Proto;
+struct IoContext;
-struct InputHandler* InputHandlerCreate(bool disable_uhid);
-int InputHandlerGetEventsFd(struct InputHandler* input_handler);
-bool InputHandlerProcessEvents(struct InputHandler* input_handler);
-bool InputHandlerHandle(struct InputHandler* input_handler, int fd);
-void InputHandlerDestroy(struct InputHandler* input_handler);
+struct IoContext* IoContextCreate(uint16_t port);
+struct Proto* IoContextRead(struct IoContext* io_context);
+bool IoContextWrite(struct IoContext* io_context, struct Proto* proto);
+void IoContextDestroy(struct IoContext* io_context);
-#endif // STREAMER_INPUT_H_
+#endif // STREAMER_IO_CONTEXT_H_
diff --git a/main.c b/main.c
index 9290be3..3548f37 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,400 +16,104 @@
*/
#include <errno.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
+#include <pipewire/pipewire.h>
#include <signal.h>
-#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#include "audio.h"
-#include "capture.h"
-#include "colorspace.h"
-#include "encode.h"
-#include "gpu.h"
-#include "input.h"
+#include "audio_context.h"
+#include "io_context.h"
#include "proto.h"
-#include "toolbox/io_muxer.h"
-#include "toolbox/perf.h"
-#include "toolbox/utils.h"
-
-// TODO(mburakov): Currently zwp_linux_dmabuf_v1 has no way to provide
-// colorspace and range information to the compositor. Maybe this would change
-// in the future, i.e keep an eye on color-representation Wayland protocol:
-// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/183
-static const enum YuvColorspace colorspace = kItuRec601;
-static const enum YuvRange range = kNarrowRange;
+#include "util.h"
static volatile sig_atomic_t g_signal;
static void OnSignal(int status) { g_signal = status; }
-struct Contexts {
- bool disable_uhid;
- const char* audio_config;
- struct AudioContext* audio_context;
- struct GpuContext* gpu_context;
- struct IoMuxer io_muxer;
- int server_fd;
-
- int client_fd;
- struct InputHandler* input_handler;
- struct CaptureContext* capture_context;
- struct EncodeContext* encode_context;
- bool drop_client;
-};
-
-static int CreateServerSocket(const char* arg) {
- int port = atoi(arg);
- if (0 > port || port > UINT16_MAX) {
- LOG("Invalid port number argument");
- return -1;
- }
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0) {
- LOG("Failed to create socket (%s)", strerror(errno));
- return -1;
- }
- if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int))) {
- LOG("Failed to reuse socket address (%s)", strerror(errno));
- goto rollback_sock;
- }
- const struct sockaddr_in addr = {
- .sin_family = AF_INET,
- .sin_port = htons((uint16_t)port),
+static bool SetupSignalHandler(int sig, void (*func)(int)) {
+ struct sigaction sa = {
+ .sa_handler = func,
};
- if (bind(sock, (const struct sockaddr*)&addr, sizeof(addr))) {
- LOG("Failed to bind socket (%s)", strerror(errno));
- goto rollback_sock;
+ if (sigemptyset(&sa.sa_mask) || sigaddset(&sa.sa_mask, sig)) {
+ LOG("Failed to configure signal set (%s)", strerror(errno));
+ return false;
}
- if (listen(sock, SOMAXCONN)) {
- LOG("Failed to listen socket (%s)", strerror(errno));
- goto rollback_sock;
+ if (sigaction(sig, &sa, NULL)) {
+ LOG("Failed to set signal action (%s)", strerror(errno));
+ return false;
}
- return sock;
-
-rollback_sock:
- close(sock);
- return -1;
+ return true;
}
-static void MaybeDropClient(struct Contexts* contexts) {
- if (contexts->encode_context) {
- EncodeContextDestroy(contexts->encode_context);
- contexts->encode_context = NULL;
- }
- if (contexts->capture_context) {
- IoMuxerForget(&contexts->io_muxer,
- CaptureContextGetEventsFd(contexts->capture_context));
- CaptureContextDestroy(contexts->capture_context);
- contexts->capture_context = NULL;
- }
- if (contexts->input_handler) {
- IoMuxerForget(&contexts->io_muxer,
- InputHandlerGetEventsFd(contexts->input_handler));
- InputHandlerDestroy(contexts->input_handler);
- contexts->input_handler = NULL;
- }
- if (contexts->client_fd != -1) {
- IoMuxerForget(&contexts->io_muxer, contexts->client_fd);
- close(contexts->client_fd);
- contexts->client_fd = -1;
- }
-}
-
-static void OnAudioContextAudioReady(void* user, const void* buffer,
- size_t size, size_t latency) {
- struct Contexts* contexts = user;
- if (contexts->client_fd == -1) return;
-
- struct Proto proto = {
- .size = (uint32_t)size,
- .type = PROTO_TYPE_AUDIO,
- .flags = 0,
- .latency = (uint16_t)MIN(latency, UINT16_MAX),
- };
- if (!WriteProto(contexts->client_fd, &proto, buffer)) {
- LOG("Failed to write audio frame");
- MaybeDropClient(contexts);
- }
-}
-
-static void OnCaptureContextFrameReady(void* user,
- const struct GpuFrame* captured_frame) {
- struct Contexts* contexts = user;
- unsigned long long timestamp = MicrosNow();
-
- if (!contexts->encode_context) {
- contexts->encode_context =
- EncodeContextCreate(contexts->gpu_context, captured_frame->width,
- captured_frame->height, colorspace, range);
- if (!contexts->encode_context) {
- LOG("Failed to create encode context");
- goto drop_client;
+static void HandleClientSession(struct IoContext* io_context) {
+ struct Proto* proto = NULL;
+ struct AudioContext* audio_context = NULL;
+ while (!g_signal) {
+ proto = IoContextRead(io_context);
+ if (!proto) {
+ LOG("Failed to read proto");
+ goto leave;
}
- }
-
- const struct GpuFrame* encoded_frame =
- EncodeContextGetFrame(contexts->encode_context);
- if (!encoded_frame) {
- LOG("Failed to get encoded frame");
- goto drop_client;
- }
- if (!GpuContextConvertFrame(contexts->gpu_context, captured_frame,
- encoded_frame)) {
- LOG("Failed to convert frame");
- goto drop_client;
- }
- if (!EncodeContextEncodeFrame(contexts->encode_context, contexts->client_fd,
- timestamp)) {
- LOG("Failed to encode frame");
- goto drop_client;
- }
- return;
-
-drop_client:
- // TODO(mburakov): Can't drop client here, because leftover code in capturing
- // functions would fail in this case. Instead just schedule dropping client
- // here, and execute that in the event loop of the main function.
- contexts->drop_client = true;
-}
-static void OnClientWriting(void* user) {
- struct Contexts* contexts = user;
- if (!IoMuxerOnRead(&contexts->io_muxer, contexts->client_fd, &OnClientWriting,
- user)) {
- LOG("Failed to reschedule client reading (%s)", strerror(errno));
- goto drop_client;
- }
- if (!InputHandlerHandle(contexts->input_handler, contexts->client_fd)) {
- LOG("Failed to handle client input");
- goto drop_client;
- }
- return;
-
-drop_client:
- MaybeDropClient(contexts);
-}
-
-static void OnInputEvents(void* user) {
- struct Contexts* contexts = user;
- if (!IoMuxerOnRead(&contexts->io_muxer,
- InputHandlerGetEventsFd(contexts->input_handler),
- &OnInputEvents, user)) {
- LOG("Failed to reschedule input events reading (%s)", strerror(errno));
- goto drop_client;
- }
- if (!InputHandlerProcessEvents(contexts->input_handler)) {
- LOG("Failed to process input events");
- goto drop_client;
- }
- return;
-
-drop_client:
- MaybeDropClient(contexts);
-}
-
-static void OnAudioContextEvents(void* user) {
- struct Contexts* contexts = user;
- if (!IoMuxerOnRead(&contexts->io_muxer,
- AudioContextGetEventsFd(contexts->audio_context),
- &OnAudioContextEvents, user)) {
- LOG("Failed to reschedule audio io (%s)", strerror(errno));
- g_signal = SIGABRT;
- return;
- }
- if (!AudioContextProcessEvents(contexts->audio_context)) {
- LOG("Failed to process audio events");
- g_signal = SIGABRT;
- return;
- }
-}
-
-static void OnCaptureContextEvents(void* user) {
- struct Contexts* contexts = user;
- if (!IoMuxerOnRead(&contexts->io_muxer,
- CaptureContextGetEventsFd(contexts->capture_context),
- &OnCaptureContextEvents, user)) {
- LOG("Failed to reschedule capture events reading (%s)", strerror(errno));
- goto drop_client;
- }
- if (!CaptureContextProcessEvents(contexts->capture_context)) {
- LOG("Failed to process capture events");
- goto drop_client;
- }
- return;
-
-drop_client:
- MaybeDropClient(contexts);
-}
-
-static void OnClientConnecting(void* user) {
- struct Contexts* contexts = user;
- if (!IoMuxerOnRead(&contexts->io_muxer, contexts->server_fd,
- &OnClientConnecting, user)) {
- LOG("Failed to reschedule accept (%s)", strerror(errno));
- g_signal = SIGABRT;
- return;
- }
- int client_fd = accept(contexts->server_fd, NULL, NULL);
- if (client_fd < 0) {
- LOG("Failed to accept client (%s)", strerror(errno));
- g_signal = SIGABRT;
- return;
- }
-
- if (contexts->client_fd != -1) {
- LOG("One client is already connected");
- close(client_fd);
- return;
- }
-
- if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &(int){1}, sizeof(int))) {
- LOG("Failed to set TCP_NODELAY (%s)", strerror(errno));
- goto drop_client;
- }
-
- contexts->client_fd = client_fd;
- if (!IoMuxerOnRead(&contexts->io_muxer, contexts->client_fd, &OnClientWriting,
- user)) {
- LOG("Failed to schedule client reading (%s)", strerror(errno));
- goto drop_client;
- }
- contexts->input_handler = InputHandlerCreate(contexts->disable_uhid);
- if (!contexts->input_handler) {
- LOG("Failed to create input handler");
- goto drop_client;
- }
- if (!IoMuxerOnRead(&contexts->io_muxer,
- InputHandlerGetEventsFd(contexts->input_handler),
- &OnInputEvents, user)) {
- LOG("Failed to schedule input events reading (%s)", strerror(errno));
- goto drop_client;
- }
- static const struct CaptureContextCallbacks kCaptureContextCallbacks = {
- .OnFrameReady = OnCaptureContextFrameReady,
- };
- contexts->capture_context = CaptureContextCreate(
- contexts->gpu_context, &kCaptureContextCallbacks, user);
- if (!contexts->capture_context) {
- LOG("Failed to create capture context");
- goto drop_client;
- }
- if (!IoMuxerOnRead(&contexts->io_muxer,
- CaptureContextGetEventsFd(contexts->capture_context),
- &OnCaptureContextEvents, user)) {
- LOG("Failed to schedule capture events reading (%s)", strerror(errno));
- goto drop_client;
- }
-
- if (contexts->audio_config) {
- struct Proto proto = {
- .size = (uint32_t)strlen(contexts->audio_config) + 1,
- .type = PROTO_TYPE_AUDIO,
- .flags = PROTO_FLAG_KEYFRAME,
- .latency = 0,
- };
- if (!WriteProto(contexts->client_fd, &proto, contexts->audio_config)) {
- LOG("Failed to write audio configuration");
- goto drop_client;
+ switch (proto->header->type) {
+ case kProtoTypeHello:
+ if (audio_context) {
+ LOG("Audio reconfiguration prohibited");
+ goto rollback_proto;
+ }
+ audio_context = AudioContextCreate(io_context, proto);
+ if (!audio_context) {
+ LOG("Failed to create audio context");
+ goto leave;
+ }
+ break;
+ case kProtoTypePing:
+ case kProtoTypeUhid:
+ break;
+ default:
+ LOG("Unexpected proto received");
+ goto rollback_proto;
}
}
- return;
-drop_client:
- MaybeDropClient(contexts);
+rollback_proto:
+ proto->Destroy(proto);
+leave:
+ if (audio_context) AudioContextDestroy(audio_context);
}
int main(int argc, char* argv[]) {
+ pw_init(&argc, &argv);
if (argc < 2) {
- LOG("Usage: %s <port> [--disable-uhid] [--audio <rate:channels>]", argv[0]);
- return EXIT_FAILURE;
- }
- if (signal(SIGINT, OnSignal) == SIG_ERR ||
- signal(SIGPIPE, SIG_IGN) == SIG_ERR ||
- signal(SIGTERM, OnSignal) == SIG_ERR) {
- LOG("Failed to set signal handlers (%s)", strerror(errno));
- return EXIT_FAILURE;
- }
-
- struct Contexts contexts = {
- .server_fd = -1,
- .client_fd = -1,
- };
- const char* audio_config = NULL;
- for (int i = 2; i < argc; i++) {
- if (!strcmp(argv[i], "--disable-uhid")) {
- contexts.disable_uhid = true;
- } else if (!strcmp(argv[i], "--audio")) {
- audio_config = argv[++i];
- if (i == argc) {
- LOG("Audio argument requires a value");
- return EXIT_FAILURE;
- }
- }
- }
-
- static struct AudioContextCallbacks kAudioContextCallbacks = {
- .OnAudioReady = OnAudioContextAudioReady,
- };
- if (audio_config) {
- contexts.audio_config = audio_config;
- contexts.audio_context =
- AudioContextCreate(audio_config, &kAudioContextCallbacks, &contexts);
- if (!contexts.audio_context) {
- LOG("Failed to create audio context");
- return EXIT_FAILURE;
- }
+ LOG("Usage: streamer <port>");
+ goto leave;
}
- contexts.gpu_context = GpuContextCreate(colorspace, range);
- if (!contexts.gpu_context) {
- LOG("Failed to create gpu context");
- goto rollback_audio_context;
+ int port = atoi(argv[1]);
+ if (0 >= port || port > UINT16_MAX) {
+ LOG("Invalid port \"%s\"", argv[1]);
+ goto leave;
}
- IoMuxerCreate(&contexts.io_muxer);
- contexts.server_fd = CreateServerSocket(argv[1]);
- if (contexts.server_fd == -1) {
- LOG("Failed to create server socket");
- goto rollback_io_muxer;
- }
-
- if (contexts.audio_context &&
- !IoMuxerOnRead(&contexts.io_muxer,
- AudioContextGetEventsFd(contexts.audio_context),
- &OnAudioContextEvents, &contexts)) {
- LOG("Failed to schedule audio io (%s)", strerror(errno));
- goto rollback_server_fd;
- }
- if (!IoMuxerOnRead(&contexts.io_muxer, contexts.server_fd,
- &OnClientConnecting, &contexts)) {
- LOG("Failed to schedule accept (%s)", strerror(errno));
- goto rollback_server_fd;
- }
+ SetupSignalHandler(SIGINT, OnSignal);
+ SetupSignalHandler(SIGPIPE, SIG_IGN);
+ SetupSignalHandler(SIGTERM, OnSignal);
while (!g_signal) {
- if (IoMuxerIterate(&contexts.io_muxer, -1) && errno != EINTR) {
- LOG("Failed to iterate io muxer (%s)", strerror(errno));
- g_signal = SIGABRT;
- }
- if (contexts.drop_client) {
- MaybeDropClient(&contexts);
- contexts.drop_client = false;
+ struct IoContext* io_context = IoContextCreate((uint16_t)port);
+ if (!io_context) {
+ LOG("Failed to create io context");
+ goto leave;
}
+
+ HandleClientSession(io_context);
+ IoContextDestroy(io_context);
}
- MaybeDropClient(&contexts);
-rollback_server_fd:
- close(contexts.server_fd);
-rollback_io_muxer:
- IoMuxerDestroy(&contexts.io_muxer);
- GpuContextDestroy(contexts.gpu_context);
-rollback_audio_context:
- if (contexts.audio_context) AudioContextDestroy(contexts.audio_context);
+leave:
+ pw_deinit();
bool result = g_signal == SIGINT || g_signal == SIGTERM;
return result ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/makefile b/makefile
index fddd5a0..6f46d1b 100644
--- a/makefile
+++ b/makefile
@@ -2,18 +2,9 @@ bin:=$(notdir $(shell pwd))
src:=$(wildcard *.c)
obj:=$(src:.c=.o)
-obj+=\
- toolbox/buffer.o \
- toolbox/io_muxer.o \
- toolbox/perf.o
-
libs:=\
- egl \
- gbm \
- glesv2 \
- libdrm \
- libva \
- libva-drm
+ libpipewire-0.3 \
+ wayland-client
protocols_dir:=\
wlr-protocols/unstable
@@ -26,19 +17,9 @@ res:=\
luma.glsl \
chroma.glsl
-ifdef USE_WAYLAND
- obj:=$(patsubst %,%.o,$(protocols)) $(obj)
- headers:=$(patsubst %,%.h,$(protocols))
- libs+=wayland-client
- CFLAGS+=-DUSE_WAYLAND
-endif
-
-ifdef USE_PIPEWIRE
- libs+=libpipewire-0.3
- CFLAGS+=-DUSE_PIPEWIRE
-endif
+obj:=$(patsubst %,%.o,$(protocols)) $(obj)
+headers:=$(patsubst %,%.h,$(protocols))
-#CFLAGS+=-DUSE_EGL_MESA_PLATFORM_SURFACELESS
CFLAGS+=$(shell pkg-config --cflags $(libs))
LDFLAGS+=$(shell pkg-config --libs $(libs))
@@ -63,8 +44,7 @@ $(bin): $(obj)
wayland-scanner private-code $< $@
clean:
- -rm $(bin) $(obj) $(headers) \
- $(foreach proto,$(protocols),$(proto).h $(proto).o)
+ -rm $(bin) $(obj) $(headers)
.PHONY: all clean
diff --git a/proto.c b/proto.c
deleted file mode 100644
index f1d2bc3..0000000
--- a/proto.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
- *
- * streamer 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.
- *
- * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "proto.h"
-
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-#include "toolbox/utils.h"
-
-#define UNCONST(x) ((void*)(uintptr_t)(x))
-
-static bool DrainBuffers(int fd, struct iovec* iovec, int count) {
- for (;;) {
- ssize_t result = writev(fd, iovec, count);
- if (result < 0) {
- if (errno == EINTR) continue;
- LOG("Failed to write (%s)", strerror(errno));
- return false;
- }
- for (int i = 0; i < count; i++) {
- size_t delta = MIN((size_t)result, iovec[i].iov_len);
- iovec[i].iov_base = (uint8_t*)iovec[i].iov_base + delta;
- iovec[i].iov_len -= delta;
- result -= delta;
- }
- if (!result) return true;
- }
-}
-
-bool WriteProto(int fd, const struct Proto* proto, const void* data) {
- struct iovec iovec[] = {
- {.iov_base = UNCONST(proto), .iov_len = sizeof(struct Proto)},
- {.iov_base = UNCONST(data), .iov_len = proto->size},
- };
- if (!DrainBuffers(fd, iovec, LENGTH(iovec))) {
- LOG("Failed to drain buffers");
- return false;
- }
- return true;
-}
diff --git a/proto.h b/proto.h
index 62f65d5..9a6a3a6 100644
--- a/proto.h
+++ b/proto.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,26 +19,31 @@
#define STREAMER_PROTO_H_
#include <assert.h>
+#include <stddef.h>
#include <stdint.h>
-#include <stdbool.h>
-#define PROTO_TYPE_MISC 0
-#define PROTO_TYPE_VIDEO 1
-#define PROTO_TYPE_AUDIO 2
-
-#define PROTO_FLAG_KEYFRAME 1
+enum ProtoType {
+ kProtoTypeHello,
+ kProtoTypePing,
+ kProtoTypePong,
+ kProtoTypeUhid,
+ kProtoTypeVideo,
+ kProtoTypeAudio,
+};
-struct Proto {
+struct ProtoHeader {
uint32_t size;
- uint8_t type;
- uint8_t flags;
- uint16_t latency;
- uint8_t data[];
+ enum ProtoType type;
+ uint64_t timestamp;
};
-static_assert(sizeof(struct Proto) == 8 * sizeof(uint8_t),
- "Suspicious proto struct size");
+static_assert(sizeof(struct ProtoHeader) == 16 * sizeof(uint8_t),
+ "Suspicious proto header struct size");
-bool WriteProto(int fd, const struct Proto* proto, const void* data);
+struct Proto {
+ void (*const Destroy)(struct Proto* self);
+ const struct ProtoHeader* const header;
+ const void* const data;
+};
#endif // STREAMER_PROTO_H_
diff --git a/queue.c b/queue.c
new file mode 100644
index 0000000..894bf2e
--- /dev/null
+++ b/queue.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
+ *
+ * streamer 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.
+ *
+ * streamer 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 streamer. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "queue.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+void QueueCreate(struct Queue* queue) {
+ memset(queue, 0, sizeof(struct Queue));
+}
+
+bool QueuePush(struct Queue* queue, void* item) {
+ if (queue->size == queue->alloc) {
+ size_t alloc = queue->alloc + 1;
+ void** buffer = malloc(alloc * sizeof(void*));
+ if (!buffer) return false;
+
+ size_t head_size = queue->read;
+ size_t tail_size = queue->size - queue->read;
+ memcpy(buffer, queue->buffer + queue->read, tail_size * sizeof(void*));
+ memcpy(buffer + tail_size, queue->buffer, head_size * sizeof(void*));
+ free(queue->buffer);
+
+ queue->buffer = buffer;
+ queue->alloc = alloc;
+ queue->read = 0;
+ queue->write = queue->size;
+ }
+
+ queue->buffer[queue->write] = item;
+ queue->write = (queue->write + 1) % queue->alloc;
+ queue->size++;
+ return true;
+}
+
+bool QueuePop(struct Queue* queue, void** pitem) {
+ if (!queue->size) return false;
+ *pitem = queue->buffer[queue->read];
+ queue->read = (queue->read + 1) % queue->alloc;
+ queue->size--;
+ return true;
+}
+
+void QueueDestroy(struct Queue* queue) { free(queue->buffer); }
diff --git a/bitstream.h b/queue.h
index 1520c57..f226aff 100644
--- a/bitstream.h
+++ b/queue.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +15,23 @@
* along with streamer. If not, see <https://www.gnu.org/licenses/>.
*/
-#ifndef STREAMER_BITSTREAM_H_
-#define STREAMER_BITSTREAM_H_
+#ifndef STREAMER_QUEUE_H_
+#define STREAMER_QUEUE_H_
+#include <stdbool.h>
#include <stddef.h>
-#include <stdint.h>
-struct Bitstream {
- void* data;
+struct Queue {
+ void** buffer;
+ size_t alloc;
size_t size;
+ size_t read;
+ size_t write;
};
-void BitstreamAppend(struct Bitstream* bitstream, size_t size, uint32_t bits);
-void BitstreamAppendUE(struct Bitstream* bitstream, uint32_t bits);
-void BitstreamAppendSE(struct Bitstream* bitstream, int32_t bits);
-void BitstreamByteAlign(struct Bitstream* bitstream);
+void QueueCreate(struct Queue* queue);
+bool QueuePush(struct Queue* queue, void* item);
+bool QueuePop(struct Queue* queue, void** pitem);
+void QueueDestroy(struct Queue* queue);
-void BitstreamInflate(struct Bitstream* bitstream,
- const struct Bitstream* source);
-
-#endif // STREAMER_BITSTREAM_H_
+#endif // STREAMER_QUEUE_H_
diff --git a/toolbox b/toolbox
deleted file mode 160000
-Subproject c5334a46523b9ac959c002fbc23a25eeccf6359
diff --git a/colorspace.h b/util.h
index 7914a1a..62e7c4e 100644
--- a/colorspace.h
+++ b/util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer.
+ * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer.
*
* streamer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,17 +15,18 @@
* along with streamer. If not, see <https://www.gnu.org/licenses/>.
*/
-#ifndef STREAMER_COLORSPACE_H_
-#define STREAMER_COLORSPACE_H_
+#ifndef STREAMER_UTIL_H_
+#define STREAMER_UTIL_H_
-enum YuvColorspace {
- kItuRec601 = 0,
- kItuRec709,
-};
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
-enum YuvRange {
- kNarrowRange = 0,
- kFullRange,
-};
+#define LENGTH(x) (sizeof(x) / sizeof *(x))
-#endif // STREAMER_COLORSPACE_H_
+#define STR_IMPL(x) #x
+#define STR(x) STR_IMPL(x)
+
+#define LOG(x, ...) \
+ fprintf(stderr, __FILE__ ":" STR(__LINE__) " " x "\n", ##__VA_ARGS__)
+
+#endif // STREAMER_UTIL_H_