From a4e6683c5b7d6a1afc6f211dca9c46df243e647b Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 01:32:40 +0200 Subject: [PATCH] Exercise modern codec playback and partial video queue telemetry --- tests/test_android_stream.c | 15 +++++-- tools/chatgui/tests/test_stream_view.cpp | 51 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/tests/test_android_stream.c b/tests/test_android_stream.c index 0fe33e65..c5a76597 100644 --- a/tests/test_android_stream.c +++ b/tests/test_android_stream.c @@ -49,7 +49,7 @@ static uint8_t *wait_event(uint64_t id, int kind, size_t *size) { static void *producer(void *arg) { (void)arg; __atomic_store_n(&producer_started, 1, __ATOMIC_RELEASE); - uint64_t pts = 500000; + uint64_t pts = 1000000; while (!__atomic_load_n(&producer_stop, __ATOMIC_ACQUIRE)) { int result = feed(3, 1, pts++, 1, video, sizeof(video)); if (result < 0) break; @@ -103,19 +103,22 @@ int main(void) { /* Commit новой revision удаляет старое видео, сохраняет аудио/epoch и не требует нового owner. */ CHECK(feed(1, 1, 200000, 0, video, sizeof(video)) == 1); CHECK(feed(1, 0, 120000, 0, (const uint8_t *)"ops", 3) == 1); - struct stream_media_format next_format = {1920, 1080, 270, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, 2}; + struct stream_media_format next_format = {1920, 1080, 270, STREAM_MEDIA_H265, STREAM_MEDIA_OPUS_STEREO, 2}; CHECK(android_stream_feed(1, 1, 300000, 0, video, sizeof(video), &next_format) == 0); CHECK(android_stream_feed(1, 1, 300000, 1, video, sizeof(video), &next_format) == 1); CHECK(feed(1, 1, 350000, 1, video, sizeof(video)) == 0); next_format.width++; CHECK(android_stream_feed(1, 1, 400000, 1, video, sizeof(video), &next_format) == -1); next_format.width--; + next_format.video_codec = STREAM_MEDIA_VP9; + CHECK(android_stream_feed(1, 1, 400000, 1, video, sizeof(video), &next_format) == -1); + next_format.video_codec = STREAM_MEDIA_H265; video_seen = audio_seen = 0; for (unsigned i = 0; i < 1000 && (!video_seen || !audio_seen); i++) { CHECK(android_stream_read_request(2, 300000, 80000, 80000) == 0); output = android_stream_pull(2, &size); if (output && output[0] == 1) { - CHECK(stream_read_be(output + 8, 8) == 300000 && stream_read_be(output + 20, 4) == 2); + CHECK(stream_read_be(output + 8, 8) == 300000 && stream_read_be(output + 20, 4) == 2 && output[24] == STREAM_MEDIA_H265); CHECK(stream_read_be(output + 2, 2) == 1920 && stream_read_be(output + 6, 2) == 270); video_seen++; } else if (output && output[0] == 2) { CHECK(stream_read_be(output + 8, 8) == 120000); audio_seen++; } else if (output) CHECK(output[0] != 6); @@ -152,6 +155,12 @@ int main(void) { CHECK(!strcmp(source_snapshot, "[]")); current_format.rotation = 0; current_format.audio_codec = 0; CHECK(android_stream_open(3, group, 0, 1280, 720, 0, 0, 0, STREAM_MEDIA_H264) == 0); u_free(wait_event(3, 4, &size)); + uint8_t *large = u_malloc(500000); CHECK(large); memset(large, 1, 500000); + CHECK(feed(3, 1, 500000, 1, large, 500000) == 1); u_free(large); + test_clock_run_for(ua, 1000); + struct android_stream_tx_stats stats; + CHECK(android_stream_tx_snapshot(3, &stats) == 0 && stats.video_bytes > 0 && stats.video_bytes < 500000); + CHECK(stats.total_bytes == stats.video_bytes && !stats.acked_1s && !stats.connections); pthread_t thread; CHECK(pthread_create(&thread, NULL, producer, NULL) == 0); while (!__atomic_load_n(&producer_started, __ATOMIC_ACQUIRE)) {} android_stream_detach(inst); diff --git a/tools/chatgui/tests/test_stream_view.cpp b/tools/chatgui/tests/test_stream_view.cpp index addfa5b2..656a5e41 100644 --- a/tools/chatgui/tests/test_stream_view.cpp +++ b/tools/chatgui/tests/test_stream_view.cpp @@ -255,6 +255,57 @@ private slots: #endif } + void modernCodecPlayback() { +#ifdef HAVE_FFMPEG + struct Fixture { uint8_t type; const char* encoder; }; + const Fixture fixtures[] = {{STREAM_MEDIA_H265, "libx265"}, {STREAM_MEDIA_VP9, "libvpx-vp9"}, {STREAM_MEDIA_AV1, "libaom-av1"}}; + for (const auto& fixture : fixtures) { + const AVCodec* encoder = avcodec_find_encoder_by_name(fixture.encoder); QVERIFY2(encoder, fixture.encoder); + AVCodecContext* context = avcodec_alloc_context3(encoder); QVERIFY(context); + context->width = 160; context->height = 90; context->pix_fmt = AV_PIX_FMT_YUV420P; context->bit_rate = 200000; + context->time_base = {1, 30}; context->framerate = {30, 1}; context->max_b_frames = 0; context->thread_count = 1; context->gop_size = 1; + AVDictionary* options = nullptr; + if (fixture.type == STREAM_MEDIA_H265) { + av_dict_set(&options, "preset", "ultrafast", 0); av_dict_set(&options, "tune", "zerolatency", 0); + av_dict_set(&options, "x265-params", "pools=none:frame-threads=1:log-level=error:bframes=0:repeat-headers=1", 0); + } else { + av_dict_set(&options, "lag-in-frames", "0", 0); av_dict_set(&options, "cpu-used", "8", 0); + av_dict_set(&options, fixture.type == STREAM_MEDIA_VP9 ? "deadline" : "usage", "realtime", 0); + } + QCOMPARE(avcodec_open2(context, encoder, &options), 0); av_dict_free(&options); + AVFrame* frame = av_frame_alloc(); QVERIFY(frame); frame->width = 160; frame->height = 90; frame->format = context->pix_fmt; + QCOMPARE(av_frame_get_buffer(frame, 32), 0); + for (int plane = 0; plane < 3; plane++) for (int y = 0; y < (plane ? 45 : 90); y++) + memset(frame->data[plane] + y * frame->linesize[plane], plane ? 128 : 80, plane ? 80 : 160); + frame->pts = 0; QCOMPARE(avcodec_send_frame(context, frame), 0); + AVPacket* packet = av_packet_alloc(); QVERIFY(packet); + int received = avcodec_receive_packet(context, packet); + if (received == AVERROR(EAGAIN)) { QCOMPARE(avcodec_send_frame(context, nullptr), 0); received = avcodec_receive_packet(context, packet); } + QCOMPARE(received, 0); QVERIFY(packet->flags & AV_PKT_FLAG_KEY); + QByteArray video(4, '\0'); + stream_write_be(reinterpret_cast(video.data()), context->extradata_size, 4); + if (context->extradata_size) video.append(reinterpret_cast(context->extradata), context->extradata_size); + video.append(reinterpret_cast(packet->data), packet->size); + av_packet_free(&packet); av_frame_free(&frame); avcodec_free_context(&context); + QCOMPARE(stream_publish_finish(publisher), STREAM_OK); stream_close(publisher); publisher = nullptr; + QCOMPARE(stream_publish_open(inst, group, nullptr, &publisher), STREAM_OK); + stream_info info{}; QCOMPARE(stream_get_info(publisher, &info), STREAM_OK); epoch = info.descriptor.source_epoch; + StreamPlayerWindow window(group, inst->node_id, epoch, fixture.encoder); QSignalSpy shown(&window, &StreamPlayerWindow::framePresented); + window.show(); + stream_media_block media{}; media.format = {160, 90, 0, fixture.type, 0, 1}; media.video_count = 1; + media.video[0] = {1, 0, (uint32_t)video.size(), 0, (uint32_t)video.size(), STREAM_MEDIA_KEY, + reinterpret_cast(video.constData())}; + uint8_t wire[7345]; size_t length = 0; + QCOMPARE(stream_media_encode(&media, wire, sizeof(wire), &length), STREAM_OK); + QCOMPARE(stream_publish_block(publisher, wire, length), STREAM_OK); + QTRY_VERIFY_WITH_TIMEOUT(shown.count() > 0, 2000); + QCOMPARE(stream_publish_finish(publisher), STREAM_OK); + QTRY_VERIFY_WITH_TIMEOUT(stream_get_info(publisher, &info) == STREAM_OK && info.owners == 1, 2000); + window.close(); QTest::qWait(20); + } +#endif + } + void finishAndDetachPendingOwner() { QCOMPARE(stream_publish_finish(publisher), STREAM_OK); QTRY_COMPARE(snapshots.last().mid(9), QByteArray("[]")); stream_close(publisher); publisher = nullptr;