Запись потока RTSP с помощью FFmpeg libavformat
Я пытаюсь записать поток RTSP с камеры Axis с помощью FFmpeg libavformat.
Я могу захватить видео из файлов, а затем сохранить его в другом файле, это нормально. Но камера отправляет странные данные, FPS - 100, а камера отправляет каждый 4-й кадр, поэтому результат FPS составляет около 25. Но libavformat устанавливает пакеты dts/pts для 90000 кадров в секунду (по умолчанию?), А новый поток файлов имеет 100 кадров в секунду. Результат - одночасовое видео с только 100 кадрами.
Вот мой код
#include <stdio.h>
#include <stdlib.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
int main(int argc, char** argv) {
AVFormatContext* context = avformat_alloc_context();
int video_stream_index;
av_register_all();
avcodec_register_all();
avformat_network_init();
//open rtsp
if(avformat_open_input(&context, "rtsp://195.200.199.8/mpeg4/media.amp",NULL,NULL) != 0){
return EXIT_FAILURE;
}
if(avformat_find_stream_info(context,NULL) < 0){
return EXIT_FAILURE;
}
//search video stream
for(int i =0;i<context->nb_streams;i++){
if(context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
video_stream_index = i;
}
AVPacket packet;
av_init_packet(&packet);
//open output file
AVOutputFormat* fmt = av_guess_format(NULL,"test2.avi",NULL);
AVFormatContext* oc = avformat_alloc_context();
oc->oformat = fmt;
avio_open2(&oc->pb, "test.avi", AVIO_FLAG_WRITE,NULL,NULL);
AVStream* stream=NULL;
int cnt = 0;
//start reading packets from stream and write them to file
av_read_play(context);//play RTSP
while(av_read_frame(context,&packet)>=0 && cnt <100){//read 100 frames
if(packet.stream_index == video_stream_index){//packet is video
if(stream == NULL){//create stream in file
stream = avformat_new_stream(oc,context->streams[video_stream_index]->codec->codec);
avcodec_copy_context(stream->codec,context->streams[video_stream_index]->codec);
stream->sample_aspect_ratio = context->streams[video_stream_index]->codec->sample_aspect_ratio;
avformat_write_header(oc,NULL);
}
packet.stream_index = stream->id;
av_write_frame(oc,&packet);
cnt++;
}
av_free_packet(&packet);
av_init_packet(&packet);
}
av_read_pause(context);
av_write_trailer(oc);
avio_close(oc->pb);
avformat_free_context(oc);
return (EXIT_SUCCESS);
}
Файл результатов находится здесь: http://dl.dropbox.com/u/1243577/test.avi
Спасибо за любой совет
Ответы
Ответ 1
Вот как я это делаю. Я обнаружил, что при получении H264 частота кадров в потоке неверна. Он отправляет 1/90000 Timebase. Я пропускаю инициализацию нового потока из входящего потока и просто копирую определенные параметры. Входной r_frame_rate должен быть точным, если max_analyze_frames работает правильно.
#include <stdio.h>
#include <stdlib.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <sys/time.h>
time_t get_time()
{
struct timeval tv;
gettimeofday( &tv, NULL );
return tv.tv_sec;
}
int main( int argc, char* argv[] )
{
AVFormatContext *ifcx = NULL;
AVInputFormat *ifmt;
AVCodecContext *iccx;
AVCodec *icodec;
AVStream *ist;
int i_index;
time_t timenow, timestart;
int got_key_frame = 0;
AVFormatContext *ofcx;
AVOutputFormat *ofmt;
AVCodecContext *occx;
AVCodec *ocodec;
AVStream *ost;
int o_index;
AVPacket pkt;
int ix;
const char *sProg = argv[ 0 ];
const char *sFileInput;
const char *sFileOutput;
int bRunTime;
if ( argc != 4 ) {
printf( "Usage: %s url outfile runtime\n", sProg );
return EXIT_FAILURE;
}
sFileInput = argv[ 1 ];
sFileOutput = argv[ 2 ];
bRunTime = atoi( argv[ 3 ] );
// Initialize library
av_log_set_level( AV_LOG_DEBUG );
av_register_all();
avcodec_register_all();
avformat_network_init();
//
// Input
//
//open rtsp
if ( avformat_open_input( &ifcx, sFileInput, NULL, NULL) != 0 ) {
printf( "ERROR: Cannot open input file\n" );
return EXIT_FAILURE;
}
if ( avformat_find_stream_info( ifcx, NULL ) < 0 ) {
printf( "ERROR: Cannot find stream info\n" );
avformat_close_input( &ifcx );
return EXIT_FAILURE;
}
snprintf( ifcx->filename, sizeof( ifcx->filename ), "%s", sFileInput );
//search video stream
i_index = -1;
for ( ix = 0; ix < ifcx->nb_streams; ix++ ) {
iccx = ifcx->streams[ ix ]->codec;
if ( iccx->codec_type == AVMEDIA_TYPE_VIDEO ) {
ist = ifcx->streams[ ix ];
i_index = ix;
break;
}
}
if ( i_index < 0 ) {
printf( "ERROR: Cannot find input video stream\n" );
avformat_close_input( &ifcx );
return EXIT_FAILURE;
}
//
// Output
//
//open output file
ofmt = av_guess_format( NULL, sFileOutput, NULL );
ofcx = avformat_alloc_context();
ofcx->oformat = ofmt;
avio_open2( &ofcx->pb, sFileOutput, AVIO_FLAG_WRITE, NULL, NULL );
// Create output stream
//ost = avformat_new_stream( ofcx, (AVCodec *) iccx->codec );
ost = avformat_new_stream( ofcx, NULL );
avcodec_copy_context( ost->codec, iccx );
ost->sample_aspect_ratio.num = iccx->sample_aspect_ratio.num;
ost->sample_aspect_ratio.den = iccx->sample_aspect_ratio.den;
// Assume r_frame_rate is accurate
ost->r_frame_rate = ist->r_frame_rate;
ost->avg_frame_rate = ost->r_frame_rate;
ost->time_base = av_inv_q( ost->r_frame_rate );
ost->codec->time_base = ost->time_base;
avformat_write_header( ofcx, NULL );
snprintf( ofcx->filename, sizeof( ofcx->filename ), "%s", sFileOutput );
//start reading packets from stream and write them to file
av_dump_format( ifcx, 0, ifcx->filename, 0 );
av_dump_format( ofcx, 0, ofcx->filename, 1 );
timestart = timenow = get_time();
ix = 0;
//av_read_play(context);//play RTSP (Shouldn't need this since it defaults to playing on connect)
av_init_packet( &pkt );
while ( av_read_frame( ifcx, &pkt ) >= 0 && timenow - timestart <= bRunTime ) {
if ( pkt.stream_index == i_index ) { //packet is video
// Make sure we start on a key frame
if ( timestart == timenow && ! ( pkt.flags & AV_PKT_FLAG_KEY ) ) {
timestart = timenow = get_time();
continue;
}
got_key_frame = 1;
pkt.stream_index = ost->id;
pkt.pts = ix++;
pkt.dts = pkt.pts;
av_interleaved_write_frame( ofcx, &pkt );
}
av_free_packet( &pkt );
av_init_packet( &pkt );
timenow = get_time();
}
av_read_pause( ifcx );
av_write_trailer( ofcx );
avio_close( ofcx->pb );
avformat_free_context( ofcx );
avformat_network_deinit();
return EXIT_SUCCESS;
}
Ответ 2
Я не думаю, что вы должны просто увеличить значение PTS. Он может работать в редких случаях, когда временная база в порядке, но в общем случае она не будет работать.
Вы должны изменить это:
pkt.pts = ix++;
pkt.dts = pkt.pts;
Для этого:
pkt.pts = av_rescale_q(pkt.pts, ifcx->streams[0]->codec->time_base, ofcx->streams[0]->time_base);
pkt.dts = av_rescale_q(pkt.dts, ifcx->streams[0]->codec->time_base, ofcx->streams[0]->time_base);
Что это значит, это преобразовать пакет PTS/DTS из единиц, используемых в кодере входного потока, в единицы выходного потока.
Кроме того, некоторые потоки имеют несколько тиков за кадр, поэтому, если видео работает с двойной скоростью, вам может понадобиться это прямо ниже указанной строки:
pkt.pts *= ifcx->streams[0]->codec->ticks_per_frame;
pkt.dts *= ifcx->streams[0]->codec->ticks_per_frame;
Ответ 3
По моему опыту с современным кодировщиком H.264, я нахожу, что продолжительность, возвращаемая ffmpeg, является всего лишь "предложением" и что в PTS есть "дрожание". Единственный точный способ определить частоту кадров или длительность - это измерить его самостоятельно, используя значения PTS.
Для кодировщика H.264, работающего со скоростью 30 кадров в секунду, продолжительность всегда отображается как 3000/90000, тогда как измеренная продолжительность обычно +/- 1, но периодически скачки говорят, что 3000 + 25 на один кадр и 3000- 25 следующий. Я сглаживаю это для записи, замечая смежные рамки с противоположным отклонением и настраивая PTS второго кадра при сохранении общей продолжительности.
Это дает мне поток со случайной (рассчитанной) продолжительностью 30001 или 2999, отражающий такт дрейфа.
При записи потока 29,97 кадров в секунду av_read_frame() всегда возвращает длительность 3000, тогда как номинальная рассчитанная длительность 3003 (правильная для 29.97) с тем же джиттером и дрейфом, как описано выше.
В моем случае я только что построил конечный автомат, чтобы очистить время. Надеюсь, это поможет кому-то.
Ответ 4
Недавно делал то же самое. У меня FPS в два раза ниже, чем у камеры. Причина была в поле AVstream- > codec- > ticks_per_frame, равном 2. Мой источник был прогрессивным, а в случае вашего чередования - тогда это может быть причиной еще одного фактора 2, дающего 4x разных FPS.
90000 Гц - это временная база по умолчанию для видеопотока, передаваемого через RTSP. Временная база отличается от FPS в разрешении. Например, кадр с временной отметкой 30000 будет показан на 1/3 секунды, если база данных равна 90000 Гц. Временная база должна быть помещена в структуру AVstream во время вывода, но AVFormatContext должен иметь реальное значение FPS.