Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions TargetBridge-Receiver/TBReceiverC/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ ifeq ($(UNAME_S),Darwin)
LDFLAGS += -framework ApplicationServices -framework AppKit -framework CoreFoundation -framework CoreGraphics -framework CoreText -framework VideoToolbox -framework CoreMedia -framework CoreVideo -framework IOSurface -framework CoreServices -framework CoreAudio -framework SystemConfiguration
endif

C_SRC = src/main.c src/net.c src/decoder.c src/display.c src/input_queue.c src/receiver_profile.c src/tb_i18n.c
C_SRC = src/main.c src/net.c src/decoder.c src/display.c src/input_queue.c src/receiver_profile.c src/session_metrics.c src/tb_i18n.c
OBJC_SRC = src/tb_gesture_bridge.m src/tb_display_tweaks.m
OBJ = $(C_SRC:.c=.o) $(OBJC_SRC:.m=.o)
BIN = tbreceiver
Expand All @@ -51,7 +51,7 @@ $(BIN): $(OBJ)
$(CC) $(CFLAGS) -fobjc-arc -c -o $@ $<

clean:
rm -f $(OBJ) $(BIN) $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN)
rm -f $(OBJ) $(BIN) $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN) $(TEST_SESSION_METRICS_BIN)

# Unit tests for the packet parser. net.c is pure POSIX, so this needs no
# ffmpeg/SDL/pkgconf — it runs anywhere, including CI, with no hardware.
Expand All @@ -63,6 +63,7 @@ endif
TEST_BIN = test_net_parser
TEST_INPUT_QUEUE_BIN = test_input_queue
TEST_RECEIVER_PROFILE_BIN = test_receiver_profile
TEST_SESSION_METRICS_BIN = test_session_metrics

test_net_parser: tests/test_net_parser.c src/net.c src/net.h src/proto.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_net_parser.c src/net.c $(TEST_LDFLAGS) -o $@
Expand All @@ -73,9 +74,13 @@ $(TEST_INPUT_QUEUE_BIN): tests/test_input_queue.c src/input_queue.c src/input_qu
$(TEST_RECEIVER_PROFILE_BIN): tests/test_receiver_profile.c src/receiver_profile.c src/receiver_profile.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_receiver_profile.c src/receiver_profile.c -o $@

test: $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN)
$(TEST_SESSION_METRICS_BIN): tests/test_session_metrics.c src/session_metrics.c src/session_metrics.h
$(CC) $(TEST_CFLAGS) -Isrc tests/test_session_metrics.c src/session_metrics.c -o $@

test: $(TEST_BIN) $(TEST_INPUT_QUEUE_BIN) $(TEST_RECEIVER_PROFILE_BIN) $(TEST_SESSION_METRICS_BIN)
./$(TEST_BIN)
./$(TEST_INPUT_QUEUE_BIN)
./$(TEST_RECEIVER_PROFILE_BIN)
./$(TEST_SESSION_METRICS_BIN)

.PHONY: all clean test
52 changes: 48 additions & 4 deletions TargetBridge-Receiver/TBReceiverC/src/decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct tb_decoder {
struct SwsContext *sws;
enum AVPixelFormat hw_pix_fmt;
int using_software_decode;
uint64_t error_count;

uint8_t *extradata;
int extradata_size;
Expand Down Expand Up @@ -130,13 +131,37 @@ int tb_dec_supports_hevc_hwdecode(void) {
return status >= 0 ? 1 : 0;
}

const char *tb_dec_backend_name(const struct tb_decoder *d) {
if (!d || !d->opened) return "pending";
if (d->using_software_decode || !d->hw_dev) return "software";
#if defined(__APPLE__)
return "VideoToolbox";
#else
return "hardware";
#endif
}

const char *tb_dec_codec_name(const struct tb_decoder *d) {
if (!d || !d->opened) return "pending";
switch (d->codec_id) {
case AV_CODEC_ID_H264: return "H.264";
case AV_CODEC_ID_HEVC: return "HEVC";
default: return "unknown";
}
}

uint64_t tb_dec_error_count(const struct tb_decoder *d) {
return d ? d->error_count : 0;
}

void tb_dec_reset(struct tb_decoder *d) {
if (!d) return;
if (d->ctx) avcodec_free_context(&d->ctx);
av_free(d->extradata);
d->extradata = NULL;
d->extradata_size = 0;
d->opened = 0;
d->error_count = 0;
/* hw_dev, hw_frame, sw_frame, pkt stay allocated for reuse. */
}

Expand Down Expand Up @@ -289,6 +314,7 @@ int tb_dec_set_param_sets(struct tb_decoder *d, const uint8_t *payload, size_t l
d->extradata = prev_ed;
d->extradata_size = prev_sz;
fprintf(stderr, "[dec] build_extradata failed\n");
d->error_count++;
return -1;
}

Expand All @@ -303,7 +329,11 @@ int tb_dec_set_param_sets(struct tb_decoder *d, const uint8_t *payload, size_t l

av_free(prev_ed);
fprintf(stderr, "[dec] param sets changed, opening decoder\n");
return open_decoder(d);
if (open_decoder(d) < 0) {
d->error_count++;
return -1;
}
return 0;
}

/* Convert AVCC (length-prefixed) frame to Annex B (start codes) in scratch. */
Expand Down Expand Up @@ -340,25 +370,36 @@ static int avcc_to_annexb(struct tb_decoder *d,
}

int tb_dec_feed_frame(struct tb_decoder *d, const uint8_t *avcc, size_t len) {
if (!d->opened) return -1;
if (!d->opened) {
d->error_count++;
return -1;
}

uint8_t *anb = NULL;
size_t anb_len = 0;
if (avcc_to_annexb(d, avcc, len, &anb, &anb_len) < 0) return -1;
if (avcc_to_annexb(d, avcc, len, &anb, &anb_len) < 0) {
d->error_count++;
return -1;
}

d->pkt->data = anb;
d->pkt->size = (int)anb_len;

int r = avcodec_send_packet(d->ctx, d->pkt);
if (r < 0 && r != AVERROR(EAGAIN)) {
fprintf(stderr, "[dec] send_packet=%d\n", r);
d->error_count++;
return -1;
}

while (1) {
r = avcodec_receive_frame(d->ctx, d->hw_frame);
if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) return 0;
if (r < 0) { fprintf(stderr, "[dec] recv_frame=%d\n", r); return -1; }
if (r < 0) {
fprintf(stderr, "[dec] recv_frame=%d\n", r);
d->error_count++;
return -1;
}

#if defined(__APPLE__)
/* Zero-copy fast path: VideoToolbox decoded frames carry a
Expand Down Expand Up @@ -389,6 +430,7 @@ int tb_dec_feed_frame(struct tb_decoder *d, const uint8_t *avcc, size_t len) {
d->sw_frame->format = AV_PIX_FMT_NV12;
if (av_hwframe_transfer_data(d->sw_frame, d->hw_frame, 0) < 0) {
fprintf(stderr, "[dec] hwframe_transfer failed\n");
d->error_count++;
av_frame_unref(d->hw_frame);
continue;
}
Expand All @@ -410,9 +452,11 @@ int tb_dec_feed_frame(struct tb_decoder *d, const uint8_t *avcc, size_t len) {
d->nv12_frame->height = out->height;
if (!d->sws || av_frame_get_buffer(d->nv12_frame, 32) < 0) {
fprintf(stderr, "[dec] could not allocate NV12 software frame\n");
d->error_count++;
} else if (sws_scale(d->sws, (const uint8_t * const *)out->data, out->linesize,
0, out->height, d->nv12_frame->data, d->nv12_frame->linesize) <= 0) {
fprintf(stderr, "[dec] software frame conversion failed\n");
d->error_count++;
} else {
d->cb(d->nv12_frame->data[0], d->nv12_frame->linesize[0],
d->nv12_frame->data[1], d->nv12_frame->linesize[1],
Expand Down
3 changes: 3 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ struct tb_decoder;
struct tb_decoder *tb_dec_create(tb_frame_cb cb, void *ud);
void tb_dec_destroy(struct tb_decoder *d);
int tb_dec_supports_hevc_hwdecode(void);
const char *tb_dec_backend_name(const struct tb_decoder *d);
const char *tb_dec_codec_name(const struct tb_decoder *d);
uint64_t tb_dec_error_count(const struct tb_decoder *d);

/* Reset decoder state on client disconnect.
* Forces re-open on next param sets so a stale FFmpeg context doesn't
Expand Down
9 changes: 9 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct tb_display {
SDL_Renderer *ren;
SDL_Texture *tex;
SDL_Texture *status_tex;
char renderer_name[64];
int tex_w, tex_h;
int quit;
int preferred_fullscreen;
Expand Down Expand Up @@ -590,6 +591,9 @@ struct tb_display *tb_disp_create(int fullscreen) {
SDL_RendererInfo info;
if (SDL_GetRendererInfo(d->ren, &info) == 0) {
fprintf(stderr, "[disp] renderer = %s\n", info.name);
snprintf(d->renderer_name, sizeof(d->renderer_name), "%s", info.name ? info.name : "unknown");
} else {
snprintf(d->renderer_name, sizeof(d->renderer_name), "%s", "unknown");
}

int win_w = 0, win_h = 0, out_w = 0, out_h = 0;
Expand Down Expand Up @@ -623,6 +627,11 @@ struct tb_display *tb_disp_create(int fullscreen) {
return d;
}

const char *tb_disp_renderer_name(struct tb_display *d) {
if (!d || d->renderer_name[0] == '\0') return "unknown";
return d->renderer_name;
}

void tb_disp_destroy(struct tb_display *d) {
if (!d) return;
if (d->system_cursor_hidden) {
Expand Down
3 changes: 3 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ int tb_disp_pop_input_event(struct tb_display *d, struct tb_input_event
/* Query active display/window/drawable information for UI/debug metadata. */
int tb_disp_get_info(struct tb_display *d, struct tb_display_info *info);

/* Actual SDL renderer selected at runtime (for example opengl or metal). */
const char *tb_disp_renderer_name(struct tb_display *d);

/* Render a simple launcher/status UI before the video stream starts. */
void tb_disp_render_status(struct tb_display *d,
const char *ip,
Expand Down
40 changes: 38 additions & 2 deletions TargetBridge-Receiver/TBReceiverC/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "decoder.h"
#include "display.h"
#include "receiver_profile.h"
#include "session_metrics.h"
#include "proto.h"
#include "tb_gesture_bridge.h"
#include "tb_display_tweaks.h"
Expand Down Expand Up @@ -65,6 +66,7 @@ struct app {
uint64_t frames;
uint64_t last_fps_tick_ms;
uint64_t last_fps_count;
int raw_stream_active;
uint64_t last_ip_check_ms;
/* Last display-tweak state reported to the sender, so changes made on this
* Mac (Control Center, System Settings) propagate back and the sender's
Expand Down Expand Up @@ -1091,16 +1093,18 @@ static void on_packet(uint8_t type, const uint8_t *payload, size_t len, void *ud
break;
case TB_PKT_PARAM_SETS:
a->session_active = 1;
a->raw_stream_active = 0;
/* tb_dec_set_param_sets is now a no-op if the sets are unchanged,
* so we don't spam a log line per keyframe. */
tb_dec_set_param_sets(a->dec, payload, len);
(void)tb_dec_set_param_sets(a->dec, payload, len);
break;
case TB_PKT_FRAME:
a->session_active = 1;
tb_dec_feed_frame(a->dec, payload, len);
(void)tb_dec_feed_frame(a->dec, payload, len);
break;
case TB_PKT_RAW_FRAME:
a->session_active = 1;
a->raw_stream_active = 1;
handle_raw_frame(a, payload, len);
break;
case TB_PKT_CURSOR:
Expand Down Expand Up @@ -1261,6 +1265,32 @@ static int send_all(int fd, const uint8_t *buf, size_t len) {
return 0;
}

static void tb_receiver_send_session_metrics(struct app *a, uint64_t receiver_fps) {
if (!a || a->client_fd < 0 || !a->session_active) return;

const char *renderer = tb_disp_renderer_name(a->disp);
const char *decoder = a->raw_stream_active ? "none" : tb_dec_backend_name(a->dec);
const char *codec = a->raw_stream_active ? "NV12 RAW" : tb_dec_codec_name(a->dec);

char json[512];
int len = tb_session_metrics_json(
json,
sizeof(json),
receiver_fps,
a->frames,
tb_dec_error_count(a->dec),
renderer,
decoder,
codec);
if (len < 0) return;

uint8_t pkt[4 + 1 + sizeof(json)];
write_be32(pkt, (uint32_t)(1 + len));
pkt[4] = TB_PKT_SESSION_METRICS;
memcpy(pkt + 5, json, (size_t)len);
(void)send_all(a->client_fd, pkt, 5 + (size_t)len);
}

static void tb_receiver_send_input_event(struct app *a,
const char *kind,
int has_dx, int dx,
Expand Down Expand Up @@ -1803,6 +1833,8 @@ static void close_client(struct app *a) {
a->session_active = 0;
a->close_requested = 0;
a->have_video_frame = 0;
a->raw_stream_active = 0;
a->last_fps_count = a->frames;
snprintf(a->input_control_mode, sizeof(a->input_control_mode), "off");
SDL_EnableScreenSaver();
tb_receiver_refresh_input_capture(a);
Expand Down Expand Up @@ -2038,6 +2070,9 @@ int main(int argc, char **argv) {
a.client_fd = c;
a.have_video_frame = 0;
a.session_active = 0;
a.raw_stream_active = 0;
a.last_fps_count = a.frames;
a.last_fps_tick_ms = t;
a.reported_night_shift = -1; /* force one report per session */
a.reported_true_tone = -1;
a.last_recv_ms = t;
Expand Down Expand Up @@ -2166,6 +2201,7 @@ int main(int argc, char **argv) {
a.last_fps_count = a.frames;
a.last_fps_tick_ms = t;
if (df > 0) fprintf(stderr, "[main] %llu fps\n", (unsigned long long)df);
tb_receiver_send_session_metrics(&a, df);
}

/* Yield when idle or when a nonblocking active socket had no data,
Expand Down
2 changes: 2 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* type 0x35 = brightness update (JSON)
* type 0x36 = clipboard update (JSON)
* type 0x37 = volume update (JSON)
* type 0x39 = receiver session metrics (JSON, receiver → sender)
*
* Compatible with the new TBDisplaySender Swift app.
*/
Expand Down Expand Up @@ -59,6 +60,7 @@
* Both are private CoreBrightness features, so the receiver reports whether it
* can honour them in its display profile. */
#define TB_PKT_DISPLAY_TWEAKS 0x38
#define TB_PKT_SESSION_METRICS 0x39
#define TB_PKT_TEST_DATA 0x40

#define TB_HDR_BYTES 5 /* 4 length + 1 type */
Expand Down
29 changes: 29 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/session_metrics.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "session_metrics.h"

#include <stdio.h>

int tb_session_metrics_json(char *dst,
size_t dst_size,
uint64_t receiver_fps,
uint64_t rendered_frames,
uint64_t decode_errors,
const char *renderer,
const char *decoder,
const char *codec) {
if (!dst || dst_size == 0) return -1;

int len = snprintf(
dst,
dst_size,
"{\"receiverFPS\":%llu,\"renderedFrames\":%llu,\"decodeErrors\":%llu,"
"\"renderer\":\"%s\",\"decoder\":\"%s\",\"codec\":\"%s\"}",
(unsigned long long)receiver_fps,
(unsigned long long)rendered_frames,
(unsigned long long)decode_errors,
renderer ? renderer : "unknown",
decoder ? decoder : "unknown",
codec ? codec : "unknown");

if (len <= 0 || (size_t)len >= dst_size) return -1;
return len;
}
20 changes: 20 additions & 0 deletions TargetBridge-Receiver/TBReceiverC/src/session_metrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef TB_SESSION_METRICS_H
#define TB_SESSION_METRICS_H

#include <stddef.h>
#include <stdint.h>

/* Build the compact JSON payload carried by TB_PKT_SESSION_METRICS.
* Returns the byte length (excluding the trailing NUL), or -1 when the
* destination is too small. String values must be stable backend names; the
* receiver only supplies its own fixed labels, never user-provided text. */
int tb_session_metrics_json(char *dst,
size_t dst_size,
uint64_t receiver_fps,
uint64_t rendered_frames,
uint64_t decode_errors,
const char *renderer,
const char *decoder,
const char *codec);

#endif /* TB_SESSION_METRICS_H */
Loading
Loading