fa6ab3069a
Несколько сессий попыток реализовать audio mixing в композитор'е.
Не достигнуто sub-секундной latency со стабильным video+audio.
Откатано на parallel mode (cfc-grid video-only, live от pipeline с audio).
Полный набор выводов и pitfall'ов — docs/LESSONS-audio-mixing-attempts.md.
Главные lesson'ы для будущей попытки:
- mpegts mux libavformat авто-инсёртит h264_mp4toannexb BSF которому
не нравится Annex-B + inline SPS/PPS — NVENC OUTPUT_SPSPPS per-frame ломает
- SPSC ring drop newest при full, не oldest (consumer's domain)
- av_new_packet (не av_malloc) для av_interleaved_write_frame ownership
- Monotonic PTS на counter (frame_idx, total_samples) — не wallclock
- mediamtx env-var path names не должны иметь '-' (parser limitation)
- Default mediamtx ReadTimeout=10s короткий для burst write'ов
Изменения в repo сохранены для будущей доработки:
- src/writer.c — mpegts backend с audio stream support
- src/audio.c — RTSP AAC consumer + lock-free SPSC ring
- include/cuframes_composer/{writer,audio}.h — public API
- examples/grid_record.c — --format=mpegts + --audio-source flags
- include/cuframes_composer/composer.h — consumer_prefix field
- docker/Dockerfile — libavformat-dev добавлен в builder/runtime
cfc-grid composer стабильно работает на видео (substantially лучше
монолитного pipeline'а с audio bag'ом). TV рекомендуется использовать
rtsp://...:554/cfc-grid + опционально rtsp://...:554/live-audio
parallel.
93 lines
3.8 KiB
C
93 lines
3.8 KiB
C
/* cuframes-composer — output writer abstraction.
|
||
*
|
||
* Энкодер NVENC отдаёт сжатый H.264 bitstream (Annex-B byte stream) +
|
||
* timestamp в callback'е. Writer берёт эту последовательность и пишет
|
||
* либо как raw H.264 в файл/stdout, либо как mpegts container с правильными
|
||
* PTS/DTS в header'ах PES packet'ов.
|
||
*
|
||
* Зачем mpegts:
|
||
* Raw H.264 в pipe не содержит timestamps на container-уровне; downstream
|
||
* ffmpeg-mux вынужден синтезировать PTS из `-r` или wallclock, что вызывает
|
||
* desync с audio и drops при mux'ировании с другим потоком. MPEG-TS даёт
|
||
* нативные PTS/DTS на каждом PES packet'е → downstream mux работает
|
||
* через `-c copy` без проблем.
|
||
*
|
||
* Форматы:
|
||
* "h264" — raw H.264 Annex-B byte stream (как до Phase 6); fwrite в file
|
||
* "mpegts" — MPEG-TS container; libavformat avformat_write_header +
|
||
* av_interleaved_write_frame
|
||
*
|
||
* Path semantics:
|
||
* "/path/to/file.h264" — обычный файл
|
||
* "-" / "pipe:1" / "/dev/stdout" — stdout (для shell-pipe в downstream)
|
||
*
|
||
* Лицензия: LGPL-2.1+
|
||
*/
|
||
|
||
#ifndef CUFRAMES_COMPOSER_WRITER_H
|
||
#define CUFRAMES_COMPOSER_WRITER_H
|
||
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
typedef struct cfc_writer cfc_writer_t;
|
||
|
||
typedef struct cfc_writer_config {
|
||
const char *path; /* "/path/file.ts", "unix:/run/.../sock", "-" */
|
||
const char *format; /* "h264" или "mpegts" */
|
||
int width, height; /* для mpegts video codecpar */
|
||
int fps_num, fps_den; /* для mpegts time_base hint */
|
||
int bitrate_kbps; /* для mpegts mux PCR pacing */
|
||
|
||
/* SPS/PPS — video codec extradata, кладётся в codecpar до write_header.
|
||
* Берётся из cfc_encoder_get_sequence_params. Обязательно для mpegts —
|
||
* mpegts muxer кладёт SPS/PPS перед первым IDR из extradata. */
|
||
const uint8_t *extradata;
|
||
size_t extradata_size;
|
||
|
||
/* Audio stream — если задан, в mpegts будет второй stream (AAC).
|
||
* extradata_audio = AudioSpecificConfig (2 байта обычно). */
|
||
int has_audio; /* 0 = video-only, 1 = add audio stream */
|
||
int audio_sample_rate; /* например 44100 */
|
||
int audio_channels; /* например 2 */
|
||
const uint8_t *audio_extradata; /* ASC bytes */
|
||
size_t audio_extradata_size;
|
||
} cfc_writer_config_t;
|
||
|
||
int cfc_writer_create(const cfc_writer_config_t *cfg, cfc_writer_t **out);
|
||
|
||
/* Записать один закодированный video frame. is_keyframe ставит флаг
|
||
* AV_PKT_FLAG_KEY. Использует av_write_frame (без интерливинг-очереди). */
|
||
int cfc_writer_write(
|
||
cfc_writer_t *w,
|
||
const uint8_t *bitstream,
|
||
size_t size,
|
||
int64_t pts_ns,
|
||
int is_keyframe
|
||
);
|
||
|
||
/* Записать audio packet (AAC ADTS либо raw — определяется extradata).
|
||
* Возвращает 0 при успехе. Thread-safe относительно write video?
|
||
* Нет — caller обязан вызывать оба write_* из одного thread'а либо
|
||
* локироваться. В нашей архитектуре video-thread drain'ит audio-ring
|
||
* и пишет сюда сам. */
|
||
int cfc_writer_write_audio(
|
||
cfc_writer_t *w,
|
||
const uint8_t *aac_data,
|
||
size_t size,
|
||
int64_t pts_ns
|
||
);
|
||
|
||
/* Закрыть writer. Для mpegts вызывает av_write_trailer. */
|
||
int cfc_writer_close(cfc_writer_t *w);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* CUFRAMES_COMPOSER_WRITER_H */
|