diff options
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | audio.c | 367 | ||||
-rw-r--r-- | audio.h | 45 | ||||
-rw-r--r-- | audio_context.c | 220 | ||||
-rw-r--r-- | audio_context.h (renamed from capture_kms.h) | 23 | ||||
-rw-r--r-- | bitstream.c | 77 | ||||
-rw-r--r-- | buffer_queue.c | 100 | ||||
-rw-r--r-- | buffer_queue.h | 42 | ||||
-rw-r--r-- | capture.c | 77 | ||||
-rw-r--r-- | capture.h | 38 | ||||
-rw-r--r-- | capture_kms.c | 225 | ||||
-rw-r--r-- | capture_wlr.c | 329 | ||||
-rw-r--r-- | capture_wlr.h | 39 | ||||
-rw-r--r-- | encode.c | 942 | ||||
-rw-r--r-- | encode.h | 40 | ||||
-rw-r--r-- | gpu.c | 802 | ||||
-rw-r--r-- | gpu.h | 54 | ||||
-rw-r--r-- | hevc.c | 708 | ||||
-rw-r--r-- | hevc.h | 97 | ||||
-rw-r--r-- | input.c | 165 | ||||
-rw-r--r-- | io_context.c | 304 | ||||
-rw-r--r-- | io_context.h (renamed from input.h) | 21 | ||||
-rw-r--r-- | main.c | 428 | ||||
-rw-r--r-- | makefile | 30 | ||||
-rw-r--r-- | proto.c | 58 | ||||
-rw-r--r-- | proto.h | 35 | ||||
-rw-r--r-- | queue.c | 60 | ||||
-rw-r--r-- | queue.h (renamed from bitstream.h) | 28 | ||||
m--------- | toolbox | 0 | ||||
-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_ @@ -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]); -} @@ -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_ @@ -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); -} @@ -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); +} @@ -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_ @@ -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; } @@ -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; -} @@ -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_ @@ -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); } @@ -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 @@ -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_ |