H.264 复用到 MP4 使用 libavformat 不播放
Posted
技术标签:
【中文标题】H.264 复用到 MP4 使用 libavformat 不播放【英文标题】:H.264 muxed to MP4 using libavformat not playing back 【发布时间】:2013-02-22 04:40:44 【问题描述】:我正在尝试将 H.264 数据复用到 MP4 文件中。将此 H.264 Annex B 数据保存到 MP4 文件中似乎没有错误,但文件无法播放。
我对文件进行了二进制比较,问题似乎出在写入 MP4 文件的页脚(预告片)的内容中。
我怀疑它必须与创建流的方式有关。
初始化:
AVOutputFormat* fmt = av_guess_format( 0, "out.mp4", 0 );
oc = avformat_alloc_context();
oc->oformat = fmt;
strcpy(oc->filename, filename);
我拥有的这个原型应用程序的一部分是为每个 IFrame 创建一个 png 文件。所以当遇到第一个 IFrame 时,我会创建视频流并写入 av 标头等:
void addVideoStream(AVCodecContext* decoder)
videoStream = av_new_stream(oc, 0);
if (!videoStream)
cout << "ERROR creating video stream" << endl;
return;
vi = videoStream->index;
videoContext = videoStream->codec;
videoContext->codec_type = AVMEDIA_TYPE_VIDEO;
videoContext->codec_id = decoder->codec_id;
videoContext->bit_rate = 512000;
videoContext->width = decoder->width;
videoContext->height = decoder->height;
videoContext->time_base.den = 25;
videoContext->time_base.num = 1;
videoContext->gop_size = decoder->gop_size;
videoContext->pix_fmt = decoder->pix_fmt;
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
videoContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
av_dump_format(oc, 0, filename, 1);
if (!(oc->oformat->flags & AVFMT_NOFILE))
if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0)
cout << "Error opening file" << endl;
avformat_write_header(oc, NULL);
我把数据包写出来:
unsigned char* data = block->getData();
unsigned char videoFrameType = data[4];
int dataLen = block->getDataLen();
// store pps
if (videoFrameType == 0x68)
if (ppsFrame != NULL)
delete ppsFrame; ppsFrameLength = 0; ppsFrame = NULL;
ppsFrameLength = block->getDataLen();
ppsFrame = new unsigned char[ppsFrameLength];
memcpy(ppsFrame, block->getData(), ppsFrameLength);
else if (videoFrameType == 0x67)
// sps
if (spsFrame != NULL)
delete spsFrame; spsFrameLength = 0; spsFrame = NULL;
spsFrameLength = block->getDataLen();
spsFrame = new unsigned char[spsFrameLength];
memcpy(spsFrame, block->getData(), spsFrameLength);
if (videoFrameType == 0x65 || videoFrameType == 0x41)
videoFrameNumber++;
if (videoFrameType == 0x65)
decodeIFrame(videoFrameNumber, spsFrame, spsFrameLength, ppsFrame, ppsFrameLength, data, dataLen);
if (videoStream != NULL)
AVPacket pkt = 0 ;
av_init_packet(&pkt);
pkt.stream_index = vi;
pkt.flags = 0;
pkt.pts = pkt.dts = 0;
if (videoFrameType == 0x65)
// combine the SPS PPS & I frames together
pkt.flags |= AV_PKT_FLAG_KEY;
unsigned char* videoFrame = new unsigned char[spsFrameLength+ppsFrameLength+dataLen];
memcpy(videoFrame, spsFrame, spsFrameLength);
memcpy(&videoFrame[spsFrameLength], ppsFrame, ppsFrameLength);
memcpy(&videoFrame[spsFrameLength+ppsFrameLength], data, dataLen);
// overwrite the start code (00 00 00 01 with a 32-bit length)
setLength(videoFrame, spsFrameLength-4);
setLength(&videoFrame[spsFrameLength], ppsFrameLength-4);
setLength(&videoFrame[spsFrameLength+ppsFrameLength], dataLen-4);
pkt.size = dataLen + spsFrameLength + ppsFrameLength;
pkt.data = videoFrame;
av_interleaved_write_frame(oc, &pkt);
delete videoFrame; videoFrame = NULL;
else if (videoFrameType != 0x67 && videoFrameType != 0x68)
// Send other frames except pps & sps which are caught and stored
pkt.size = dataLen;
pkt.data = data;
setLength(data, dataLen-4);
av_interleaved_write_frame(oc, &pkt);
终于关闭文件了:
av_write_trailer(oc);
int i = 0;
for (i = 0; i < oc->nb_streams; i++)
av_freep(&oc->streams[i]->codec);
av_freep(&oc->streams[i]);
if (!(oc->oformat->flags & AVFMT_NOFILE))
avio_close(oc->pb);
av_free(oc);
如果我单独获取 H.264 数据并进行转换:
ffmpeg -i recording.h264 -vcodec copy recording.mp4
文件的“页脚”除外。
我的程序的输出: readrec 录音.tcp out.mp4 **** 开始 **** 01-03-2013 14:26:01 180000 输出#0,mp4,到“out.mp4”: 流 #0:0:视频:h264、yuv420p、352x288、q=2-31、512 kb/s、90k tbn、25 tbc **** 结束 **** 01-03-2013 14:27:01 102000 写入了 1499 个视频帧。
如果我尝试使用 ffmpeg 转换使用 CODE 创建的 MP4 文件:
ffmpeg -i out.mp4 -vcodec copy out2.mp4
ffmpeg version 0.11.1 Copyright (c) 2000-2012 the FFmpeg developers
built on Mar 7 2013 12:49:22 with suncc 0x5110
configuration: --extra-cflags=-KPIC -g --disable-mmx
--disable-protocol=udp --disable-encoder=nellymoser --cc=cc --cxx=CC
libavutil 51. 54.100 / 51. 54.100
libavcodec 54. 23.100 / 54. 23.100
libavformat 54. 6.100 / 54. 6.100
libavdevice 54. 0.100 / 54. 0.100
libavfilter 2. 77.100 / 2. 77.100
libswscale 2. 1.100 / 2. 1.100
libswresample 0. 15.100 / 0. 15.100
h264 @ 12eaac0] no frame!
Last message repeated 1 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 23 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 74 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 64 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 34 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 49 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 24 times
[h264 @ 12eaac0] Partitioned H.264 support is incomplete
[h264 @ 12eaac0] no frame!
Last message repeated 23 times
[h264 @ 12eaac0] sps_id out of range
[h264 @ 12eaac0] no frame!
Last message repeated 148 times
[h264 @ 12eaac0] sps_id (32) out of range
Last message repeated 1 times
[h264 @ 12eaac0] no frame!
Last message repeated 33 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 128 times
[h264 @ 12eaac0] sps_id (32) out of range
Last message repeated 1 times
[h264 @ 12eaac0] no frame!
Last message repeated 3 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 3 times
[h264 @ 12eaac0] slice type too large (0) at 0 0
[h264 @ 12eaac0] decode_slice_header error
[h264 @ 12eaac0] no frame!
Last message repeated 309 times
[h264 @ 12eaac0] sps_id (32) out of range
Last message repeated 1 times
[h264 @ 12eaac0] no frame!
Last message repeated 192 times
[h264 @ 12eaac0] Partitioned H.264 support is incomplete
[h264 @ 12eaac0] no frame!
Last message repeated 73 times
[h264 @ 12eaac0] sps_id (32) out of range
Last message repeated 1 times
[h264 @ 12eaac0] no frame!
Last message repeated 99 times
[h264 @ 12eaac0] sps_id (32) out of range
Last message repeated 1 times
[h264 @ 12eaac0] no frame!
Last message repeated 197 times
[mov,mp4,m4a,3gp,3g2,mj2 @ 12e3100] decoding for stream 0 failed
[mov,mp4,m4a,3gp,3g2,mj2 @ 12e3100] Could not find codec parameters
(Video: h264 (avc1 / 0x31637661), 393539 kb/s)
out.mp4: could not find codec parameters
我真的不知道问题出在哪里,除非它与流的设置方式有关。我查看了其他人正在做类似事情的一些代码,并尝试在设置流时使用此建议,但无济于事!
给我一个 H.264/AAC 混合(同步)文件的最终代码如下。首先是一些背景信息。数据来自 IP 摄像机。数据通过第 3 方 API 以视频/音频数据包的形式呈现。视频包以 RTP 有效载荷数据(无标头)的形式呈现,由 NALU 组成,这些 NALU 被重构并转换为附件 B 格式的 H.264 视频。 AAC 音频以原始 AAC 形式呈现,并转换为 adts 格式以启用播放。这些数据包已被放入比特流格式,允许传输时间戳(自 1970 年 1 月 1 日以来为 64 位毫秒)以及其他一些内容。
这或多或少是一个原型,在任何方面都不干净。它可能泄漏得很糟糕。不过,我确实希望这能帮助其他人尝试实现与我相似的目标。
全局:
AVFormatContext* oc = NULL;
AVCodecContext* videoContext = NULL;
AVStream* videoStream = NULL;
AVCodecContext* audioContext = NULL;
AVStream* audiostream = NULL;
AVCodec* videoCodec = NULL;
AVCodec* audioCodec = NULL;
int vi = 0; // Video stream
int ai = 1; // Audio stream
uint64_t firstVideoTimeStamp = 0;
uint64_t firstAudioTimeStamp = 0;
int audioStartOffset = 0;
char* filename = NULL;
Boolean first = TRUE;
int videoFrameNumber = 0;
int audioFrameNumber = 0;
主要:
int main(int argc, char* argv[])
if (argc != 3)
cout << argv[0] << " <stream playback file> <output mp4 file>" << endl;
return 0;
char* input_stream_file = argv[1];
filename = argv[2];
av_register_all();
fstream inFile;
inFile.open(input_stream_file, ios::in);
// Used to store the latest pps & sps frames
unsigned char* ppsFrame = NULL;
int ppsFrameLength = 0;
unsigned char* spsFrame = NULL;
int spsFrameLength = 0;
// Setup MP4 output file
AVOutputFormat* fmt = av_guess_format( 0, filename, 0 );
oc = avformat_alloc_context();
oc->oformat = fmt;
strcpy(oc->filename, filename);
// Setup the bitstream filter for AAC in adts format. Could probably also achieve
// this by stripping the first 7 bytes!
AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("aac_adtstoasc");
if (!bsfc)
cout << "Error creating adtstoasc filter" << endl;
return -1;
while (inFile.good())
TcpAVDataBlock* block = new TcpAVDataBlock();
block->readStruct(inFile);
DateTime dt = block->getTimestampAsDateTime();
switch (block->getPacketType())
case TCP_PACKET_H264:
if (firstVideoTimeStamp == 0)
firstVideoTimeStamp = block->getTimeStamp();
unsigned char* data = block->getData();
unsigned char videoFrameType = data[4];
int dataLen = block->getDataLen();
// pps
if (videoFrameType == 0x68)
if (ppsFrame != NULL)
delete ppsFrame; ppsFrameLength = 0;
ppsFrame = NULL;
ppsFrameLength = block->getDataLen();
ppsFrame = new unsigned char[ppsFrameLength];
memcpy(ppsFrame, block->getData(), ppsFrameLength);
else if (videoFrameType == 0x67)
// sps
if (spsFrame != NULL)
delete spsFrame; spsFrameLength = 0;
spsFrame = NULL;
spsFrameLength = block->getDataLen();
spsFrame = new unsigned char[spsFrameLength];
memcpy(spsFrame, block->getData(), spsFrameLength);
if (videoFrameType == 0x65 || videoFrameType == 0x41)
videoFrameNumber++;
// Extract a thumbnail for each I-Frame
if (videoFrameType == 0x65)
decodeIFrame(h264, spsFrame, spsFrameLength, ppsFrame, ppsFrameLength, data, dataLen);
if (videoStream != NULL)
AVPacket pkt = 0 ;
av_init_packet(&pkt);
pkt.stream_index = vi;
pkt.flags = 0;
pkt.pts = videoFrameNumber;
pkt.dts = videoFrameNumber;
if (videoFrameType == 0x65)
pkt.flags = 1;
unsigned char* videoFrame = new unsigned char[spsFrameLength+ppsFrameLength+dataLen];
memcpy(videoFrame, spsFrame, spsFrameLength);
memcpy(&videoFrame[spsFrameLength], ppsFrame, ppsFrameLength);
memcpy(&videoFrame[spsFrameLength+ppsFrameLength], data, dataLen);
pkt.data = videoFrame;
av_interleaved_write_frame(oc, &pkt);
delete videoFrame; videoFrame = NULL;
else if (videoFrameType != 0x67 && videoFrameType != 0x68)
pkt.size = dataLen;
pkt.data = data;
av_interleaved_write_frame(oc, &pkt);
break;
case TCP_PACKET_AAC:
if (firstAudioTimeStamp == 0)
firstAudioTimeStamp = block->getTimeStamp();
uint64_t millseconds_difference = firstAudioTimeStamp - firstVideoTimeStamp;
audioStartOffset = millseconds_difference * 16000 / 1000;
cout << "audio offset: " << audioStartOffset << endl;
if (audioStream != NULL)
AVPacket pkt = 0 ;
av_init_packet(&pkt);
pkt.stream_index = ai;
pkt.flags = 1;
pkt.pts = audioFrameNumber*1024;
pkt.dts = audioFrameNumber*1024;
pkt.data = block->getData();
pkt.size = block->getDataLen();
pkt.duration = 1024;
AVPacket newpacket = pkt;
int rc = av_bitstream_filter_filter(bsfc, audioContext,
NULL,
&newpacket.data, &newpacket.size,
pkt.data, pkt.size,
pkt.flags & AV_PKT_FLAG_KEY);
if (rc >= 0)
//cout << "Write audio frame" << endl;
newpacket.pts = audioFrameNumber*1024;
newpacket.dts = audioFrameNumber*1024;
audioFrameNumber++;
newpacket.duration = 1024;
av_interleaved_write_frame(oc, &newpacket);
av_free_packet(&newpacket);
else
cout << "Error filtering aac packet" << endl;
break;
case TCP_PACKET_START:
break;
case TCP_PACKET_END:
break;
delete block;
inFile.close();
av_write_trailer(oc);
int i = 0;
for (i = 0; i < oc->nb_streams; i++)
av_freep(&oc->streams[i]->codec);
av_freep(&oc->streams[i]);
if (!(oc->oformat->flags & AVFMT_NOFILE))
avio_close(oc->pb);
av_free(oc);
delete spsFrame; spsFrame = NULL;
delete ppsFrame; ppsFrame = NULL;
cout << "Wrote " << videoFrameNumber << " video frames." << endl;
return 0;
添加流流/编解码器并在名为 addVideoAndAudioStream() 的函数中创建标头。这个函数是从 decodeIFrame() 调用的,所以有一些假设(不一定很好) 1.先有视频包 2. AAC 存在
decodeIFrame 是一种单独的原型,我在其中为每个 I Frame 创建缩略图。生成缩略图的代码来自:https://gnunet.org/svn/Extractor/src/plugins/thumbnailffmpeg_extractor.c
decodeIFrame 函数将一个 AVCodecContext 传递给 addVideoAudioStream:
void addVideoAndAudioStream(AVCodecContext* decoder = NULL)
videoStream = av_new_stream(oc, 0);
if (!videoStream)
cout << "ERROR creating video stream" << endl;
return;
vi = videoStream->index;
videoContext = videoStream->codec;
videoContext->codec_type = AVMEDIA_TYPE_VIDEO;
videoContext->codec_id = decoder->codec_id;
videoContext->bit_rate = 512000;
videoContext->width = decoder->width;
videoContext->height = decoder->height;
videoContext->time_base.den = 25;
videoContext->time_base.num = 1;
videoContext->gop_size = decoder->gop_size;
videoContext->pix_fmt = decoder->pix_fmt;
audioStream = av_new_stream(oc, 1);
if (!audioStream)
cout << "ERROR creating audio stream" << endl;
return;
ai = audioStream->index;
audioContext = audioStream->codec;
audioContext->codec_type = AVMEDIA_TYPE_AUDIO;
audioContext->codec_id = CODEC_ID_AAC;
audioContext->bit_rate = 64000;
audioContext->sample_rate = 16000;
audioContext->channels = 1;
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
videoContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
audioContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
av_dump_format(oc, 0, filename, 1);
if (!(oc->oformat->flags & AVFMT_NOFILE))
if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0)
cout << "Error opening file" << endl;
avformat_write_header(oc, NULL);
据我所知,一些假设似乎并不重要,例如: 1. 比特率。实际视频比特率约为 262k,而我指定为 512kbit 2.AAC频道。我指定了单声道,虽然实际输出是来自内存的立体声
您仍然需要知道视频和音频的帧速率(时基)。
与许多其他示例相反,在视频包上设置 pts 和 dts 时,它无法播放。我需要知道时基(25fps),然后根据该时基设置 pts 和 dts,即第一帧 = 0(PPS,SPS,I),第二帧 = 1(中间帧,不管它叫什么;)) .
AAC 我还必须假设它是 16000 赫兹。每个 AAC 数据包 1024 个样本(我认为您也可以使用 AAC @ 960 个样本)来确定音频“偏移量”。我将此添加到 pts & dts。所以 pts/dts 是要回放的样本号。您还需要确保在写入之前在数据包中设置了 1024 的持续时间。
--
我今天还发现Annex B 与任何其他播放器都不兼容,所以应该使用AVCC 格式。
这些网址有帮助: Problem to Decode H264 video over RTP with ffmpeg (libavcodec) http://aviadr1.blogspot.com.au/2010/05/h264-extradata-partially-explained-for.html
在构建视频流的时候,我填写了extradata&extradata_size:
// Extradata contains PPS & SPS for AVCC format
int extradata_len = 8 + spsFrameLen-4 + 1 + 2 + ppsFrameLen-4;
videoContext->extradata = (uint8_t*)av_mallocz(extradata_len);
videoContext->extradata_size = extradata_len;
videoContext->extradata[0] = 0x01;
videoContext->extradata[1] = spsFrame[4+1];
videoContext->extradata[2] = spsFrame[4+2];
videoContext->extradata[3] = spsFrame[4+3];
videoContext->extradata[4] = 0xFC | 3;
videoContext->extradata[5] = 0xE0 | 1;
int tmp = spsFrameLen - 4;
videoContext->extradata[6] = (tmp >> 8) & 0x00ff;
videoContext->extradata[7] = tmp & 0x00ff;
int i = 0;
for (i=0;i<tmp;i++)
videoContext->extradata[8+i] = spsFrame[4+i];
videoContext->extradata[8+tmp] = 0x01;
int tmp2 = ppsFrameLen-4;
videoContext->extradata[8+tmp+1] = (tmp2 >> 8) & 0x00ff;
videoContext->extradata[8+tmp+2] = tmp2 & 0x00ff;
for (i=0;i<tmp2;i++)
videoContext->extradata[8+tmp+3+i] = ppsFrame[4+i];
写出帧时,不要在前面加上 SPS 和 PPS 帧,只写出 I 帧和 P 帧。另外,将前4个字节(0x00 0x00 0x00 0x01)中的附件B起始码替换为I/P帧的大小。
【问题讨论】:
为什么要把SPS+PPS+I-frame结合起来写呢?此外,setLength()
函数可能是负责任的,但如果您的二进制文件与 ffmpeg 命令行重新混合的输出比较显示流中没有差异,则这不太可能。
将 SPS 和 PPS 与 I Frame 结合在一起是事后才想到的。我最初将它们分开,然后它也不起作用。我将它们结合起来是因为当我在 iframe 上进行解码时,它需要 sps 和 pps 才能解码,并且它没有将它们分开。 setLength() 只是用 32 位长度替换起始代码,正如你所说,在页脚之前没有什么不同。
解码器可以将 SPS 和 PPS 结合使用,但对于 muxer(mp4 格式)可能会很危险。我也相信,当你将切片发送到 muxer 时,你应该去掉 NALU 标头。
没有任何 NALU 标头,因为它已被剥离。发送给解码器的格式是附件B。附件B起始码(4字节0x00 0x00 0x00 0x01)被替换为H.264数据包的长度,这与我在ffmpeg创建的MP4中看到的格式一致.我会将代码改回不将 SPS 和 PPS 与 MP4 文件的 I 帧结合起来。即使以这种方式创建 MP4 文件,它仍然无法正常播放/解码或使用 ffmpeg 处理。
好的,我终于有结果了。我决定使用 av_read_packet 从文件中读取 h264 流并使用相同的方法将其写出,因此不会更改数据包等。av_read_packet 从 h.264 文件中读取 SPS + PPS + I 帧。我把它写到 MP4 文件中。这是可以玩的。我不认为你可以在 MP4 中嵌入附件 B 格式,但这证明我错了。十六进制转储显示 00 00 00 01 起始码。我删除了“setLength”以保留起始代码,这有效,但是,我确实需要设置一个 pts/dts 值,否则它只会播放一秒钟。
【参考方案1】:
请让我总结一下:您的(原始)代码的问题是av_interleaved_write_frame()
的输入不应以数据包长度开头。如果您不去除00 00 00 01
开始代码,该文件可能仍然可以播放,但恕我直言是播放器的一种恢复行为,我不会指望这一点。
【讨论】:
那种。使用 ffmpeg 进行转换,即 ffmpeg -i file.h264 -vcodec copy out.mp4 似乎将起始码(00 00 00 01)替换为帧的长度。至少这与文件的十六进制转储一致。通过尝试从代码中做同样的事情,由于某种原因没有工作。它必须与流的设置方式甚至 pts/dts 值有关,我不确定。但是,不剥离肯定会修复它。我应该粘贴我刚刚显示的最终结果的代码,因为我确实在 MP4 文件中同步了视频和音频。我会在几个小时后上传。 感谢您的帮助,亚历克斯。您关于起始代码的 cmets 让我想从不同的角度看待它,所以我将其作为答案。以上是关于H.264 复用到 MP4 使用 libavformat 不播放的主要内容,如果未能解决你的问题,请参考以下文章
FFMPEG 如何将 MJPEG 编码数据复用到 mp4 或 avi 容器 c++
使用 ffmpeg 将视频转换为 MP4 (H.264/AAC)