使用FFmpeg API即时重新启动mp4

时间:2013-06-14 22:44:07

标签: c ffmpeg mp4 codec transcoding

我的目标是流出mpegts。作为输入,我采用mp4文件流。也就是说,视频制作人将mp4文件写入我尝试使用的流中。视频可能是一分钟到十分钟。由于生产者将字节写入流,原始写入的mp4标头不完整(ftyp之前的前32个字节是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中执行,直到读入整个文件。
  2. 我的代码示例:

    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可以做到这一点。也就是说,准备/打开输入 - >将初始输入数据读入输入缓冲区 - >从输入缓冲区读取帧/包 - >将数据包写入输出 - >重复读取输入数据,直到流结束。

    我确实清除了谷歌并且只看到了两种选择:提供自定义URLProtocol并使用AVFMT_NOFILE。

    自定义网址协议
    对于我想要完成的事情,这听起来像是一种向后的方式。我知道最好在有文件源时使用它。而我试图从字节流中读取。另外,我认为它不符合我的需求的另一个原因是自定义URLProtocol需要编译成ffmpeg lib,对吗?或者有没有办法在运行时手动注册它?

    AVFMT NOFILE
    这看起来似乎应该最适合我。标志本身表示没有底层源文件,并假设我将处理所有输入数据的读取和配置。问题是到目前为止我还没有看到任何在线代码片段,但我的假设如下:

    我真的希望从任何人那里得到一些关于大脑食物的建议,因为我是ffmpeg和数字媒体的新手,我的第二个问题是我可以在输入输入的同时输出输出。

    1. 正如我上面提到的,我有一个mp4文件字节流的句柄,因为它将被写入硬盘。格式为mp4(h.264和aac)。我需要在将其流出之前将其重新调整为mpegts。这应该不难,因为mp4和mpegts只是容器。从我到目前为止所学到的,mp4文件看起来如下:

      [包含格式版本的标题信息]
      MDAT
      [流数据,在我的情况下h.264和aac流]
      [一些拖车分离器]
      [预告片数据]

    2. 如果这是正确的,我应该能够通过简单地开始在“mdat”标识符之后读取流来获取h.264和aac交错数据的句柄,对吗?

      如果确实如此,我决定采用AVFMT_NOFILE管理输入数据的方法,我可以直接摄取流数据(进入AVFormatContext缓冲区) - > av_read_frame - >处理它 - >使用更多数据填充AVFormatContext - > av_read_frame - >等等直到流的结束。

      我知道,这是一个满口而且倾注我的想法,但我会感激任何讨论,指针,想法!

2 个答案:

答案 0 :(得分:2)

好的,另一个问题由自我研究和回答......

事实证明,正如我在问题中提出的那样,mp4文件直到最后都没有写完。在直接磁盘写入文件期间,生产者将寻求回到视频的开头并更新所有指向各种原子的指针。也就是说,mp4的一般结构是ftyp - > mdat - > MOOV。 moov包含有关所包含轨道的所有元数据。不幸的是,它是最后写的。但是,它的位置位于标题中。这就是为什么需要搜索的原因:mdat具有不同的长度(因为它包含原始编码帧,它们可以有x个)。因此,moov原子被mdat的长度偏移。当生产者完成写入文件时,它将使用moov的正确位置更新标题。

有关其他参考资料:Android broadcasting without disk writes

如果采用这种方法,最终文件必须“固定”。

在指定链接的评论部分修复文件有一个有用的建议:

  

为了帮助那些有问题,SDK似乎试图寻求插入mdat原子的大小值,以及moov标头。   我在此示例中设置编码器以生成THREE_GPP文件。   为了播放输出THREE_GPP,您需要首先在mdat原子之前的前28个字节中创建标头(应该都是零)。   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是mdat原子中的'm'第一个字节。进行的四个字节需要修改,以包含流中包含输出moov原子的字节的整数值(应在停止记录时输出)。只要正确设置此标头,并且播放器可以找到moov原子 - 所有内容都应该正确播放。   此外,这里的套接字方法不是很灵活 - 您可以对网络执行更精细的数据包更改(我现在正在尝试实时流式传输),通过提供本地套接字,然后连接到本地套接字并独立处理其输出(例如在一个线程中),以便通过UDP,RTP等进行传输。

     

- 杰森

然而,显而易见的是,这对现场可播放视频的流媒体没有任何帮助。

我现在面临的唯一可能是尝试获取rtp(通过SipDroid或SpyCamera方法)并通过NDK端的ffmpeg进行转换。

答案 1 :(得分:0)

如果您愿意,您必须创建自定义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到Coder's Diary指示我正确的方向。