FFMPEG 在解码流时对每一帧提出请求,性能降低

Posted

技术标签:

【中文标题】FFMPEG 在解码流时对每一帧提出请求,性能降低【英文标题】:FFMPEG making requests for each frame when decoding a stream, slow performance 【发布时间】:2020-08-22 10:28:06 【问题描述】:

我在从 iPhone 播放 MOV 相机捕获的文件时遇到问题。我的 FFMPEG 实现播放大多数文件格式都没有问题,这个问题仅适用于相机捕获的 MOV。

在尝试打开文件时,我可以在日志中看到发出了许多请求,每个请求只解码一帧,然后再发出新请求,导致视频缓冲速度极慢。 缓冲大约几秒钟的视频大约需要一分钟。

另外要提的是,同样的有问题的文件在本地播放时没有问题。问题是在流式传输时尝试解码。

我使用 cocoapods mobile-ffmpeg-https 4.2 在 Xcode 11、ios SDK 13 上编译了我的代码。

这是我的代码的粗略表示,它非常标准:

    这是我打开 AVFormatContext 的方法:
AVFormatContext *context = avformat_alloc_context();
    context->interrupt_callback.callback = FFMPEGFormatContextIOHandler_IO_CALLBACK;
    context->interrupt_callback.opaque = (__bridge void *)(handler);

    av_log_set_level(AV_LOG_TRACE);

    int result = avformat_open_input(&context, [request.urlAsString UTF8String], NULL, NULL);

    if (result != 0) 
        if (context != NULL) 
            avformat_free_context(context);
        

        return nil;
    

    result = avformat_find_stream_info(context, NULL);

    if (result < 0) 
        avformat_close_input(&context);
        return nil;
    
    视频解码器是这样打开的,音频解码器几乎一模一样
AVCodecParameters *params = context->streams[streamIndex]->codecpar;
    AVCodec *codec = avcodec_find_decoder(params->codec_id);

    if (codec == NULL) 
        return NULL;
    

    AVCodecContext *codecContext = avcodec_alloc_context3(codec);

    if (codecContext == NULL) 
        return NULL;
    

    codecContext->thread_count = 6;

    int result = avcodec_parameters_to_context(codecContext, params);

    if (result < 0) 
        avcodec_free_context(&codecContext);
        return NULL;
    

    result = avcodec_open2(codecContext, codec, NULL);

    if (result < 0) 
        avcodec_free_context(&codecContext);
        return NULL;
    
    我从服务器读取数据是这样的:
AVPacket packet;

int result = av_read_frame(formatContext, &avPacket);

if (result == 0) 
   avcodec_send_packet(codecContext, &avPacket);

   // .... decode ....

打开解码器后的日志:

// [tls] Request is made here
// [tls] Request response headers are here
Probing mov,mp4,m4a,3gp,3g2,mj2 score:100 size:2048
Probing mp3 score:1 size:2048
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] Format mov,mp4,m4a,3gp,3g2,mj2 probed with size=2048 and score=100
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] type:'ftyp' parent:'root' sz: 20 8 23077123
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] ISO: File Type Major Brand: qt  
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] type:'wide' parent:'root' sz: 8 28 23077123
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] type:'mdat' parent:'root' sz: 23066642 36 23077123
// [tls] Request is made here
// [tls] Request response headers are here
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 0, sample 4, dts 133333
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 1, sample 48, dts 1114558
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 2, sample 1, dts 2666667
[h264 @ 0x116080200] nal_unit_type: 1(Coded slice of a non-IDR picture), nal_ref_idc: 1
// [tls] Request is made here
// [tls] Request response headers are here
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 0, sample 4, dts 133333
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 1, sample 48, dts 1114558
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x115918e00] stream 2, sample 1, dts 2666667
[h264 @ 0x116080200] nal_unit_type: 1(Coded slice of a non-IDR picture), nal_ref_idc: 1
// [tls] Request is made here
// [tls] Request response headers are here
// ...

这些是我在日志中发现的一些警告

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] interrupted
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] stream 0: start_time: 0.000 duration: 11.833
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] stream 1: start_time: 0.000 duration: 11.832
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] stream 2: start_time: 0.000 duration: 11.833
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] stream 3: start_time: 0.000 duration: 11.833
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] format: start_time: 0.000 duration: 11.833 bitrate=15601 kb/s
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] Could not find codec parameters for stream 0 (Video: h264, 1 reference frame (avc1 / 0x31637661), none(bt709, left), 1920x1080, 1/1200, 15495 kb/s): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11c030800] After avformat_find_stream_info() pos: 23077123 bytes read:16293 seeks:1 frames:0

同样在调用 avformat_open_input(...) 时,在返回之前会发出 2 个 GET 请求。 请注意“Probing mp3 score:1”,其他 MOV 文件或任何其他文件均未显示。

我尝试过不同版本的 ffmpeg,我尝试过处理流的延迟,我尝试删除我的自定义中断回调,但没有任何效果。

代码适用于我测试过的任何其他视频(mp4、mkv、avi)。

测试文件的元数据:

Metadata:
    major_brand     : qt  
    minor_version   : 0
    compatible_brands: qt  
    creation_time   : 2019-04-14T08:17:03.000000Z
    com.apple.quicktime.make: Apple
    com.apple.quicktime.model: iPhone 7
    com.apple.quicktime.software: 12.2
    com.apple.quicktime.creationdate: 2019-04-14T11:17:03+0300
  Duration: 00:00:16.83, bitrate: N/A
    Stream #0:0(und), 0, 1/600: Video: h264, 1 reference frame (avc1 / 0x31637661), none(bt709), 1920x1080 (0x0), 0/1, 15301 kb/s, 30 fps, 30 tbr, 600 tbn (default)
    Metadata:
      creation_time   : 2019-04-14T08:17:03.000000Z
      handler_name    : Core Media Video
      encoder         : H.264
    Stream #0:1(und), 0, 1/44100: Audio: aac (mp4a / 0x6134706D), 44100 Hz, mono, 100 kb/s (default)
    Metadata:
      creation_time   : 2019-04-14T08:17:03.000000Z
      handler_name    : Core Media Audio
    Stream #0:2(und), 0, 1/600: Data: none (mebx / 0x7862656D), 0/1, 0 kb/s (default)
    Metadata:
      creation_time   : 2019-04-14T08:17:03.000000Z
      handler_name    : Core Media Metadata
    Stream #0:3(und), 0, 1/600: Data: none (mebx / 0x7862656D), 0/1, 0 kb/s (default)
    Metadata:
      creation_time   : 2019-04-14T08:17:03.000000Z
      handler_name    : Core Media Metadata

【问题讨论】:

【参考方案1】:

我找到了一个解决方法(有点):

将 AVFormatContext 的 io_open 回调设置为您自己的函数,然后在调用该函数时,在调用默认的 io_open 后更改缓冲区大小。

static int (*IO_OPEN_DEFAULT)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options);

int IO_OPEN_OVERRIDE(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options) 
    int result = IO_OPEN_DEFAULT(s, pb, url, flags, options);
    pb[0]->buffer_size = 41239179;
    return result;

这解决了这个问题。您设置的值通常非常大(20 到 40MB)。您可以在打开格式上下文时从第二个网络请求字节范围中获取该值(第一个网络请求使用字节范围 0-* 进行,然后第二个网络请求使用字节范围 XXXX-* 进行,其中 XXXX 应该是缓冲区大小)。

解决此问题的原因是,通过缓冲所有数据,aviocontext 不再需要发出新的网络请求来获取音频数据。音频数据已经被缓冲(或者至少是第一个位置)。

可能有更好的方法来解决此问题,似乎苹果 MOV 文件出于某种原因将其视频和音频数据与这些大块分开,这导致 ffmpeg 为每一帧发出一百万个网络请求。

【讨论】:

以上是关于FFMPEG 在解码流时对每一帧提出请求,性能降低的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片

怎么使用ffmpeg连续截图

FFmpeg 细碎知识整理

音视频开发中如何使用ffmpeg 一帧H264解码YUV420P?

我可以使用ffmpeg或ffprobe获取h.264每一帧的引用列表吗?

使用FFmpeg获取视频每一帧的信息