libavformat: cuframes_packets demuxer для encoded packet ring (cuframes v0.2)
Companion к существующему cuframes://. URL `cuframes_packets://<key>`
открывает encoded packet ring у publisher'а (поднятого с --enable-packet-ring)
и выдаёт raw H.264/H.265 stream без декодирования.
Use case в Frigate config:
cameras:
cam_parking:
ffmpeg:
inputs:
- path: cuframes://cam-parking # decoded NV12 для detect
roles: [detect]
- path: cuframes_packets://cam-parking # encoded H.264 для record
input_args: -f cuframes_packets
roles: [record]
→ 1 RTSP connection к камере (вместо 2-3).
Implementation:
- read_header: cuframes_subscriber_create + enable_packets + get_codec_params
(codec_id + extradata → AVStream.codecpar).
- read_packet: cuframes_subscriber_next_packet → memcpy в AVPacket.
PACKET_OVERRUN → EAGAIN (library resync'нет на next call).
- AV_PKT_FLAG_KEY/CORRUPT/DISCONTINUITY mapping из cuframes flags.
- pts/dts: ns from publisher → µs timebase.
Build wiring:
- configure: cuframes_packets_demuxer_deps="libcuframes"
- libavformat/Makefile: CONFIG_CUFRAMES_PACKETS_DEMUXER → cuframes_packetsdec.o
- libavformat/allformats.c: extern ff_cuframes_packets_demuxer
This commit is contained in:
@@ -3530,6 +3530,7 @@ libfdk_aac_decoder_deps="libfdk_aac"
|
|||||||
libfdk_aac_encoder_deps="libfdk_aac"
|
libfdk_aac_encoder_deps="libfdk_aac"
|
||||||
libfdk_aac_encoder_select="audio_frame_queue"
|
libfdk_aac_encoder_select="audio_frame_queue"
|
||||||
cuframes_demuxer_deps="libcuframes"
|
cuframes_demuxer_deps="libcuframes"
|
||||||
|
cuframes_packets_demuxer_deps="libcuframes"
|
||||||
libgme_demuxer_deps="libgme"
|
libgme_demuxer_deps="libgme"
|
||||||
libgsm_decoder_deps="libgsm"
|
libgsm_decoder_deps="libgsm"
|
||||||
libgsm_encoder_deps="libgsm"
|
libgsm_encoder_deps="libgsm"
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ OBJS-$(CONFIG_CODEC2RAW_MUXER) += rawenc.o
|
|||||||
OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o
|
OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o
|
||||||
OBJS-$(CONFIG_CRC_MUXER) += crcenc.o
|
OBJS-$(CONFIG_CRC_MUXER) += crcenc.o
|
||||||
OBJS-$(CONFIG_CUFRAMES_DEMUXER) += cuframesdec.o
|
OBJS-$(CONFIG_CUFRAMES_DEMUXER) += cuframesdec.o
|
||||||
|
OBJS-$(CONFIG_CUFRAMES_PACKETS_DEMUXER) += cuframes_packetsdec.o
|
||||||
OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o
|
OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o
|
||||||
OBJS-$(CONFIG_DATA_MUXER) += rawenc.o
|
OBJS-$(CONFIG_DATA_MUXER) += rawenc.o
|
||||||
OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o hlsplaylist.o
|
OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o hlsplaylist.o
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ extern const FFOutputFormat ff_codec2raw_muxer;
|
|||||||
extern const FFInputFormat ff_concat_demuxer;
|
extern const FFInputFormat ff_concat_demuxer;
|
||||||
extern const FFOutputFormat ff_crc_muxer;
|
extern const FFOutputFormat ff_crc_muxer;
|
||||||
extern const FFInputFormat ff_cuframes_demuxer;
|
extern const FFInputFormat ff_cuframes_demuxer;
|
||||||
|
extern const FFInputFormat ff_cuframes_packets_demuxer;
|
||||||
extern const FFInputFormat ff_dash_demuxer;
|
extern const FFInputFormat ff_dash_demuxer;
|
||||||
extern const FFOutputFormat ff_dash_muxer;
|
extern const FFOutputFormat ff_dash_muxer;
|
||||||
extern const FFInputFormat ff_data_demuxer;
|
extern const FFInputFormat ff_data_demuxer;
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* cuframes_packets input demuxer для FFmpeg 7.x.
|
||||||
|
*
|
||||||
|
* Принимает URL вида "cuframes_packets://<key>" — подключается к
|
||||||
|
* cuframes-publisher'у v0.2+ (с активированным packet ring) и выдаёт
|
||||||
|
* encoded NAL units (H.264/H.265). В отличие от родственного `cuframes`
|
||||||
|
* demuxer'а (NV12 raw video), этот выдаёт **encoded** stream — для
|
||||||
|
* consumer'ов которые делают `-c:v copy` (record/mux without decode).
|
||||||
|
*
|
||||||
|
* Use case (Frigate):
|
||||||
|
* detect role: cuframes://camA — decoded NV12, GPU shared
|
||||||
|
* record role: cuframes_packets://camA — encoded H.264, без второго RTSP
|
||||||
|
*
|
||||||
|
* Лицензия: LGPL-2.1+ (соответствует libcuframes и FFmpeg LGPL builds)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libavformat/avformat.h"
|
||||||
|
#include "libavformat/demux.h"
|
||||||
|
#include "libavformat/internal.h"
|
||||||
|
#include "libavutil/avstring.h"
|
||||||
|
#include "libavutil/opt.h"
|
||||||
|
|
||||||
|
#include <cuframes/cuframes.h>
|
||||||
|
|
||||||
|
typedef struct CuframesPacketsDemuxerContext {
|
||||||
|
const AVClass *class_;
|
||||||
|
/* options */
|
||||||
|
int connect_timeout_ms;
|
||||||
|
int read_timeout_ms;
|
||||||
|
/* state */
|
||||||
|
cuframes_subscriber_t *sub;
|
||||||
|
int64_t first_pts_ns;
|
||||||
|
int got_first_pts;
|
||||||
|
/* первый packet получаем в read_header чтобы узнать codec/extradata
|
||||||
|
* валидно (publisher мог ещё не вызвать set_codec_extradata к моменту
|
||||||
|
* subscribe — но к моменту первого keyframe — точно) */
|
||||||
|
int pending_first_packet;
|
||||||
|
} CuframesPacketsDemuxerContext;
|
||||||
|
|
||||||
|
#define OFFSET(x) offsetof(CuframesPacketsDemuxerContext, x)
|
||||||
|
#define D AV_OPT_FLAG_DECODING_PARAM
|
||||||
|
static const AVOption cuframes_packets_options[] = {
|
||||||
|
{ "connect_timeout", "wait for publisher (ms); -1 = forever",
|
||||||
|
OFFSET(connect_timeout_ms), AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, D },
|
||||||
|
{ "read_timeout", "wait for next packet (ms); -1 = forever",
|
||||||
|
OFFSET(read_timeout_ms), AV_OPT_TYPE_INT, { .i64 = 10000 }, -1, INT_MAX, D },
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const AVClass cuframes_packets_demuxer_class = {
|
||||||
|
.class_name = "cuframes_packets demuxer",
|
||||||
|
.item_name = av_default_item_name,
|
||||||
|
.option = cuframes_packets_options,
|
||||||
|
.version = LIBAVUTIL_VERSION_INT,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *parse_key(const char *url)
|
||||||
|
{
|
||||||
|
if (av_strstart(url, "cuframes_packets://", &url))
|
||||||
|
return url;
|
||||||
|
if (av_strstart(url, "cuframes_packets:", &url))
|
||||||
|
return url;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map cuframes packet flags → AVPacket flags */
|
||||||
|
static int packet_flags_to_av(uint32_t cf_flags)
|
||||||
|
{
|
||||||
|
int f = 0;
|
||||||
|
if (cf_flags & CUFRAMES_PKT_FLAG_KEY) f |= AV_PKT_FLAG_KEY;
|
||||||
|
if (cf_flags & CUFRAMES_PKT_FLAG_CORRUPT) f |= AV_PKT_FLAG_CORRUPT;
|
||||||
|
#ifdef AV_PKT_FLAG_DISCONTINUITY
|
||||||
|
if (cf_flags & CUFRAMES_PKT_FLAG_DISCONTINUITY) f |= AV_PKT_FLAG_DISCONTINUITY;
|
||||||
|
#endif
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cuframes_packets_read_header(AVFormatContext *s)
|
||||||
|
{
|
||||||
|
CuframesPacketsDemuxerContext *c = s->priv_data;
|
||||||
|
const char *key = parse_key(s->url);
|
||||||
|
if (!key || !*key) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "cuframes_packets: empty key in URL '%s'\n", s->url);
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subscribe to frames ring first (наследие v1 socket handshake), затем
|
||||||
|
* enable_packets для opening packet shm. */
|
||||||
|
cuframes_subscriber_config_t cfg = {0};
|
||||||
|
cfg.key = key;
|
||||||
|
cfg.consumer_name = NULL; /* auto-generated */
|
||||||
|
cfg.mode = CUFRAMES_MODE_NEWEST_ONLY;
|
||||||
|
cfg.cuda_device = 0;
|
||||||
|
cfg.connect_timeout_ms = c->connect_timeout_ms;
|
||||||
|
|
||||||
|
int rc = cuframes_subscriber_create(&cfg, &c->sub);
|
||||||
|
if (rc != CUFRAMES_OK) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "cuframes_packets: subscriber_create('%s'): %s\n",
|
||||||
|
key, cuframes_strerror(rc));
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = cuframes_subscriber_enable_packets(c->sub);
|
||||||
|
if (rc != CUFRAMES_OK) {
|
||||||
|
av_log(s, AV_LOG_ERROR,
|
||||||
|
"cuframes_packets: enable_packets('%s'): %s (publisher без packet ring?)\n",
|
||||||
|
key, cuframes_strerror(rc));
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Получаем codec params (extradata может быть пустым если publisher ещё не
|
||||||
|
* установил — это OK, decoder соберёт SPS/PPS из in-band keyframes). */
|
||||||
|
uint32_t codec_id = 0;
|
||||||
|
const void *extradata = NULL;
|
||||||
|
size_t extradata_sz = 0;
|
||||||
|
int param_rc = cuframes_subscriber_get_codec_params(c->sub, &codec_id,
|
||||||
|
&extradata, &extradata_sz);
|
||||||
|
if (param_rc != CUFRAMES_OK && param_rc != CUFRAMES_ERR_NO_CODEC_PARAMS) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "cuframes_packets: get_codec_params: %s\n",
|
||||||
|
cuframes_strerror(param_rc));
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_id == 0) {
|
||||||
|
av_log(s, AV_LOG_WARNING,
|
||||||
|
"cuframes_packets: publisher не указал codec_id, угадываем H.264\n");
|
||||||
|
codec_id = AV_CODEC_ID_H264;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVStream *st = avformat_new_stream(s, NULL);
|
||||||
|
if (!st) return AVERROR(ENOMEM);
|
||||||
|
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
st->codecpar->codec_id = (enum AVCodecID)codec_id;
|
||||||
|
|
||||||
|
/* Copy extradata если есть (publisher успел вызвать set_codec_extradata) */
|
||||||
|
if (extradata_sz > 0 && extradata) {
|
||||||
|
st->codecpar->extradata = av_mallocz(extradata_sz + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||||
|
if (!st->codecpar->extradata) return AVERROR(ENOMEM);
|
||||||
|
memcpy(st->codecpar->extradata, extradata, extradata_sz);
|
||||||
|
st->codecpar->extradata_size = (int)extradata_sz;
|
||||||
|
av_log(s, AV_LOG_INFO,
|
||||||
|
"cuframes_packets: connected to '%s' codec_id=%u extradata=%zu bytes\n",
|
||||||
|
key, codec_id, extradata_sz);
|
||||||
|
} else {
|
||||||
|
av_log(s, AV_LOG_INFO,
|
||||||
|
"cuframes_packets: connected to '%s' codec_id=%u (no extradata yet)\n",
|
||||||
|
key, codec_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Timestamps: cuframes публикует pts в наносекундах. Используем µs timebase. */
|
||||||
|
avpriv_set_pts_info(st, 64, 1, 1000000);
|
||||||
|
|
||||||
|
c->got_first_pts = 0;
|
||||||
|
c->pending_first_packet = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cuframes_packets_read_packet(AVFormatContext *s, AVPacket *pkt)
|
||||||
|
{
|
||||||
|
CuframesPacketsDemuxerContext *c = s->priv_data;
|
||||||
|
|
||||||
|
cuframes_packet_t *cp = NULL;
|
||||||
|
int rc = cuframes_subscriber_next_packet(c->sub, &cp, c->read_timeout_ms);
|
||||||
|
|
||||||
|
if (rc == CUFRAMES_ERR_TIMEOUT || rc == CUFRAMES_ERR_WOULD_BLOCK) {
|
||||||
|
return AVERROR(EAGAIN);
|
||||||
|
}
|
||||||
|
if (rc == CUFRAMES_ERR_DISCONNECTED) {
|
||||||
|
return AVERROR_EOF;
|
||||||
|
}
|
||||||
|
if (rc == CUFRAMES_ERR_PACKET_OVERRUN) {
|
||||||
|
/* Library уже resync'нула на last keyframe — на next call всё восстановится.
|
||||||
|
* Signal upstream EAGAIN (FFmpeg-side ретрай) + warning. */
|
||||||
|
av_log(s, AV_LOG_WARNING,
|
||||||
|
"cuframes_packets: overrun — slow consumer, resync to keyframe\n");
|
||||||
|
return AVERROR(EAGAIN);
|
||||||
|
}
|
||||||
|
if (rc != CUFRAMES_OK || !cp) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "cuframes_packets: next_packet: %s\n",
|
||||||
|
cuframes_strerror(rc));
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *data = cuframes_packet_data(cp);
|
||||||
|
size_t size = cuframes_packet_size(cp);
|
||||||
|
int64_t pts = cuframes_packet_pts(cp);
|
||||||
|
int64_t dts = cuframes_packet_dts(cp);
|
||||||
|
uint32_t fl = cuframes_packet_flags(cp);
|
||||||
|
|
||||||
|
/* Anchor PTS на первом packet — downstream ожидает start from ~0 */
|
||||||
|
if (!c->got_first_pts) {
|
||||||
|
c->first_pts_ns = pts;
|
||||||
|
c->got_first_pts = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = av_new_packet(pkt, (int)size);
|
||||||
|
if (err < 0) {
|
||||||
|
cuframes_subscriber_release_packet(c->sub, cp);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
memcpy(pkt->data, data, size);
|
||||||
|
pkt->stream_index = 0;
|
||||||
|
pkt->pts = (pts - c->first_pts_ns) / 1000; /* ns → µs */
|
||||||
|
pkt->dts = (dts != 0) ? (dts - c->first_pts_ns) / 1000 : pkt->pts;
|
||||||
|
pkt->flags |= packet_flags_to_av(fl);
|
||||||
|
|
||||||
|
cuframes_subscriber_release_packet(c->sub, cp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cuframes_packets_read_close(AVFormatContext *s)
|
||||||
|
{
|
||||||
|
CuframesPacketsDemuxerContext *c = s->priv_data;
|
||||||
|
if (c->sub) {
|
||||||
|
cuframes_subscriber_destroy(c->sub);
|
||||||
|
c->sub = NULL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cuframes_packets_probe(const AVProbeData *p)
|
||||||
|
{
|
||||||
|
/* URL-based protocol — probe не используется, см. AVFMT_NOFILE */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FFInputFormat ff_cuframes_packets_demuxer = {
|
||||||
|
.p.name = "cuframes_packets",
|
||||||
|
.p.long_name = NULL_IF_CONFIG_SMALL("cuframes encoded packet ring (H.264/H.265 passthrough)"),
|
||||||
|
.p.flags = AVFMT_NOFILE,
|
||||||
|
.p.priv_class = &cuframes_packets_demuxer_class,
|
||||||
|
.priv_data_size = sizeof(CuframesPacketsDemuxerContext),
|
||||||
|
.read_probe = cuframes_packets_probe,
|
||||||
|
.read_header = cuframes_packets_read_header,
|
||||||
|
.read_packet = cuframes_packets_read_packet,
|
||||||
|
.read_close = cuframes_packets_read_close,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user