【音视频笔记】Mediacodec+Muxer生成mp4,浏览器无法播放问题处理

这篇具有很好参考价值的文章主要介绍了【音视频笔记】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编码和合成视频的相关代码参考了一些开源项目及博客。
但由于开发周期紧急,没有足够的时间来仔细研究和排查,当时就采用了一种曲线救国的方案。

曲线修复方案

能想到这个方案也比较偶然。当时查阅了一些资料和博客,用到了ffmpegffprobe工具对问题视频进行分析。

在尝试了使用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=arm64
  CPU=armv8-a
  TOOL_CPU_NAME=aarch64
  # CROSS_PREFIX, CC and CXX for arm64
  CROSS_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/$API
  ADD_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_adtstoasc

make 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;
        // 编码数据写入muxer
        mMediaMuxer.writeSampleData(mAudioTrackIndex, outBuffer, bufferInfo);
        // 释放 outBuffer
        mAudioCodec.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 here
    if (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);
                }
                // 释放 outBuffer
                mAudioCodec.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这个错误输入;反之不写入就是正常的。文章来源地址https://www.toymoban.com/news/detail-730869.html

到了这里,关于【音视频笔记】Mediacodec+Muxer生成mp4,浏览器无法播放问题处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【音视频开发】FFmpeg转换与封装 I - MP4格式

    1 FFmpeg转换与封装 1.1 MP4格式转换 1.1.1 MP4格式标准         FFmpeg支持的媒体封装格式具有多样性与全面性,与此, 我们还可以使用FFmpeg来对媒体格式进行转换与封装 。 在互联网常见的格式中,跨平台最好的应该是 MP4 文件,因为 MP4 文件既可以在PC 平台的Flashplayer中播放,

    2024年02月08日
    浏览(83)
  • 用ffmpeg解析mp4文件得到时长、比特率、音视频信息

    以下是使用C++语言调用FFmpeg获取视频流和音频流信息的示例代码: 上述代码通过 AVFormatContext 结构体和FFmpeg库函数 avformat_open_input 、 avformat_find_stream_info 等,获取MP4文件的视频流和音频流信息,并将结果存储到 MediaInfo 类中。在实际应用中,可以将上述代码封装成一个函数,

    2024年02月12日
    浏览(66)
  • Qt音视频开发47-文字和图片水印(可存储到MP4中)

    近期花了两周时间闭门啃硬骨头,主要就解决三个问题(音视频同步存储和推流、图片水印并将水印信息存储到文件或者推流、rtsp推流),这三个问题困扰了很多年,以至于找遍了网络和翻遍ffplay代码以及ffmpeg示例的代码,通过不下于上百次方案的调整和测试,几乎每次都是

    2024年02月16日
    浏览(53)
  • 音视频知识:MPEG-4、H264、MP4、AAC之间的关系

    MPEG-4 一种编码标准。是国际标准化组织 (ISO) 主要针对消费类应用,已经针对运动图像压缩定义的标准。MPEG(Moving Picture Experts Group)标准包括 MPEG1、MPEG2与 MPEG4。 MPEG-4标准目前分为27个部分,统称为ISO/IEC14496国际标准。其中第10部分(ISO/IEC 14496-10)就是熟悉的高级视频编码

    2024年02月14日
    浏览(68)
  • JavaScript音视频,使用JavaScript如何在浏览器录制电脑摄像头画面为MP4视频文件并下载视频文件到本地

    本章介绍使用JavaScript如何在浏览器录制电脑摄像头画面为MP4视频文件并下载视频文件到本地。 1、使用navigator.mediaDevices.getUserMedia获取摄像头画面 2、将获取到的摄像头画面渲染到canvas画板上 3、将canvas转换为blob对象 4、通过document.createElement(‘a’)调用 href 方法获取此链接并触

    2024年02月02日
    浏览(83)
  • 「Python|音视频处理|场景案例」如何使用ffmpeg下载m3u8视频到本地并保存成mp4

    本文主要介绍如何使用ffmpeg批量下载视频到本地并保存成指定格式。 当我们希望将网页上的视频下载到本地的时候,我们可能获取到的视频地址是指向 .m3u8 格式的,当视频多的时候,手动下载并使用工具转换格式就显得工作量过大。 ffmpeg 是一个强大的音视频处理工具,具体

    2024年02月16日
    浏览(76)
  • Qt/C++音视频开发69-保存监控pcm音频数据到mp4文件/监控录像/录像存储和回放/264/265/aac/pcm等

    用ffmpeg做音视频保存到mp4文件,都会遇到一个问题,尤其是在视频监控行业,就是监控摄像头设置的音频是PCM/G711A/G711U,解码后对应的格式是pcm_s16be/pcm_alaw/pcm_mulaw,将这个原始的音频流保存到mp4文件是会报错的,在调用avformat_write_header写文件头的时候提示(-22) Invalid argument,

    2024年04月11日
    浏览(57)
  • Android音视频-MediaCodec

    原文:https://mp.weixin.qq.com/s?__biz=MzU3NTA3MDU1OQ==mid=2247484865idx=1sn=174b8ca702466e83e72c7115d91b06eachksm=fd298df1ca5e04e7b2df9dc9f21e5cfe3e910204c905d8605f648ce6f6404432a83ae52a23a3scene=178cur_album_id=1638784435628064770#rd MediaCodec 支持处理三种数据类型,分别是压缩数据(compressed data)、原始音频数据(raw audio d

    2023年04月08日
    浏览(80)
  • ffmpeg把RTSP流分段录制成MP4,如果能把ffmpeg.exe改成ffmpeg.dll用,那音视频开发的难度直接就降一个维度啊

    比如,原来我们要用ffmpeg录一段RTSP视频流转成MP4,我们有两种方案: 方案一:可以使用以下命令将rtsp流分段存储为mp4文件 ffmpeg -i rtsp://example.com/stream -vcodec copy -acodec aac -f segment -segment_time 3600 -reset_timestamps 1 -strftime 1 output_%Y-%m-%d_%H-%M-%S.mp4 方案二:可以直接调用ffmpeg库avcode

    2024年02月10日
    浏览(59)
  • Android 音视频入门 (四)- 记录一次MediaCodec ,天呐

    isEncoding = true //开始编码 mMediaCodec.start() //构建连接器。 mWorkerThread = HandlerThread(“WorkerThread-Encoder”) mWorkerThread.start() mHandler = Handler(mWorkerThread.looper) } 注意,我们并不在此处就开启Muxer,我们会在子线程中接受数据的时候的某个状态开始进行混合。 mCameraDevice.setPreviewCallback {

    2024年04月16日
    浏览(46)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包