如何混合(合并)视频和音频,以便音频在输出视频中循环,以防持续时间太短?

2023-12-28

背景

我需要将视频文件和音频文件合并为一个视频文件,以便:

  1. 输出视频文件的持续时间与输入视频文件的持续时间相同
  2. 输出文件中的音频将仅是输入音频文件的音频。如果太短,则会循环到最后(如果需要可以在最后停止)。这意味着一旦音频播放完毕而视频尚未播放,我应该一次又一次地播放它,直到视频结束(音频的串联)。

正如我所读到的,这种合并操作的技术术语称为“混合”。

举个例子,假设我们有一个 10 秒的输入视频和一个 4 秒的音频文件,输出视频将是 10 秒(始终与输入视频相同),音频将播放 2.5 次(前 2 次)覆盖前 8 秒,然后是其余 4 秒中的 2 秒)。

存在的问题

虽然我找到了如何混合视频和音频的解决方案(here https://stackoverflow.com/a/31591485/878126),我遇到了多个问题:

  1. 我不知道如何在需要时循环写入音频内容。无论我如何尝试,它总是给我一个错误

  2. 输入文件必须是特定的文件格式。否则,它可能会引发异常,或者(在极少数情况下)更糟糕:创建包含黑色内容的视频文件。更重要的是:有时“.mkv”文件(例如)可能没问题,有时则不会被接受(并且两者都可以在视频播放器应用程序上播放)。

  3. 当前代码处理缓冲区而不是实际持续时间。这意味着在许多情况下,我可能会停止混合音频,即使我不应该这样做,并且输出视频文件的音频内容将比原始文件更短,即使视频足够长。

我尝试过的

  • 我尝试使音频的 MediaExtractor 在每次到达结尾时都转到开头,方法是:

            if (audioBufferInfo.size < 0) {
                Log.d("AppLog", "reached end of audio, looping...")
                audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, 0)
            }
    
  • 为了检查文件的类型,我尝试使用MediaMetadataRetriever然后检查 mime 类型。我认为受支持的可以在文档中找到(here https://developer.android.com/guide/topics/media/media-formats)如那些标有“编码器”的。对此不太确定。我也不知道其中提到的哑剧类型属于哪种类型。

  • 我还尝试重新初始化与音频相关的所有内容,但也不起作用。

这是我当前的多路复用本身代码(完整的示例项目可用here https://github.com/AndroidDeveloperLB/VideoAndAudioMux) :

object VideoAndAudioMuxer {
    //   based on:  https://stackoverflow.com/a/31591485/878126
    @WorkerThread
    fun joinVideoAndAudio(videoFile: File, audioFile: File, outputFile: File): Boolean {
        try {
            //            val videoMediaMetadataRetriever = MediaMetadataRetriever()
            //            videoMediaMetadataRetriever.setDataSource(videoFile.absolutePath)
            //            val videoDurationInMs =
            //                videoMediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
            //            val videoMimeType =
            //                videoMediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
            //            val audioMediaMetadataRetriever = MediaMetadataRetriever()
            //            audioMediaMetadataRetriever.setDataSource(audioFile.absolutePath)
            //            val audioDurationInMs =
            //                audioMediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
            //            val audioMimeType =
            //                audioMediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
            //            Log.d(
            //                "AppLog",
            //                "videoDuration:$videoDurationInMs audioDuration:$audioDurationInMs videoMimeType:$videoMimeType audioMimeType:$audioMimeType"
            //            )
            //            videoMediaMetadataRetriever.release()
            //            audioMediaMetadataRetriever.release()
            outputFile.delete()
            outputFile.createNewFile()
            val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
            val sampleSize = 256 * 1024
            //video
            val videoExtractor = MediaExtractor()
            videoExtractor.setDataSource(videoFile.absolutePath)
            videoExtractor.selectTrack(0)
            videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
            val videoFormat = videoExtractor.getTrackFormat(0)
            val videoTrack = muxer.addTrack(videoFormat)
            val videoBuf = ByteBuffer.allocate(sampleSize)
            val videoBufferInfo = MediaCodec.BufferInfo()
//            Log.d("AppLog", "Video Format $videoFormat")
            //audio
            val audioExtractor = MediaExtractor()
            audioExtractor.setDataSource(audioFile.absolutePath)
            audioExtractor.selectTrack(0)
            audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
            val audioFormat = audioExtractor.getTrackFormat(0)
            val audioTrack = muxer.addTrack(audioFormat)
            val audioBuf = ByteBuffer.allocate(sampleSize)
            val audioBufferInfo = MediaCodec.BufferInfo()
//            Log.d("AppLog", "Audio Format $audioFormat")
            //
            muxer.start()
//            Log.d("AppLog", "muxing video&audio...")
            //            val minimalDurationInMs = Math.min(videoDurationInMs, audioDurationInMs)
            while (true) {
                videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, 0)
                audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, 0)
                if (audioBufferInfo.size < 0) {
                    //                    Log.d("AppLog", "reached end of audio, looping...")
                    //TODO somehow start from beginning of the audio again, for looping till the video ends
                    //                    audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                    //                    audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, 0)
                }
                if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) {
//                    Log.d("AppLog", "reached end of video")
                    videoBufferInfo.size = 0
                    audioBufferInfo.size = 0
                    break
                } else {
                    //                    val donePercentage = videoExtractor.sampleTime / minimalDurationInMs / 10L
                    //                    Log.d("AppLog", "$donePercentage")
                    // video muxing
                    videoBufferInfo.presentationTimeUs = videoExtractor.sampleTime
                    videoBufferInfo.flags = videoExtractor.sampleFlags
                    muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo)
                    videoExtractor.advance()
                    // audio muxing
                    audioBufferInfo.presentationTimeUs = audioExtractor.sampleTime
                    audioBufferInfo.flags = audioExtractor.sampleFlags
                    muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo)
                    audioExtractor.advance()
                }
            }
            muxer.stop()
            muxer.release()
//            Log.d("AppLog", "success")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
//            Log.d("AppLog", "Error " + e.message)
        }
        return false
    }
}
  • 我也尝试过使用 FFMPEG 库(here https://superuser.com/a/1319950/152400 and here https://github.com/umair13adil/KotlinFFMpeg/issues/11) ,看看如何做。它工作得很好,但有一些可能的问题:该库似乎占用了大量空间,烦人的许可条款,并且由于某种原因,示例无法播放我必须创建的输出文件,除非我删除了文件中的某些内容。命令将使转换速度慢得多。我真的更喜欢使用内置的 API,而不是使用这个库,即使它是一个非常强大的库...另外,似乎对于某些输入文件,它没有循环...

问题

  1. 如何混合视频和音频文件,以便在音频比视频短(持续时间)的情况下音频会循环播放?

  2. 我怎样才能做到这一点,以便在视频结束时精确地剪切音频(视频和音频上都没有剩余)?

  3. 在调用此函数之前如何检查当前设备是否可以处理给定的输入文件并实际复用它们?有没有一种方法可以在运行时检查此类操作支持哪些内容,而不是依赖将来可能会更改的文档列表?


我也有同样的场景。

  • 1: When audioBufferInfo.sizepresentationTimeUs.

  • 2:获取视频时长,当音频循环到时长时(使用presentationTimeUs也),切。

  • 3:音频文件需要MediaFormat.MIMETYPE_AUDIO_AMR_NB or MediaFormat.MIMETYPE_AUDIO_AMR_WB or MediaFormat.MIMETYPE_AUDIO_AAC。在我的测试机器上,它运行良好。

这是代码:

private fun muxing(musicName: String) {
    val saveFile = File(DirUtils.getPublicMediaPath(), "$saveName.mp4")
    if (saveFile.exists()) {
        saveFile.delete()
        PhotoHelper.sendMediaScannerBroadcast(saveFile)
    }
    try {
        // get the video file duration in microseconds
        val duration = getVideoDuration(mSaveFile!!.absolutePath)

        saveFile.createNewFile()

        val videoExtractor = MediaExtractor()
        videoExtractor.setDataSource(mSaveFile!!.absolutePath)

        val audioExtractor = MediaExtractor()
        val afdd = MucangConfig.getContext().assets.openFd(musicName)
        audioExtractor.setDataSource(afdd.fileDescriptor, afdd.startOffset, afdd.length)

        val muxer = MediaMuxer(saveFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)

        videoExtractor.selectTrack(0)
        val videoFormat = videoExtractor.getTrackFormat(0)
        val videoTrack = muxer.addTrack(videoFormat)

        audioExtractor.selectTrack(0)
        val audioFormat = audioExtractor.getTrackFormat(0)
        val audioTrack = muxer.addTrack(audioFormat)

        var sawEOS = false
        val offset = 100
        val sampleSize = 1000 * 1024
        val videoBuf = ByteBuffer.allocate(sampleSize)
        val audioBuf = ByteBuffer.allocate(sampleSize)
        val videoBufferInfo = MediaCodec.BufferInfo()
        val audioBufferInfo = MediaCodec.BufferInfo()

        videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
        audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)

        muxer.start()

        val frameRate = videoFormat.getInteger(MediaFormat.KEY_FRAME_RATE)
        val videoSampleTime = 1000 * 1000 / frameRate

        while (!sawEOS) {
            videoBufferInfo.offset = offset
            videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset)

            if (videoBufferInfo.size < 0) {
                sawEOS = true
                videoBufferInfo.size = 0

            } else {
                videoBufferInfo.presentationTimeUs += videoSampleTime
                videoBufferInfo.flags = videoExtractor.sampleFlags
                muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo)
                videoExtractor.advance()
            }
        }

        var sawEOS2 = false
        var sampleTime = 0L
        while (!sawEOS2) {

            audioBufferInfo.offset = offset
            audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset)

            if (audioBufferInfo.presentationTimeUs >= duration) {
                sawEOS2 = true
                audioBufferInfo.size = 0
            } else {
                if (audioBufferInfo.size < 0) {
                    sampleTime = audioBufferInfo.presentationTimeUs
                    audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
                    continue
                }
            }
            audioBufferInfo.presentationTimeUs = audioExtractor.sampleTime + sampleTime
            audioBufferInfo.flags = audioExtractor.sampleFlags
            muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo)
            audioExtractor.advance()
        }

        muxer.stop()
        muxer.release()
        videoExtractor.release()
        audioExtractor.release()
        afdd.close()
    } catch (e: Exception) {
        LogUtils.e(TAG, "Mixer Error:" + e.message)
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何混合(合并)视频和音频,以便音频在输出视频中循环,以防持续时间太短? 的相关文章

  • Android 中多个蓝牙连接的自定义 UUID

    我有一个 Android 设备作为服务器连接到多个蓝牙 Android 客户端 我了解 UUID 的概念以及它的独特之处 我的问题是 我可以为连接到我的服务器的所有客户端使用相同的 UUID 吗 如果没有 我如何以编程方式为我的客户端生成
  • Android短音的正确播放方法?

    我正在创建一个应用程序 屏幕上将有多个图像 这些图像将是按钮 点击时会播放短促的声音 我对此进行了研究 只能找到我当前用来播放声音的方法 这似乎根本没有响应 我希望声音能够快速播放并且能够响应多次快速点击 我不确定这在 Android 中是
  • Android Studio:XML 布局中的“包装在容器中”

    编辑 XML 布局文件时 Eclipse 有一项称为 包裹在容器中 的功能 重新格式化 gt Android gt 可让您选择一个或多个视图并在其周围包裹您选择的布局 Android Studio中有类似的东西吗 目前正在实施中 问题 69
  • 如何正确释放Android MediaPlayer

    我正在尝试向我的 Android 应用程序添加一个按钮 当点击该按钮时它会播放 MP3 我已经让它工作了 但没有办法释放 mediaPlayer 对象 因此即使在我离开活动后它仍然会继续播放 如果我在react 方法之外初始化MediaPl
  • 按下按钮时应用不同的样式

    有没有办法在按下按钮时将样式应用于按钮 如果我有一种风格样式 xml
  • 如何在我现有的 Android 应用程序中使用 Telegram API(包括聊天应用程序)?

    我想使用 telegram API 在我现有的 Android 应用程序中开发聊天功能 我不知道如何实施 我认为 看看Telegram 数据库库 测试版 从这里TDLib https core telegram org tdlib 俄语 但
  • 自定义首选项中的android首选项水平分隔线?

    我创建了自己的自定义首选项对象来扩展首选项 我创建它们只是因为这些自定义数据类型没有首选项 一切正常 但我的自定义首选项没有相同的外观 因为它们缺少系统首选项对象具有的水平分隔线 我已经查找了创建水平分隔线的代码 但我找不到它是在哪里完成的
  • 以编程方式将文本颜色设置为主要 Android 文本视图

    如何设置我的文本颜色TextView to android textColorPrimary以编程方式 我已经尝试了下面的代码 但它将 textColorPrimary 和 textColorPrimary Inverse 的文本颜色始终设
  • Android:后台Activity可以执行代码吗?

    后台的活动是否被视为 正在运行 并且可以执行代码 还是处于挂起状态 他们暂停了 活动生命周期 http developer android com reference android app Activity html ActivityLi
  • 从 BroadcastReceiver 类调用活动方法

    我知道我可以做一个内部接收器类来调用接收器中的任何方法 但我的主要活动太大了 要做的事情也很多 因此 我需要一个扩展广播接收器的类 但它不是内部类 并且可以从我的主要活动中调用一种方法 我不知道是否可能 但我的活动是家庭活动和 single
  • Android - AudioRecord类不读取数据,audioData和fftArray返回零

    我是 Android 新手 一直在开发音调分析器应用程序 最低 SDK 8 我读了很多关于如何实现 Audiorecord 类的文章 但我想知道为什么它在我录制时不读取任何数据 我尝试显示 audioData 和 fftArray 的值 但
  • 使用片段时应用程序崩溃

    我正在处理碎片和 我的代码中有一个我找不到的问题 logcat 指向我的一个片段中的这段代码 Override public View onCreateView LayoutInflater inflater ViewGroup conta
  • okhttp 获取失败响应

    我已经在我的 android 客户端中实现了 okhttp 来进行网络调用 当我收到失败响应时 我会收到失败代码以及与该代码相关的文本作为消息 但我没有收到服务器发送给我的自定义失败响应 在我实施的代码中的失败响应中 我收到的消息只是 错误
  • Android 2.3 模拟器在更新位置时崩溃

    我正在使用 Eclipse 编写和调试 Android 应用程序 我需要做的事情之一是更新设备的位置 因此我尝试使用模拟器控制窗口中的位置控制面板 在 手动 选项卡上 我选择 十进制 输入有效的纬度和经度 然后单击 发送 不幸的是 接下来发
  • Dagger 2 没有生成我的组件类

    我正在使用 Dagger 2 创建我的依赖注入 几个小时前它还在工作 但现在不再生成组件 这是我创建组件的地方 public class App extends Application CacheComponent mCacheCompon
  • 通过电子邮件发送文本文件附件

    我正在尝试附加一个文本文件以便通过电子邮件发送 但每当我打开电子邮件应用程序时 它都会说该文件不存在 请帮助 Intent i new Intent Intent ACTION SEND i setType text plain i put
  • Dagger 2 中“HasFragmentInjector”的实际用法是什么

    我之前已经实现了 dagger2 v2 2 但现在他们也添加了 dagger android 部分 所以我正在用它创建示例项目 我知道旧的方法论 Provide and Modules and 成分等注释 但从 Dagger 2 8 开始
  • 发布的 Android apk 出现错误“包文件未正确签名”

    我最近将我的应用程序上传到 Android 市场 但是由于错误 下载时它拒绝运行 包文件未正确签名 我首先使用 eclipse 发布了数据包 右键单击导出 创建密钥库然后发布 但它拒绝工作 然后我下载了 keytool 和 jarsigne
  • Android 屏幕方向错误

    我使用的是 Android HTC HERO 2 1 版本 我写的活动
  • Git 实验分支还是单独的实验存储库?

    我正在开发一个 Android 应用程序 并且在整个开发周期中一直使用 Git 现在 我想构建并发布实验性功能 供人们尝试和安装 同时仍将原始的 稳定的应用程序安装在他们的设备上 现在 这意味着我需要使用不同的包名称 这会更改开发项目中的一

随机推荐