Ремуксирование mp4 на лету с помощью FFmpeg API

Моя цель - транслировать mpegts. В качестве входных данных я беру поток файлов mp4. То есть производитель видео записывает mp4 файл в поток, с которым я пытаюсь работать. Видео может длиться от одной минуты до десяти минут. Поскольку производитель записывает байты в поток, первоначально записанный заголовок mp4 не является полным (первые 32 байта до ftyp равны 0x00, потому что он еще не знает различных смещений... которые, я думаю, записываются после записи):

Вот как выглядит заголовок типичного mp4:

00 00 00 18   66 74 79 70   69 73 6f 6d   00 00 00 00   ....ftypisom.... 
69 73 6f 6d   33 67 70 34   00 01 bb 8c   6d 64 61 74   isom3gp4..»Œmdat

Вот как выглядит заголовок «в процессе» mp4:

00 00 00 00   66 74 79 70   69 73 6f 6d   00 00 00 00   ....ftypisom.... 
69 73 6f 6d   33 67 70 34   00 00 00 18   3f 3f 3f 3f   isom3gp4....????
6d 64 61 74                                             mdat

Это мое предположение, но я предполагаю, что как только производитель завершает запись, он обновляет заголовок, записывая все необходимые смещения.

При попытке выполнить эту работу я столкнулся с двумя проблемами:

  1. Я создал собственный AVIO с функцией чтения, которая не поддерживает поиск. В моей программе-драйвере я решил транслировать правильно отформатированный файл mp4. Я могу определить его формат ввода. Когда я пытаюсь открыть его, я вижу, что моя пользовательская функция чтения выполняется в avformat_open_input до тех пор, пока не будет прочитан весь файл.

Мой пример кода:

av_register_all();

AVFormatContext* pCtx = avformat_alloc_context();
pCtx->pb = avio_alloc_context(
    pBuffer,         // internal buffer
    iBufSize,        // internal buffer size
    0,               // bWriteable (1=true,0=false)
    stream,          // user data ; will be passed to our callback functions
    read_stream,     // read callback function
    NULL,            // write callback function (not used in this example)
    NULL             // seek callback function
);
pCtx->pb->seekable = 0;
pCtx->pb->write_flag = 0;
pCtx->iformat = av_find_input_format( "mp4" );
pCtx->flags |= AVFMT_FLAG_CUSTOM_IO;

avformat_open_input( &pCtx, "", pCtx->iformat, NULL );

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

Таким образом, мне нужно найти способ открыть ввод без попытки его чтения и читать только при выполнении av_read_frame. Возможно ли это сделать с помощью пользовательского AVIO. То есть подготовить/открыть ввод -> прочитать начальные входные данные во входной буфер -> прочитать кадр/пакет из входного буфера -> записать пакет на выход -> повторить чтение входных данных до конца потока.

Я покопался в Google и увидел только две альтернативы: предоставление собственного URLProtocol и использование AVFMT_NOFILE.

Пользовательский URL-протокол
Это звучит как немного обратный путь к тому, что я пытаюсь выполнить. Я понимаю, что его лучше всего использовать, когда доступен источник файла. В то время как я пытаюсь читать из потока байтов. Кроме того, еще одна причина, по которой я думаю, что это не соответствует моим потребностям, заключается в том, что пользовательский URLProtocol необходимо скомпилировать в библиотеку ffmpeg, верно? Или есть способ вручную зарегистрировать его во время выполнения?

AVFMT NOFILE
Мне кажется, это должно работать лучше всего. Сам флаг говорит об отсутствии базового исходного файла и предполагает, что я возьму на себя все операции чтения и предоставления входных данных. Проблема в том, что я пока не видел фрагментов кода в Интернете, но мое предположение таково:

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

  1. Как я упоминал выше, у меня есть дескриптор потока байтов файла mp4, поскольку он будет записан на жесткий диск. Формат mp4 (h.264 и aac). Мне нужно преобразовать его в mpegts перед потоковой передачей. Это не должно быть сложно, потому что mp4 и mpegts — это просто контейнеры. Из того, что я узнал до сих пор, файл mp4 выглядит следующим образом:

    [информация заголовка, содержащая версии формата]
    mdat
    [данные потока, в моем случае потоки h.264 и aac]
    [какой-то разделитель трейлера]
    [данные трейлера]

Если это правильно, я должен получить дескриптор чередующихся данных h.264 и aac, просто начав читать поток после идентификатора «mdat», верно?

Если это правда, и я решил использовать подход AVFMT_NOFILE для управления входными данными, я могу просто принять потоковые данные (в буфер AVFormatContext) -> av_read_frame -> обработать его -> заполнить AVFormatContext дополнительными данными -> av_read_frame -> и так далее до конца стрима.

Я знаю, что это полный рот и свалка моих мыслей, но я был бы признателен за любое обсуждение, указатели, мысли!


person Vadym    schedule 14.06.2013    source источник


Ответы (2)


Хорошо, еще один вопрос, изученный и на который я ответил сам...

Оказывается, как я предположил в вопросе, файл mp4 не дописан до конца. Во время прямой записи на диск в файл производитель будет возвращаться к началу видео и обновлять все указатели на различные атомы. То есть общая структура mp4 такова: ftyp -> mdat -> moov. Где moov содержит всю мету о содержащихся треках. К сожалению, это написано последним. Однако его расположение указано в шапке. Вот почему требуется поиск: mdat имеет разную длину (поскольку он содержит необработанные закодированные кадры, их может быть x количество). Таким образом, атом moov компенсируется длиной mdat. Когда производитель закончит запись файла, он обновит заголовок, указав правильное расположение moov.

Дополнительные ссылки: Трансляция Android без записи на диск

При таком подходе финальный файл должен быть «исправлен».

В комментариях по указанной ссылке есть полезное предложение по исправлению файла:

Просто чтобы помочь тем, у кого есть проблемы, SDK пытается вставить значения размера атома mdat, а также заголовок moov. В этом примере я установил кодировщик для создания файла THREE_GPP. Чтобы воспроизвести вывод THREE_GPP, вам нужно сначала создать заголовок в первых 28 байтах до атома mdat (все они должны быть нулями). 00 00 00 18 66 74 79 70 33 67 70 34 00 00 03 00 33 67 70 34 33 67 70 36 00 02 F1 4D 6D 6D — это первый байт m в атоме mdat. Четыре следующих байта, которые необходимо изменить, чтобы включить целочисленное значение байта в ваш поток, содержащий выходной атом moov (который должен быть выведен после остановки записи). Пока этот заголовок установлен правильно и игрок может найти атом moov, все должно воспроизводиться правильно. Кроме того, метод сокета здесь не очень гибкий - вы можете выполнять более тонкие изменения пакетных данных в сети (в данный момент я пытаюсь сделать это для прямой трансляции), предоставив ему локальный сокет, а затем подключившись к этот локальный сокет и обрабатывает его вывод независимо (например, в потоке) для передачи по UDP, RTP и т. д.

-- Джейсон

Однако должно стать очевидным, что это совсем не помогает при потоковой передаче живого воспроизводимого видео.

Теперь я столкнулся только с возможностью попытаться получить rtp (методами SipDroid или SpyCamera) и конвертировать через ffmpeg на стороне NDK.

person Vadym    schedule 17.06.2013

Вам нужно будет создать собственный AVIOContext, если хотите.

AVIOContext требует реализации только следующих функций:

int (*read_packet)(void *opaque, uint8_t *buf, int buf_size) int (*write_packet)(void *opaque, uint8_t *buf, int buf_size) int (*seek)(void *opaque, int64_t offset, int whence)

h/t в дневник кодера, чтобы указать мне правильное направление.

person Steven Edwards    schedule 25.10.2015