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

22
tests/test_opus_codec.c

@ -211,6 +211,25 @@ static int test_multiple_frames(void) {
return ok ? 0 : 1; 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 main(void) {
int failed = 0; int failed = 0;
failed += test_encode_decode_silence(); failed += test_encode_decode_silence();
@ -220,7 +239,8 @@ int main(void) {
failed += test_null_args(); failed += test_null_args();
failed += test_default_sample_rate(); failed += test_default_sample_rate();
failed += test_multiple_frames(); 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; return failed ? 1 : 0;
} }

Loading…
Cancel
Save