我的目标是流出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
这是我的猜测,但我认为一旦制作人完成录制,它会通过写下所有必要的偏移来更新标题。
我在尝试完成这项工作时遇到了两个问题:
我的代码示例:
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和数字媒体的新手,我的第二个问题是我可以在输入输入的同时输出输出。
正如我上面提到的,我有一个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 - >等等直到流的结束。
我知道,这是一个满口而且倾注我的想法,但我会感激任何讨论,指针,想法!
答案 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指示我正确的方向。