Browse Source

Generate continuous stereo Opus in live playback regression

master
evgeny 5 days ago
parent
commit
50005b108f
  1. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt
  2. 44
      tools/chatgui/tests/test_stream_view.cpp

2
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt

@ -156,7 +156,6 @@ fun StreamScreen(channelId: String, channelName: String, sources: List<StreamSou
}
}
}
@Composable
private fun CaptureModeStatus(config: StreamCaptureConfig?, pending: StreamCaptureConfig?, notice: Int?) {
config?.let { Text(it.mode.label, style = MaterialTheme.typography.bodySmall) }
@ -284,4 +283,3 @@ private fun LiveStreamSurface(group: Long, source: Long, epoch: Long, localPrevi
}
}
}

44
tools/chatgui/tests/test_stream_view.cpp

@ -87,7 +87,7 @@ class StreamViewTest : public QObject {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop test missing record id=%llu", (unsigned long long)id); return false;
}
void publish(const QByteArray& video, const QByteArray& audio = {}, unsigned videoFrames = 1) {
void publish(const QByteArray& video, const QList<QByteArray>& audio = {}, unsigned videoFrames = 1) {
stream_media_block media{}; media.format = {160, 90, rotation, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, revision};
mediaPts += 100000;
media.first_frame_offset = 0; media.video_count = videoFrames;
@ -95,9 +95,9 @@ class StreamViewTest : public QObject {
static_cast<uint32_t>(video.size()), 0, static_cast<uint32_t>(video.size()), STREAM_MEDIA_KEY,
reinterpret_cast<const uint8_t*>(video.constData())};
if (!audio.isEmpty()) {
media.audio_count = videoFrames > 1 ? 5 : 1;
media.audio_count = audio.size();
for (unsigned i = 0; i < media.audio_count; i++) media.audio[i] = {mediaPts + i * 20000,
static_cast<uint32_t>(audio.size()), 960, reinterpret_cast<const uint8_t*>(audio.constData())};
static_cast<uint32_t>(audio[i].size()), 960, reinterpret_cast<const uint8_t*>(audio[i].constData())};
}
uint8_t wire[7080]; size_t length = 0;
QCOMPARE(stream_media_encode(&media, wire, sizeof(wire), &length), STREAM_OK);
@ -146,7 +146,7 @@ private slots:
void mediaAndPressure() {
QVERIFY(gui_stream_open(102, group, inst->node_id, epoch)); StreamRecord event;
QVERIFY(receive(102, event)); QCOMPARE(event.kind, StreamRecordKind::Opened);
const QByteArray frame(1500, 'k'); const QByteArray audio("opus"); publish(frame, audio);
const QByteArray frame(1500, 'k'); const QByteArray audio("opus"); publish(frame, {audio});
QVERIFY(receive(102, event)); QCOMPARE(event.kind, StreamRecordKind::Video); QCOMPARE(event.data, frame);
QCOMPARE(event.format.rotation, uint16_t(90));
QVERIFY(receive(102, event)); QCOMPARE(event.kind, StreamRecordKind::Audio); QCOMPARE(event.data, audio);
@ -176,24 +176,33 @@ private slots:
AVPacket* packet = av_packet_alloc(); QVERIFY(packet); QCOMPARE(avcodec_receive_packet(context, packet), 0);
QByteArray video(reinterpret_cast<const char*>(packet->data), packet->size);
av_packet_free(&packet); av_frame_free(&frame); avcodec_free_context(&context);
QByteArray audio;
std::unique_ptr<opus_codec_encoder_t, decltype(&opus_codec_encoder_destroy)> opus(nullptr, opus_codec_encoder_destroy);
uint64_t sampleFrame = 0;
#ifdef TEST_NULL_AUDIO
QVERIFY(SoundManager::instance()->init());
auto* opus = opus_codec_encoder_create(48000, 2); QVERIFY(opus);
QCOMPARE(opus_codec_encoder_application_set(opus, OPUS_CODEC_APP_AUDIO), 0);
QCOMPARE(opus_codec_encoder_bitrate_set(opus, 128000), 0);
int16_t pcm[1920];
for (unsigned i = 0; i < 960; i++) {
pcm[2 * i] = static_cast<int16_t>(6000 * std::sin(i * 0.05));
pcm[2 * i + 1] = static_cast<int16_t>(1500 * std::sin(i * 0.13));
}
uint8_t encoded[1275]; int length = opus_codec_encode(opus, pcm, 960, encoded, sizeof(encoded)); QVERIFY(length > 0);
audio = QByteArray(reinterpret_cast<const char*>(encoded), length); opus_codec_encoder_destroy(opus);
opus.reset(opus_codec_encoder_create(48000, 2)); QVERIFY(opus);
QCOMPARE(opus_codec_encoder_application_set(opus.get(), OPUS_CODEC_APP_AUDIO), 0);
QCOMPARE(opus_codec_encoder_bitrate_set(opus.get(), 128000), 0);
nonzeroSamples = 0; leftEnergy = rightEnergy = 0; playbackDelayUs = 200000;
#endif
StreamPlayerWindow window(group, inst->node_id, epoch, "Source");
QSignalSpy shown(&window, &StreamPlayerWindow::framePresented); window.show();
QTimer sender; connect(&sender, &QTimer::timeout, this, [this, video, audio] { publish(video, audio, 3); }); sender.start(100);
QTimer sender;
connect(&sender, &QTimer::timeout, this, [this, video, &opus, &sampleFrame] {
QList<QByteArray> packets;
if (opus) for (unsigned frame = 0; frame < 5; frame++) {
int16_t pcm[1920]; uint8_t encoded[1275];
for (unsigned i = 0; i < 960; i++) {
pcm[2 * i] = static_cast<int16_t>(6000 * std::sin((sampleFrame + i) * 0.05));
pcm[2 * i + 1] = static_cast<int16_t>(1500 * std::sin((sampleFrame + i) * 0.13));
}
sampleFrame += 960;
int length = opus_codec_encode(opus.get(), pcm, 960, encoded, sizeof(encoded)); QVERIFY(length > 0);
packets.append(QByteArray(reinterpret_cast<const char*>(encoded), length));
}
publish(video, packets, 3);
});
sender.start(100);
QTRY_VERIFY_WITH_TIMEOUT(shown.count() > 0, 5000);
int beforeSteady = shown.count(); QTest::qWait(1500);
QVERIFY2(shown.count() >= beforeSteady + 30, "30fps video must keep advancing with 200ms physical audio latency");
@ -204,6 +213,9 @@ private slots:
#ifdef TEST_NULL_AUDIO
QTRY_VERIFY_WITH_TIMEOUT(nonzeroSamples.load() > 0, 5000);
QVERIFY(rightEnergy.load() > 0);
qInfo("Stereo playback energies: left=%llu right=%llu ratio=%.3f",
(unsigned long long)leftEnergy.load(), (unsigned long long)rightEnergy.load(),
static_cast<double>(leftEnergy.load()) / rightEnergy.load());
QVERIFY2(leftEnergy.load() > rightEnergy.load() * 8 && leftEnergy.load() < rightEnergy.load() * 25,
"Independent stereo channels must reach the playback device without downmix");
auto* volume = window.findChild<QSlider*>(); QVERIFY(volume); volume->setValue(0);

Loading…
Cancel
Save