[解决思路]关于h264裸流合成mp4时时间戳添加问题

这篇具有很好参考价值的文章主要介绍了[解决思路]关于h264裸流合成mp4时时间戳添加问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题场景:

        使用GPU编码(Opencv)生成的h264视频片段中不包含时间戳信息,且含有B帧,直接合成mp4后会导致播放出现问题(瞬间播放完成)。因此,在合成时需要手动添加时间戳。

心路历程:

        发现生成的视频会瞬间播放完成后,意识到是时间戳的问题,检查时间戳代码:

while (av_read_frame(inputFormatContext, &packet) >= 0) {
    if (packet.pts == AV_NOPTS_VALUE){
        // 判断pts是否为空
        // 添加时间戳... 
    }
}

        一开始没注意到有B帧,想着把pts、dts设置相等,并且按照间隔递增就可以了:

AVStream *inStream = inputFormatContext->streams[packet.stream_index];
AVStream *outStream = outputFormatContext->streams[packet.stream_index];

// 计算两帧之间的间隔,以输入流的时间基计算
int64_t frameDuration = av_rescale_q(1, av_inv_q(inStream->time_base), inStream->r_frame_rate);
// 转换时间基为输出流的时间基
int64_t _t = av_rescale_q(frameDuration, inStream->time_base, outStream->time_base);

// 当前帧的时间戳等于上一帧的时间戳加上两帧之间的间隔
packet.pts = last_pts + _t;
// dts设置成一样
packet.dts = packet.pts;

        对于只有I和P帧的h264流,上述方法可以领流正常播放,但如果h264中有B帧,则会导致在某些播放器上播放异常——帧显示顺序错误,物体“颤抖”着移动

        对于含有B帧的视频,其dts和pts应该是按照这种顺序进行的:

I B P B P B P
dts 1 2 3 4 5 6 7
pts 1 3 2 5 4 7 6

        注:dts需要递增,解码需要按照顺序解码。任何一帧的pts应大于等于dts,因为需要先解码后才能显示。因此上述需要调整:

I B P B P B P
dts 1 2 3 4 5 6 7
pts 2 4 3 6 5 8 7

        为了使视频打开时就有画面能播放,因此需要将第一帧的pts调整为0,dts是可以为负数的,但需要保持递增。调整后:

I B P B P B P
dts -1 0 1 2 3 4 5
pts 0 2 1 4 3 6 5

        以上是最简单的例子,也可以推导出变形:

I B B P B B P
dts -2 -1 0 1 2 3 4
pts 0 2 3 1 5 6 4

        因此,时间戳的规律和我们的视频中有多少连续的B帧有关

        以上讨论的假设都是一个帧组中只有一个I帧的情况下,如果一个GOP中有多个I帧时情况会更复杂一点(没做整理,本文暂不讨论):

I(IDR) B P B P B I B P B P I(IDR) ...
dts 1 2 3 4 5 6 7 8 9 10 11 12
pts 1 3 2 5 4 7 6 9 8 11 10 12

        总结:加时间戳的前提是要知道h264文件帧的类型结构(是否有B帧、连续的B帧数量、一个GOP是否有多个I帧)

后记:

        由于我的文件都是我自己生成的,帧结构都是统一的,因此可以轻松的找出添加时间戳的规律。但对于不同帧结构的原始流,找出统一规律还是比较麻烦,本文不再阐述。

        附对于最简单的含有B帧的处理代码(IBPBPBPBP):

// 将多个h264文件合并成mp4文件
int mutexMp4File(){
    // 创建输出文件   
    // m_outputFile:输出文件名 xxx.mp4
    AVFormatContext* outputFormatContext = nullptr;
    if (avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, m_outputFile.c_str()) < 0) {
        return -1;
    }

    // 准备工作:遍历输入文件列表   
    // m_inputFiles:输入文件名列表
    for (const auto& inputFile : m_inputFiles) {
        // 打开输入文件
        AVFormatContext* inputFormatContext = nullptr;
        if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
            return -1;
        }

        // 查找输入文件的流信息
        if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
            avformat_close_input(&inputFormatContext);
            avformat_free_context(outputFormatContext);
            return -1;
        }

        // 复制输入文件的流到输出文件
        for (unsigned int i = 0; i < inputFormatContext->nb_streams; ++i) {
            // 获取输入流
            AVStream* inputStream = inputFormatContext->streams[i];
            // 创建输出流
            AVStream* outputStream = avformat_new_stream(outputFormatContext, nullptr);
            if (outputStream == nullptr) {
                avformat_close_input(&inputFormatContext);
                avformat_free_context(outputFormatContext);
                return -1;
            }
            // 复制输入流的编解码器参数到输出流
            if (avcodec_parameters_copy(outputStream->codecpar, inputStream->codecpar) < 0) {
                avformat_close_input(&inputFormatContext);
                avformat_free_context(outputFormatContext);
                return -1;
            }
            // 设置输出流的编解码器标志为"copy"
            outputStream->codecpar->codec_tag = 0;
        }

        // 关闭输入文件
        avformat_close_input(&inputFormatContext);
    }

    // 打开输出文件
    AVIOContext* outputIOContext = nullptr;
    if (avio_open(&outputFormatContext->pb, m_outputFile.c_str(), AVIO_FLAG_WRITE) < 0) {
        avformat_free_context(outputFormatContext);
        return -1;
    }

    // 写入文件头部
    if (avformat_write_header(outputFormatContext, nullptr) < 0) {
        avformat_free_context(outputFormatContext);
        return -1;
    }

    int64_t gdts_notime = 0;//拼接多个文件的时间戳

    // 遍历输入文件列表,写入数据到输出文件
    for (const auto& inputFile : m_inputFiles) {
        // 打开输入文件
        AVFormatContext* inputFormatContext = nullptr;
        if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
            avformat_free_context(outputFormatContext);
            return -1;
        }
        // 查找输入文件的流信息
        if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
            avformat_close_input(&inputFormatContext);
            avformat_free_context(outputFormatContext);
            return -1;
        }

        // 从输入文件读取数据并写入输出文件
        AVPacket packet;
        int64_t p_max_dts = 0;
        int i = 0;
        while (av_read_frame(inputFormatContext, &packet) >= 0) {
            AVStream *inStream = inputFormatContext->streams[packet.stream_index];
            AVStream *outStream = outputFormatContext->streams[packet.stream_index];
            if (packet.pts == AV_NOPTS_VALUE){
                // 起始 只能针对 IBPBPBP 连续B帧为1的流
                int nalu_type = 0,startIndex = 0;
                if (packet.data[0]==0x00 && packet.data[1]==0x00 && packet.data[2]==0x01){
                    nalu_type = 0x1f & packet.data[3];
                    startIndex = 3;
                }else if(packet.data[0]==0x00 && packet.data[1]==0x00 && packet.data[2]==0x00 && packet.data[3]==0x01){
                    nalu_type = 0x1f & packet.data[4];
                    startIndex = 4;
                }
                
                int64_t frameDuration = av_rescale_q(1, av_inv_q(inStream->time_base), inStream->r_frame_rate);
                int64_t _t = av_rescale_q(frameDuration, inStream->time_base, outStream->time_base);

                p_max_dts = _t*(i+1);
                packet.dts = p_max_dts + gdts_notime - _t;
                int xt = (int)packet.data[startIndex+1] & 0xC0;
                if (nalu_type == 0x05 || nalu_type == 0x06 || nalu_type == 0x07 || nalu_type == 0x08){ //I帧开头
                    packet.pts = packet.dts + _t;
                }else if(nalu_type == 0x01 && xt == 0x80){ //P帧
                    packet.pts = packet.dts;
                }else if(nalu_type == 0x01 && xt == 0xC0){ //B帧
                    packet.pts = packet.dts + _t*2;
                }else{
                    // Error!
                    packet.pts = packet.dts;
                }

            }else{
                // Error!
            }
            
            // 将包的流索引设置为输出流索引
            packet.stream_index = inStream->index;
            // 写入输出文件
            av_interleaved_write_frame(outputFormatContext, &packet);
            av_packet_unref(&packet);
            i++;
        }
        gdts_notime += p_max_dts;
        // 关闭输入文件
        avformat_close_input(&inputFormatContext);
    }
    // 写入文件尾部
    av_write_trailer(outputFormatContext);
    // 关闭输出文件
    avio_close(outputFormatContext->pb);
    // 释放资源
    avformat_free_context(outputFormatContext);
    return 0;
}

        代码中的求帧类型的方法是旁门左道且有针对性的,可能不适合其他场景。

       

        对于文中任何错误,欢迎指正。文章来源地址https://www.toymoban.com/news/detail-811101.html

到了这里,关于[解决思路]关于h264裸流合成mp4时时间戳添加问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • wireshark导出H264裸流

    下载抓包工具:首先,您需要下载并安装一个网络抓包工具,例如Wireshark(https://www.wireshark.org)或tcpdump(https://www.tcpdump.org)。这些工具可用于捕获网络流量并保存为pcap或pcapng文件。在这里插入代码片 打开抓包工具:打开所选择的抓包工具,并启动网络监控。 下载rtp_h264

    2024年02月15日
    浏览(38)
  • FFmpeg解码H264视频裸流(直接可用)

    1、写在前面 此文章主要包含解码H264视频流数据,主要有以下几点: 1、H264视频帧为Annex B格式,若使用AVCC格式可自行研究; 2、H264视频裸流,非解码视频文件(若有需要我后期可添加这部分代码); 3、支持输出RGB24或YUV420格式,其他可自行修改; 4、FFmpeg官网代码迭代及接口变

    2024年04月25日
    浏览(41)
  • 9i物联网浏览器(cef_114.2.120&114.2.100,支持H264视频mp3,mp4)WinForm-CefSharp(5735分支)多媒体版本体验

    更新:2023.6.25 版本: Cef_114.2.110和114.2.100 +chromium- 114.0.5735.134 的32位和64位 说明:支持图片,mp3,mp4(H264)多媒体 测试环境:windows server 2019(其他环境没测,一般问题.NET4.5.2+及vc++运行库问题) 测试网址:www.html5test.com 声明 :本博是交流学习的内容,应用案例定制版浏览器

    2024年02月16日
    浏览(48)
  • Mp4文件提取详细H.264和MP3文件

    要将视频分开为H.264(视频编码)和MP3(音频编码)文件,你可以使用FFmpeg进行操作。以下是将视频和音频从一个视频文件中分开并保存为H.264和MP3文件的示例命令行操作: FFmpeg安装 这个命令将从 input.mp4 中提取视频流并将其编码为H.264,并将其保存为 output_video.mp4 。 -c:v li

    2024年02月09日
    浏览(53)
  • 关于Ubuntu python程序利用lixb264生成h264格式的视频相关问题

    从官网下载:https://www.nasm.us/pub/nasm/releasebuilds/2.13.03/ 安装包页面上的nasm-2.13.03.tar.bz2 x264库下载地址:http://www.videolan.org/developers/x264.html 下载 x264-master.tar.bz2 利用命令 x264 --version 查看是否配置成功 从官网下载ffmpeg ffmpeg-snapshot.tar.bz2 sudo ldconfig 若转出视频为h.264格式则说明成功

    2023年04月08日
    浏览(89)
  • 使用FFMPEG库封装264视频和acc音频数据到MP4文件中

    ffmepeg 4.4 一段H264的视频文件 一段acc格式的音频文件 1.使用avformat_open_input分别打开视频和音频文件,初始化其AVFormatContext,使用avformat_find_stream_info获取编码器基本信息 2.使用avformat_alloc_output_context2初始化输出的AVFormatContext结构 3.使用函数avformat_new_stream给输出的AVFormatContext结

    2024年02月11日
    浏览(44)
  • FFmpeg从入门到入魔(3):提取MP4中的H.264和AAC

        最近在开发中遇到了一个问题,即无法提取到MP4中H264流的关键帧进行处理,且保存到本地的AAC音频也无法正常播放。经过调试分析发现,这是由于解封装MP4得到的H264和AAC是ES流,它们缺失解码时必要的 起始码 / SPS / PPS 和 adts头 。 1. MP4格式解析 1.1 MP4简介  MP4封装格式是

    2023年04月16日
    浏览(43)
  • 使用FFMPEG分离mp4/flv文件中的264视频和aac音频

    ffmpeg 4.4 一个MP4或flv格式的视频文件 大致分为以下几个简单步骤: 1.使用avformat_open_input 函数打开文件并初始化结构AVFormatContext 2.查找是否存在音频和视频信息 3.构建一个h264_mp4toannexb比特流的过滤器,用来给视频avpaket包添加头信息 4.打开2个输出文件(音频, 视频) 5.循环读

    2024年02月15日
    浏览(44)
  • OpenCV 报错:FFMPEG: tag 0x34363258/‘X264‘ is not supported with codec id 27 and format ‘mp4 / MP4‘

    首先说一下报错的地方,是在使用VideoWriter保存视频时: 出现如下错误: 经过查找网上资料,发现是cv2.VideoWriter_fourcc()参数存在问题, 解决方法: 将 修改为: 即可完美解决问题。

    2024年02月07日
    浏览(65)
  • 关于FFmpeg将m3u8合并成mp4之后时长不对的解决方法

    导致时长不对的合并方法 concat方法会导致合并的视频时长有问题 时长正确的合并方法 使用读取m3u8文件的方式合并,合并出的视频时长是正确的。(注意:生成本地m3u8文件的时候要注意ts文件是否在下载时已经解密。如果已经解密,则生成的m3u8文件中不需要#EXT-X-KEY。) 参考

    2024年02月11日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包