FFMPEG делает запросы для каждого кадра при декодировании потока, низкая производительность - PullRequest
1 голос
/ 07 мая 2020

У меня проблема с воспроизведением файлов, снятых камерой MOV с iPhone. Моя реализация FFMPEG не имеет проблем с воспроизведением большинства форматов файлов, эта проблема характерна только для MOV, снятого камерой.

При попытке открыть файл я вижу в журналах, что сделано много запросов, каждый запрос только декодирует один кадр, прежде чем сделать новый запрос, что приводит к очень медленной буферизации видео. Буферизация нескольких секунд видео занимает примерно минуту.

Еще одна вещь, о которой следует упомянуть, это то, что тот же самый проблемный файл c воспроизводится локально без проблем. Проблема заключается в попытке декодирования во время потоковой передачи.

Я скомпилировал свой код на Xcode 11, iOS SDK 13, с cocoapods mobile-ffmpeg-https 4.2.

Вот приблизительное представление моего кода, это довольно стандартно:

  1. Вот как я открываю 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. Обратите внимание на «Оценка mp3: 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 Ответ

0 голосов
/ 03 июля 2020

Я нашел исправление для этого (вроде):

Установите обратный вызов io_open для AVFormatContext для своей собственной функции, а затем, когда это будет вызвано, после вызова 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 до 40 МБ). Вы можете получить это значение из второго диапазона байтов сетевого запроса при открытии контекста формата (первый сетевой запрос выполняется с диапазоном байтов 0- *, а затем второй сетевой запрос выполняется с диапазоном байтов XXXX- *, где XXXX должен быть размер буфера).

Причина, по которой это устраняет проблему, заключается в том, что благодаря буферизации всех этих данных aviocontext больше не нужно делать новый сетевой запрос для получения аудиоданных. Аудиоданные уже буферизированы (или, по крайней мере, первая позиция).

Может быть лучший способ исправить эту проблему, похоже, что файлы Apple MOV разделяют свои видео- и аудиоданные этими огромными фрагментами для по какой-то причине, и это заставляет ffmpeg делать миллион сетевых запросов для каждого кадра.

...