Browse Source

Document stereo live audio and compact rotation controls

master
evgeny 6 days ago
parent
commit
727106f4e9
  1. 17
      doc/stream.txt
  2. 22
      tests/test_opus_codec.c

17
doc/stream.txt

@ -754,7 +754,8 @@ KEY=bit0 video flags; остальные биты запрещены. frame_id>0
video_codec=1: AVC/H.264 Annex B, без B-кадров. Каждый KEY содержит SPS, PPS и IDR в одном
логическом кадре; это позволяет позднему viewer настроить декодер после получения полного KEY.
audio_codec=0: нет аудио; audio_codec=1: Opus, 48000Hz mono, 960 samples (20ms), packet<=1275 bytes.
audio_codec=0: нет аудио; audio_codec=1: Opus mono, audio_codec=2: Opus stereo.
Оба формата: 48000Hz, 960 кадров на канал (20ms), packet<=1275 bytes; interleaved stereo содержит 1920 отсчётов PCM.
Opus-пакеты между медиаблоками не разрезаются. Размеры треков не включают header/описания.
stream_media проверяет весь блок до изменения view. Receiver собирает один последовательный кадр.
@ -774,10 +775,10 @@ Android:
partial output собирается в access unit. Codec config передаётся с каждым IDR.
- Camera MediaCodec Surface timestamps и AudioTimestamp.TIMEBASE_MONOTONIC приводятся к общему
origin System.nanoTime; время callback/получения блока не заменяет capture PTS.
- Микрофон трансляции использует AudioRecord.CAMCORDER, 48kHz mono. Перед Opus AUDIO/64kbps
- Микрофон трансляции использует AudioRecord.CAMCORDER, 48kHz stereo. Перед Opus AUDIO/128kbps
применяется тот же audio_compressor, что в TX рации: общие настройки включения, max_gain и rise_rate,
lookback=200ms, lookahead=0, release=rise*5, target=0.25. История и gain принадлежат только audio worker
трансляции. Изменения настроек рации применяются на следующем PCM-кадре, без перезапуска микрофона,
трансляции; оба канала используют общий gain, сохраняющий стереобаланс. Изменения настроек рации применяются на следующем PCM-кадре, без перезапуска микрофона,
Opus или source_epoch; compressor не добавляет задержку и не меняет capture PTS.
- Camera/codec callbacks — HandlerThread, Opus encoder — audio capture thread, decoder/AudioTrack — viewer worker.
- Native bridge держит pthread mutex, ограниченные очереди и один pending post либо timer.
@ -797,9 +798,9 @@ Android:
монотонные часы. Короткие audio gaps заполняются Opus PLC, большой разрыв сбрасывает timeline.
Локальный preview использует обычную подписку на собственный источник с отключённым воспроизведением
своего микрофона, чтобы исключить акустическую обратную связь. Локальный preview ориентируется по сенсору своей камеры
и текущему положению экрана; ручной поворот трансляции меняет только метку «Верх», показывающую верх картинки зрителей.
и текущему положению экрана; верх картинки зрителей отмечается красной гранью на кнопках поворота.
Положение камеры хранится per-revision отдельно от wire, чтобы queued кадры не использовали параметры новой камеры.
На SM проверено: при ручных 0/90/180/270 размеры и ориентация превью сохраняются, меняется только положение метки.
Ручной поворот сохраняет размеры и ориентацию локального превью; метка «Верх» на видео не рисуется.
Удалённый viewer применяет video rotation источника без локальной метки. Камера при закрытии preview продолжает работать.
Viewer закрывается на Activity ON_STOP и создаётся заново на ON_START; publisher от UI не зависит.
Видимый player удерживает экран включённым. Preview ограничен оставшимся местом, кнопки управления не перекрывает.
@ -813,10 +814,11 @@ Android:
несколько выбираются из списка по имени/ID. Пункт меню «Трансляции» также открывает управление публикацией.
Выбранная эпоха проверяется actor до подписки и по фактически открытому handle. Перезапуск источника
требует нового выбора. Ошибка viewer kind=6 передаёт stream_close_reason в flags.
- Ручная ориентация доступна до запуска и во время своей трансляции: 0/90/180/270 градусов относительно
- Ручная ориентация доступна до запуска и во время своей трансляции: три компактные кнопки 0/90/270 градусов относительно
обычного положения устройства. Поворот не зависит от текущего положения экрана; SENSOR_ORIENTATION и направление
камеры учитываются при расчёте video rotation. Камера/режим/ориентация хранятся и подтверждаются одной конфигурацией.
При смене камеры выбранная ориентация сохраняется; rollback восстанавливает всю предыдущую конфигурацию.
Активная кнопка подсвечивается. Верх отмечается на самой кнопке с учётом передней/задней камеры и положения экрана.
- Смена камеры/режима/ориентации сохраняет publisher owner, source_epoch, микрофон, origin и frame IDs.
Camera callbacks и codec output отсеиваются по поколению. Новая камера открывается после onClosed предыдущей;
close timeout=3s, KEY/подтверждение режима timeout=8s.
@ -860,7 +862,8 @@ Desktop:
- transport/stream_bridge владеет одним viewer и ограничивает RX 4MiB/256 записей.
Attach/detach выполняются в uasync; команды и копирование медиаданных защищены mutex.
Post отменяется до destroy ядра; GUI/decoder не хранят заимствованные instance/handle pointers.
- stream_media_receiver собирает H.264 access units и выдаёт Opus 48kHz mono/20ms.
- stream_media_receiver собирает H.264 access units и выдаёт Opus 48kHz mono/stereo/20ms.
Android AudioTrack и desktop output работают в stereo; mono-пакеты upmix выполняет libopus.
FFmpeg/Opus декодируются отдельным worker; Qt-очередь не накапливает кадры.
Очередь декодированного видео ограничена 128 кадрами и 256MiB, аудио — 25 пакетами (500ms).
Кадры сохраняются до достижения их PTS аудиочасами. При пределе видео отбрасывается новый

22
tests/test_opus_codec.c

@ -211,6 +211,25 @@ static int test_multiple_frames(void) {
return ok ? 0 : 1;
}
/* Stereo-output плееров также принимает mono Opus; upmix выполняет сам libopus. */
static int test_mono_packet_stereo_output(void) {
printf("test_mono_packet_stereo_output: ");
opus_codec_encoder_t* encoder = opus_codec_encoder_create(48000, 1);
opus_codec_decoder_t* decoder = opus_codec_decoder_create(48000, 2);
int ok = encoder && decoder;
if (ok) {
int16_t input[960], output[1920]; uint8_t packet[1275];
for (unsigned i = 0; i < 960; i++) input[i] = (int16_t)(6000 * sin(i * 0.1));
int length = opus_codec_encode(encoder, input, 960, packet, sizeof(packet));
ok = length > 0 && opus_codec_decode(decoder, packet, length, output, 960) == 960;
int audible = 0;
if (ok) for (unsigned i = 0; i < 960; i++) { audible |= output[2 * i] != 0; if (output[2 * i] != output[2 * i + 1]) ok = 0; }
ok = ok && audible;
}
opus_codec_encoder_destroy(encoder); opus_codec_decoder_destroy(decoder);
printf("%s\n", ok ? "PASS" : "FAIL"); return ok ? 0 : 1;
}
int main(void) {
int failed = 0;
failed += test_encode_decode_silence();
@ -220,7 +239,8 @@ int main(void) {
failed += test_null_args();
failed += test_default_sample_rate();
failed += test_multiple_frames();
failed += test_mono_packet_stereo_output();
printf("\n%s: %d/%d tests passed\n", failed ? "FAIL" : "OK", 7 - failed, 7);
printf("\n%s: %d/%d tests passed\n", failed ? "FAIL" : "OK", 8 - failed, 8);
return failed ? 1 : 0;
}

Loading…
Cancel
Save