Есть специалисты по gstreamer'у на форуме? Есть поток от ip камеры, допустим хочу нарезать
на mp4 чанки длиной по 10сек. Пайплайн сделал, несложно, но необходимо получать для каждого фрейма
capture time, т.е. timestamp камеры. Ну или для каждого чанка timestamp главного кадра.
Как это можно сделать?
Здравствуйте, Sharov, Вы писали:
S>Заранее благодарю.
Про pts уже написали, но это не с каждой камеры есть и не всегда означает именно таймстемп.
Если у тебя rtsp камера, то в каждом пакете с кадром есть ntp таймстемп. В зависимости от условий, для сетевых камер такая штука может быть намного лучше и полезнее, чем pts. Например, для синхронизации видео с нескольких разных камер. Или для установления времени какого-нибудь события.
Как получить тайкой таймстемп? Ручками при разборе протокола. Например, он есть в ffmpeg, но оно дальше никуда не идёт и остаётся внутри ffmpeg. Вот тут есть патч для ffmpeg, который ntp таймстемп не теряет, а прикрепляет к AVPacket'у. Такого в стандартном ffmpeg нет и не будет. Как можно такое получить от gstreamer — хз, надо искать, но сомневаюсь, что есть.
Здравствуйте, Nuzhny, Вы писали:
N>Здравствуйте, Sharov, Вы писали:
S>>Заранее благодарю.
N>Про pts уже написали, но это не с каждой камеры есть и не всегда означает именно таймстемп. N>Если у тебя rtsp камера, то в каждом пакете с кадром есть ntp таймстемп. В зависимости от условий, для сетевых камер такая штука может быть намного лучше и полезнее, чем pts. Например, для синхронизации видео с нескольких разных камер. Или для установления времени какого-нибудь события.
Да, выше написал про физ. время камеры. Но мне для gstreamer'а нужно.
Видел. Вот у меня пайплайн rtspsrc ! rtph264depay ! h264parse ! splitmuxsink .
При этом я использую свой sink, а не filesink (splitmuxsink["sink"] = appSink)
Вот код appsink'а:
private static void AppSink_NewSample(object o, NewSampleArgs args)
{
if (o is AppSink aps)
{
var sample = aps.PullSample();
var buf = sample.Buffer;
buf.Map(out var info, MapFlags.Read);
//application/x-rtpvar tsMeta = buf.GetReferenceTimestampMeta();
//NOTE tsMeta shouldn't be null there...
buf.Unmap(info);
}
Counter++;
}
И вот tsMeta возвращает пустую структуру, timestamp 0. И что надо добавить, чтобы там эта инф-ия была
Имею в виду "How to reuse pipeline to read different mp4 files. Few questions." и "Pipeline set state to Ready hangs pipeline.".
1. decodebin и playbin — это, грубо говоря, такие хитрые элементы, которые, фактически, служат всего лишь для того, чтобы подобрать правильные элементы для построения графа. По моему опыту с точки зрения быстродействия что filesrc + decodebin, что playbin — один хрен. Особенно если учеть, что основные затраты времени — они уже после формирования, непосредственно при воспроизведении. Если ты посмотришь результирующий граф, то увидишь, насколько он изменился.
2. Относительно переиспользования и зависания. У нас в своё время не получилось, правда, это было раннем этапе. Именно поэтому я полез в реализацию qt multimedia, и, собственно, перетащил её вариант, использующий gstreamer на винду, полагая, что qt-шникам виднее, как правильно использовать. Там на каждой изменение, например, для записи (preview -> record, record -> preview) строится новый pipeline. Сейчас сильно подозреваю, что проблема с переиспользованием в том, что при завершении воспроизведения файла приходит eos, и какие-то элементы переходит в условно "невосстановимое" состояние. Но надо, конечно, включать более детальный лог и смотреть исходники.
3. Ну и дополнительно. Не знаю, как сделано для шарпа, но внутри там всё в основном асинхронное, т.е., если ты высталяешь некое состояние пайплайну, то это не зачит, что оно на том же стеке будет выставлено. Не учёт этого зачастую приводит к проблемам.
Ну и последнее: не ожидай, что тебе в рассылке на каждый вопрос ответят. Как показывает практика, там точно отвечают либо на критичный, либо на интересный для отвечающего вопрос.
Надеюсь, что был полезен. Удачи!
Re[2]: Относительно твоих вопросов в рассылке gstreamer'а
Здравствуйте, Conductor, Вы писали:
C>Имею в виду "How to reuse pipeline to read different mp4 files. Few questions." и "Pipeline set state to Ready hangs pipeline.".
О, круто благодарю.
C>1. decodebin и playbin — это, грубо говоря, такие хитрые элементы, которые, фактически, служат всего лишь для того, чтобы подобрать правильные элементы для построения графа. По моему опыту с точки зрения быстродействия что filesrc + decodebin, что playbin — один хрен. Особенно если учеть, что основные затраты времени — они уже после формирования, непосредственно при воспроизведении. Если ты посмотришь результирующий граф, то увидишь, насколько он изменился.
С playbin у меня вообще не пошло: пытался добавить свой appsink вместо video-sink -- вылетало окно с показом кадра,
пытался линковать playbin и appsink -- просто падало. Т.е. пока filesrc + decodebin+appsink без альтернативы.
C>2. Относительно переиспользования и зависания. У нас в своё время не получилось, правда, это было раннем этапе. Именно поэтому я полез в реализацию qt multimedia, и, собственно, перетащил её вариант, использующий gstreamer на винду, полагая, что qt-шникам виднее, как правильно использовать. Там на каждой изменение, например, для записи (preview -> record, record -> preview) строится новый pipeline. Сейчас сильно подозреваю, что проблема с переиспользованием в том, что при завершении воспроизведения файла приходит eos, и какие-то элементы переходит в условно "невосстановимое" состояние. Но надо, конечно, включать более детальный лог и смотреть исходники.
Да, я еще погоняю под Debug. У меня такой вариант использования -- нахожу нужный кадр, конвертирую в jpeg, перевожу pipe в Ready.
И далее по кругу, с другим файлом. Ну т.е. один pipe для чтения файлов, а не 1 к 1. Ну так вот, после pipe.SetState(Ready) -- блочится
на этой строке. Буду более детально смотреть, т.к. совершенное не понятно что не так. Тут пишут, что надо в Ready переводить.
C>3. Ну и дополнительно. Не знаю, как сделано для шарпа, но внутри там всё в основном асинхронное, т.е., если ты высталяешь некое состояние пайплайну, то это не зачит, что оно на том же стеке будет выставлено. Не учёт этого зачастую приводит к проблемам.
Ага, многопоток там лютый. Иной раз жаль, что нету опции делать все синхронно. Т.е. вся логика на колбэках строится по сути.
C>Ну и последнее: не ожидай, что тебе в рассылке на каждый вопрос ответят. Как показывает практика, там точно отвечают либо на критичный, либо на интересный для отвечающего вопрос.
Ну вот в июле отвечали более-менее, с августа вообще тишина -- период отпусков, видимо.
C>Надеюсь, что был полезен. Удачи!
Благодарю! Кстати, я там еще один вопрос задал: зная номер n кадра из видео как быстрее его получить --
frame stepper (pullsample) n-1 раз, или можно как-то сделать Seek с буфферами?
Кодом людям нужно помогать!
Re[3]: Относительно твоих вопросов в рассылке gstreamer'а
Здравствуйте, Sharov, Вы писали:
S>С playbin у меня вообще не пошло: пытался добавить свой appsink вместо video-sink -- вылетало окно с показом кадра, S>пытался линковать playbin и appsink -- просто падало. Т.е. пока filesrc + decodebin+appsink без альтернативы.
Сложно так сказать почему — смотреть надо.
S>Да, я еще погоняю под Debug. У меня такой вариант использования -- нахожу нужный кадр, конвертирую в jpeg, перевожу pipe в Ready. S>И далее по кругу, с другим файлом. Ну т.е. один pipe для чтения файлов, а не 1 к 1. Ну так вот, после pipe.SetState(Ready) -- блочится S>на этой строке. Буду более детально смотреть, т.к. совершенное не понятно что не так. Тут пишут, что надо в Ready переводить.
У тебя там блочится на первой же итерации? И такой вопрос: а не занимает ли у тебя перевод конвертация достаточно продолжительное время? По моему опыту в большинстве случаев виснет если буфера переполнены. Я бы попробовал queue в пайплайн добавить. Сначала без ограничения размера и посмотрел бы, как размер памяти процесса себя ведёт. Ну и менял бы параметры, если используется SetState, только после того, как получено сообщение, что состояние изменено. Ты bus-овские сообщения обрабатываешь?
S>Благодарю! Кстати, я там еще один вопрос задал: зная номер n кадра из видео как быстрее его получить -- S>frame stepper (pullsample) n-1 раз, или можно как-то сделать Seek с буфферами?
Хороший вопрос. Сам не пробовал, но из общих соображений понятно, что gst_element_seek должен быть быстрее, однако ограничение есть:
GST_FORMAT_DEFAULT (1) –
the default format of the pad/element. This can be samples for raw audio, frames/fields for raw video (some, but not all, elements support this; use GST_FORMAT_TIME if you don't have a good reason to query for samples/frames)
У тебя же не raw. Можно, конечно, еще попробовать по требуемому номеру кадра и fps расчитать временную метку и уже по ней seek делать. Ну и проверить, насколько точно получается.
И такой вопрос: ты решения вопросов по времени получаемых кадров в результате нашёл (предыдущие темы)?
У нас в конечном счёте решение такое сложилось (камеры в 85-90% случаев аналоговые, без времени, для оставшихся 10-15% rtsp-случаев используется тот же механизм): используем время того компа, на котором осуществляется запись — есть буфер текущего кадра, и приходящее от камеры обновляет его, запись осуществляется с фиксированным fps (appsrc) и данные берёт из буфера текущего кадра. Таким образом отвязываясь от состояния камеры, потерь кадров и т.д. Что есть в буфере кадра на текущий момент, то и считаем актуальным для камеры. Понятно, что в случае rtsp есть задержка, но для наших задач приемлемо.
Re[4]: Относительно твоих вопросов в рассылке gstreamer'а
S>>Да, я еще погоняю под Debug. У меня такой вариант использования -- нахожу нужный кадр, конвертирую в jpeg, перевожу pipe в Ready. S>>И далее по кругу, с другим файлом. Ну т.е. один pipe для чтения файлов, а не 1 к 1. Ну так вот, после pipe.SetState(Ready) -- блочится S>>на этой строке. Буду более детально смотреть, т.к. совершенное не понятно что не так. Тут пишут, что надо в Ready переводить. C>У тебя там блочится на первой же итерации?
Да.
C>И такой вопрос: а не занимает ли у тебя перевод конвертация достаточно продолжительное время?
Я вообще убрал конвертацию и просто n раз делаю pullsample в цикле (3 кадра читаю и иду дальше).
C>По моему опыту в большинстве случаев виснет если буфера переполнены. Я бы попробовал queue в пайплайн добавить. Сначала без ограничения размера и посмотрел бы, как размер памяти процесса себя ведёт.
В данном случае нет.
S>>Благодарю! Кстати, я там еще один вопрос задал: зная номер n кадра из видео как быстрее его получить -- S>>frame stepper (pullsample) n-1 раз, или можно как-то сделать Seek с буфферами? C>Хороший вопрос. Сам не пробовал, но из общих соображений понятно, что gst_element_seek должен быть быстрее, однако ограничение есть: C>
C> GST_FORMAT_DEFAULT (1) –
C>the default format of the pad/element. This can be samples for raw audio, frames/fields for raw video (some, but not all, elements support this; use GST_FORMAT_TIME if you don't have a good reason to query for samples/frames)
C>У тебя же не raw. Можно, конечно, еще попробовать по требуемому номеру кадра и fps расчитать временную метку и уже по ней seek делать. Ну и проверить, насколько точно получается.
mp4 файл. Наверное не raw. Хороший вариант с fps и временной меткой, попробую.
C>И такой вопрос: ты решения вопросов по времени получаемых кадров в результате нашёл (предыдущие темы)?
Пока как-то так:
foreach (Pad pad in splitmuxsink.Pads)
{
if (pad.Direction == PadDirection.Sink)
{
pad.AddProbe(
PadProbeType.Buffer,
(_, probeInfo) =>
{
var buf = probeInfo.Buffer;
var metaTs = buf.GetReferenceTimestampMeta();
var metaTs = buf.GetReferenceTimestampMeta();
if (metaTs.Timestamp > 0)
{
//есть точное время кадра, берем его
}
else
{
//чего-то тут придумываем
}
return PadProbeReturn.Ok;
});
}
}
Тут нашел детальное описание вопроса.
C>У нас в конечном счёте решение такое сложилось (камеры в 85-90% случаев аналоговые, без времени, для оставшихся 10-15% rtsp-случаев используется тот же механизм): используем время того компа, на котором осуществляется запись — есть буфер текущего кадра, и приходящее от камеры обновляет его, запись осуществляется с фиксированным fps (appsrc) и данные берёт из буфера текущего кадра. Таким образом отвязываясь от состояния камеры, потерь кадров и т.д. Что есть в буфере кадра на текущий момент, то и считаем актуальным для камеры. Понятно, что в случае rtsp есть задержка, но для наших задач приемлемо.
У меня для задачи критично иметь время камеры. Соотв. для случаев когда его нет, надо что-то придумывать.
Кстати, код выше (AddProbe) он вообще корректен для получения времени кадра, т.е. я могу рассчитывать на то, что в probeInfo.Buffer находится прям целый
кадр, а не какой-то чанк или вообще какие-нибудь странные данные? Пока кажется, что в буффере действительно находится фрейм.
Кодом людям нужно помогать!
Re[5]: Относительно твоих вопросов в рассылке gstreamer'а
Здравствуйте, Sharov, Вы писали:
C>>По моему опыту в большинстве случаев виснет если буфера переполнены. Я бы попробовал queue в пайплайн добавить. Сначала без ограничения размера и посмотрел бы, как размер памяти процесса себя ведёт.
S>Вот тут я добавил больше деталей и debug сообщения -- https://lists.freedesktop.org/archives/gstreamer-devel//2023-August/081632.html
Ну вот, в пуле буфера кончились. Конкретно здесь с queue промашка вышла — я забыл, что у appsink есть своя собственная очередь с неограниченным размером по умолчанию, так что здесь дополнительная очередь проблемы не решает. А скажи, пожалуйста, если в том месте, где у тебя SetState(State.Null), вместо Null Paused выставить (у тебя же всё равно зависание на первой итерации происходит), тоже виснуть будет? Я повторил твой пример (под lin, надо будет под win попробовать, потому как по логам явная отсылка к d3d11, ты, кстати, с какой версией gstreamer работаешь?), но зависания не добился. Ежели в любом случае виснуть будет, то, на мой взгляд, следуюший шаг — это после того как все элементы в пайплайн добавятся, найти этот bufferpool и выставить ему max_buffers в 0.
S>Кстати, код выше (AddProbe) он вообще корректен для получения времени кадра, т.е. я могу рассчитывать на то, что в probeInfo.Buffer находится прям целый S>кадр, а не какой-то чанк или вообще какие-нибудь странные данные? Пока кажется, что в буффере действительно находится фрейм.
Врать не буду — не знаю, могу ориентироваться только на описание splitmuxsink. Вообще-то, ты же ему на вход вроде уже закодированный поток передаёшь, или я чего-то не понимаю.
"This element wraps a muxer and a sink, and starts a new file when the mux contents are about to cross a threshold of maximum size of maximum time, splitting at video keyframe boundaries."
Re[6]: Относительно твоих вопросов в рассылке gstreamer'а
S>>Вот тут я добавил больше деталей и debug сообщения -- https://lists.freedesktop.org/archives/gstreamer-devel//2023-August/081632.html C>Ну вот, в пуле буфера кончились. Конкретно здесь с queue промашка вышла — я забыл, что у appsink есть своя собственная очередь с неограниченным размером по умолчанию, так что здесь дополнительная очередь проблемы не решает. А скажи, пожалуйста, если в том месте, где у тебя SetState(State.Null), вместо Null Paused выставить (у тебя же всё равно зависание на первой итерации происходит), тоже виснуть будет? Я повторил твой пример (под lin, надо будет под win попробовать, потому как по логам явная отсылка к d3d11, ты, кстати, с какой версией gstreamer работаешь?), но зависания не добился. Ежели в любом случае виснуть будет, то, на мой взгляд, следуюший шаг — это после того как все элементы в пайплайн добавятся, найти этот bufferpool и выставить ему max_buffers в 0.
Вот если Paused, то не виснет. Правда пишет после
** WARNING **: 13:07:57.677: Changing the `location' property on filesrc when a file is open is not supported.
и по сути работает с одним файлом (самым первым), т.е. читать будет только из него.
Версия самая последняя (1.22 что ли).
S>>Кстати, код выше (AddProbe) он вообще корректен для получения времени кадра, т.е. я могу рассчитывать на то, что в probeInfo.Buffer находится прям целый S>>кадр, а не какой-то чанк или вообще какие-нибудь странные данные? Пока кажется, что в буффере действительно находится фрейм. C>Врать не буду — не знаю, могу ориентироваться только на описание splitmuxsink. Вообще-то, ты же ему на вход вроде уже закодированный поток передаёшь, или я чего-то не понимаю.
pipe в этом случае такой (это не то, о чем речь выше -- там я читаю mp4 файл):
rtspsrc ! rtph264depay ! h264parse ! splitmuxsink
Здравствуйте, Conductor, Вы писали:
C> Я повторил твой пример (под lin, надо будет под win попробовать, потому как по логам явная отсылка к d3d11, ты, кстати, с какой версией gstreamer работаешь?), но зависания не добился. Ежели в любом случае виснуть будет, то, на мой взгляд, следуюший шаг — это после того как все элементы в пайплайн добавятся, найти этот bufferpool и выставить ему max_buffers в 0.
Код целиком:
public static Pipeline BuildVideoReaderPipeline()
{
// Build video reader pipelinevar fileSrc = ElementFactory.Make("filesrc", "filesrc");
if (fileSrc == null)
{
throw new ArgumentNullException(nameof(fileSrc));
}
var decodebin = ElementFactory.Make("decodebin", "decodebin");
if (decodebin == null)
{
throw new ArgumentNullException(nameof(decodebin));
}
var appSink = new AppSink("my_appsink");
//appSink.NewSample += AppSink_NewSample;
appSink.EmitSignals = true;
appSink["sync"] = false;
decodebin.PadAdded += (o, args) =>
{
var appsinkPad = appSink.GetStaticPad("sink");
if (appsinkPad.IsLinked)
{
return;
}
var caps = args.NewPad.QueryCaps();
if (caps == null)
return;
args.NewPad.Link(appsinkPad);
};
fileSrc.PadAdded += (o, args) =>
{
var decPad = decodebin.GetStaticPad("sink");
if (decPad.IsLinked)
{
return;
}
var caps = args.NewPad.QueryCaps();
if (caps == null)
return;
args.NewPad.Link(decPad);
};
var pipeline = new Pipeline("video reader pipe");
pipeline.Add(fileSrc, decodebin, appSink);
var linkResult = Element.Link(fileSrc, decodebin);
linkResult = Element.Link(decodebin, appSink);
return pipeline;
}
public static void SearchFrameByNumber(Pipeline readerPipe, IEnumerable<string> videoFiles, int frameNumber)
{
if (readerPipe == null) throw new ArgumentNullException(nameof(readerPipe));
var fileSrc = readerPipe.GetByName("filesrc");
var appSink = (AppSink) readerPipe.GetByName("my_appsink");
readerPipe.SetName("frame_stepper");
var decodebin = readerPipe.GetByName("decodebin");
foreach (var videoFile in videoFiles)
{
fileSrc["location"] = videoFile;
readerPipe.SetState(State.Playing);
var step = 1; //fn тоже с 1 начинаетсяwhile (step < frameNumber)
{
appSink.PullSample();
step++;
}
Debug.SetDefaultThreshold(DebugLevel.Debug);
readerPipe.SetState(State.Null); //<-- тут виснет
fileSrc.Unlink(decodebin);
fileSrc.Link(decodebin);
}
}
Кодом людям нужно помогать!
Re[7]: Относительно твоих вопросов в рассылке gstreamer'а
Здравствуйте, Conductor, Вы писали:
C>Здравствуйте, Sharov, Вы писали:
S>>Вот если Paused, то не виснет. Правда пишет после S>>
S>>** WARNING **: 13:07:57.677: Changing the `location' property on filesrc when a file is open is not supported.
S>>и по сути работает с одним файлом (самым первым), т.е. читать будет только из него. C>Ну вот, а теперь, дождавшись того, что он перешёл в Paused, если его в Null перевести, зависнет?
Тоже самое. Собственно, если автомат состояний у них так и работает -- в Null через Paused,
соотв. что так что так результат одинаков. Виснет на SetState(Null). Выше код приложил. Может что-то не так
с параметрами appsink?