使用libx264

时间:2016-10-15 00:49:10

标签: c++ video ffmpeg libav libx264

Heyo人,

我正在尝试将H264格式的RTSP流转码/重新转换为仅包含H264视频流的MPEG4容器。基本上,网络摄像头输出到MP4容器中。

我可以使用以下代码生成编码不佳的MP4:

// Variables here for demo
AVFormatContext * video_file_output_format = nullptr;
AVFormatContext * rtsp_format_context = nullptr;
AVCodecContext * video_file_codec_context = nullptr;
AVCodecContext * rtsp_vidstream_codec_context = nullptr;
AVPacket packet = {0};
AVStream * video_file_stream = nullptr;
AVCodec * rtsp_decoder_codec = nullptr;
int errorNum = 0, video_stream_index = 0;
std::string outputMP4file = "D:\\somemp4file.mp4";

// begin
AVDictionary * opts = nullptr;
av_dict_set(&opts, "rtsp_transport", "tcp", 0);

if ((errorNum = avformat_open_input(&rtsp_format_context, uriANSI.c_str(), NULL, &opts)) < 0) {
    errOut << "Connection failed: avformat_open_input failed with error " << errorNum << ":\r\n" << ErrorRead(errorNum);
    TacticalAbort();
    return;
}

rtsp_format_context->max_analyze_duration = 50000;
if ((errorNum = avformat_find_stream_info(rtsp_format_context, NULL)) < 0) {
    errOut << "Connection failed: avformat_find_stream_info failed with error " << errorNum << ":\r\n" << ErrorRead(errorNum);
    TacticalAbort();
    return;
}

video_stream_index = errorNum = av_find_best_stream(rtsp_format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

if (video_stream_index < 0) {
    errOut << "Connection in unexpected state; made a connection, but there was no video stream.\r\n"
        "Attempts to find a video stream resulted in error " << errorNum << ": " << ErrorRead(errorNum);
    TacticalAbort();
    return;
}

rtsp_vidstream_codec_context = rtsp_format_context->streams[video_stream_index]->codec;

av_init_packet(&packet);

if (!(video_file_output_format = av_guess_format(NULL, outputMP4file.c_str(),  NULL))) {
    TacticalAbort();
    throw std::exception("av_guess_format");
}

if (!(rtsp_decoder_codec = avcodec_find_decoder(rtsp_vidstream_codec_context->codec_id))) {
    errOut << "Connection failed: connected, but avcodec_find_decoder returned null.\r\n"
        "Couldn't find codec with an AV_CODEC_ID value of " << rtsp_vidstream_codec_context->codec_id << ".";
    TacticalAbort();
    return;
}

video_file_format_context = avformat_alloc_context();
video_file_format_context->oformat = video_file_output_format;

if (strcpy_s(video_file_format_context->filename, sizeof(video_file_format_context->filename), outputMP4file.c_str())) {
    errOut << "Couldn't open video file: strcpy_s failed with error " << errno << ".";
    std::string log = errOut.str();
    TacticalAbort();
    throw std::exception("strcpy_s");
}

if (!(video_file_encoder_codec = avcodec_find_encoder(video_file_output_format->video_codec))) {
    TacticalAbort();
    throw std::exception("avcodec_find_encoder");
}

// MARKER ONE

if (!outputMP4file.empty() &&
    !(video_file_output_format->flags & AVFMT_NOFILE) &&
    (errorNum = avio_open2(&video_file_format_context->pb, outputMP4file.c_str(), AVIO_FLAG_WRITE, nullptr, &opts)) < 0) {
    errOut << "Couldn't open video file \"" << outputMP4file << "\" for writing : avio_open2 failed with error " << errorNum << ": " << ErrorRead(errorNum);
    TacticalAbort();
    return;
}

// Create stream in MP4 file
if (!(video_file_stream = avformat_new_stream(video_file_format_context, video_file_encoder_codec))) {
    TacticalAbort();
    return;
}

AVCodecContext * video_file_codec_context = video_file_stream->codec;

// MARKER TWO

// error -22/-21 in avio_open2 if this is skipped
if ((errorNum = avcodec_copy_context(video_file_codec_context, rtsp_vidstream_codec_context)) != 0) {
    TacticalAbort();
    throw std::exception("avcodec_copy_context");
}

//video_file_codec_context->codec_tag = 0;

/*
// MARKER 3 - is this not needed? Examples suggest not.
if ((errorNum = avcodec_open2(video_file_codec_context, video_file_encoder_codec, &opts)) < 0)
{
    errOut << "Couldn't open video file codec context: avcodec_open2 failed with error " << errorNum << ": " << ErrorRead(errorNum);
    std::string log = errOut.str();
    TacticalAbort();
    throw std::exception("avcodec_open2, video file");
}*/

//video_file_format_context->flags |= AVFMT_FLAG_GENPTS;
if (video_file_format_context->oformat->flags & AVFMT_GLOBALHEADER)
{
    video_file_codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}

if ((errorNum = avformat_write_header(video_file_format_context, &opts)) < 0) {
    errOut << "Couldn't open video file: avformat_write_header failed with error " << errorNum << ":\r\n" << ErrorRead(errorNum);
    std::string log = errOut.str();
    TacticalAbort();
    return;
}

但是,有几个问题:

  1. 我无法将任何x264选项传递给输出文件。输出H264与输入H264的配置文件/电平相匹配 - 将摄像机切换到不同型号的H264电平。
  2. 输出文件的时间关闭,显着。
  3. 输出文件的持续时间大量关闭。虽然游戏时间不匹配,但几秒钟的镜头会变成几小时。 (FWIW,我正在使用VLC播放它们。)
  4. 传递x264选项

    如果我手动增加每个数据包的PTS,并将DTS设置为等于PTS,则它播放得太快,在一秒钟的播放时间内播放约2-3秒的素材,持续时间为数小时。这段录像也模糊了几秒钟,一秒钟内拍摄了大约10秒的镜头。

    如果我让FFMPEG决定(有或没有GENPTS标志),该文件具有可变的帧速率(可能与预期的一样),但它会立即播放整个文件并且持续时间也很长(超过40小时)几秒钟)。持续时间不是“真实的”,因为文件会在瞬间播放。

    在Marker One,我尝试通过将选项传递给avio_open2来设置个人资料。 libx264简单地忽略了这些选项。我试过了:

    av_dict_set(&opts, "vprofile", "main", 0);
    av_dict_set(&opts, "profile", "main", 0); // error, missing '('
    // FF_PROFILE_H264_MAIN equals 77, so I also tried
    av_dict_set(&opts, "vprofile", "77", 0); 
    av_dict_set(&opts, "profile", "77", 0);
    

    它似乎确实阅读了配置文件设置,但它不使用它们。在Marker Two,我尝试在avio_open2之后avformat_write_header之前设置它。

    // I tried all 4 av_dict_set from earlier, passing it to avformat_write_header.
    // None had any effect, they weren't consumed.
    av_opt_set(video_file_codec_context, "profile", "77", 0);
    av_opt_set(video_file_codec_context, "profile", "main", 0);
    video_file_codec_context->profile = FF_PROFILE_H264_MAIN;
    av_opt_set(video_file_codec_context->priv_data, "profile", "77", 0);
    av_opt_set(video_file_codec_context->priv_data, "profile", "main", 0);
    

    与privdata混淆使程序不稳定,但那时我正在尝试任何事情。 我想通过传递设置来解决问题1,因为我认为它是解决问题2或3的任何尝试的瓶颈。

    我已经在一个月的大部分时间里都在摆弄这个。我已经浏览了几十个文档,Q&amp; As,例子。很多人都过时了。

    任何帮助都将不胜感激。

    干杯

1 个答案:

答案 0 :(得分:3)

好的,首先,我没有使用ffmpeg,而是使用名为libav的ffmpeg分支。不要混淆,ffmpeg更新,并且在一些Linux发行版中使用了libav。

为Visual Studio编译

一旦我升级到主分支,我不得不再次手动编译它,因为我在Visual Studio中使用它,并且唯一的静态库是G ++,因此链接不能很好地工作。

官方指南是https://trac.ffmpeg.org/wiki/CompilationGuide/MSVC。 首先,编译如此工作正常: 确保VS处于PATH状态。您的PATH应按此顺序读取

C:\Program Files (x86)\Microsoft Visual Studio XX.0\VC\bin D:\MinGW\msys64\mingw32\bin D:\MinGW\msys64\usr\bin D:\MinGW\bin

然后运行CMD提示,不需要特殊类型 在CMD中,运行

(your path to MinGW)\msys64\msys2_shell.cmd -full-path

在创建的MinGW窗口中,运行:

$ cd /your dev path/ 然后

$ git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg 大约五分钟后,您将在子文件夹ffmpeg中拥有FFMPEG源代码。 通过以下方式访问来源:

$ cd ffmpeg

然后运行:

$ which link

如果它不提供来自PATH的VS路径,而是usr / link或usr / bin / link,则重命名为:

$ mv /usr/bin/link.exe /usr/bin/msys-link.exe

如果确实跳过$ mv步骤。

最后,运行此命令:

$ ./configure --toolchain=msvc以及您想要的其他cmdlines

它会长时间处于非活动状态。一旦完成,你将获得几页输出。

然后运行:

$ make

$ make install

即使你得到Windows DLL,它也会生成.a文件。只需重命名为.lib。

使用FFMPEG

要从FFMPEG RTSP 复制到文件,使用源配置文件,级别等,只需使用:

  1. 阅读网络框架av_read_frame(rtsp_format_context)
  2. 传递给MP4 av_write_frame(video_file_format_context)
  3. 您无需打开AVCodecContext,解码器或编码器;只是avformat_open_input,视频文件是AVFormatContext和AVIOContext。

    如果你想重新编码,你必须:

    1. 阅读网络框架av_read_frame(rtsp_format_context)
    2. 将数据包传递给解码器avcodec_send_packet(rtsp_decoder_context)
    3. 从解码器(循环中)avcodec_receive_frame(rtsp_decoder_context)
    4. 读取帧
    5. 将每个解码后的帧发送到编码器avcodec_send_frame(video_file_encoder_context)
    6. 从编码器(循环中)avcodec_receive_packet(video_file_encoder_context)
    7. 读取数据包
    8. 将每个编码数据包发送到输出视频av_write_frame(video_file_format_context)
    9. 一些陷阱

      • 手动复制宽度,高度和像素格式。对于H264,它是YUV420P。

        级别3.1的示例,配置文件高:

        AVCodecParameters * video_file_codec_params = video_file_stream->codecpar;
        video_file_codec_params->profile = FF_PROFILE_H264_HIGH;
        video_file_codec_params->format = AV_PIX_FMT_YUV420P;
        video_file_codec_params->level = 31;
        video_file_codec_params->width = rtsp_vidstream->codecpar->width;
        video_file_codec_params->height = rtsp_vidstream->codecpar->height;
        
      • libx264通过avcodec_open2中的opts参数接受H264预设。 “非常快”预设示例:

        AVDictionary * mydict;
        av_dict_set(&mydict, "preset", "veryfast", 0);
        avcodec_open2(video_file_encoder_context, video_file_encoder_codec, &opts)
        // avcodec_open2 returns < 0 for errors.
        // Recognised options will be removed from the mydict variable.
        // If all are recognised, mydict will be NULL.
        
      • 输出时间是一个不稳定的事情。在``。

        之前使用它
        video_file_stream->avg_frame_rate = rtsp_vidstream->avg_frame_rate;
        video_file_stream->r_frame_rate = rtsp_vidstream->r_frame_rate;
        video_file_stream->time_base = rtsp_vidstream->time_base;
        video_file_encoder_context->time_base = rtsp_vidstream_codec_context->time_base;
        // Decreasing GOP size for more seek positions doesn't end well.
        // libx264 forces the new GOP size.
        video_file_encoder_context->gop_size = rtsp_vidstream_codec_context->gop_size;
        if ((errorNum = avcodec_open2(video_file_encoder_context,...)) < 0) {
            // an error...
        }
        
      • H264可以双倍速度写入文件,因此播放速度加倍。要更改此设置,请手动编写数据包的时间:

        packet->pts = packet->dts = frameNum++;
        av_packet_rescale_ts(packet, video_file_encoder_context->time_base, video_file_stream->time_base);
        packet->pts *= 2;
        packet->dts *= 2;
        av_interleaved_write_frame(video_file_format_context, packet)
        // av_interleaved_write_frame returns < 0 for errors.
        

        注意我们将av_write_frame切换为av_interleaved_write_frame,并设置PTS和DTS。 frameNum应该是int64_t,并且应该从0开始(虽然这不是必需的)。

        另请注意,av_rescale_ts调用的参数是视频文件编码器上下文,视频文件流 - 不涉及RTSP。

      • VLC媒体播放器不会播放以4或更低的FPS编码的H.264流。 因此,如果您的RTSP流显示第一个解码帧并且从未进展,或者在视频结束前显示纯绿色,请确保您的FPS足够高。 (那是VLC v2.2.4)

      如果我在GitHub上发布,我会在这里标记一个链接。我也会根据要求发送源代码。