使用FFmpeg libavformat记录RTSP流

17

我正在尝试使用FFmpeg libavformat记录来自Axis相机的RTSP流。我可以从文件中获取视频并将其保存到另一个文件,这很好。但是相机发送的数据很奇怪,帧率为100,相机每隔4帧发送一次,因此结果帧率约为25。但是libavformat设置了90000 fps(默认值?)的数据包dts / pts,并且新文件流具有100fps。结果是一个只有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

感谢任何建议


在你的代码中,你只记录了前100帧,这是有意为之吗? - ciphor
摄像头的格式是什么?是h.264吗? - ransh
请参阅 https://dev59.com/qWgv5IYBdhLWcg3wSe0f。 - rogerdpack
4个回答

7

以下是我的做法。我发现在接收H264流时,帧速率不正确。它发送1/90000的时间基准。我跳过了从传入流中初始化新流的步骤,只复制了某些参数。如果max_analyze_frames正常工作,则传入的r_frame_rate应该是准确的。

#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;
}

av_dump_format(ofcx,0,ofcx->filename,1); 给我报错了。 - patrick
我得到了相同的错误.. 如果您找到了解决方案,请帮助我们解决... 预先感谢。 - Anny

4

我认为你不能只是简单地增加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;

即使将此应用于上面的代码,我得到的mp4文件似乎具有不正确的时间。在播放时,一个mp4文件中约10秒的录制视频会立即完成。我可以浏览所有帧,但它无法播放。[mp4 @ 0x1040e9000] 应用程序提供的持续时间:1967652701196434754 / 时间戳:749796 超出了mov/mp4格式的范围 [mp4 @ 0x1040e9000] pts没有值 - stevex

1
在我使用现代H.264编码器的经验中,我发现ffmpeg返回的持续时间只是一个“建议”,而PTS存在一些“抖动”。确定帧率或持续时间的唯一准确方法是使用PTS值自己测量。
对于以30fps运行的H.264编码器,持续时间始终报告为3000/90000,而测量的持续时间通常为+/-1,但间歇性地跳跃,例如一个帧为3000+25,下一个为3000-25。我通过注意到任何相邻的带有相反偏差的帧并调整第二帧的PTS而保留总持续时间来平滑录制。
这给了我一个偶尔(计算出的)持续时间为30001或2999的流,反映了时钟漂移。
当记录29.97fps流时,av_read_frame()始终返回持续时间为3000,而名义上计算出的持续时间为3003(对于29.97是正确的),具有与上述描述相同的抖动和漂移。
在我的情况下,我只是建立了一个状态机来清理时间。希望这能帮助某些人。

0
最近也遇到了同样的问题,相机发送的帧率是实际帧率的两倍。原因在于AVstream->codec->ticks_per_frame字段设置为2。我的源是逐行扫描的,如果你的源是交错的,则可能会出现另一个因素为2的情况,使帧率相差4倍。
通过RTSP发送的视频流的默认时间基为90000 Hz。时间基与FPS的分辨率不同。例如,如果时间基为90000 Hz,则具有时间戳30000的帧将在1/3秒处显示。应该在输出过程中将时间基放入AVstream结构中,但AVFormatContext应具有实际的FPS值。

亲爱的Tosha,哪个是解决方案?更改ticks_per_frame?更改fps?更改timebase? - Didac Perez Parera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接