使用libavcodec将原始H264帧放入mpegts容器

36

我十分期望能得到以下问题的帮助:

我有一款带摄像头的小工具,生成 H264 压缩视频帧,并将这些帧发送到我的应用程序中。这些帧不在容器中,只是原始数据。

我想使用 ffmpeg 和 libav 函数创建一个视频文件,以便以后使用。

如果我对这些帧进行解码和编码,一切都正常,我可以获得一个有效的视频文件(解码/编码步骤是常规的 libav 命令,没有任何花哨的东西,我从万能的互联网上找到它们,非常可靠)... 但是,我会浪费很多时间来进行解码和编码,因此我想跳过此步骤,直接将帧放入输出流中。现在,问题来了。

这是我为生成编码所想出的代码:

AVFrame* picture;

avpicture_fill((AVPicture*) picture, (uint8_t*)frameData, 
                 codecContext->pix_fmt, codecContext->width,
                 codecContext->height);
int outSize = avcodec_encode_video(codecContext, videoOutBuf, 
                 sizeof(videoOutBuf), picture);
if (outSize > 0) 
{
    AVPacket packet;
    av_init_packet(&packet);
    packet.pts = av_rescale_q(codecContext->coded_frame->pts,
                  codecContext->time_base, videoStream->time_base);
    if (codecContext->coded_frame->key_frame) 
    {
        packet.flags |= PKT_FLAG_KEY;
    }
    packet.stream_index = videoStream->index;
    packet.data =  videoOutBuf;
    packet.size =  outSize;

    av_interleaved_write_frame(context, &packet);
    put_flush_packet(context->pb);
}

变量的含义如下:

frameData 是从摄像头获取并在先前步骤中进行解码的帧数据,而 videoOutBuf 则是一个普通的 uint8_t 缓冲区,用于存储这些数据。

我修改了应用程序,不再对帧进行解码,而是直接传递数据,如下:

    AVPacket packet;
    av_init_packet(&packet);

    packet.stream_index = videoStream->index;
    packet.data = (uint8_t*)frameData;
    packet.size = currentFrameSize;

    av_interleaved_write_frame(context, &packet);
    put_flush_packet(context->pb);

其中frameData是原始的H264视频帧,currentFrameSize是原始H264视频帧的大小,即每个帧从设备获取的字节数。

突然间应用程序不再正常工作了,生成的视频无法播放。这很明显是由于我没有为数据包设置正确的PTS。我的做法如下(我很绝望,你可以从这种做法中看出来:)

    packet.pts = timestamps[timestamp_counter ++];

在这里,timestamps实际上是由上述工作代码生成的PTS列表,并写入文件中(是的,你没看错,我记录了所有10分钟会话的PTS,并想要使用它们)。

应用程序仍然无法正常工作。

现在,我毫无头绪,所以问题来了:

我想使用libav函数创建一个“mpegts”视频流,在流中插入已编码的视频帧并创建一个视频文件。怎么做?

谢谢, f.

2个回答

28

我相信如果你设置以下内容,就能看到视频播放。

packet.flags |= AV_PKT_FLAG_KEY;
packet.pts = packet.dts = 0;

您应该根据h264数据包头设置packet.flags。您可以尝试这位堆栈溢出用户的建议,直接从流中提取。

如果您还要添加音频,则pts / dts将变得更加重要。我建议您学习这个教程

编辑

我抽出了从我的测试应用程序中起作用的内容。由于某种原因,对我来说,dts / pts值为零有效,但除0或AV_NOPTS_VALUE之外的值无效。我想知道我们是否有不同版本的ffmpeg。我拥有来自git://git.videolan.org/ffmpeg.git的最新版本。

fftest.cpp

#include <string>

#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif

//#define _M
#define _M printf( "%s(%d) : MARKER\n", __FILE__, __LINE__ )

extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
};


AVFormatContext *fc = 0;
int vi = -1, waitkey = 1;

// < 0 = error
// 0 = I-Frame
// 1 = P-Frame
// 2 = B-Frame
// 3 = S-Frame
int getVopType( const void *p, int len )
{   
    if ( !p || 6 >= len )
        return -1;

    unsigned char *b = (unsigned char*)p;

    // Verify NAL marker
    if ( b[ 0 ] || b[ 1 ] || 0x01 != b[ 2 ] )
    {   b++;
        if ( b[ 0 ] || b[ 1 ] || 0x01 != b[ 2 ] )
            return -1;
    } // end if

    b += 3;

    // Verify VOP id
    if ( 0xb6 == *b )
    {   b++;
        return ( *b & 0xc0 ) >> 6;
    } // end if

    switch( *b )
    {   case 0x65 : return 0;
        case 0x61 : return 1;
        case 0x01 : return 2;
    } // end switch

    return -1;
}

void write_frame( const void* p, int len )
{
    if ( 0 > vi )
        return;

    AVStream *pst = fc->streams[ vi ];

    // Init packet
    AVPacket pkt;
    av_init_packet( &pkt );
    pkt.flags |= ( 0 >= getVopType( p, len ) ) ? AV_PKT_FLAG_KEY : 0;   
    pkt.stream_index = pst->index;
    pkt.data = (uint8_t*)p;
    pkt.size = len;

    // Wait for key frame
    if ( waitkey )
        if ( 0 == ( pkt.flags & AV_PKT_FLAG_KEY ) )
            return;
        else
            waitkey = 0;

    pkt.dts = AV_NOPTS_VALUE;
    pkt.pts = AV_NOPTS_VALUE;

//  av_write_frame( fc, &pkt );
    av_interleaved_write_frame( fc, &pkt );
}

void destroy()
{
    waitkey = 1;
    vi = -1;

    if ( !fc )
        return;

_M; av_write_trailer( fc );

    if ( fc->oformat && !( fc->oformat->flags & AVFMT_NOFILE ) && fc->pb )
        avio_close( fc->pb ); 

    // Free the stream
_M; av_free( fc );

    fc = 0;
_M; 
}

int get_nal_type( void *p, int len )
{
    if ( !p || 5 >= len )
        return -1;

    unsigned char *b = (unsigned char*)p;

    // Verify NAL marker
    if ( b[ 0 ] || b[ 1 ] || 0x01 != b[ 2 ] )
    {   b++;
        if ( b[ 0 ] || b[ 1 ] || 0x01 != b[ 2 ] )
            return -1;
    } // end if

    b += 3;

    return *b;
}

int create( void *p, int len )
{
    if ( 0x67 != get_nal_type( p, len ) )
        return -1;

    destroy();

    const char *file = "test.avi";
    CodecID codec_id = CODEC_ID_H264;
//  CodecID codec_id = CODEC_ID_MPEG4;
    int br = 1000000;
    int w = 480;
    int h = 354;
    int fps = 15;

    // Create container
_M; AVOutputFormat *of = av_guess_format( 0, file, 0 );
    fc = avformat_alloc_context();
    fc->oformat = of;
    strcpy( fc->filename, file );

    // Add video stream
_M; AVStream *pst = av_new_stream( fc, 0 );
    vi = pst->index;

    AVCodecContext *pcc = pst->codec;
_M; avcodec_get_context_defaults2( pcc, AVMEDIA_TYPE_VIDEO );
    pcc->codec_type = AVMEDIA_TYPE_VIDEO;

    pcc->codec_id = codec_id;
    pcc->bit_rate = br;
    pcc->width = w;
    pcc->height = h;
    pcc->time_base.num = 1;
    pcc->time_base.den = fps;

    // Init container
_M; av_set_parameters( fc, 0 );

    if ( !( fc->oformat->flags & AVFMT_NOFILE ) )
        avio_open( &fc->pb, fc->filename, URL_WRONLY );

_M; av_write_header( fc );

_M; return 1;
}

int main( int argc, char** argv )
{
    int f = 0, sz = 0;
    char fname[ 256 ] = { 0 };
    char buf[ 128 * 1024 ];

    av_log_set_level( AV_LOG_ERROR );
    av_register_all();

    do
    {
        // Raw frames in v0.raw, v1.raw, v2.raw, ...
//      sprintf( fname, "rawvideo/v%lu.raw", f++ );
        sprintf( fname, "frames/frame%lu.bin", f++ );
        printf( "%s\n", fname );

        FILE *fd = fopen( fname, "rb" );
        if ( !fd )
            sz = 0;
        else
        {
            sz = fread( buf, 1, sizeof( buf ) - FF_INPUT_BUFFER_PADDING_SIZE, fd );
            if ( 0 < sz )
            {
                memset( &buf[ sz ], 0, FF_INPUT_BUFFER_PADDING_SIZE );          

                if ( !fc )
                    create( buf, sz );

                if ( fc )
                    write_frame( buf, sz );

            } // end if

            fclose( fd );

        } // end else

    } while ( 0 < sz );

    destroy();
}

抱歉,这个不起作用 :( [libx264 @ 0x7fb9500243e0] 无效的DTS:PTS小于DTS [mpegts @ 0x7fb950021650] 应用程序在流0中提供了无效的、非单调递增的dts给复用器:3600 >= 3600 [libx264 @ 0x7fb9500243e0] 非严格单调递增的PTS -9223372036854775808 <= -9223372036854775808 [libx264 @ 0x7fb9500243e0] 无效的DTS:PTS小于DTS - Ferenc Deak
我看到错误在 ./libavformat/utils.c:2965。你可以尝试使用 packet.pts = packet.dts = AV_NOPTS_VALUE 来避免这个错误。但是我现在意识到这可能仍然不足,因为你正在处理 raw H264 数据包。你的流中是否有所有头文件或只有 VOP ... 即 ... 你的所有帧是否都以 0x000001b6 开始?你能否发布一下你用于初始化视频流的代码? - bob2
0x00000167 是 SPS,0x00000168 是 PPS,整个内容(00 00 00 01 67 42 00 1E 8D 68 1E 0B FE 00 00 00 01 68 CE)应该设置在 extradata 字段中。如果你想压缩头部和几帧,我会对上面的代码进行更改。 - bob2
1
代码基本上按原样运行,只是文件读取缓冲区太小了。我拍到了你键盘的漂亮视频 :) 但是,关键帧检测并不完全正确,所以我进行了修改。我不确定FFMpeg中是否有内置函数来进行关键帧检测,这将很好。当我设置extradata时,显然我是错误的,因为FFMpeg仍然拒绝提取视频参数。我将在有时间时进一步研究此问题,因为我认为我自己最终也需要类似的功能。直接解码SEI将很麻烦。 - bob2
@user1058600 我想在我的iOS应用程序中记录H264数据。我能够成功播放来自DVR的实时流。请帮助我解决这个问题。 - Khushboo
显示剩余4条评论

-10

你可以创建一个进程从控制台调用ffmpeg。

处理像000001.jpg、000002.jpg、000003.jpg等文件的命令行示例:

ffmpeg -i c:\frames\%06d.jpg -r 16 -vcodec mpeg4 -an -y c:\video\some_video.avi

来自ffmpeg文档的其他示例


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接