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)

h.264视频编码加生成QT7可播放的mp4文件

如何从 H.264 帧和音频帧的集合创建 mp4 文件?

哪些用户代理支持 HTML5 <video> 标签中的 mp4/H.264 视频?

Nodejs将h264/h265转码成mp4或某一帧图片