Browse Source

Test stream audio toggles and viewer scoped radio suppression

master
evgeny 3 days ago
parent
commit
559d66ae20
  1. 67
      tests/test_stream_service.c
  2. 25
      tools/chatgui/tests/test_stream_publish.cpp

67
tests/test_stream_service.c

@ -9,6 +9,7 @@
#include "../src/stream/stream.h"
#include "../src/stream/stream_member.h"
#include "../src/stream/stream_priv.h"
#include "../src/radio/radio.h"
#include "../src/chat/chat_core.h"
#include "../src/chat/merkle_tree.h"
#include "../src/routing_layer/topo_node_sqlite.h"
@ -1011,7 +1012,7 @@ static void test_profile_atomicity(void) {
uint64_t before_version, after_version;
profile(0, GROUP1, before, before_signature, &before_version); root_hash(0, GROUP1, before_hash);
memset(huge, 'p', MS_USERINFO_MAX); huge[MS_USERINFO_MAX] = 0;
struct chat_profile_option options[] = {{"stream_source", "0000000000000001:8:2:1189"}, {"stream_subscriptions", huge}};
struct chat_profile_option options[] = {{"stream_source", "0000000000000001:8:2:1189:0"}, {"stream_subscriptions", huge}};
CHECK(chat_core_set_my_profile_options(nodes[0], "710001", options, 2) < 0);
profile(0, GROUP1, after, after_signature, &after_version); root_hash(0, GROUP1, after_hash);
CHECK(!strcmp(before, after) && before_version == after_version && !memcmp(before_signature, after_signature, 64));
@ -1385,21 +1386,22 @@ static void test_supernode_relay(void) {
/* Канонический список исключает неоднозначные IDs, дубли и обрезание значений. */
static void test_member_format(void) {
struct stream_member_state state = {.source = {.group_id = GROUP1, .source_node_id = 1, .source_epoch = 2,
.n = 8, .r = 2, .block_size = STREAM_RS_DEFAULT_SIZE}, .published = 1, .count = 2,
.n = 8, .r = 2, .block_size = STREAM_RS_DEFAULT_SIZE}, .published = 1, .audio_enabled = 1, .count = 2,
.subscriptions = {{1, 2}, {UINT64_MAX, UINT64_MAX, 1}}};
char source[MS_USERINFO_MAX + 1], subscriptions[MS_USERINFO_MAX + 1], json[2 * MS_USERINFO_MAX + 64];
CHECK(stream_member_format(&state, source, subscriptions) == STREAM_OK);
snprintf(json, sizeof(json), "{\"stream_source\":\"%s\",\"stream_subscriptions\":\"%s\"}", source, subscriptions);
struct stream_member_state parsed;
CHECK(stream_member_parse(GROUP1, 1, json, &parsed) == STREAM_OK);
CHECK(parsed.published && parsed.source.source_epoch == 2 && parsed.count == 2);
CHECK(parsed.published && parsed.audio_enabled && parsed.source.source_epoch == 2 && parsed.count == 2);
CHECK(parsed.subscriptions[1].source_node_id == UINT64_MAX && parsed.subscriptions[1].source_epoch == UINT64_MAX);
CHECK(!parsed.subscriptions[0].relay_only && parsed.subscriptions[1].relay_only);
const char *bad[] = {
"{\"stream_subscriptions\":\"1:2\"}",
"{\"stream_subscriptions\":\"0000000000000001:0000000000000002,\"}",
"{\"stream_subscriptions\":\"0000000000000001:0000000000000002,0000000000000001:0000000000000002\"}",
"{\"stream_source\":\"0000000000000002:256:2:1189\"}",
"{\"stream_source\":\"0000000000000002:256:2:1189:1\"}",
"{\"stream_source\":\"0000000000000002:8:2:1189:2\",\"stream_subscriptions\":\"0000000000000001:0000000000000002\"}",
"{\"stream_source\":\"0000000000000002:8:2:1189\"}",
"{\"stream_source\":\"\",\"stream_source\":\"\"}"
};
@ -1408,6 +1410,62 @@ static void test_member_format(void) {
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] member format: full 64-bit IDs, canonical lists, duplicate/overflow rejection");
}
static void wait_source_audio(uint64_t epoch, int enabled) {
uint64_t deadline = get_time_tb() + 100000;
for (;;) {
struct stream_member_state state;
if (stream_member_load(nodes[1], GROUP1, nodes[0]->node_id, &state, NULL) == STREAM_OK
&& state.published && state.source.source_epoch == epoch && state.audio_enabled == enabled) return;
CHECK(get_time_tb() < deadline); test_clock_poll(ua, 100);
}
}
static void test_viewer_radio_mute(void) {
struct STREAM_HANDLE *publisher = NULL, *raw = NULL, *relay = NULL, *first = NULL, *second = NULL;
struct stream_info info;
uint64_t source = nodes[0]->node_id;
CHECK(stream_publish_open(nodes[0], GROUP1, NULL, &publisher) == STREAM_OK);
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK);
CHECK(stream_get_info(publisher, &info) == STREAM_OK);
uint64_t epoch = info.descriptor.source_epoch;
wait_source_audio(epoch, 1);
CHECK(!radio_member_silenced(nodes[1], GROUP1, source));
CHECK(stream_relay_open(nodes[1], &info.descriptor, &relay) == STREAM_OK);
CHECK(stream_subscribe_open(nodes[1], GROUP1, source, &raw) == STREAM_OK);
CHECK(!radio_member_silenced(nodes[1], GROUP1, source));
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK);
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO | STREAM_MEDIA_TRACK_AUDIO, &second) == STREAM_OK);
CHECK(radio_member_silenced(nodes[1], GROUP1, source) && !radio_member_muted(nodes[1], GROUP1, source));
CHECK(!radio_member_silenced(nodes[1], GROUP2, source));
CHECK(!radio_member_silenced(nodes[1], GROUP1, nodes[2]->node_id));
stream_close(first); first = NULL;
CHECK(radio_member_silenced(nodes[1], GROUP1, source));
CHECK(radio_set_member_muted(nodes[1], GROUP1, source, 1) == 0);
CHECK(stream_publish_set_audio_enabled(publisher, 0) == STREAM_OK); wait_source_audio(epoch, 0);
CHECK(!stream_radio_muted(nodes[1], GROUP1, source) && radio_member_silenced(nodes[1], GROUP1, source));
CHECK(radio_set_member_muted(nodes[1], GROUP1, source, 0) == 0);
CHECK(!radio_member_silenced(nodes[1], GROUP1, source));
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK); wait_source_audio(epoch, 1);
CHECK(radio_member_silenced(nodes[1], GROUP1, source));
stream_close(second); second = NULL;
CHECK(!radio_member_silenced(nodes[1], GROUP1, source));
CHECK(stream_media_open(nodes[1], GROUP1, source, epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK);
CHECK(radio_member_silenced(nodes[1], GROUP1, source));
stream_close(publisher); publisher = NULL;
CHECK(stream_publish_open(nodes[0], GROUP1, NULL, &publisher) == STREAM_OK);
CHECK(stream_publish_set_audio_enabled(publisher, 1) == STREAM_OK);
CHECK(stream_get_info(publisher, &info) == STREAM_OK && info.descriptor.source_epoch != epoch);
wait_source_audio(info.descriptor.source_epoch, 1);
CHECK(!stream_handle_active(first) && !radio_member_silenced(nodes[1], GROUP1, source));
stream_close(first); stream_close(raw); stream_close(relay); first = NULL;
CHECK(stream_media_open(nodes[1], GROUP1, source, info.descriptor.source_epoch, STREAM_MEDIA_TRACK_VIDEO, &first) == STREAM_OK);
CHECK(radio_member_silenced(nodes[1], GROUP1, source));
stream_cancel_group(nodes[1], GROUP1);
CHECK(!radio_member_silenced(nodes[1], GROUP1, source));
stream_close(first); stream_close(publisher);
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] viewer radio mute: source audio, manual mute, group isolation, owners, relay, epoch, close");
}
/* Пауза player не останавливает live ingest/selector; FIN сохраняет источник до дочитывания локального owner. */
static void test_delayed_media_reader(void) {
struct STREAM_HANDLE *publisher = NULL, *remote = NULL, *preview = NULL;
@ -1766,6 +1824,7 @@ int main(int argc, char **argv) {
for (unsigned i = 0; i < 4; i++) stream_set_event_callback(nodes[i], NULL, NULL);
test_partial_bootstrap_and_end();
test_delayed_media_reader();
test_viewer_radio_mute();
for (unsigned i = 0; i < 3; i++) topo_group_peer_close(connections[i]);
for (unsigned i = 0; i < 4; i++) utun_instance_destroy(nodes[i]);
uasync_destroy(ua, 1); test_clock_finish();

25
tools/chatgui/tests/test_stream_publish.cpp

@ -172,6 +172,31 @@ private slots:
gui_stream_publish_stop(id); QVERIFY(record(viewer, StreamRecordKind::End, out)); QTRY_VERIFY_WITH_TIMEOUT(ended(id), 4000);
QCOMPARE(gui_stream_publish_feed(id, true, 300000, true, second, &format), -1); gui_stream_stop(viewer);
}
void audioToggleKeepsEpochAndTrack() {
uint64_t id = 104;
stream_media_format format{160, 90, 0, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, 1};
QVERIFY(gui_stream_publish_open(id, group, format)); QTRY_VERIFY(live(id));
stream_publisher_status status{}; QVERIFY(gui_stream_publish_status(id, status)); QVERIFY(status.audio_enabled);
uint64_t epoch = status.epoch;
stream_member_state member{};
QCOMPARE(stream_member_load(inst, group, inst->node_id, &member, nullptr), STREAM_OK); QVERIFY(member.audio_enabled);
QVERIFY(gui_stream_publish_audio(id, false));
QTRY_VERIFY(gui_stream_publish_status(id, status) && !status.audio_enabled);
QCOMPARE(status.epoch, epoch);
QCOMPARE(stream_member_load(inst, group, inst->node_id, &member, nullptr), STREAM_OK); QVERIFY(!member.audio_enabled);
QVERIFY(!gui_stream_publish_audio(id + 1, true));
QVERIFY(gui_stream_publish_audio(id, true));
QTRY_VERIFY(gui_stream_publish_status(id, status) && status.audio_enabled); QCOMPARE(status.epoch, epoch);
auto* encoder = opus_codec_encoder_create(48000, 2); QVERIFY(encoder);
int16_t silence[1920]{}; uint8_t packet[STREAM_MEDIA_MAX_OPUS];
int size = opus_codec_encode(encoder, silence, 960, packet, sizeof(packet)); QVERIFY(size > 0);
opus_codec_encoder_destroy(encoder);
QCOMPARE(gui_stream_publish_feed(id, false, 100000, false, QByteArray(reinterpret_cast<const char*>(packet), size)), 1);
gui_stream_publish_stop(id); QVERIFY(!gui_stream_publish_audio(id, false)); QTRY_VERIFY_WITH_TIMEOUT(ended(id), 4000);
format.audio_codec = 0;
QVERIFY(gui_stream_publish_open(++id, group, format)); QTRY_VERIFY(live(id));
QVERIFY(!gui_stream_publish_audio(id, true)); gui_stream_publish_stop(id); QTRY_VERIFY_WITH_TIMEOUT(ended(id), 4000);
}
void liveSoftwareBitrateKeepsDecoder() {
#ifdef HAVE_FFMPEG
LiveVideoEncoder encoder; LiveVideoConfig config; config.width = config.height = 16;

Loading…
Cancel
Save