215 lines
11 KiB
C
215 lines
11 KiB
C
#include <string.h>
|
|
|
|
#include "../../lib/debug_config.h"
|
|
#include "../../lib/mem.h"
|
|
#include "../../lib/u_async.h"
|
|
#include "stream_source.h"
|
|
|
|
struct stream_source {
|
|
struct UASYNC *ua;
|
|
struct stream_descriptor descriptor;
|
|
struct stream_encoder *encoder;
|
|
struct stream_buffer *buffer;
|
|
uint8_t *input;
|
|
size_t capacity;
|
|
size_t used;
|
|
uint64_t next_seq;
|
|
uint64_t last_batch_ms;
|
|
uint64_t emitted_bytes;
|
|
uint64_t report_ms;
|
|
void *timer;
|
|
stream_source_ready_fn ready;
|
|
void *arg;
|
|
int busy;
|
|
int has_batch;
|
|
int failed;
|
|
int ended;
|
|
int write_mode; /* 0 — не выбран, 1 — байты, 2 — целые пачки. */
|
|
};
|
|
|
|
/* Ошибка не скрывает уже принятый префикс: прекращаем таймер и дальнейшие write. */
|
|
static int source_fail(struct stream_source *source, int result) {
|
|
source->failed = 1;
|
|
if (source->timer) { uasync_cancel_timeout(source->ua, source->timer); source->timer = NULL; }
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source failed source=%llx seq=%llu pending=%zu result=%d",
|
|
(unsigned long long)source->descriptor.source_node_id, (unsigned long long)source->next_seq, source->used, result);
|
|
return result;
|
|
}
|
|
|
|
/* Сформировать блоки и сохранить их до уведомления потребителей; busy установлен внешней операцией. */
|
|
static int source_flush(struct stream_source *source) {
|
|
if (!source->used) return STREAM_OK;
|
|
if (source->timer) { uasync_cancel_timeout(source->ua, source->timer); source->timer = NULL; }
|
|
uint64_t now_ms = get_time_tb() / 10;
|
|
if (source->next_seq == UINT64_MAX || (source->has_batch && now_ms < source->last_batch_ms)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source sequence/clock invalid seq=%llu now=%llu previous=%llu",
|
|
(unsigned long long)source->next_seq, (unsigned long long)now_ms, (unsigned long long)source->last_batch_ms);
|
|
return source_fail(source, STREAM_ERR_STATE);
|
|
}
|
|
uint8_t flags = source->has_batch && now_ms - source->last_batch_ms >= STREAM_TIMESTAMP_PERIOD_MS ? STREAM_BLOCK_TIME_RESET : 0;
|
|
const uint8_t *blocks[RS_MAX_BLOCKS];
|
|
size_t block_length = 0;
|
|
int result = stream_encoder_encode(source->encoder, source->next_seq, (uint16_t)now_ms, flags,
|
|
source->input, source->used, blocks, &block_length);
|
|
if (result != STREAM_OK) return source_fail(source, result);
|
|
for (unsigned i = 0; i < source->descriptor.n; i++) {
|
|
result = stream_buffer_put(source->buffer, blocks[i], block_length);
|
|
if (result != STREAM_OK) return source_fail(source, result < 0 ? result : STREAM_ERR_STATE);
|
|
}
|
|
uint64_t seq = source->next_seq++;
|
|
source->emitted_bytes += source->used;
|
|
source->used = 0;
|
|
source->has_batch = 1;
|
|
source->last_batch_ms = now_ms;
|
|
if (!source->report_ms || now_ms - source->report_ms >= 1000) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "source progress source=%llx seq=%llu bytes=%llu timestamp=%u flags=%u part=%zu max=%zu",
|
|
(unsigned long long)source->descriptor.source_node_id, (unsigned long long)seq,
|
|
(unsigned long long)source->emitted_bytes, (uint16_t)now_ms, flags,
|
|
block_length, stream_block_capacity(&source->descriptor));
|
|
source->report_ms = now_ms;
|
|
}
|
|
if (source->ready) source->ready(source, seq, source->arg);
|
|
return STREAM_OK;
|
|
}
|
|
|
|
/* Срабатывание единственного таймера неполной пачки; handle больше не принадлежит таймерному heap. */
|
|
static void source_flush_timer(void *arg) {
|
|
struct stream_source *source = arg;
|
|
source->timer = NULL;
|
|
source->busy = 1;
|
|
int result = source_flush(source);
|
|
source->busy = 0;
|
|
if (result != STREAM_OK) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source timed flush failed result=%d", result);
|
|
}
|
|
|
|
/* Создать только реальную локальную публикацию; память удалённых анонсов сюда не попадает. */
|
|
int stream_source_create(struct stream_source **out, struct UASYNC *ua, const struct stream_descriptor *d,
|
|
const uint8_t private_key[32], stream_source_ready_fn ready, void *arg) {
|
|
if (!out || *out || !ua || !private_key || stream_descriptor_validate(d) != STREAM_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source create invalid arguments out=%p ua=%p", (void *)out, (void *)ua);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
struct stream_source *source = u_calloc(1, sizeof(*source));
|
|
if (!source) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source context allocation failed"); return STREAM_ERR_MEMORY; }
|
|
source->ua = ua;
|
|
source->descriptor = *d;
|
|
source->ready = ready;
|
|
source->arg = arg;
|
|
source->capacity = stream_input_size(d);
|
|
source->input = u_malloc((uint32_t)source->capacity);
|
|
int result = source->input ? stream_encoder_create(&source->encoder, d, private_key) : STREAM_ERR_MEMORY;
|
|
if (result == STREAM_OK)
|
|
result = stream_buffer_create(&source->buffer, d, stream_encoder_public_key(source->encoder), STREAM_BUFFER_BYTES);
|
|
if (result != STREAM_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source initialization failed source=%llx result=%d", (unsigned long long)d->source_node_id, result);
|
|
stream_source_destroy(source);
|
|
return result;
|
|
}
|
|
*out = source;
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "source created group=%llx source=%llx epoch=%llx input=%zu flush=100ms",
|
|
(unsigned long long)d->group_id, (unsigned long long)d->source_node_id,
|
|
(unsigned long long)d->source_epoch, source->capacity);
|
|
return STREAM_OK;
|
|
}
|
|
|
|
void stream_source_destroy(struct stream_source *source) {
|
|
if (!source) return;
|
|
if (source->busy) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source destroy inside callback rejected"); return; }
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "source destroy source=%llx batches=%llu bytes=%llu pending=%zu ended=%d failed=%d",
|
|
(unsigned long long)source->descriptor.source_node_id, (unsigned long long)source->next_seq,
|
|
(unsigned long long)source->emitted_bytes, source->used, source->ended, source->failed);
|
|
if (source->timer) uasync_cancel_timeout(source->ua, source->timer);
|
|
stream_buffer_destroy(source->buffer);
|
|
stream_encoder_destroy(source->encoder);
|
|
u_free(source->input);
|
|
u_free(source);
|
|
}
|
|
|
|
size_t stream_source_input_capacity(const struct stream_source *source) {
|
|
if (!source) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source capacity null context"); return 0; }
|
|
return source->capacity;
|
|
}
|
|
|
|
struct stream_buffer *stream_source_buffer(struct stream_source *source) {
|
|
if (!source) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source buffer null context"); return NULL; }
|
|
return source->buffer;
|
|
}
|
|
|
|
/* Принимать ограниченную порцию, дополнять старую пачку и заводить таймер только на новый остаток. */
|
|
int stream_source_write(struct stream_source *source, const uint8_t *data, size_t length, size_t *accepted) {
|
|
if (accepted) *accepted = 0;
|
|
if (!source || !accepted || (length && !data) || length > source->capacity) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source write invalid arguments length=%zu", length);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
if (source->busy || source->failed || source->ended) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "source write state rejected busy=%d failed=%d ended=%d", source->busy, source->failed, source->ended);
|
|
return STREAM_ERR_STATE;
|
|
}
|
|
if (!length) return STREAM_OK;
|
|
if (source->write_mode == 2) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source byte write conflicts with block mode length=%zu", length);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
source->write_mode = 1;
|
|
source->busy = 1;
|
|
int result = STREAM_OK;
|
|
while (*accepted < length) {
|
|
size_t take = source->capacity - source->used;
|
|
if (take > length - *accepted) take = length - *accepted;
|
|
memcpy(source->input + source->used, data + *accepted, take);
|
|
source->used += take;
|
|
*accepted += take;
|
|
if (source->used == source->capacity && (result = source_flush(source)) != STREAM_OK) break;
|
|
}
|
|
if (result == STREAM_OK && source->used && !source->timer) {
|
|
source->timer = uasync_set_timeout(source->ua, STREAM_SOURCE_FLUSH_TB, source, source_flush_timer, "stream_source_flush");
|
|
if (!source->timer) result = source_fail(source, STREAM_ERR_MEMORY);
|
|
}
|
|
source->busy = 0;
|
|
return result;
|
|
}
|
|
|
|
/* Finish отменяет таймер и публикует остаток сразу; дальнейшие байты требуют нового source_epoch. */
|
|
int stream_source_finish(struct stream_source *source) {
|
|
if (!source || source->busy || source->failed) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source finish invalid state present=%d", source != NULL);
|
|
return source ? STREAM_ERR_STATE : STREAM_ERR_ARGUMENT;
|
|
}
|
|
if (source->ended) return STREAM_OK;
|
|
source->busy = 1;
|
|
int result = source_flush(source);
|
|
source->busy = 0;
|
|
if (result == STREAM_OK) {
|
|
source->ended = 1;
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "source finished source=%llx batches=%llu bytes=%llu",
|
|
(unsigned long long)source->descriptor.source_node_id, (unsigned long long)source->next_seq,
|
|
(unsigned long long)source->emitted_bytes);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Сохранить границу медиаблока даже для неполной пачки; общий flush подписывает и сохраняет все подблоки. */
|
|
int stream_source_write_block(struct stream_source *source, const uint8_t *data, size_t length) {
|
|
if (!source || !data || !length || length > source->capacity) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source block write invalid length=%zu capacity=%zu", length, source ? source->capacity : 0);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
if (source->busy || source->failed || source->ended) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source block write rejected busy=%d failed=%d ended=%d",
|
|
source->busy, source->failed, source->ended);
|
|
return STREAM_ERR_STATE;
|
|
}
|
|
if (source->write_mode == 1) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "source block write conflicts with byte mode pending=%zu", source->used);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
source->write_mode = 2;
|
|
source->busy = 1;
|
|
memcpy(source->input, data, length);
|
|
source->used = length;
|
|
int result = source_flush(source);
|
|
source->busy = 0;
|
|
return result;
|
|
}
|