前言

使用ffmpeg编译好的程序在电脑上进行音视频转换,可以参考这篇:《windows电脑FFmpeg安装教程手把手详解_windows安装ffmpeg》,而我们要做的是在游戏引擎中集成ffmpeg源码用来录制游戏视频。

我们游戏目前只支持录制avi格式的视频,但是近期有个运营需求:在上架商品的时候在游戏内录制一段视频提供给网页端进行播放。

首先简单的了解了一下,ffmpeg是支持录制mp4格式的,于是简短地改了几行代码就实现了录制mp4,然后把录制出来的视频发给网页同学部署测试。

第二天收到反馈我们录制的视频无法在网页上播放,由于我也是第一次接触ffmpeg,不知道为什么mp4无法在浏览器上播放,整个过程就是不断通过chatgpt查阅资料,不断修改代码调试,最终在某个夜晚跑通了。

问题:浏览器上无法播放mp4

我们游戏录制出来的mp4,右键 - 打开方式,选择浏览器,或者直接拖动mp4文件到浏览器里面,让它打开,表现为无法播放

查看视频详细信息

通过ffmpeg工具提供的一些指令用来查看视频的详细信息,有助于调试

ffprobe -v quiet -print_format json -show_format -show_streams 你的文件名

ffprobe -v quiet -print_format json -show_format -show_streams video.mp4

查看视频每一帧的信息:

ffprobe -show_packets -of xml -i video.mp4

使用ffmpeg将mp4转为h264文件

我的第一个验证想法,使用ffmpeg把游戏录制的视频转换看看转换之后的视频是否在浏览器上播放,结果:转换后就可以在浏览器中就可以播放了

指令:ffmpeg -i video.mp4 -vcodec h264 -crf 10 test.mp4

说明:-crf 的数值是0~51,代表压缩等级,值越大画质越差,体积越小

使用h264编码

通过chatgpt查阅资料,了解到需要把mp4使用h264编码,于是就改了这个接口,这样来看格式虽然是h264了,但是仍然无法在网页上播放

avformat_alloc_output_context2(&oc, NULL, NULL, file_name);
//把第三个参数,输出格式强制指定为H264
avformat_alloc_output_context2(&oc, NULL, "h264", file_name);

对比差异

既然通过格式转换是可以播放的,那就对比一下两个视频文件的详细对比差异,差异如下所示:

可以播放的视频 游戏录制的视频
"codec_tag_string": "avc1", "codec_tag_string": "[0][0][0][0]",
"is_avc": "true", "is_avc": "false",

游戏录制的缺少了以下部分字段

"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 45824,
"duration": "3.580000",
"bit_rate": "2161030",
"nb_frames": "179",

手动设置codec_tag

一开始我的重点方向是:codec_tag

在 MP4 文件中,codec_tag 是一个用于标识视频和音频编解码器的标签。它通常是一个四个字母的代码,例如“avc1”表示 H.264 视频编解码器,“mp4a”表示 AAC 音频编解码器。codec_tag 可以帮助播放器确定正确的解码器来解码视频和音频流。在使用 MP4 文件时,确保你的播放器支持所使用的 codec_tag。

然后使用chatgpt查到示例代码加到游戏内但代码编译不通过,原因我们是自己编译的ffmpeg.lib,还需要修改export才能用某些接口,这个问题后面再说

但是手动设置tag之后,问题依赖存在

断点查证

在断点的时候发现调用:avcodec_find_encoder传入的格式并不是h264,而是mpeg4

于是手动在上面添加了一 行用来修改编码格式,但是还是一样的结果

其实在这个时候,我还是有些分不清楚codec_tag和codec的关系

查一些正确的示例

所以让chatgpt给我举例一些使用ffmpeg来编码h264的视频,然后对照我们的代码来分析是问题出在那里,后面了解到某位同事对ffmpeg比较懂,已于向他请教,大大加速了查证过程。

视频每一帧的数据中无pts和dst

ffprobe -show_packets -of xml -i video.mp4,使用这个指令来查看视频中每帧的数据,发现录制出来的视频没有pts和dst

<ffprobe>
Input #0, h264, from 'video.mp4':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuv420p(progressive), 1904x1002 [SAR 1:1 DAR 952:501], 25 fps, 100 tbr, 1200k tbn
<packets>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="34080" pos="0" flags="K__"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="4995" pos="34080" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="817" pos="39075" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="428" pos="39892" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="89" pos="40320" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="169" pos="40409" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="75" pos="40578" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="71" pos="40653" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="71" pos="40724" flags="___"/>
<packet codec_type="video" stream_index="0" duration="24000" duration_time="0.020000" size="672" pos="40795" flags="___"/>
</packets>
</ffprobe>

正常情况下每帧的视频数据中是有pts和dst的,于是在写入每帧数据时手动给pts和dst赋值

av_packet_rescale_ts(pkt, *time_base, st->time_base);
pkt->stream_index = st->index;
this->video_st.write_size += pkt->size;
int step = 1000 / this->fps;//ptk.pts每帧加的数值=1000/帧率
pkt->pts = step * m_frameNum;
pkt->dts = step * m_frameNum;
/* Write the compressed frame to the media file. */
return av_interleaved_write_frame(fmt_ctx, pkt);

在av_interleaved_write_frame之前写入pts,之后pts的值就无效了

手动计算pts播放起来非常卡

这样改完之后pts的值是有了,但是播放速度不正常,表现会非常卡,原因就是pts计算错误

assert中断

试过强制修改codec_id,但是碰到问题:在结束录制的时候会中多线程的Assert导致录制得到的视频数据是空的,代码:lib\cstdmf\concurrency.hpp void grab() MF_ASSERT(id_ != gid);

让ffmpeg自动选择输出格式

反复阅读代码然后不断尝试,发现在调用avformat_alloc_output_context2接口不指定格式,而只给输出文件的后缀,录制出来的视频是有pts和dts的!!!

这一下就回到最初的地方,在最早的时候我就是没有设置这个选项的。

接口定义和解释如下:

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename);

ctx:输出格式上下文的指针,函数执行成功后会将创建的上下文赋值给该指针。

oformat:输出格式,可以为 NULL,表示让 FFmpeg 自动选择输出格式。

format_name:输出格式的名称,可以为 NULL,表示让 FFmpeg 自动选择输出格式。

filename:输出文件名,可以为 NULL,表示不需要输出到文件。

pts有了但格式是mpeg4

然后修改接口,再次编译验证,这一次pts在视频帧数据中有了,但为啥视频格式会变成mpeg4???

测试了一下mpeg4在浏览器上也无法播放,但是可以在win10自带的播放上可以播放。

mp4好了但avi坏了

查看代码,确实有一处地方指定了mpeg4,代码:video_codec = avcodec_find_encoder(AV_CODEC_ID_MPEG4);

于是把它改为video_codec = avcodec_find_encoder(AV_CODEC_ID_H264);,这样终于好了。

再来验证一下以前的录制avi格式,结果发现坏了,以前的avi格式录制不了,断点一下,发现中上面提到的assert了。盲猜是avi不能使用h264,于是修改了一下mp4使用h264,其它格式使用mpeg4,再编译验证,这样就好了。

总结一下:

  1. 初始化avformat_alloc_output_context2不要指定格式,让ffmpeg自动调用,但需要输出的文件后缀为mp4
  2. 调用avcodec_find_encoder把mp4设置h246格式,但是对于avi改为AV_CODEC_ID_MPEG4
  3. 指定视频的文件头
  4. 其它地方不要再手动去修改codec_id,否则在结束录制的时候会出错,导致视频为空
  5. 多观察ffmpeg每一个接口的返回值,特别是非成功的情况下要进行处理

这几个接口需要关注返回值是否成功:

  1. avformat_alloc_output_context2 初始化
  2. avcodec_find_encoder 查找编码器的函数
  3. avformat_write_header 写入视频的header
  4. av_interleaved_write_frame 写入视频每一帧的数据
  5. av_write_trailer 结束录制

参考内容

ffmpeg实现将H264裸流封装成.mp4或.avi文件_ffmpeg对裸流封装-CSDN博客

[原]零基础学习视频解码之FFMpeg中比较重要的函数以及数据结构 - 雪夜&流星 - 博客园 (cnblogs.com)

FFmpeg从入门到入魔(3):提取MP4中的H.264和AAC - 掘金 (juejin.cn)

YUV编码为H264 H264封装为MP4 - 知乎 (zhihu.com)

使用工具

MP4封装格式—音视频基础知识 · FFmpeg原理 (xianwaizhiyin.net)

下载 Mp4 Explorer (apponic.com)

踩坑ffmpeg录制的mp4无法在浏览器上播放的更多相关文章

  1. 小程序踩坑记录-上传图片及canvas裁剪图片后上传至服务器

    最近在写微信小程序的上传图片功能,趟过了一些坑记录一下. 想要满足的需求是,从手机端上传图片至服务器,为了避免图片过大影响传输效率,需要把图片裁剪至适当大小后再传输 主要思路是,通过wx.choose ...

  2. 踩坑录- Spring Boot - CORS 跨域 - 浏览器同源策略

    1.解决办法,创建一个过滤器,处理所有response响应头 import java.io.IOException; import javax.servlet.Filter; import javax ...

  3. [转载]为什么有些MP4文件在Chrome浏览器上播放不了?

    http://blog.sina.com.cn/s/blog_6bb7ebcc0101c2ja.html Chrome浏览器支持HTML5,它支持原生播放部分的MP4格式(不用通过Flash等插件). ...

  4. ffmpeg 踩坑实录 添加实时水印(二)

    一.背景介绍 最近领导要求做一个视频录制的相关项目.其中,需要对视频文件进行添加 实时时间水印.于是,我想到了使用之前的ffmpeg来做. 二.ffmpeg实际操作 首先把需要添加水印的视频文件,上传 ...

  5. 你真的了解字典(Dictionary)吗? C# Memory Cache 踩坑记录 .net 泛型 结构化CSS设计思维 WinForm POST上传与后台接收 高效实用的.NET开源项目 .net 笔试面试总结(3) .net 笔试面试总结(2) 依赖注入 C# RSA 加密 C#与Java AES 加密解密

    你真的了解字典(Dictionary)吗?   从一道亲身经历的面试题说起 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点.为了便于描述,我把上面 ...

  6. OpenCV+Qt+CMake安装+十种踩坑

    平台:win10 x64+opencv-3.4.1 + qt-x86-5.9.0 + cmake3.13.4 x64 OpenCV+Qt+CMake安装,及目前安装完后打包:mingw32-make时 ...

  7. I.MX6 FFmpeg 录制视频

    /************************************************************************* * I.MX6 FFmpeg 录制视频 * 说明: ...

  8. Android原生编解码接口 MediaCodec 之——踩坑

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/gb702250823/article/d ...

  9. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  10. Spark踩坑记——数据库(Hbase+Mysql)

    [TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...

随机推荐

  1. Solution -「YunoOI 2016」镜中的昆虫

    Description Link. 区间推平: 区间数颜色. Solution 考虑无修的情况,我们是采用维护每个数的 \(pre\) 来做的.具体一点就是对于每一个 \(a_{i}\) 维护 \(p ...

  2. 9.2 运用API实现线程同步

    Windows 线程同步是指多个线程一同访问共享资源时,为了避免资源的并发访问导致数据的不一致或程序崩溃等问题,需要对线程的访问进行协同和控制,以保证程序的正确性和稳定性.Windows提供了多种线程 ...

  3. crontab guru

    https://crontab.guru/every-5-minutes Cron Job Monitoring crontab guru The quick and simple editor fo ...

  4. How to start with Gradle?

    How to start with Gradle? Download the latest Gradle release from http://www.gradle.org/downloads Se ...

  5. Python3 Keras分词器Tokenizer

    import keras.preprocessing.sequence from keras.preprocessing.text import Tokenizer samples = ['我 爱 你 ...

  6. 在线问诊 Python、FastAPI、Neo4j — 提供咨询接口服务

    目录 构建服务层 接口路由层 PostMan 调用 采用 Fast API 搭建服务接口: https://www.cnblogs.com/vipsoft/p/17684079.html Fast A ...

  7. K8s部署轻量级日志收集系统EFK(elasticsear + filebeat + kibana)

    目录 K8s部署EFK(elasticsear + filebeat + kibana)日志收集 一.准备镜像 二.搭建Elasticsearch + kibana 1.在可执行kubectl命令的服 ...

  8. [NSSCTF 2022 Spring Recruit]ezgame

    打开题目,发现是一个网页小游戏,就开始F12 提示到,需要分数超过65,才会得到flag 但不可能用手点吧(不怕麻烦还是可以) flag肯定是藏在了某个地方,仔细找找 发现有一个css,js文件,依次 ...

  9. 重温dp——最长上升公共子序列

    一道经典的dp了 题目描述 给出 1,2,-,n 的两个排列 P1 和 P2​ ,求它们的最长公共子序列. 输入格式 第一行是一个数 n. 接下来两行,每行为 n 个数,为自然数 1,2,-,n 的一 ...

  10. mybatis-plus使用心得

    mybatis-plus是一款基于mybatis的持久层框架,在mybatis上只做增强不做改变.基本使用流程: 导入依赖坐标: <dependency> <groupId>c ...