【音视频笔记】Mediacodec+Muxer生成mp4,浏览器无法播放问题处理
文章目录
- 背景
- 解决过程
- 曲线修复方案
- 解决问题根源
背景
最近在测试视频录制功能时发现,AudioRecord + MediaCodec + MediaMuxer生成的MP4,PC浏览器无法播放 ,但是Android、Windows、Mac的播放器应用都能正常播放。虽然不禁想吐槽浏览器视频组件的容错性差,但我也意识生成的文件格式肯定也是有问题的。
然后尝试了合成MP4视频时,只保留视频通道,不要音频,发现拖到浏览器中可以正常播放。使用ffprobe检查有问题的MP4文件,有如下错误输出:
[aac @ 0x7f95c9c0e7c0] Input buffer exhausted before END element found
至此,基本确定问题出现在生成的音频数据上。
解决过程
由于此前个人音视频开发经验不足,MediaCodec、MediaMuxer编码和合成视频的相关代码参考了一些开源项目及博客。
但由于开发周期紧急,没有足够的时间来仔细研究和排查,当时就采用了一种曲线救国的方案。
曲线修复方案
能想到这个方案也比较偶然。当时查阅了一些资料和博客,用到了ffmpeg和ffprobe工具对问题视频进行分析。
在尝试了使用ffmpeg工具对问题视频进行转换后,意外地发现,虽然命令也会报错[aac @ 0x7f95c9c0e7c0] Input buffer exhausted before END element found,但是,问题视频经过fmpeg转换后,生成的新视频,用ffprobe命令查看是没有错误输出的,也可以正常播放!也就是说,ffmpeg在处理转换有问题的音频时,会自动跳过那些有问题的数据。
由此,想到了一个比较曲折的方案:先用AudioRecord + MediaCodec + MediaMuxer生成MP4,然后使用ffmpeg命令对生成的视频进行一点无关紧要的转换(重点是让它处理掉有问题的数据),然后就能得到一个格式正确的音频数据,然后用MediaExtractor提取出原MP4中的视频数据,最后用MediaMuxer合成最终格式正确的mp4文件。
因为是音频有问题,所以实践中我就使用了如下命令来转换:
ffmpeg -i input.mp4 -vn -ab 96k out.m4a
-vn参数指定不要视频数据,-ab 96k将音频码率转为96k。
现在,只需要裁剪、交叉编译一个满足以上需求的arm版本的ffmpeg可执行程序就好了。关于如何裁剪和编译ffmpeg,网上音视频相关的技术文章一大把,就不赘述细节了。
这里记录一下我反复测试编译配置参数后,能输出较小体积(约2.6MB)arm版ffmpeg可执行命令的编译脚本,方便以后查看。因为我只需要处理音频,所以这个配置编译出的ffmpeg只能解码MP4和aac,并且只支持输出m4a音频。
#!/bin/sh# NDK路径,根据电脑环境配置情况调整
NDK_HOME="/Users/shenyong/Library/Android/sdk/ndk/21.4.7075529"
TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64"
SYSROOT="$TOOLCHAIN/sysroot"# 默认使用arm编译配置
API=29
ARCH=arm
CPU=armv7-a
TOOL_CPU_NAME=armv7a
# CROSS_PREFIX, CC and CXX for arm
CROSS_PREFIX="$TOOLCHAIN/bin/arm-linux-androideabi-"
CC="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-androideabi$API-clang"
CXX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-androideabi$API-clang++"
OUTPUT_DIR="./android/$CPU"
OPTIMIZE_CFLAGS="-march=$CPU"function config_arm64() {ARCH=arm64CPU=armv8-aTOOL_CPU_NAME=aarch64# CROSS_PREFIX, CC and CXX for arm64CROSS_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android-"CC="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android$API-clang"CXX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android$API-clang++"OUTPUT_DIR="./android/$CPU"OPTIMIZE_CFLAGS="-march=$CPU"#libmediandk.so路径MEDIA_NDK_LIB=$TOOLCHAIN/sysroot/usr/lib/aarch64-linux-android/$APIADD_MEDIA_NDK_SO="--extra-ldflags=-L$MEDIA_NDK_LIB --extra-libs=-lmediandk "
}# 如果需要编译arm64版本,将以下行取消注释即可
config_arm64#清除之前的编译配置及输出
make distclean./configure \
--prefix=$OUTPUT_DIR \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--enable-cross-compile \
--cross-prefix=$CROSS_PREFIX \
--sysroot=$SYSROOT \
--cc=$CC \
--cxx=$CXX \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS " \
--disable-shared \
--enable-static \
--enable-neon \
--disable-asm \
--disable-gpl \
--disable-postproc \
--enable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-doc \
--disable-symver \
--disable-protocols \
--enable-protocol=file \
--disable-network \
--disable-jni \
--disable-mediacodec \
--disable-hwaccels \
--disable-encoders \
--enable-encoder=aac \
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=mpeg4 \
--disable-muxers \
--enable-muxer=ipod \
--disable-demuxers \
--enable-demuxer=aac \
--enable-demuxer=mpegvideo \
--enable-demuxer=mov \
--disable-parsers \
--enable-parser=aac \
--enable-parser=mpeg4video \
--enable-parser=mpegaudio \
--disable-filters \
--disable-bsfs \
--enable-bsf=aac_adtstoascmake clean
make -j12
make install
解决问题根源
既然自己分析找不到问题根源,就看看别人正常工作的代码有什么不一样吧,于是开始在GitHub上找相似功能的开源库。在运行AudioVideoRecordingSample这个演示库后,发现别人生成的视频和音频,用ffprobe命令检查格式都是正确的。
仔细分析对比后,终于找到了问题点。网上各种博客的示例代码中,都是在dequeueOutputBuffer()返回的输出buffer下标大于0时,就直接写入Muxer,关键部分类似这样:
int outputBufferIndex = mAudioCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// 将mMediaCodec的指定的格式的数据轨道,设置到mMediaMuxer上mAudioTrackIndex = mMediaMuxer.addTrack(mAudioCodec.getOutputFormat());// ...
} else {while (outputBufferIndex >= 0) {// 获取数据ByteBuffer outBuffer = mAudioCodec.getOutputBuffers()[outputBufferIndex];audioPts = (System.nanoTime() - startNanoTime) / 1000;bufferInfo.presentationTimeUs = audioPts;// 编码数据写入muxermMediaMuxer.writeSampleData(mAudioTrackIndex, outBuffer, bufferInfo);// 释放 outBuffermAudioCodec.releaseOutputBuffer(outputBufferIndex, false);outputBufferIndex = mAudioCodec.dequeueOutputBuffer(bufferInfo, 0);}
}
但是,我发现AudioVideoRecordingSample这个库在获取的outputBufferIndex >= 0时,还有一个关键的处理:
// ...
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// You shoud set output format to muxer here when you target Android4.3 or less// but MediaCodec#getOutputFormat can not call here(because INFO_OUTPUT_FORMAT_CHANGED don't come yet)// therefor we should expand and prepare output format from buffer data.// This sample is for API>=18(>=Android 4.3), just ignore this flag hereif (DEBUG) Log.d(TAG, "drain:BUFFER_FLAG_CODEC_CONFIG");mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {// ...muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
}
/// ...
就是判断当前的mBufferInfo有BUFFER_FLAG_CODEC_CONFIG这个标志时,把size置为0了,所以这一次回调的数据,是没有写入muxer的。于是赶紧看了一眼BUFFER_FLAG_CODEC_CONFIG的官方文档:
/*** This indicated that the buffer marked as such contains codec* initialization / codec specific data instead of media data.*/public static final int BUFFER_FLAG_CODEC_CONFIG = 2;
这才恍然大悟!当BufferInfo有这个标志的时候,buffer包含编解码器初始化或编解码器特定的数据而不是媒体数据!
于是在自己的代码中也上这个判断处理,生成的视频文件再用ffprobe查看,也能正常输出信息,没有报错了。关键代码如下:
while (true) {try {// 返回有效数据填充的输出缓冲区的索引int outputBufferIndex = mAudioCodec.dequeueOutputBuffer(bufferInfo, 0);if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// 将mMediaCodec的指定的格式的数据轨道,设置到mMediaMuxer上mAudioTrackIndex = mMediaMuxer.addTrack(mAudioCodec.getOutputFormat());} else {while (outputBufferIndex >= 0) {// 获取数据ByteBuffer outBuffer = mAudioCodec.getOutputBuffers()[outputBufferIndex];// 修改音频的 pts,基准时间戳audioPts = (System.nanoTime() - startNanoTime) / 1000;bufferInfo.presentationTimeUs = audioPts;if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {Log.w(TAG, "audio BUFFER_FLAG_CODEC_CONFIG bufferInfo.size: " + bufferInfo.size);// 配置回调,不是有效的媒体数据,不写入。如果写入了,会导致mp4文件有错误数据帧,// 容错性不够好的播放器(比如pc浏览器)可能无法正常播放视频。bufferInfo.size = 0;}// 写入音频数据if (bufferInfo.size > 0) {mMediaMuxer.writeSampleData(mAudioTrackIndex, outBuffer, bufferInfo);}if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {Log.w(TAG, "audio BUFFER_FLAG_END_OF_STREAM bufferInfo.size: " + bufferInfo.size);}// 释放 outBuffermAudioCodec.releaseOutputBuffer(outputBufferIndex, false);if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {Log.w(TAG, "audio got BUFFER_FLAG_END_OF_STREAM flag. audioPts: "+ bufferInfo.presentationTimeUs + "bufferInfo.size: " + bufferInfo.size);if (shouldExit) {onDestroy();return;}}outputBufferIndex = mAudioCodec.dequeueOutputBuffer(bufferInfo, 0);}}} catch (Exception e) {e.printStackTrace();}
}
这样一来,使用Mediacodec+Muxer就能生成格式正确的mp4视频文件了,无需其他处理,效率大大提高。
从打印日志来看,带这个标志的一般就是第一个输出的buffer,并且数据量很少:
2023-09-21 10:16:36.664 BaseVid...corder W audio BUFFER_FLAG_CODEC_CONFIG bufferInfo.size: 2
2023-09-21 10:16:36.675 BaseVid...corder W video BUFFER_FLAG_CODEC_CONFIG bufferInfo.size: 30
最后经过测试验证,也确实是这样的:
只要bufferInfo有BUFFER_FLAG_CODEC_CONFIG标志时,把buffer数据写入muxer了,用ffprobe查看生成的视频文件,就一定会有[aac @ 0x7f95c9c0e7c0] Input buffer exhausted before END element found这个错误输入;反之不写入就是正常的。
相关文章:
【音视频笔记】Mediacodec+Muxer生成mp4,浏览器无法播放问题处理
文章目录 背景解决过程曲线修复方案 解决问题根源 背景 最近在测试视频录制功能时发现,AudioRecord MediaCodec MediaMuxer生成的MP4,PC浏览器无法播放 ,但是Android、Windows、Mac的播放器应用都能正常播放。虽然不禁想吐槽浏览器视频组件…...
debug过程中,矩阵左乘右乘相关概念梳理
1. 变换点或者变换向量 1.1左乘 矩阵左乘通常是指对”目标点“进行左乘,即: A ′ R ∗ A AR*A A′R∗A 其中,A为原始3维点,表示一个3*1的列向量,R为33的旋转矩阵,A‘为变换后的点 B ′ T ∗ B BT*B B′T∗B 其中…...
Ubuntu 安装Kafka
在本指南中,我们将逐步演示如何在 Ubuntu 22.04 上安装 Apache Kafka。 在大数据中,数以百万计的数据源生成了大量的数据记录流,这些数据源包括社交媒体平台、企业系统、移动应用程序和物联网设备等。如此庞大的数据带来的主要挑战有两个方面…...
洗地机性价比高的是哪款?高性价比洗地机排名
洗地机已成为当下备受欢迎的智能家电之一,但在挑选合适的洗地机时,面对各种新词汇和功能选择,可能会让人感到困惑。因此,为了帮助大家在购买洗地机时不踩坑,我们基于市面上主流品牌的综合分析对比,总结出来…...
安装konga
创建konga数据库 docker run --rm pantsel/konga:latest -c prepare -a postgres -u postgresql://kong:kong{IP}:5432/konga这里要注意docker部署时IP不能直接访问localhost 安装konga docker run -p 1337:1337 \--network kong-net \--name konga \-e "NODE_ENVprodu…...
算法基础之高精度总结
目录 高精度算法分类 高精度加减乘除的异同点 加和乘 相同点 减和除 相同点 不同点 处理前导0的方式 高精度算法分类 分类:加、减、乘、除 其中加减乘都适用于两个数都是高精度,除法因为除数是高精度的话不好用整除的方法,所以除法时…...
oracle TNS Listener 远程投毒漏洞修复
有个客户在等保测评过程,测评公司扫出一个关于oracle的漏洞如下: 客户是RAC环境11.2.0.4,在生产修复漏洞前我做了如下测试验证: 测试环境准备: RAC一套11.2.0.4 实例名dbserver [oraclehisdb1 ~]$ cat /etc/hosts …...
第二章:最新版零基础学习 PYTHON 教程(第一节 - Python 输入/输出–在 Python 中获取输入)
开发人员经常需要与用户交互,以获取数据或提供某种结果。如今,大多数程序都使用对话框来要求用户提供某种类型的输入。而Python为我们提供了两个内置函数来读取键盘输入。 目录 输入(提示) raw_input(提示) 输入函数在 Python 中的工作原理:...
react create-react-app v5 从零搭建项目
前言: 好久没用 create-react-app做项目了,这次为了个h5项目,就几个页面,决定自己搭建一个(ps:mmp 好久没用,搭建的时候遇到一堆问题)。 我之前都是使用 umi 。后台管理系统的项目 使用 antd-…...
2023软件测试八股文,涵盖所有面试题
Part1 1、你的测试职业发展是什么? 测试经验越多,测试能力越高。所以我的职业发展是需要时间积累的,一步步向着高级测试工程师奔去。而且我也有初步的职业规划,前3年积累测试经验,按如何做好测试工程师的要点去要求自…...
性能压力测试的定义及步骤是什么
在今天的数字化时代,软件系统的性能和稳定性对于企业的成功至关重要。为了确保软件在高负载和压力情况下的正常运行,性能压力测试成为了不可或缺的环节。本文将介绍性能压力测试的定义、步骤。 一、性能压力测试的定义和目标 性能压力测试是通过模拟实际…...
Selenium自动化中处理鼠标悬停并操作的方法
因为测试中遇到要鼠标悬停显示Tooltip,并操作tip上的内容,现记录如下。 方法一:通过鼠标链式操作 from selenium.webdriver.common.action_chains import ActionChains as ACac AC(self.driver)lst self.driver.find_element_by_xpath(//…...
python socket 编程实现猜数字
项目地址 https://gitee.com/lmk73444/learn_spring/tree/master/doc/1_x%E5%AD%A6%E4%B9%A0/002_py_socket python socket试验 mkdir /root/git_proj cd /root/git_proj# 首次 clone 项目 git clone https://gitee.com/lmk73444/learn_spring.git# 非首次 # 更新项目 cd /ro…...
20个提升效率的JS简写技巧,告别屎山!
JavaScript 中有很多简写技巧,可以缩短代码长度、减少冗余,并且提高代码的可读性和可维护性。本文将介绍 20 个提升效率的 JS 简写技巧,助你告别屎山,轻松编写优雅的代码! 移除数组假值 可以使用 filter() 结合 Bool…...
Pikachu靶场——SSRF 服务端请求伪造
文章目录 1 SSRF 服务端请求伪造1.1 SSRF(curl)1.1.1 漏洞防御 1.2 SSRF(file_get_content)1.2.1 漏洞防御1.2.3 SSRF 防御 1 SSRF 服务端请求伪造 SSRF(Server-Side Request Forgery:服务器端请求伪造) 其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&a…...
Android file
写文件——FileOutputStream openFileOutput 读文件——FileInputStream openFileInput openFileOutput写文件时当文件不存在,Android自动创建。 通过BufferedWriter直接写入字符串 public void writeFile(String inputText) {FileOutputStream outputStream nul…...
【计算机网络】计网常见面试题总结
目录 一、谈一谈对OSI七层模型和TCP/IP四层模型的理解? 二、谈一谈TCP协议的三次握手过程? 三、TCP协议为什么要三次握手?两次、四次不行吗? 四、谈一谈TCP协议的四次挥手过程? 五、什么是流量控制? …...
SpringMVC 学习(七)JSON
9. JSON 9.1 简介 JSON(JavaScript Object Notation,JS 对象标记)是一种轻量级数据交换格式,采用独立于编程语言的文本格式储存和表示数据,易于机器解析和生成,提升网络传输效率。 任何 JavaScript 支持…...
重学C++ | std::set 的原理
std::set 是C标准库中的容器之一,它基于红黑树实现。std::set 利用红黑树的特性来实现有序的插入、查找和删除操作,并且具有较好的平均和最坏情况下的时间复杂度。 当向 std::set 插入元素时,它会按照特定的比较函数(bool less<…...
AnV-X6使用及总结
目录 1 简介2 安装3 基础概念3.1 画布Graph3.2 基类Cell3.3 节点Node3.4 边Edge 4 使用4.1 创建节点4.2 节点连线4.3 事件系统 5 总结 1 简介 AntV是一个数据可视化(https://x6.antv.antgroup.com/)的工具(https://antv.vision/zh/ …...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
