From 8d2a9518ef406f43caeb8bfea483949c7d93a903 Mon Sep 17 00:00:00 2001 From: Mikhail Burakov Date: Sat, 10 Aug 2024 10:59:02 +0200 Subject: Major rewrite of streamer (WIP) --- .gitmodules | 3 - audio.c | 367 ---------------------- audio.h | 45 --- audio_context.c | 220 +++++++++++++ audio_context.h | 29 ++ bitstream.c | 77 ----- bitstream.h | 37 --- buffer_queue.c | 100 ------ buffer_queue.h | 42 --- capture.c | 77 ----- capture.h | 38 --- capture_kms.c | 225 -------------- capture_kms.h | 32 -- capture_wlr.c | 329 -------------------- capture_wlr.h | 39 --- colorspace.h | 31 -- encode.c | 942 -------------------------------------------------------- encode.h | 40 --- gpu.c | 802 ----------------------------------------------- gpu.h | 54 ---- hevc.c | 708 ------------------------------------------ hevc.h | 97 ------ input.c | 165 ---------- input.h | 31 -- io_context.c | 304 ++++++++++++++++++ io_context.h | 32 ++ main.c | 428 ++++--------------------- makefile | 30 +- proto.c | 58 ---- proto.h | 35 ++- queue.c | 60 ++++ queue.h | 37 +++ toolbox | 1 - util.h | 32 ++ 34 files changed, 805 insertions(+), 4742 deletions(-) delete mode 100644 audio.c delete mode 100644 audio.h create mode 100644 audio_context.c create mode 100644 audio_context.h delete mode 100644 bitstream.c delete mode 100644 bitstream.h delete mode 100644 buffer_queue.c delete mode 100644 buffer_queue.h delete mode 100644 capture.c delete mode 100644 capture.h delete mode 100644 capture_kms.c delete mode 100644 capture_kms.h delete mode 100644 capture_wlr.c delete mode 100644 capture_wlr.h delete mode 100644 colorspace.h delete mode 100644 encode.c delete mode 100644 encode.h delete mode 100644 gpu.c delete mode 100644 gpu.h delete mode 100644 hevc.c delete mode 100644 hevc.h delete mode 100644 input.c delete mode 100644 input.h create mode 100644 io_context.c create mode 100644 io_context.h delete mode 100644 proto.c create mode 100644 queue.c create mode 100644 queue.h delete mode 160000 toolbox create mode 100644 util.h 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 . - */ - -#ifdef USE_PIPEWIRE - -#include "audio.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 . - */ - -#ifndef STREAMER_AUDIO_H_ -#define STREAMER_AUDIO_H_ - -#include -#include - -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 . + */ + +#include "audio_context.h" + +#include +#include +#include +#include +#include +#include +#include + +#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/audio_context.h b/audio_context.h new file mode 100644 index 0000000..57d8110 --- /dev/null +++ b/audio_context.h @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +#ifndef STREAMER_AUDIO_CONTEXT_H_ +#define STREAMER_AUDIO_CONTEXT_H_ + +struct AudioContext; +struct IoContext; +struct Proto; + +struct AudioContext* AudioContextCreate(struct IoContext* io_context, + struct Proto* proto_hello); +void AudioContextDestroy(struct AudioContext* audio_context); + +#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 . - */ - -#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/bitstream.h b/bitstream.h deleted file mode 100644 index 1520c57..0000000 --- a/bitstream.h +++ /dev/null @@ -1,37 +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 . - */ - -#ifndef STREAMER_BITSTREAM_H_ -#define STREAMER_BITSTREAM_H_ - -#include -#include - -struct Bitstream { - void* data; - size_t size; -}; - -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 BitstreamInflate(struct Bitstream* bitstream, - const struct Bitstream* source); - -#endif // STREAMER_BITSTREAM_H_ 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 . - */ - -#include "buffer_queue.h" - -#include -#include -#include - -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 . - */ - -#ifndef STREAMER_BUFFER_QUEUE_H_ -#define STREAMER_BUFFER_QUEUE_H_ - -#include -#include -#include - -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 . - */ - -#include "capture.h" - -#include -#include -#include -#include -#include - -#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 . - */ - -#ifndef STREAMER_CAPTURE_H_ -#define STREAMER_CAPTURE_H_ - -#include - -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 . - */ - -#include "capture_kms.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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_kms.h b/capture_kms.h deleted file mode 100644 index 52531d2..0000000 --- a/capture_kms.h +++ /dev/null @@ -1,32 +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 . - */ - -#ifndef STREAMER_CAPTURE_KMS_H_ -#define STREAMER_CAPTURE_KMS_H_ - -#include "capture.h" - -struct CaptureContextKms; - -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_ 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 . - */ - -#ifdef USE_WAYLAND - -#include "capture_wlr.h" - -#include -#include -#include -#include -#include - -#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 . - */ - -#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/colorspace.h b/colorspace.h deleted file mode 100644 index 7914a1a..0000000 --- a/colorspace.h +++ /dev/null @@ -1,31 +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 . - */ - -#ifndef STREAMER_COLORSPACE_H_ -#define STREAMER_COLORSPACE_H_ - -enum YuvColorspace { - kItuRec601 = 0, - kItuRec709, -}; - -enum YuvRange { - kNarrowRange = 0, - kFullRange, -}; - -#endif // STREAMER_COLORSPACE_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 . - */ - -#include "encode.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 . - */ - -#ifndef STREAMER_ENCODE_H_ -#define STREAMER_ENCODE_H_ - -#include -#include - -#include "colorspace.h" - -struct EncodeContext; -struct GpuContext; -struct GpuFrame; - -struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context, - uint32_t width, uint32_t height, - enum YuvColorspace colorspace, - enum YuvRange range); -const struct GpuFrame* EncodeContextGetFrame( - struct EncodeContext* encode_context); -bool EncodeContextEncodeFrame(struct EncodeContext* encode_context, int fd, - unsigned long long timestamp); -void EncodeContextDestroy(struct EncodeContext* encode_context); - -#endif // STREAMER_ENCODE_H_ diff --git a/gpu.c b/gpu.c deleted file mode 100644 index ea455e8..0000000 --- a/gpu.c +++ /dev/null @@ -1,802 +0,0 @@ -/* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. - * - * streamer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * streamer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with streamer. If not, see . - */ - -#include "gpu.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS -#include -#include -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - -#include "toolbox/utils.h" - -#define _(...) __VA_ARGS__ -#define LOOKUP_FUNCTION(a, b, c) \ - gpu_context->b = (a)eglGetProcAddress(#b); \ - if (!gpu_context->b) { \ - LOG("Failed to look up " #b " function"); \ - goto c; \ - } - -// TODO(mburakov): It should be theoretically possible to do everything in a -// single pass using a compute shader and GLES3. Unfortunately my test machine -// reports the primary framebuffer as multiplane. This is probably the reason -// why texture created from it can not be sampled using imageLoad in a compute -// shader even though it's still RGB. Fallback to GLES2 and per-plane textures -// for now, and figure out details later. - -extern const char _binary_vertex_glsl_start[]; -extern const char _binary_vertex_glsl_end[]; -extern const char _binary_luma_glsl_start[]; -extern const char _binary_luma_glsl_end[]; -extern const char _binary_chroma_glsl_start[]; -extern const char _binary_chroma_glsl_end[]; - -struct GpuContext { -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS - int render_node; - struct gbm_device* device; -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - EGLDisplay display; - EGLContext context; - PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; - GLuint program_luma; - GLuint program_chroma; - GLint sample_offsets; - GLuint framebuffer; - GLuint vertices; -}; - -struct GpuFrameImpl { - struct GpuFrame size; - int dmabuf_fds[4]; - EGLImage images[2]; - GLuint textures[2]; -}; - -static const char* EglErrorString(EGLint error) { - static const char* const egl_error_strings[] = { - "EGL_SUCCESS", "EGL_NOT_INITIALIZED", "EGL_BAD_ACCESS", - "EGL_BAD_ALLOC", "EGL_BAD_ATTRIBUTE", "EGL_BAD_CONFIG", - "EGL_BAD_CONTEXT", "EGL_BAD_CURRENT_SURFACE", "EGL_BAD_DISPLAY", - "EGL_BAD_MATCH", "EGL_BAD_NATIVE_PIXMAP", "EGL_BAD_NATIVE_WINDOW", - "EGL_BAD_PARAMETER", "EGL_BAD_SURFACE", "EGL_CONTEXT_LOST", - }; - return EGL_SUCCESS <= error && error <= EGL_CONTEXT_LOST - ? egl_error_strings[error - EGL_SUCCESS] - : "???"; -} - -static const char* GlErrorString(GLenum error) { - static const char* const gl_error_strings[] = { - "GL_INVALID_ENUM", - "GL_INVALID_VALUE", - "GL_INVALID_OPERATION", - "GL_STACK_OVERFLOW", - "GL_STACK_UNDERFLOW", - "GL_OUT_OF_MEMORY", - "GL_INVALID_FRAMEBUFFER_OPERATION", - "GL_CONTEXT_LOST", - }; - if (error == GL_NO_ERROR) return "GL_NO_ERROR"; - return GL_INVALID_ENUM <= error && error <= GL_CONTEXT_LOST - ? gl_error_strings[error - GL_INVALID_ENUM] - : "???"; -} - -#define DEFINE_CHECK_BUILDABLE_FUNCTION(postfix, err_msg, getter_fn, \ - status_enum, logger_fn) \ - static bool CheckBuildable##postfix(GLuint buildable) { \ - GLenum error = glGetError(); \ - if (error != GL_NO_ERROR) { \ - LOG(err_msg " (%s)", GlErrorString(error)); \ - return false; \ - } \ - GLint status; \ - getter_fn(buildable, status_enum, &status); \ - if (status != GL_TRUE) { \ - GLint log_length; \ - getter_fn(buildable, GL_INFO_LOG_LENGTH, &log_length); \ - char message[log_length]; \ - memset(message, 0, sizeof(message)); \ - logger_fn(buildable, log_length, NULL, message); \ - LOG("%s", message); \ - return false; \ - } \ - return true; \ - } - -DEFINE_CHECK_BUILDABLE_FUNCTION(Shader, "Failed to compile shader", - glGetShaderiv, GL_COMPILE_STATUS, - glGetShaderInfoLog) -DEFINE_CHECK_BUILDABLE_FUNCTION(Program, "Failed to link program", - glGetProgramiv, GL_LINK_STATUS, - glGetProgramInfoLog) - -static bool HasExtension(const char* haystack, const char* needle) { - bool result = !!strstr(haystack, needle); - if (!result) LOG("Unsupported extension %s", needle); - return result; -} - -static GLuint CreateGlProgram(const char* vs_begin, const char* vs_end, - const char* fs_begin, const char* fs_end) { - GLuint program = 0; - GLuint vertex = glCreateShader(GL_VERTEX_SHADER); - if (!vertex) { - LOG("Failed to create vertex shader (%s)", GlErrorString(glGetError())); - goto bail_out; - } - GLsizei size = (GLsizei)(vs_end - vs_begin); - glShaderSource(vertex, 1, &vs_begin, &size); - glCompileShader(vertex); - if (!CheckBuildableShader(vertex)) goto delete_vs; - - GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER); - if (!fragment) { - LOG("Failed to create fragment shader (%s)", GlErrorString(glGetError())); - goto delete_vs; - } - size = (GLsizei)(fs_end - fs_begin); - glShaderSource(fragment, 1, &fs_begin, &size); - glCompileShader(fragment); - if (!CheckBuildableShader(fragment)) goto delete_fs; - - program = glCreateProgram(); - if (!program) { - LOG("Failed to create shader program (%s)", GlErrorString(glGetError())); - goto delete_fs; - } - glAttachShader(program, vertex); - glAttachShader(program, fragment); - glLinkProgram(program); - if (!CheckBuildableProgram(program)) { - glDeleteProgram(program); - program = 0; - goto delete_fs; - } - -delete_fs: - glDeleteShader(fragment); -delete_vs: - glDeleteShader(vertex); -bail_out: - return program; -} - -static const GLfloat* GetColorspaceMatrix(enum YuvColorspace colorspace) { - static const GLfloat rec601[] = { - _(0.299f, 0.587f, 0.114f), - _(-0.168736f, -0.331264f, 0.5f), - _(0.5f, -0.418688f, -0.081312f), - }; - static const GLfloat rec709[] = { - _(0.2126f, 0.7152f, 0.0722f), - _(-0.1146f, -0.3854f, 0.5f), - _(0.5f, -0.4542f, -0.0458f), - }; - switch (colorspace) { - case kItuRec601: - return rec601; - case kItuRec709: - return rec709; - default: - __builtin_unreachable(); - } -} - -static const GLfloat* GetRangeVectors(enum YuvRange range) { - static const GLfloat narrow[] = { - _(16.f / 255.f, 16.f / 255.f, 16.f / 255.f), - _((235.f - 16.f) / 255.f, (240.f - 16.f) / 255.f, (240.f - 16.f) / 255.f), - }; - static const GLfloat full[] = { - _(0.f, 0.f, 0.f), - _(1.f, 1.f, 1.f), - }; - switch (range) { - case kNarrowRange: - return narrow; - case kFullRange: - return full; - default: - __builtin_unreachable(); - } -} - -static bool SetupCommonUniforms(GLuint program, enum YuvColorspace colorspace, - enum YuvRange range) { - struct { - const char* name; - GLint location; - } uniforms[] = { - {.name = "img_input"}, - {.name = "colorspace"}, - {.name = "ranges"}, - }; - - for (size_t i = 0; i < LENGTH(uniforms); i++) { - uniforms[i].location = glGetUniformLocation(program, uniforms[i].name); - if (uniforms[i].location == -1) { - LOG("Failed to locate %s uniform (%s)", uniforms[i].name, - GlErrorString(glGetError())); - return false; - } - } - - glUseProgram(program); - glUniform1i(uniforms[0].location, 0); - glUniformMatrix3fv(uniforms[1].location, 1, GL_TRUE, - GetColorspaceMatrix(colorspace)); - glUniform3fv(uniforms[2].location, 2, GetRangeVectors(range)); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - LOG("Failed to set img_input uniform (%s)", GlErrorString(glGetError())); - return false; - } - return true; -} - -struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace, - enum YuvRange range) { - struct GpuContext* gpu_context = malloc(sizeof(struct GpuContext)); - if (!gpu_context) { - LOG("Failed to allocate gpu context (%s)", strerror(errno)); - return NULL; - } - *gpu_context = (struct GpuContext){ -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS - .render_node = -1, -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - .display = EGL_NO_DISPLAY, - .context = EGL_NO_CONTEXT, - .sample_offsets = -1, - }; - - const char* egl_ext = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (!egl_ext) { - LOG("Failed to query platformless egl extensions (%s)", - EglErrorString(eglGetError())); - goto rollback_gpu_context; - } - - LOG("EGL_EXTENSIONS: %s", egl_ext); - // TODO(mburakov): Quite surprisingly EGL_MESA_platform_surfaceless does not - // provide support for AMD_FMT_MOD_TILE_VER_GFX11 dmabuf modifier on my AMD - // system. For comparison, on my Intel system all the modifiers ever found on - // framebuffers are reported as supported by EGL. Maybe that's because the - // support for RDNA3 is still quite young in MESA. -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS - if (!HasExtension(egl_ext, "EGL_MESA_platform_gbm")) - goto rollback_gpu_context; - gpu_context->render_node = open("/dev/dri/renderD128", O_RDWR); - if (gpu_context->render_node == -1) { - LOG("Failed to open render node (%s)", strerror(errno)); - goto rollback_gpu_context; - } - gpu_context->device = gbm_create_device(gpu_context->render_node); - if (!gpu_context->device) { - LOG("Failed to create gbm device (%s)", strerror(errno)); - goto rollback_render_node; - } - gpu_context->display = - eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gpu_context->device, NULL); -#else // USE_EGL_MESA_PLATFORM_SURFACELESS - if (!HasExtension(egl_ext, "EGL_MESA_platform_surfaceless")) - goto rollback_gpu_context; - gpu_context->display = - eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL); -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - if (gpu_context->display == EGL_NO_DISPLAY) { - LOG("Failed to get egl display (%s)", EglErrorString(eglGetError())); -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS - goto rollback_device; -#else // USE_EGL_MESA_PLATFORM_SURFACELESS - goto rollback_gpu_context; -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - } - - EGLint major, minor; - if (!eglInitialize(gpu_context->display, &major, &minor)) { - LOG("Failed to initialize egl display (%s)", EglErrorString(eglGetError())); - goto rollback_display; - } - - LOG("Initialized EGL %d.%d", major, minor); - egl_ext = eglQueryString(gpu_context->display, EGL_EXTENSIONS); - if (!egl_ext) { - LOG("Failed to query egl extensions (%s)", EglErrorString(eglGetError())); - goto rollback_display; - } - - LOG("EGL_EXTENSIONS: %s", egl_ext); - if (!HasExtension(egl_ext, "EGL_KHR_surfaceless_context") || - !HasExtension(egl_ext, "EGL_KHR_no_config_context") || - !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import") || - !HasExtension(egl_ext, "EGL_EXT_image_dma_buf_import_modifiers")) - goto rollback_display; - LOOKUP_FUNCTION(PFNEGLQUERYDMABUFFORMATSEXTPROC, eglQueryDmaBufFormatsEXT, - rollback_display) - LOOKUP_FUNCTION(PFNEGLQUERYDMABUFMODIFIERSEXTPROC, eglQueryDmaBufModifiersEXT, - rollback_display) - - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - LOG("Failed to bind egl api (%s)", EglErrorString(eglGetError())); - goto rollback_display; - } - - static const EGLint context_attribs[] = { - _(EGL_CONTEXT_MAJOR_VERSION, 3), - _(EGL_CONTEXT_MINOR_VERSION, 1), - EGL_NONE, - }; - gpu_context->context = eglCreateContext( - gpu_context->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs); - if (gpu_context->context == EGL_NO_CONTEXT) { - LOG("Failed to create egl context (%s)", EglErrorString(eglGetError())); - goto rollback_display; - } - - if (!eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE, - gpu_context->context)) { - LOG("Failed to make egl context current (%s)", - EglErrorString(eglGetError())); - goto rollback_context; - } - - const char* gl_ext = (const char*)glGetString(GL_EXTENSIONS); - if (!gl_ext) { - LOG("Failed to get gl extensions (%s)", GlErrorString(glGetError())); - goto rollback_context; - } - - LOG("GL_EXTENSIONS: %s", gl_ext); - if (!HasExtension(gl_ext, "GL_OES_EGL_image")) goto rollback_context; - LOOKUP_FUNCTION(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, - glEGLImageTargetTexture2DOES, rollback_context) - - gpu_context->program_luma = - CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, - _binary_luma_glsl_start, _binary_luma_glsl_end); - if (!gpu_context->program_luma || - !SetupCommonUniforms(gpu_context->program_luma, colorspace, range)) { - LOG("Failed to create luma program"); - goto rollback_context; - } - - gpu_context->program_chroma = - CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, - _binary_chroma_glsl_start, _binary_chroma_glsl_end); - if (!gpu_context->program_chroma || - !SetupCommonUniforms(gpu_context->program_chroma, colorspace, range)) { - LOG("Failed to create chroma program"); - goto rollback_program_luma; - } - gpu_context->sample_offsets = - glGetUniformLocation(gpu_context->program_chroma, "sample_offsets"); - if (gpu_context->sample_offsets == -1) { - LOG("Failed to find sample_offsets uniform (%s)", - GlErrorString(glGetError())); - goto rollback_program_chroma; - } - - glGenFramebuffers(1, &gpu_context->framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, gpu_context->framebuffer); - glGenBuffers(1, &gpu_context->vertices); - glBindBuffer(GL_ARRAY_BUFFER, gpu_context->vertices); - static const GLfloat vertices[] = {0, 0, 1, 0, 1, 1, 0, 1}; - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glEnableVertexAttribArray(0); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - LOG("Failed to create gl objects (%s)", GlErrorString(glGetError())); - goto rollback_buffers; - } - return gpu_context; - -rollback_buffers: - if (gpu_context->vertices) glDeleteBuffers(1, &gpu_context->vertices); - if (gpu_context->framebuffer) - glDeleteFramebuffers(1, &gpu_context->framebuffer); -rollback_program_chroma: - glDeleteProgram(gpu_context->program_chroma); -rollback_program_luma: - glDeleteProgram(gpu_context->program_luma); -rollback_context: - eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglDestroyContext(gpu_context->display, gpu_context->context); -rollback_display: - eglTerminate(gpu_context->display); -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS -rollback_device: - gbm_device_destroy(gpu_context->device); -rollback_render_node: - close(gpu_context->render_node); -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS -rollback_gpu_context: - free(gpu_context); - return NULL; -} - -static void DumpEglImageParams(const EGLAttrib* attribs) { - for (; *attribs != EGL_NONE; attribs += 2) { - switch (attribs[0]) { - case EGL_HEIGHT: - LOG("\tEGL_HEIGHT: %ld", attribs[1]); - break; - case EGL_WIDTH: - LOG("\tEGL_WIDTH: %ld", attribs[1]); - break; - case EGL_LINUX_DRM_FOURCC_EXT: - LOG("\tEGL_LINUX_DRM_FOURCC_EXT: %.4s", (const char*)&attribs[1]); - break; - case EGL_DMA_BUF_PLANE0_FD_EXT: - case EGL_DMA_BUF_PLANE1_FD_EXT: - case EGL_DMA_BUF_PLANE2_FD_EXT: - LOG("\tEGL_DMA_BUF_PLANE%ld_FD_EXT: %ld", - (attribs[0] - EGL_DMA_BUF_PLANE0_FD_EXT) / 3, attribs[1]); - break; - case EGL_DMA_BUF_PLANE0_OFFSET_EXT: - case EGL_DMA_BUF_PLANE1_OFFSET_EXT: - case EGL_DMA_BUF_PLANE2_OFFSET_EXT: - LOG("\tEGL_DMA_BUF_PLANE%ld_OFFSET_EXT: %ld", - (attribs[0] - EGL_DMA_BUF_PLANE0_OFFSET_EXT) / 3, attribs[1]); - break; - case EGL_DMA_BUF_PLANE0_PITCH_EXT: - case EGL_DMA_BUF_PLANE1_PITCH_EXT: - case EGL_DMA_BUF_PLANE2_PITCH_EXT: - LOG("\tEGL_DMA_BUF_PLANE%ld_PITCH_EXT: %ld", - (attribs[0] - EGL_DMA_BUF_PLANE0_PITCH_EXT) / 3, attribs[1]); - break; - case EGL_DMA_BUF_PLANE3_FD_EXT: - LOG("\tEGL_DMA_BUF_PLANE3_FD_EXT: %ld", attribs[1]); - break; - case EGL_DMA_BUF_PLANE3_OFFSET_EXT: - LOG("\tEGL_DMA_BUF_PLANE3_OFFSET_EXT: %ld", attribs[1]); - break; - case EGL_DMA_BUF_PLANE3_PITCH_EXT: - LOG("\tEGL_DMA_BUF_PLANE3_PITCH_EXT: %ld", attribs[1]); - break; - case EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: - case EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: - case EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT: - case EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT: - case EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT: - case EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT: - case EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT: - case EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT: - LOG("\tEGL_DMA_BUF_PLANE%ld_MODIFIER_%s_EXT: 0x%08lx", - (attribs[0] - EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT) / 2, - attribs[0] & 1 ? "LO" : "HI", attribs[1]); - break; - } - } -} - -static bool IsFourccSupported(struct GpuContext* gpu_context, uint32_t fourcc) { - EGLint num_formats; - if (!gpu_context->eglQueryDmaBufFormatsEXT(gpu_context->display, 0, NULL, - &num_formats)) { - LOG("Faield to get number of supported dmabuf formats (%s)", - EglErrorString(eglGetError())); - return false; - } - EGLint formats[num_formats]; - if (!gpu_context->eglQueryDmaBufFormatsEXT(gpu_context->display, num_formats, - formats, &num_formats)) { - LOG("Failed to get supported dmabuf formats (%s)", - EglErrorString(eglGetError())); - return false; - } - for (int i = 0; i < num_formats; i++) { - if ((uint32_t)formats[i] == fourcc) return true; - } - LOG("Format %.4s is unsupported by egl", (const char*)&fourcc); - LOG("Supported formats are:"); - for (int i = 0; i < num_formats; i++) { - LOG("\t%.4s", (const char*)&formats[i]); - } - return false; -} - -static bool IsModifierSupported(struct GpuContext* gpu_context, uint32_t fourcc, - uint64_t modifier) { - EGLint num_modifiers; - if (!gpu_context->eglQueryDmaBufModifiersEXT( - gpu_context->display, (GLint)fourcc, 0, NULL, NULL, &num_modifiers)) { - LOG("Failed to get number of supported dmabuf modifiers (%s)", - EglErrorString(eglGetError())); - return false; - } - EGLuint64KHR modifiers[num_modifiers]; - EGLBoolean external_only[num_modifiers]; - if (!gpu_context->eglQueryDmaBufModifiersEXT( - gpu_context->display, (GLint)fourcc, num_modifiers, modifiers, - external_only, &num_modifiers)) { - LOG("Failed to get supported dmabuf modifiers (%s)", - EglErrorString(eglGetError())); - return false; - } - for (int i = 0; i < num_modifiers; i++) { - if (modifiers[i] == modifier && !external_only[i]) return true; - } - LOG("Modifier 0x%016lx for format %.4s is unsupported by egl", modifier, - (const char*)&fourcc); - LOG("Supported modifiers for format %.4s are:", (const char*)&fourcc); - for (int i = 0; i < num_modifiers; i++) { - LOG("\t0x%016lx%s", modifiers[i], - external_only[i] ? " (external only)" : ""); - } - return false; -} - -static EGLImage CreateEglImage(struct GpuContext* gpu_context, uint32_t width, - uint32_t height, uint32_t fourcc, size_t nplanes, - const struct GpuFramePlane* planes) { - static const EGLAttrib attrib_keys[] = { - EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, - EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_FD_EXT, - EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, - EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, - EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, - EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_FD_EXT, - EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, - EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, - }; - - EGLAttrib attrib_list[7 + LENGTH(attrib_keys) * 2] = { - _(EGL_HEIGHT, height), - _(EGL_WIDTH, width), - _(EGL_LINUX_DRM_FOURCC_EXT, fourcc), - }; - - EGLAttrib* pairs = &attrib_list[6]; - const EGLAttrib* key = &attrib_keys[0]; - for (size_t i = 0; i < nplanes; i++) { - if (planes[i].dmabuf_fd == -1) break; - *pairs++ = *key++; - *pairs++ = planes[i].dmabuf_fd; - *pairs++ = *key++; - *pairs++ = planes[i].offset; - *pairs++ = *key++; - *pairs++ = planes[i].pitch; - *pairs++ = *key++; - *pairs++ = planes[i].modifier & UINT32_MAX; - *pairs++ = *key++; - *pairs++ = planes[i].modifier >> 32; - } - - *pairs = EGL_NONE; - if (!IsFourccSupported(gpu_context, fourcc)) goto failure; - for (size_t i = 0; i < nplanes; i++) { - if (!IsModifierSupported(gpu_context, fourcc, planes[i].modifier)) - goto failure; - } - EGLImage image = eglCreateImage(gpu_context->display, EGL_NO_CONFIG_KHR, - EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list); - if (image == EGL_NO_IMAGE) { - LOG("Failed to create egl image (%s)", EglErrorString(eglGetError())); - goto failure; - } - return image; - -failure: - LOG("Attributes list for failed egl image:"); - DumpEglImageParams(attrib_list); - return EGL_NO_IMAGE; -} - -static GLuint CreateTexture(struct GpuContext* gpu_context, EGLImage image) { - GLuint texture = 0; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - gpu_context->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - LOG("Failed to create texture (%s)", GlErrorString(error)); - glDeleteTextures(1, &texture); - return 0; - } - return texture; -} - -struct GpuFrame* GpuContextCreateFrame(struct GpuContext* gpu_context, - uint32_t width, uint32_t height, - uint32_t fourcc, size_t nplanes, - const struct GpuFramePlane* planes) { - struct GpuFrameImpl* gpu_frame_impl = malloc(sizeof(struct GpuFrameImpl)); - if (!gpu_frame_impl) { - LOG("Failed to allocate gpu frame (%s)", strerror(errno)); - return NULL; - } - *gpu_frame_impl = (struct GpuFrameImpl){ - .size.width = width, - .size.height = height, - .dmabuf_fds = {-1, -1, -1, -1}, - .images = {EGL_NO_IMAGE, EGL_NO_IMAGE}, - }; - - if (fourcc == DRM_FORMAT_NV12) { - gpu_frame_impl->images[0] = CreateEglImage(gpu_context, width, height, - DRM_FORMAT_R8, 1, &planes[0]); - if (gpu_frame_impl->images[0] == EGL_NO_IMAGE) { - LOG("Failed to create luma plane image"); - goto rollback_gpu_frame; - } - gpu_frame_impl->images[1] = CreateEglImage( - gpu_context, width / 2, height / 2, DRM_FORMAT_GR88, 1, &planes[1]); - if (gpu_frame_impl->images[1] == EGL_NO_IMAGE) { - LOG("Failed to create chroma plane image"); - goto rollback_images; - } - } else { - gpu_frame_impl->images[0] = - CreateEglImage(gpu_context, width, height, fourcc, nplanes, planes); - if (gpu_frame_impl->images[0] == EGL_NO_IMAGE) { - LOG("Failed to create multiplanar image"); - goto rollback_gpu_frame; - } - } - - for (size_t i = 0; i < LENGTH(gpu_frame_impl->images); i++) { - if (gpu_frame_impl->images[i] == EGL_NO_IMAGE) break; - gpu_frame_impl->textures[i] = - CreateTexture(gpu_context, gpu_frame_impl->images[i]); - if (!gpu_frame_impl->textures[i]) { - LOG("Failed to create texture"); - goto rollback_textures; - } - } - - for (size_t i = 0; i < nplanes; i++) - gpu_frame_impl->dmabuf_fds[i] = planes[i].dmabuf_fd; - return (struct GpuFrame*)gpu_frame_impl; - -rollback_textures: - for (size_t i = LENGTH(gpu_frame_impl->textures); i; i--) { - if (gpu_frame_impl->textures[i - 1]) - glDeleteTextures(1, &gpu_frame_impl->textures[i - 1]); - } -rollback_images: - for (size_t i = LENGTH(gpu_frame_impl->images); i; i--) { - if (gpu_frame_impl->images[i - 1] != EGL_NO_IMAGE) - eglDestroyImage(gpu_context->device, gpu_frame_impl->images[i - 1]); - } -rollback_gpu_frame: - free(gpu_frame_impl); - return NULL; -} - -static bool GpuFrameConvertImpl(GLuint from, GLuint to) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - to, 0); - GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) { - LOG("Framebuffer is incomplete (0x%x)", framebuffer_status); - return false; - } - - glBindTexture(GL_TEXTURE_2D, from); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - LOG("Failed to convert plane (%s)", GlErrorString(error)); - return false; - } - return true; -} - -bool GpuContextConvertFrame(struct GpuContext* gpu_context, - const struct GpuFrame* from, - const struct GpuFrame* to) { - const struct GpuFrameImpl* from_impl = (const void*)from; - const struct GpuFrameImpl* to_impl = (const void*)to; - - glUseProgram(gpu_context->program_luma); - glViewport(0, 0, (GLsizei)to->width, (GLsizei)to->height); - if (!GpuFrameConvertImpl(from_impl->textures[0], to_impl->textures[0])) { - LOG("Failed to convert luma plane"); - return false; - } - - const GLfloat sample_offsets[] = { - _(0.f, 0.f), - _(1.f / (GLfloat)from->width, 0.f), - _(0.f, 1.f / (GLfloat)from->height), - _(1.f / (GLfloat)from->width, 1.f / (GLfloat)from->height), - }; - - glUseProgram(gpu_context->program_chroma); - glUniform2fv(gpu_context->sample_offsets, 4, sample_offsets); - glViewport(0, 0, (GLsizei)to->width / 2, (GLsizei)to->height / 2); - if (!GpuFrameConvertImpl(from_impl->textures[0], to_impl->textures[1])) { - LOG("Failed to convert chroma plane"); - return false; - } - - EGLSync sync = eglCreateSync(gpu_context->display, EGL_SYNC_FENCE, NULL); - if (sync == EGL_NO_SYNC) { - LOG("Failed to create egl fence sync (%s)", EglErrorString(eglGetError())); - return false; - } - eglClientWaitSync(gpu_context->display, sync, 0, EGL_FOREVER); - eglDestroySync(gpu_context->display, sync); - return true; -} - -void GpuContextDestroyFrame(struct GpuContext* gpu_context, - struct GpuFrame* gpu_frame) { - struct GpuFrameImpl* gpu_frame_impl = (void*)gpu_frame; - for (size_t i = LENGTH(gpu_frame_impl->textures); i; i--) { - if (gpu_frame_impl->textures[i - 1]) - glDeleteTextures(1, &gpu_frame_impl->textures[i - 1]); - } - for (size_t i = LENGTH(gpu_frame_impl->images); i; i--) { - if (gpu_frame_impl->images[i - 1] != EGL_NO_IMAGE) - eglDestroyImage(gpu_context->device, gpu_frame_impl->images[i - 1]); - } - CloseUniqueFds(gpu_frame_impl->dmabuf_fds); - free(gpu_frame_impl); -} - -void GpuContextDestroy(struct GpuContext* gpu_context) { - glDeleteBuffers(1, &gpu_context->vertices); - glDeleteFramebuffers(1, &gpu_context->framebuffer); - glDeleteProgram(gpu_context->program_chroma); - glDeleteProgram(gpu_context->program_luma); - eglMakeCurrent(gpu_context->display, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglDestroyContext(gpu_context->display, gpu_context->context); - eglTerminate(gpu_context->display); -#ifndef USE_EGL_MESA_PLATFORM_SURFACELESS - gbm_device_destroy(gpu_context->device); - close(gpu_context->render_node); -#endif // USE_EGL_MESA_PLATFORM_SURFACELESS - free(gpu_context); -} - -void CloseUniqueFds(int fds[4]) { - // TODO(mburakov): Meh, but still better than looping... - if (fds[3] != -1 && fds[3] != fds[2] && fds[3] != fds[1] && fds[3] != fds[0]) - close(fds[3]); - if (fds[2] != -1 && fds[2] != fds[1] && fds[2] != fds[0]) close(fds[2]); - if (fds[1] != -1 && fds[1] != fds[0]) close(fds[1]); - if (fds[0] != -1) close(fds[0]); -} diff --git a/gpu.h b/gpu.h deleted file mode 100644 index 6d07366..0000000 --- a/gpu.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. - * - * streamer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * streamer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with streamer. If not, see . - */ - -#ifndef STREAMER_GPU_H_ -#define STREAMER_GPU_H_ - -#include -#include -#include - -#include "colorspace.h" - -struct GpuFrame { - uint32_t width; - uint32_t height; -}; - -struct GpuFramePlane { - int dmabuf_fd; - uint32_t pitch; - uint32_t offset; - uint64_t modifier; -}; - -struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace, - enum YuvRange range); -struct GpuFrame* GpuContextCreateFrame(struct GpuContext* gpu_context, - uint32_t width, uint32_t height, - uint32_t fourcc, size_t nplanes, - const struct GpuFramePlane* planes); -bool GpuContextConvertFrame(struct GpuContext* gpu_context, - const struct GpuFrame* from, - const struct GpuFrame* to); -void GpuContextDestroyFrame(struct GpuContext* gpu_context, - struct GpuFrame* gpu_frame); -void GpuContextDestroy(struct GpuContext* gpu_context); - -void CloseUniqueFds(int fds[4]); - -#endif // STREAMER_GPU_H_ diff --git a/hevc.c b/hevc.c deleted file mode 100644 index 9d65386..0000000 --- a/hevc.c +++ /dev/null @@ -1,708 +0,0 @@ -/* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. - * - * streamer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * streamer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with streamer. If not, see . - */ - -#include "hevc.h" - -#include -#include -#include - -#include "bitstream.h" - -// mburakov: Below entries are hardcoded by ffmpeg: -static const uint8_t vps_video_parameter_set_id = 0; -static const bool vps_base_layer_internal_flag = 1; -static const bool vps_base_layer_available_flag = 1; -static const uint8_t vps_max_layers_minus1 = 0; -static const uint8_t vps_max_sub_layers_minus1 = 0; -static const bool vps_temporal_id_nesting_flag = 1; -static const uint8_t general_profile_space = 0; -static const bool general_progressive_source_flag = 1; -static const bool general_interlaced_source_flag = 0; -static const bool general_non_packed_constraint_flag = 1; -static const bool general_frame_only_constraint_flag = 1; -static const bool general_one_picture_only_constraint_flag = 0; -static const bool vps_sub_layer_ordering_info_present_flag = 0; -static const uint32_t vps_max_latency_increase_plus1 = 0; -static const uint8_t vps_max_layer_id = 0; -static const uint32_t vps_num_layer_sets_minus1 = 0; -static const bool vps_poc_proportional_to_timing_flag = 0; -static const uint32_t vps_num_hrd_parameters = 0; -static const uint8_t sps_video_parameter_set_id = vps_video_parameter_set_id; -static const uint8_t sps_max_sub_layers_minus1 = vps_max_sub_layers_minus1; -static const bool sps_temporal_id_nesting_flag = vps_temporal_id_nesting_flag; -static const uint32_t sps_seq_parameter_set_id = 0; -static const uint32_t log2_max_pic_order_cnt_lsb_minus4 = 8; -static const bool sps_sub_layer_ordering_info_present_flag = - vps_sub_layer_ordering_info_present_flag; -static const uint32_t sps_max_latency_increase_plus1 = - vps_max_latency_increase_plus1; -static const uint32_t num_short_term_ref_pic_sets = 0; -static const bool long_term_ref_pics_present_flag = 0; -static const uint8_t video_format = 5; -static const bool vui_poc_proportional_to_timing_flag = - vps_poc_proportional_to_timing_flag; -static const bool vui_hrd_parameters_present_flag = 0; -static const uint32_t pps_pic_parameter_set_id = 0; -static const uint32_t pps_seq_parameter_set_id = sps_seq_parameter_set_id; -static const bool short_term_ref_pic_set_sps_flag = 0; - -// mburakov: Below entries are defaulted by ffmpeg: -static const bool general_inbld_flag = 0; -static const bool vps_extension_flag = 0; -static const bool overscan_info_present_flag = 0; -static const bool frame_field_info_present_flag = 0; -static const bool default_display_window_flag = 0; -static const bool sps_extension_present_flag = 0; -static const bool output_flag_present_flag = 0; -static const uint8_t num_extra_slice_header_bits = 0; -static const bool cabac_init_present_flag = 0; -static const bool pps_slice_chroma_qp_offsets_present_flag = 0; -static const bool deblocking_filter_control_present_flag = 0; -static const bool lists_modification_present_flag = 0; -static const bool slice_segment_header_extension_present_flag = 0; -static const bool pps_extension_present_flag = 0; -static const uint32_t motion_vector_resolution_control_idc = 0; -static const bool pps_slice_act_qp_offsets_present_flag = 0; -static const bool chroma_qp_offset_list_enabled_flag = 0; -static const bool deblocking_filter_override_enabled_flag = 0; -static const bool deblocking_filter_override_flag = 0; -static const uint32_t num_entry_point_offsets = 0; -static const bool inter_ref_pic_set_prediction_flag = 0; - -// 7.3.1.2 NAL unit header syntax -static void PackNalUnitHeader(struct Bitstream* bitstream, - uint8_t nal_unit_type) { - BitstreamAppend(bitstream, 32, 0x00000001); - BitstreamAppend(bitstream, 1, 0); // forbidden_zero_bit - BitstreamAppend(bitstream, 6, nal_unit_type); - BitstreamAppend(bitstream, 6, 0); // nuh_layer_id - BitstreamAppend(bitstream, 3, 1); // nuh_temporal_id_plus1 -} - -// 7.3.3 Profile, tier and level syntax -static void PackProfileTierLevel(struct Bitstream* bitstream, - const VAEncSequenceParameterBufferHEVC* seq, - bool profilePresentFlag, - uint8_t maxNumSubLayersMinus1) { - if (profilePresentFlag) { - BitstreamAppend(bitstream, 2, general_profile_space); - BitstreamAppend(bitstream, 1, seq->general_tier_flag); - BitstreamAppend(bitstream, 5, seq->general_profile_idc); - - // mburakov: ffmpeg deduces general_profile_compatibility_flag. - bool general_profile_compatibility_flag[32] = {0}; - general_profile_compatibility_flag[seq->general_profile_idc] = 1; - if (general_profile_compatibility_flag[1]) - general_profile_compatibility_flag[2] = 1; - if (general_profile_compatibility_flag[3]) { - general_profile_compatibility_flag[1] = 1; - general_profile_compatibility_flag[2] = 1; - } - - for (uint8_t j = 0; j < 32; j++) - BitstreamAppend(bitstream, 1, general_profile_compatibility_flag[j]); - - BitstreamAppend(bitstream, 1, general_progressive_source_flag); - BitstreamAppend(bitstream, 1, general_interlaced_source_flag); - BitstreamAppend(bitstream, 1, general_non_packed_constraint_flag); - BitstreamAppend(bitstream, 1, general_frame_only_constraint_flag); - if (seq->general_profile_idc == 4 || - general_profile_compatibility_flag[4] || - seq->general_profile_idc == 5 || - general_profile_compatibility_flag[5] || - seq->general_profile_idc == 6 || - general_profile_compatibility_flag[6] || - seq->general_profile_idc == 7 || - general_profile_compatibility_flag[7] || - seq->general_profile_idc == 8 || - general_profile_compatibility_flag[8] || - seq->general_profile_idc == 9 || - general_profile_compatibility_flag[9] || - seq->general_profile_idc == 10 || - general_profile_compatibility_flag[10] || - seq->general_profile_idc == 11 || - general_profile_compatibility_flag[11]) { - // TODO(mburakov): Implement this! - abort(); - } else if (seq->general_profile_idc == 2 || - general_profile_compatibility_flag[2]) { - BitstreamAppend(bitstream, 7, 0); // general_reserved_zero_7bits - BitstreamAppend(bitstream, 1, general_one_picture_only_constraint_flag); - BitstreamAppend(bitstream, 24, 0); // general_reserved_zero_35bits - BitstreamAppend(bitstream, 11, 0); // general_reserved_zero_35bits - } else { - BitstreamAppend(bitstream, 24, 0); // general_reserved_zero_43bits - BitstreamAppend(bitstream, 19, 0); // general_reserved_zero_43bits - } - - if (seq->general_profile_idc == 1 || - general_profile_compatibility_flag[1] || - seq->general_profile_idc == 2 || - general_profile_compatibility_flag[2] || - seq->general_profile_idc == 3 || - general_profile_compatibility_flag[3] || - seq->general_profile_idc == 4 || - general_profile_compatibility_flag[4] || - seq->general_profile_idc == 5 || - general_profile_compatibility_flag[5] || - seq->general_profile_idc == 9 || - general_profile_compatibility_flag[9] || - seq->general_profile_idc == 11 || - general_profile_compatibility_flag[11]) { - BitstreamAppend(bitstream, 1, general_inbld_flag); - } else { - BitstreamAppend(bitstream, 1, 0); // general_reserved_zero_bit - } - } - - BitstreamAppend(bitstream, 8, seq->general_level_idc); - for (uint8_t i = 0; i < maxNumSubLayersMinus1; i++) { - // TODO(mburakov): Implement this! - abort(); - } - if (maxNumSubLayersMinus1 > 0) { - for (uint8_t i = maxNumSubLayersMinus1; i < 8; i++) - BitstreamAppend(bitstream, 2, 0); // reserved_zero_2bits - } - for (uint8_t i = 0; i < maxNumSubLayersMinus1; i++) { - // TODO(mburakov): Implement this! - abort(); - } -} - -// 7.3.2.11 RBSP trailing bits syntax -static void PackRbspTrailingBits(struct Bitstream* bitstream) { - BitstreamAppend(bitstream, 1, 1); // rbsp_stop_one_bit - BitstreamByteAlign(bitstream); // rbsp_alignment_zero_bit -} - -// 7.3.2.1 Video parameter set RBSP syntax -void PackVideoParameterSetNalUnit(struct Bitstream* bitstream, - const VAEncSequenceParameterBufferHEVC* seq, - const struct MoreVideoParameters* mvp) { - const typeof(seq->vui_fields.bits)* vui_bits = &seq->vui_fields.bits; - - PackNalUnitHeader(bitstream, VPS_NUT); - - char buffer_on_the_stack[64]; - struct Bitstream vps_rbsp = { - .data = buffer_on_the_stack, - .size = 0, - }; - - BitstreamAppend(&vps_rbsp, 4, vps_video_parameter_set_id); - BitstreamAppend(&vps_rbsp, 1, vps_base_layer_internal_flag); - BitstreamAppend(&vps_rbsp, 1, vps_base_layer_available_flag); - BitstreamAppend(&vps_rbsp, 6, vps_max_layers_minus1); - BitstreamAppend(&vps_rbsp, 3, vps_max_sub_layers_minus1); - BitstreamAppend(&vps_rbsp, 1, vps_temporal_id_nesting_flag); - BitstreamAppend(&vps_rbsp, 16, 0xffff); // vps_reserved_0xffff_16bits - - PackProfileTierLevel(&vps_rbsp, seq, 1, vps_max_sub_layers_minus1); - - BitstreamAppend(&vps_rbsp, 1, vps_sub_layer_ordering_info_present_flag); - for (uint8_t i = (vps_sub_layer_ordering_info_present_flag - ? 0 - : vps_max_sub_layers_minus1); - i <= vps_max_sub_layers_minus1; i++) { - if (i != vps_max_sub_layers_minus1) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppendUE(&vps_rbsp, mvp->vps_max_dec_pic_buffering_minus1); - BitstreamAppendUE(&vps_rbsp, mvp->vps_max_num_reorder_pics); - BitstreamAppendUE(&vps_rbsp, vps_max_latency_increase_plus1); - } - - BitstreamAppend(&vps_rbsp, 6, vps_max_layer_id); - BitstreamAppendUE(&vps_rbsp, vps_num_layer_sets_minus1); - for (uint8_t i = 1; i <= vps_max_layers_minus1; i++) { - for (uint8_t j = 0; j <= vps_max_layer_id; j++) { - // TODO(mburakov): Implement this! - abort(); - } - } - - BitstreamAppend(&vps_rbsp, 1, vui_bits->vui_timing_info_present_flag); - if (vui_bits->vui_timing_info_present_flag) { - BitstreamAppend(&vps_rbsp, 32, - seq->vui_num_units_in_tick); // vps_num_units_in_tick - BitstreamAppend(&vps_rbsp, 32, seq->vui_time_scale); // vps_time_scale - BitstreamAppend(&vps_rbsp, 1, vps_poc_proportional_to_timing_flag); - if (vps_poc_proportional_to_timing_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppendUE(&vps_rbsp, vps_num_hrd_parameters); - for (uint32_t i = 0; i < vps_num_hrd_parameters; i++) { - // TODO(mburakov): Implement this! - abort(); - } - } - - BitstreamAppend(&vps_rbsp, 1, vps_extension_flag); - if (vps_extension_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - PackRbspTrailingBits(&vps_rbsp); - BitstreamInflate(bitstream, &vps_rbsp); -} - -// E.2.1 VUI parameters syntax -static void PackVuiParameters(struct Bitstream* bitstream, - const VAEncSequenceParameterBufferHEVC* seq, - const struct MoreSeqParameters* msp) { - const typeof(seq->vui_fields.bits)* vui_bits = &seq->vui_fields.bits; - - BitstreamAppend(bitstream, 1, vui_bits->aspect_ratio_info_present_flag); - if (vui_bits->aspect_ratio_info_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(bitstream, 1, overscan_info_present_flag); - if (overscan_info_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(bitstream, 1, msp->video_signal_type_present_flag); - if (msp->video_signal_type_present_flag) { - BitstreamAppend(bitstream, 3, video_format); - BitstreamAppend(bitstream, 1, msp->video_full_range_flag); - BitstreamAppend(bitstream, 1, msp->colour_description_present_flag); - if (msp->colour_description_present_flag) { - BitstreamAppend(bitstream, 8, msp->colour_primaries); - BitstreamAppend(bitstream, 8, msp->transfer_characteristics); - BitstreamAppend(bitstream, 8, msp->matrix_coeffs); - } - } - - BitstreamAppend(bitstream, 1, msp->chroma_loc_info_present_flag); - if (msp->chroma_loc_info_present_flag) { - BitstreamAppendUE(bitstream, msp->chroma_sample_loc_type_top_field); - BitstreamAppendUE(bitstream, msp->chroma_sample_loc_type_bottom_field); - } - - BitstreamAppend(bitstream, 1, vui_bits->neutral_chroma_indication_flag); - if (vui_bits->neutral_chroma_indication_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(bitstream, 1, vui_bits->field_seq_flag); - BitstreamAppend(bitstream, 1, frame_field_info_present_flag); - BitstreamAppend(bitstream, 1, default_display_window_flag); - if (default_display_window_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(bitstream, 1, vui_bits->vui_timing_info_present_flag); - if (vui_bits->vui_timing_info_present_flag) { - BitstreamAppend(bitstream, 32, seq->vui_num_units_in_tick); - BitstreamAppend(bitstream, 32, seq->vui_time_scale); - BitstreamAppend(bitstream, 1, vui_poc_proportional_to_timing_flag); - if (vui_poc_proportional_to_timing_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(bitstream, 1, vui_hrd_parameters_present_flag); - if (vui_hrd_parameters_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - } - - BitstreamAppend(bitstream, 1, vui_bits->bitstream_restriction_flag); - if (vui_bits->bitstream_restriction_flag) { - BitstreamAppend(bitstream, 1, vui_bits->tiles_fixed_structure_flag); - BitstreamAppend(bitstream, 1, - vui_bits->motion_vectors_over_pic_boundaries_flag); - BitstreamAppend(bitstream, 1, vui_bits->restricted_ref_pic_lists_flag); - BitstreamAppendUE(bitstream, seq->min_spatial_segmentation_idc); - BitstreamAppendUE(bitstream, seq->max_bytes_per_pic_denom); - BitstreamAppendUE(bitstream, seq->max_bits_per_min_cu_denom); - BitstreamAppendUE(bitstream, vui_bits->log2_max_mv_length_horizontal); - BitstreamAppendUE(bitstream, vui_bits->log2_max_mv_length_vertical); - } -} - -void PackSeqParameterSetNalUnit(struct Bitstream* bitstream, - const VAEncSequenceParameterBufferHEVC* seq, - const struct MoreSeqParameters* msp) { - const typeof(seq->seq_fields.bits)* seq_bits = &seq->seq_fields.bits; - - PackNalUnitHeader(bitstream, SPS_NUT); - - char buffer_on_the_stack[64]; - struct Bitstream sps_rbsp = { - .data = buffer_on_the_stack, - .size = 0, - }; - - BitstreamAppend(&sps_rbsp, 4, sps_video_parameter_set_id); - BitstreamAppend(&sps_rbsp, 3, sps_max_sub_layers_minus1); - BitstreamAppend(&sps_rbsp, 1, sps_temporal_id_nesting_flag); - - PackProfileTierLevel(&sps_rbsp, seq, 1, sps_max_sub_layers_minus1); - - BitstreamAppendUE(&sps_rbsp, sps_seq_parameter_set_id); - BitstreamAppendUE(&sps_rbsp, seq_bits->chroma_format_idc); - if (seq_bits->chroma_format_idc == 3) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppendUE(&sps_rbsp, seq->pic_width_in_luma_samples); - BitstreamAppendUE(&sps_rbsp, seq->pic_height_in_luma_samples); - - bool conformance_window_flag = - msp->conf_win_left_offset || msp->conf_win_right_offset || - msp->conf_win_top_offset || msp->conf_win_bottom_offset; - BitstreamAppend(&sps_rbsp, 1, conformance_window_flag); - if (conformance_window_flag) { - BitstreamAppendUE(&sps_rbsp, msp->conf_win_left_offset); - BitstreamAppendUE(&sps_rbsp, msp->conf_win_right_offset); - BitstreamAppendUE(&sps_rbsp, msp->conf_win_top_offset); - BitstreamAppendUE(&sps_rbsp, msp->conf_win_bottom_offset); - } - - BitstreamAppendUE(&sps_rbsp, seq_bits->bit_depth_luma_minus8); - BitstreamAppendUE(&sps_rbsp, seq_bits->bit_depth_chroma_minus8); - BitstreamAppendUE(&sps_rbsp, log2_max_pic_order_cnt_lsb_minus4); - - BitstreamAppend(&sps_rbsp, 1, sps_sub_layer_ordering_info_present_flag); - for (uint8_t i = (sps_sub_layer_ordering_info_present_flag - ? 0 - : sps_max_sub_layers_minus1); - i <= sps_max_sub_layers_minus1; i++) { - if (i != sps_max_sub_layers_minus1) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppendUE(&sps_rbsp, msp->sps_max_dec_pic_buffering_minus1); - BitstreamAppendUE(&sps_rbsp, msp->sps_max_num_reorder_pics); - BitstreamAppendUE(&sps_rbsp, sps_max_latency_increase_plus1); - } - - BitstreamAppendUE(&sps_rbsp, seq->log2_min_luma_coding_block_size_minus3); - BitstreamAppendUE(&sps_rbsp, seq->log2_diff_max_min_luma_coding_block_size); - BitstreamAppendUE(&sps_rbsp, seq->log2_min_transform_block_size_minus2); - BitstreamAppendUE(&sps_rbsp, seq->log2_diff_max_min_transform_block_size); - BitstreamAppendUE(&sps_rbsp, seq->max_transform_hierarchy_depth_inter); - BitstreamAppendUE(&sps_rbsp, seq->max_transform_hierarchy_depth_intra); - - BitstreamAppend(&sps_rbsp, 1, seq_bits->scaling_list_enabled_flag); - if (seq_bits->scaling_list_enabled_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&sps_rbsp, 1, seq_bits->amp_enabled_flag); - BitstreamAppend(&sps_rbsp, 1, seq_bits->sample_adaptive_offset_enabled_flag); - BitstreamAppend(&sps_rbsp, 1, seq_bits->pcm_enabled_flag); - if (seq_bits->pcm_enabled_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppendUE(&sps_rbsp, num_short_term_ref_pic_sets); - for (uint32_t i = 0; i < num_short_term_ref_pic_sets; i++) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&sps_rbsp, 1, long_term_ref_pics_present_flag); - if (long_term_ref_pics_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&sps_rbsp, 1, seq_bits->sps_temporal_mvp_enabled_flag); - BitstreamAppend(&sps_rbsp, 1, seq_bits->strong_intra_smoothing_enabled_flag); - BitstreamAppend(&sps_rbsp, 1, seq->vui_parameters_present_flag); - if (seq->vui_parameters_present_flag) PackVuiParameters(&sps_rbsp, seq, msp); - BitstreamAppend(&sps_rbsp, 1, sps_extension_present_flag); - if (sps_extension_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - PackRbspTrailingBits(&sps_rbsp); - BitstreamInflate(bitstream, &sps_rbsp); -} - -// 7.3.2.3.1 General picture parameter set RBSP syntax -void PackPicParameterSetNalUnit(struct Bitstream* bitstream, - const VAEncPictureParameterBufferHEVC* pic) { - const typeof(pic->pic_fields.bits)* pic_bits = &pic->pic_fields.bits; - - PackNalUnitHeader(bitstream, PPS_NUT); - - char buffer_on_the_stack[64]; - struct Bitstream pps_rbsp = { - .data = buffer_on_the_stack, - .size = 0, - }; - - BitstreamAppendUE(&pps_rbsp, pps_pic_parameter_set_id); - BitstreamAppendUE(&pps_rbsp, pps_seq_parameter_set_id); - BitstreamAppend(&pps_rbsp, 1, - pic_bits->dependent_slice_segments_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, output_flag_present_flag); - BitstreamAppend(&pps_rbsp, 3, num_extra_slice_header_bits); - BitstreamAppend(&pps_rbsp, 1, pic_bits->sign_data_hiding_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, cabac_init_present_flag); - BitstreamAppendUE(&pps_rbsp, pic->num_ref_idx_l0_default_active_minus1); - BitstreamAppendUE(&pps_rbsp, pic->num_ref_idx_l1_default_active_minus1); - BitstreamAppendSE(&pps_rbsp, - pic->pic_init_qp - 26); // init_qp_minus26 - BitstreamAppend(&pps_rbsp, 1, pic_bits->constrained_intra_pred_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->transform_skip_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->cu_qp_delta_enabled_flag); - if (pic_bits->cu_qp_delta_enabled_flag) - BitstreamAppendUE(&pps_rbsp, pic->diff_cu_qp_delta_depth); - BitstreamAppendSE(&pps_rbsp, pic->pps_cb_qp_offset); - BitstreamAppendSE(&pps_rbsp, pic->pps_cr_qp_offset); - BitstreamAppend(&pps_rbsp, 1, pps_slice_chroma_qp_offsets_present_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->weighted_pred_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->weighted_bipred_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->transquant_bypass_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->tiles_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, pic_bits->entropy_coding_sync_enabled_flag); - - if (pic_bits->tiles_enabled_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&pps_rbsp, 1, - pic_bits->pps_loop_filter_across_slices_enabled_flag); - BitstreamAppend(&pps_rbsp, 1, deblocking_filter_control_present_flag); - if (deblocking_filter_control_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&pps_rbsp, 1, pic_bits->scaling_list_data_present_flag); - if (pic_bits->scaling_list_data_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - BitstreamAppend(&pps_rbsp, 1, lists_modification_present_flag); - BitstreamAppendUE(&pps_rbsp, pic->log2_parallel_merge_level_minus2); - BitstreamAppend(&pps_rbsp, 1, slice_segment_header_extension_present_flag); - BitstreamAppend(&pps_rbsp, 1, pps_extension_present_flag); - - if (pps_extension_present_flag) { - // TODO(mburakov): Implement this! - abort(); - } - - PackRbspTrailingBits(&pps_rbsp); - BitstreamInflate(bitstream, &pps_rbsp); -} - -// 7.3.7 Short-term reference picture set syntax -static void PackStRefPicSet(struct Bitstream* bitstream, uint32_t stRpsIdx, - const struct MoreSliceParamerters* msp) { - if (stRpsIdx != 0) - BitstreamAppend(bitstream, 1, inter_ref_pic_set_prediction_flag); - if (inter_ref_pic_set_prediction_flag) { - // TODO(mburakov): Implement this! - abort(); - } else { - BitstreamAppendUE(bitstream, msp->num_negative_pics); - BitstreamAppendUE(bitstream, msp->num_positive_pics); - for (uint32_t i = 0; i < msp->num_negative_pics; i++) { - BitstreamAppendUE(bitstream, msp->negative_pics[i].delta_poc_s0_minus1); - BitstreamAppend(bitstream, 1, - msp->negative_pics[i].used_by_curr_pic_s0_flag); - } - for (uint32_t i = 0; i < msp->num_positive_pics; i++) { - BitstreamAppendUE(bitstream, msp->positive_pics[i].delta_poc_s1_minus1); - BitstreamAppend(bitstream, 1, - msp->positive_pics[i].used_by_curr_pic_s1_flag); - } - } -} - -// 7.3.6.1 General slice segment header syntax -void PackSliceSegmentHeaderNalUnit(struct Bitstream* bitstream, - const VAEncSequenceParameterBufferHEVC* seq, - const VAEncPictureParameterBufferHEVC* pic, - const VAEncSliceParameterBufferHEVC* slice, - const struct MoreSliceParamerters* msp) { - const typeof(seq->seq_fields.bits)* seq_bits = &seq->seq_fields.bits; - const typeof(pic->pic_fields.bits)* pic_bits = &pic->pic_fields.bits; - const typeof(slice->slice_fields.bits)* slice_bits = - &slice->slice_fields.bits; - - PackNalUnitHeader(bitstream, pic->nal_unit_type); - - char buffer_on_the_stack[64]; - struct Bitstream slice_rbsp = { - .data = buffer_on_the_stack, - .size = 0, - }; - - BitstreamAppend(&slice_rbsp, 1, msp->first_slice_segment_in_pic_flag); - if (pic->nal_unit_type >= BLA_W_LP && pic->nal_unit_type <= RSV_IRAP_VCL23) - BitstreamAppend(&slice_rbsp, 1, pic_bits->no_output_of_prior_pics_flag); - BitstreamAppendUE(&slice_rbsp, slice->slice_pic_parameter_set_id); - if (!msp->first_slice_segment_in_pic_flag) { - if (pic_bits->dependent_slice_segments_enabled_flag) - BitstreamAppend(&slice_rbsp, 1, slice_bits->dependent_slice_segment_flag); - // TODO(mburakov): Implement this!!! - abort(); - } - - if (!slice_bits->dependent_slice_segment_flag) { - for (uint32_t i = 0; i < num_extra_slice_header_bits; i++) { - // TODO(mburakov): Implement this!!! - abort(); - } - BitstreamAppendUE(&slice_rbsp, slice->slice_type); - if (output_flag_present_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (seq_bits->separate_colour_plane_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (pic->nal_unit_type != IDR_W_RADL && pic->nal_unit_type != IDR_N_LP) { - uint32_t slice_pic_order_cnt_lsb = - pic->decoded_curr_pic.pic_order_cnt & - (1 << (log2_max_pic_order_cnt_lsb_minus4 + 4)) - 1; - BitstreamAppend(&slice_rbsp, log2_max_pic_order_cnt_lsb_minus4 + 4, - slice_pic_order_cnt_lsb); - BitstreamAppend(&slice_rbsp, 1, short_term_ref_pic_set_sps_flag); - if (!short_term_ref_pic_set_sps_flag) - PackStRefPicSet(&slice_rbsp, num_short_term_ref_pic_sets, msp); - else if (num_short_term_ref_pic_sets > 1) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (long_term_ref_pics_present_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (seq_bits->sps_temporal_mvp_enabled_flag) { - BitstreamAppend(&slice_rbsp, 1, - slice_bits->slice_temporal_mvp_enabled_flag); - } - } - if (seq_bits->sample_adaptive_offset_enabled_flag) { - BitstreamAppend(&slice_rbsp, 1, slice_bits->slice_sao_luma_flag); - uint32_t ChromaArrayType = !seq_bits->separate_colour_plane_flag - ? seq_bits->chroma_format_idc - : 0; - if (ChromaArrayType != 0) - BitstreamAppend(&slice_rbsp, 1, slice_bits->slice_sao_chroma_flag); - } - if (slice->slice_type == P || slice->slice_type == B) { - BitstreamAppend(&slice_rbsp, 1, - slice_bits->num_ref_idx_active_override_flag); - if (slice_bits->num_ref_idx_active_override_flag) { - BitstreamAppendUE(&slice_rbsp, slice->num_ref_idx_l0_active_minus1); - if (slice->slice_type == B) - BitstreamAppendUE(&slice_rbsp, slice->num_ref_idx_l1_active_minus1); - } - if (lists_modification_present_flag /* && NumPicTotalCurr > 1*/) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (slice->slice_type == B) - BitstreamAppend(&slice_rbsp, 1, slice_bits->mvd_l1_zero_flag); - if (cabac_init_present_flag) - BitstreamAppend(&slice_rbsp, 1, slice_bits->cabac_init_flag); - if (slice_bits->slice_temporal_mvp_enabled_flag) { - if (slice->slice_type == B) - BitstreamAppend(&slice_rbsp, 1, slice_bits->collocated_from_l0_flag); - if ((slice_bits->collocated_from_l0_flag && - slice->num_ref_idx_l0_active_minus1 > 0) || - (!slice_bits->collocated_from_l0_flag && - slice->num_ref_idx_l1_active_minus1 > 0)) - BitstreamAppendUE(&slice_rbsp, pic->collocated_ref_pic_index); - } - if ((pic_bits->weighted_pred_flag && slice->slice_type == P) || - (pic_bits->weighted_bipred_flag && slice->slice_type == B)) { - // TODO(mburakov): Implement this!!! - abort(); - } - BitstreamAppendUE( - &slice_rbsp, - 5 - slice->max_num_merge_cand); // five_minus_max_num_merge_cand - if (motion_vector_resolution_control_idc == 2) { - // TODO(mburakov): Implement this!!! - abort(); - } - } - BitstreamAppendSE(&slice_rbsp, slice->slice_qp_delta); - if (pps_slice_chroma_qp_offsets_present_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (pps_slice_act_qp_offsets_present_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (chroma_qp_offset_list_enabled_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (deblocking_filter_override_enabled_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (deblocking_filter_override_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - if (pic_bits->pps_loop_filter_across_slices_enabled_flag && - (slice_bits->slice_sao_luma_flag || slice_bits->slice_sao_chroma_flag || - !slice_bits->slice_deblocking_filter_disabled_flag)) { - BitstreamAppend(&slice_rbsp, 1, - slice_bits->slice_loop_filter_across_slices_enabled_flag); - } - } - if (pic_bits->tiles_enabled_flag || - pic_bits->entropy_coding_sync_enabled_flag) { - BitstreamAppendUE(&slice_rbsp, num_entry_point_offsets); - if (num_entry_point_offsets > 0) { - // TODO(mburakov): Implement this!!! - abort(); - } - } - if (slice_segment_header_extension_present_flag) { - // TODO(mburakov): Implement this!!! - abort(); - } - - PackRbspTrailingBits(&slice_rbsp); - BitstreamInflate(bitstream, &slice_rbsp); -} diff --git a/hevc.h b/hevc.h deleted file mode 100644 index e82f32c..0000000 --- a/hevc.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. - * - * streamer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * streamer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with streamer. If not, see . - */ - -#ifndef STREAMER_HEVC_H_ -#define STREAMER_HEVC_H_ - -#include -#include - -// 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 . - */ - -#include "input.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/input.h b/input.h deleted file mode 100644 index a3ed01e..0000000 --- a/input.h +++ /dev/null @@ -1,31 +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 . - */ - -#ifndef STREAMER_INPUT_H_ -#define STREAMER_INPUT_H_ - -#include - -struct InputHandler; - -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); - -#endif // STREAMER_INPUT_H_ 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 . + */ + +#include "io_context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proto.h" +#include "queue.h" +#include "util.h" + +struct IoContext { + int fd; + atomic_bool running; + struct Queue prio; + struct Queue queue; + + mtx_t mutex; + cnd_t cond; + thrd_t thread; +}; + +struct ProtoImpl { + struct Proto proto; + struct ProtoHeader header; + uint8_t data[]; +}; + +static bool IsPrioProto(const struct Proto* proto) { + return proto->header->type == kProtoTypePing || + proto->header->type == kProtoTypePong; +} + +static void ProtoDestroy(struct Proto* proto) { free(proto); } + +static bool ReadAll(int fd, void* buffer, size_t size) { + for (uint8_t* ptr = buffer; size;) { + ssize_t result = read(fd, ptr, size); + if (result <= 0) { + LOG("Failed to read socket (%s)", strerror(errno)); + return false; + } + size -= (size_t)result; + ptr += result; + } + return true; +} + +static bool WriteAll(int fd, struct iovec* iov, size_t count) { + while (count) { + int max_count = (int)MIN(count, UIO_MAXIOV); + ssize_t result = writev(fd, iov, max_count); + if (result <= 0) { + LOG("Failed to write socket (%s)", strerror(errno)); + return false; + } + + for (;;) { + if ((size_t)result < iov->iov_len) { + iov->iov_len -= (size_t)result; + iov->iov_base = (uint8_t*)iov->iov_base + result; + break; + } + result -= (ssize_t)iov->iov_len; + count--; + iov++; + } + } + return true; +} + +static bool IoContextDequeue(struct IoContext* io_context, + struct Proto** pproto) { + if (mtx_lock(&io_context->mutex) != thrd_success) { + LOG("Failed to lock mutex (%s)", strerror(errno)); + return false; + } + + void* item = NULL; + while (!QueuePop(&io_context->prio, &item) && + !QueuePop(&io_context->queue, &item) && + atomic_load_explicit(&io_context->running, memory_order_relaxed)) { + assert(cnd_wait(&io_context->cond, &io_context->mutex) == thrd_success); + } + + assert(mtx_unlock(&io_context->mutex) == thrd_success); + *pproto = item; + return true; +} + +static int IoContextThreadProc(void* arg) { + struct IoContext* io_context = arg; + for (;;) { + struct Proto* proto; + if (!IoContextDequeue(io_context, &proto)) { + LOG("Failed to dequeue proto"); + goto leave; + } + + if (!proto) { + // mburakov: running was set to false externally. + return 0; + } + + struct iovec iov[] = { + {.iov_base = (void*)(uintptr_t)(proto->header), + .iov_len = sizeof(struct ProtoHeader)}, + {.iov_base = (void*)(uintptr_t)(proto->data), + .iov_len = proto->header->size}, + }; + bool result = WriteAll(io_context->fd, iov, LENGTH(iov)); + proto->Destroy(proto); + if (!result) { + LOG("Failed to write proto"); + goto leave; + } + } + +leave: + atomic_store_explicit(&io_context->running, false, memory_order_relaxed); + return 0; +} + +struct IoContext* IoContextCreate(uint16_t port) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + LOG("Failed to create socket (%s)", strerror(errno)); + return NULL; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int))) { + LOG("Failed to reuse socket address (%s)", strerror(errno)); + goto rollback_sock; + } + + const struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = INADDR_ANY, + }; + if (bind(sock, (const struct sockaddr*)&sa, sizeof(sa))) { + LOG("Failed to bind socket (%s)", strerror(errno)); + goto rollback_sock; + } + + if (listen(sock, SOMAXCONN)) { + LOG("Failed to listen socket (%s)", strerror(errno)); + goto rollback_sock; + } + + struct IoContext* io_context = malloc(sizeof(struct IoContext)); + if (!io_context) { + LOG("Failed to allocate io context (%s)", strerror(errno)); + goto rollback_sock; + } + + io_context->fd = accept(sock, NULL, NULL); + if (io_context->fd == -1) { + LOG("Failed to accept socket (%s)", strerror(errno)); + goto rollback_io_context; + } + + if (setsockopt(io_context->fd, IPPROTO_TCP, TCP_NODELAY, &(int){1}, + sizeof(int))) { + LOG("Failed to set TCP_NODELAY (%s)", strerror(errno)); + goto rollback_fd; + } + + atomic_init(&io_context->running, true); + QueueCreate(&io_context->prio); + QueueCreate(&io_context->queue); + if (mtx_init(&io_context->mutex, mtx_plain) != thrd_success) { + LOG("Failed to init mutex (%s)", strerror(errno)); + goto rollback_fd; + } + + if (cnd_init(&io_context->cond) != thrd_success) { + LOG("Failed to init condition variable (%s)", strerror(errno)); + goto rollback_mutex; + } + + if (thrd_create(&io_context->thread, &IoContextThreadProc, io_context) != + thrd_success) { + LOG("Failed to create thread (%s)", strerror(errno)); + goto rollback_cond; + } + + assert(!close(sock)); + return io_context; + +rollback_cond: + cnd_destroy(&io_context->cond); +rollback_mutex: + mtx_destroy(&io_context->mutex); +rollback_fd: + assert(!close(io_context->fd)); +rollback_io_context: + free(io_context); +rollback_sock: + assert(!close(sock)); + return NULL; +} + +struct Proto* IoContextRead(struct IoContext* io_context) { + struct ProtoHeader header; + if (!ReadAll(io_context->fd, &header, sizeof(header))) { + LOG("Failed to read proto header"); + return NULL; + } + + struct ProtoImpl* proto_impl = malloc(sizeof(struct ProtoImpl) + header.size); + if (!proto_impl) { + LOG("Failed to allocate proto (%s)", strerror(errno)); + return NULL; + } + + if (!ReadAll(io_context->fd, &proto_impl->data, header.size)) { + LOG("Failed to read proto body"); + goto rollback_proto_impl; + } + + proto_impl->header = header; + const struct Proto proto = { + .Destroy = ProtoDestroy, + .header = &proto_impl->header, + .data = proto_impl->data, + }; + memcpy(proto_impl, &proto, sizeof(proto)); + return &proto_impl->proto; + +rollback_proto_impl: + free(proto_impl); + return NULL; +} + +bool IoContextWrite(struct IoContext* io_context, struct Proto* proto) { + if (!atomic_load_explicit(&io_context->running, memory_order_relaxed)) { + LOG("Io context is not running"); + goto rollback_proto; + } + + struct Queue* queue = + IsPrioProto(proto) ? &io_context->prio : &io_context->queue; + if (mtx_lock(&io_context->mutex) != thrd_success) { + LOG("Failed to lock mutex (%s)", strerror(errno)); + goto rollback_proto; + } + + if (!QueuePush(queue, proto)) { + LOG("Failed to queue proto"); + goto rollback_lock; + } + + assert(cnd_broadcast(&io_context->cond) == thrd_success); + assert(mtx_unlock(&io_context->mutex) == thrd_success); + return true; + +rollback_lock: + assert(mtx_unlock(&io_context->mutex) == thrd_success); +rollback_proto: + proto->Destroy(proto); + return false; +} + +void IoContextDestroy(struct IoContext* io_context) { + atomic_store_explicit(&io_context->running, false, memory_order_relaxed); + assert(cnd_broadcast(&io_context->cond) == thrd_success); + assert(thrd_join(io_context->thread, NULL) == thrd_success); + cnd_destroy(&io_context->cond); + mtx_destroy(&io_context->mutex); + for (void* item; QueuePop(&io_context->prio, &item); free(item)); + QueueDestroy(&io_context->prio); + for (void* item; QueuePop(&io_context->queue, &item); free(item)); + QueueDestroy(&io_context->queue); + assert(!close(io_context->fd)); + free(io_context); +} diff --git a/io_context.h b/io_context.h new file mode 100644 index 0000000..9e880fd --- /dev/null +++ b/io_context.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STREAMER_IO_CONTEXT_H_ +#define STREAMER_IO_CONTEXT_H_ + +#include +#include + +struct Proto; +struct IoContext; + +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_IO_CONTEXT_H_ diff --git a/main.c b/main.c index 9290be3..3548f37 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. + * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer. * * streamer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,400 +16,104 @@ */ #include -#include -#include +#include #include -#include +#include +#include #include #include #include -#include -#include -#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 [--disable-uhid] [--audio ]", 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 "); + goto leave; } - contexts.gpu_context = GpuContextCreate(colorspace, range); - if (!contexts.gpu_context) { - LOG("Failed to create gpu context"); - goto rollback_audio_context; + int port = atoi(argv[1]); + if (0 >= port || port > UINT16_MAX) { + LOG("Invalid port \"%s\"", argv[1]); + goto leave; } - IoMuxerCreate(&contexts.io_muxer); - contexts.server_fd = CreateServerSocket(argv[1]); - if (contexts.server_fd == -1) { - LOG("Failed to create server socket"); - goto rollback_io_muxer; - } - - if (contexts.audio_context && - !IoMuxerOnRead(&contexts.io_muxer, - AudioContextGetEventsFd(contexts.audio_context), - &OnAudioContextEvents, &contexts)) { - LOG("Failed to schedule audio io (%s)", strerror(errno)); - goto rollback_server_fd; - } - if (!IoMuxerOnRead(&contexts.io_muxer, contexts.server_fd, - &OnClientConnecting, &contexts)) { - LOG("Failed to schedule accept (%s)", strerror(errno)); - goto rollback_server_fd; - } + SetupSignalHandler(SIGINT, OnSignal); + SetupSignalHandler(SIGPIPE, SIG_IGN); + SetupSignalHandler(SIGTERM, OnSignal); while (!g_signal) { - if (IoMuxerIterate(&contexts.io_muxer, -1) && errno != EINTR) { - LOG("Failed to iterate io muxer (%s)", strerror(errno)); - g_signal = SIGABRT; - } - if (contexts.drop_client) { - MaybeDropClient(&contexts); - contexts.drop_client = false; + struct IoContext* io_context = IoContextCreate((uint16_t)port); + if (!io_context) { + LOG("Failed to create io context"); + goto leave; } + + HandleClientSession(io_context); + IoContextDestroy(io_context); } - MaybeDropClient(&contexts); -rollback_server_fd: - close(contexts.server_fd); -rollback_io_muxer: - IoMuxerDestroy(&contexts.io_muxer); - GpuContextDestroy(contexts.gpu_context); -rollback_audio_context: - if (contexts.audio_context) AudioContextDestroy(contexts.audio_context); +leave: + pw_deinit(); bool result = g_signal == SIGINT || g_signal == SIGTERM; return result ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/makefile b/makefile index fddd5a0..6f46d1b 100644 --- a/makefile +++ b/makefile @@ -2,18 +2,9 @@ bin:=$(notdir $(shell pwd)) src:=$(wildcard *.c) obj:=$(src:.c=.o) -obj+=\ - toolbox/buffer.o \ - toolbox/io_muxer.o \ - toolbox/perf.o - libs:=\ - egl \ - gbm \ - glesv2 \ - libdrm \ - libva \ - libva-drm + libpipewire-0.3 \ + wayland-client protocols_dir:=\ wlr-protocols/unstable @@ -26,19 +17,9 @@ res:=\ luma.glsl \ chroma.glsl -ifdef USE_WAYLAND - obj:=$(patsubst %,%.o,$(protocols)) $(obj) - headers:=$(patsubst %,%.h,$(protocols)) - libs+=wayland-client - CFLAGS+=-DUSE_WAYLAND -endif - -ifdef USE_PIPEWIRE - libs+=libpipewire-0.3 - CFLAGS+=-DUSE_PIPEWIRE -endif +obj:=$(patsubst %,%.o,$(protocols)) $(obj) +headers:=$(patsubst %,%.h,$(protocols)) -#CFLAGS+=-DUSE_EGL_MESA_PLATFORM_SURFACELESS CFLAGS+=$(shell pkg-config --cflags $(libs)) LDFLAGS+=$(shell pkg-config --libs $(libs)) @@ -63,8 +44,7 @@ $(bin): $(obj) wayland-scanner private-code $< $@ clean: - -rm $(bin) $(obj) $(headers) \ - $(foreach proto,$(protocols),$(proto).h $(proto).o) + -rm $(bin) $(obj) $(headers) .PHONY: all clean diff --git a/proto.c b/proto.c deleted file mode 100644 index f1d2bc3..0000000 --- a/proto.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. - * - * streamer is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * streamer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with streamer. If not, see . - */ - -#include "proto.h" - -#include -#include -#include -#include -#include - -#include "toolbox/utils.h" - -#define UNCONST(x) ((void*)(uintptr_t)(x)) - -static bool DrainBuffers(int fd, struct iovec* iovec, int count) { - for (;;) { - ssize_t result = writev(fd, iovec, count); - if (result < 0) { - if (errno == EINTR) continue; - LOG("Failed to write (%s)", strerror(errno)); - return false; - } - for (int i = 0; i < count; i++) { - size_t delta = MIN((size_t)result, iovec[i].iov_len); - iovec[i].iov_base = (uint8_t*)iovec[i].iov_base + delta; - iovec[i].iov_len -= delta; - result -= delta; - } - if (!result) return true; - } -} - -bool WriteProto(int fd, const struct Proto* proto, const void* data) { - struct iovec iovec[] = { - {.iov_base = UNCONST(proto), .iov_len = sizeof(struct Proto)}, - {.iov_base = UNCONST(data), .iov_len = proto->size}, - }; - if (!DrainBuffers(fd, iovec, LENGTH(iovec))) { - LOG("Failed to drain buffers"); - return false; - } - return true; -} diff --git a/proto.h b/proto.h index 62f65d5..9a6a3a6 100644 --- a/proto.h +++ b/proto.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Mikhail Burakov. This file is part of streamer. + * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer. * * streamer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,26 +19,31 @@ #define STREAMER_PROTO_H_ #include +#include #include -#include -#define PROTO_TYPE_MISC 0 -#define PROTO_TYPE_VIDEO 1 -#define PROTO_TYPE_AUDIO 2 - -#define PROTO_FLAG_KEYFRAME 1 +enum ProtoType { + kProtoTypeHello, + kProtoTypePing, + kProtoTypePong, + kProtoTypeUhid, + kProtoTypeVideo, + kProtoTypeAudio, +}; -struct Proto { +struct ProtoHeader { uint32_t size; - uint8_t type; - uint8_t flags; - uint16_t latency; - uint8_t data[]; + enum ProtoType type; + uint64_t timestamp; }; -static_assert(sizeof(struct Proto) == 8 * sizeof(uint8_t), - "Suspicious proto struct size"); +static_assert(sizeof(struct ProtoHeader) == 16 * sizeof(uint8_t), + "Suspicious proto header struct size"); -bool WriteProto(int fd, const struct Proto* proto, const void* data); +struct Proto { + void (*const Destroy)(struct Proto* self); + const struct ProtoHeader* const header; + const void* const data; +}; #endif // STREAMER_PROTO_H_ diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..894bf2e --- /dev/null +++ b/queue.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer. + * + * streamer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * streamer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with streamer. If not, see . + */ + +#include "queue.h" + +#include +#include +#include + +void QueueCreate(struct Queue* queue) { + memset(queue, 0, sizeof(struct Queue)); +} + +bool QueuePush(struct Queue* queue, void* item) { + if (queue->size == queue->alloc) { + size_t alloc = queue->alloc + 1; + void** buffer = malloc(alloc * sizeof(void*)); + if (!buffer) return false; + + size_t head_size = queue->read; + size_t tail_size = queue->size - queue->read; + memcpy(buffer, queue->buffer + queue->read, tail_size * sizeof(void*)); + memcpy(buffer + tail_size, queue->buffer, head_size * sizeof(void*)); + free(queue->buffer); + + queue->buffer = buffer; + queue->alloc = alloc; + queue->read = 0; + queue->write = queue->size; + } + + queue->buffer[queue->write] = item; + queue->write = (queue->write + 1) % queue->alloc; + queue->size++; + return true; +} + +bool QueuePop(struct Queue* queue, void** pitem) { + if (!queue->size) return false; + *pitem = queue->buffer[queue->read]; + queue->read = (queue->read + 1) % queue->alloc; + queue->size--; + return true; +} + +void QueueDestroy(struct Queue* queue) { free(queue->buffer); } diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..f226aff --- /dev/null +++ b/queue.h @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +#ifndef STREAMER_QUEUE_H_ +#define STREAMER_QUEUE_H_ + +#include +#include + +struct Queue { + void** buffer; + size_t alloc; + size_t size; + size_t read; + size_t write; +}; + +void QueueCreate(struct Queue* queue); +bool QueuePush(struct Queue* queue, void* item); +bool QueuePop(struct Queue* queue, void** pitem); +void QueueDestroy(struct Queue* queue); + +#endif // STREAMER_QUEUE_H_ diff --git a/toolbox b/toolbox deleted file mode 160000 index c5334a4..0000000 --- a/toolbox +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c5334a46523b9ac959c002fbc23a25eeccf63599 diff --git a/util.h b/util.h new file mode 100644 index 0000000..62e7c4e --- /dev/null +++ b/util.h @@ -0,0 +1,32 @@ +/* + * 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 . + */ + +#ifndef STREAMER_UTIL_H_ +#define STREAMER_UTIL_H_ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define LENGTH(x) (sizeof(x) / sizeof *(x)) + +#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_ -- cgit v1.2.3