Graba el flujo RTSP con FFmpeg libavformat
ffmpeg rtsp (4)
Estoy tratando de grabar la transmisión RTSP de la cámara Axis con FFmpeg libavformat. Puedo capturar el video de los archivos y luego guardarlo en otro archivo, está bien. Pero la cámara envía datos extraños, el FPS es 100 y la cámara envía cada 4to fotograma, por lo que el resultado es FPS aproximadamente 25. Pero el formato de libavatat establece paquetes dts / pts para 90000 fps (¿predeterminado?) Y la nueva secuencia de archivos tiene 100 fps. El resultado es un video de una hora con solo 100 cuadros.
Aqui esta mi codigo
#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);
}
El archivo de resultados está aquí: http://dl.dropbox.com/u/1243577/test.avi
Gracias por cualquier consejo
Así es como lo hago. Lo que encontré fue que cuando recibía H264 la tasa de cuadros en el flujo no es correcta. Envía 1/90000 Base de tiempo. Omito la inicialización del nuevo flujo desde el flujo entrante y solo copio ciertos parámetros. La r_frame_rate entrante debe ser precisa si max_analyze_frames funciona correctamente.
#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;
}
En mi experiencia con un moderno codificador H.264, estoy descubriendo que la duración devuelta por ffmpeg es solo una "sugerencia" y que hay algo de "jitter" en el PTS. La única manera precisa de determinar la velocidad de cuadros o la duración es medirla usted mismo utilizando los valores PTS.
Para un codificador H.264 que se ejecuta a 30 fps, la duración siempre se informa como 3000/90000, mientras que la duración medida generalmente es de +/- 1, pero periódicamente salta, por ejemplo, 3000 + 25 un fotograma y 3000-25 el siguiente. Lo estoy suavizando para grabar, observando cualquier cuadro adyacente con una desviación opuesta y ajustando el PTS del segundo cuadro manteniendo la duración total.
Esto me da un flujo con una duración ocasional (calculada) de 30001 o 2999, reflejando la deriva del reloj.
Cuando se graba un flujo de 29.97fps, av_read_frame () siempre devuelve una duración de 3000, mientras que la duración calculada nominal es de 3003 (correcta para 29.97) con el mismo jitter y deriva como se describió anteriormente.
En mi caso, acabo de construir una máquina de estado para limpiar el tiempo. Espero que esto ayude a alguien.
No creo que debas incrementar el valor de PTS de esa manera. Puede funcionar en raras ocasiones en que la base de tiempo es la correcta, pero en el caso general no funcionará.
Deberías cambiar esto:
pkt.pts = ix++;
pkt.dts = pkt.pts;
A esto:
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);
Lo que eso hace es convertir el PTS / DTS del paquete de las unidades utilizadas en el códec del flujo de entrada a las unidades del flujo de salida.
Además, algunas transmisiones tienen múltiples tics por fotograma, por lo que si el video se ejecuta a doble velocidad, es posible que tenga que hacerlo justo debajo de la línea anterior:
pkt.pts *= ifcx->streams[0]->codec->ticks_per_frame;
pkt.dts *= ifcx->streams[0]->codec->ticks_per_frame;
Recientemente estaba haciendo lo mismo. Tuve FPS dos veces más bajo que la cámara enviada. La razón estaba en el campo AVstream-> codec-> ticks_per_frame, establecido en 2. Mi fuente era progresiva, y en caso de que la tuya estuviera intercalada, entonces esa podría ser una razón de otro factor de 2, lo que da 4 veces más FPS. 90000 Hz es la base de tiempo predeterminada para la transmisión de video enviada a través de RTSP. La base de tiempo es diferente de FPS en resolución. Por ejemplo, un cuadro con la marca de tiempo 30000 se mostrará a 1/3 de segundo si la base de tiempo es 90000 Hz. La base de tiempo se debe colocar en la estructura AVstream durante la salida, pero AVFormatContext debe tener un valor real de FPS.