2

我的目标是流出 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 中执行,直到读入整个文件。

我的代码示例:

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
对于我想要完成的工作,这听起来有点倒退。我知道最好在有可用文件源时使用它。而我正在尝试从字节流中读取。另外,我认为它不符合我需要的另一个原因是需要将自定义 URLProtocol 编译到 ffmpeg lib 中,对吗?或者有没有办法在运行时手动注册它?

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

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

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

    [包含格式版本的标题信息]
    mdat
    [流数据,在我的情况下是 h.264 和 aac 流]
    [一些预告片分隔符]
    [预告片数据]

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

如果这是真的并且我决定使用 AVFMT_NOFILE 方法来管理输入数据,我可以只摄取流数据(进入 AVFormatContext 缓冲区)-> av_read_frame -> 处理它 -> 用更多数据填充 AVFormatContext -> av_read_frame -> 等等直到流结束。

我知道,这是我的想法和想法,但我将不胜感激任何讨论、指点和想法!

4

2 回答 2

2

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

事实证明,正如我在问题中所推测的那样,mp4 文件直到最后才完全写入。在直接磁盘写入文件期间,生产者将回溯到视频的开头并更新所有指向各种原子的指针。即mp4的一般结构是ftyp -> mdat -> moov。其中 moov 包含有关所包含曲目的所有元数据。可惜最后写了。但是,它的位置位于标题中。这就是为什么需要查找的原因:mdat 的长度不同(因为它包含原始编码帧,所以可以有 x 个)。因此,moov 原子偏移了 mdat 的长度。当生产者完成写入文件时,它将使用 moov 的正确位置更新标头。

其他参考:Android 广播没有磁盘写入

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

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

只是为了帮助那些有问题的人,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 atom- 一切都应该正确播放。还,

——杰森

但是,很明显,这对直播可播放视频的流式传输毫无帮助。

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

于 2013-06-17T22:56:49.347 回答
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 to Coder's Diary为我指明了正确的方向。

于 2015-10-25T11:34:48.450 回答