人间观察

其实人这一辈子 真的遇不到几个真心对你好爱你的人 如果有幸能牵手 那就别并肩 好好的 别老是冷冰冰 说反话

简介

短视频的编辑功能有很多,比如:添加背景音乐,剪切,拼接视频/音频,特效,贴纸等等。
本文介绍为MP4视频增加背景音乐(或者控制视频原始的音量大小),其中涉及到音视频的解码,视频和音频的指定时长的提取,音频视频的分离,混合视频音频生成mp4,音频混音,音量调节以及一些音频处理细节和注意的地方等技术。采用的是Android的硬编解码MediaCodec+ MediaExtractor+ MediaMuxer。

效果图

处理方案/原理

如图所示如果我们想要把原始的mp4视频增加背景音。我们需要如下几个步骤。

  1. 选择音频轨道进行解码提取音频,借助于MediaExtractorMediaCodec,然后将解码后的pcm保存到临时文件video.pcm
  2. 解码背景音的mp3文件,借助于MediaCodec,然后将解码后的pcm保存到临时文件bgm.pcm
  3. 核心的一步pcm数据的处理,比如混音等,后文详细介绍。然后混音后的pcm保存到临时文件mix.pcm
  4. 混音后的pcm转wav,主要是加入音频的信息(采样频率,采样位数,声道数)在文件头中加入42byte的wav的头信息
  5. 将wav编码为aac音频并写入文件中。借助于MediaCodecMediaMuxer混合生成器
  6. 将原始视频的中的视频写入到文件,借助于MediaMuxerMediaExtractor

第1,2步的解码这里不多介绍了可以参考之前的文章。第4步pcm转为wav也可以参考之前文章。第5,6步主要是MediaMuxer后文介绍下。主要是第2步重点介绍下。

混音

混音本质就是对数据的处理。

PCM数据描述

我们先回顾下pcm的描述,也可以看下之前的音频基础那篇文章的介绍。我们这里说下采样位数。

采样位数:表示每个采样点用多少比特表示。常用的量化位数有8位、16位和32位。比如采样位数为8bit时,每个采样点可以表示256个不同的采样值,当采样位数为16bit时,每个采样点可以表示65536个不同的采样值。采样位数的大小影响声音的质量,采样位数越多,量化后的波形越接近原始波形,声音的质量越高,需要的存储空间就越大,反之同理。一般情况下,采样位数是16bit,就像mp3的cd音质,你听起来也没那么失真。

采样点数据有符号和无符号之分,比如:8bit的样本数据,有符号的是[-128 , 127],无符号的是[0,255 ]。16bit的样本数据,有符号的是 [-32768 , 32767],无符号的是[0, 65535]. 大多数pcm样本数据使用整形表示,对于一些高精度的场景使用float 浮点型表示。对于我们平时看到的听到的音视频都是整形。

PCM的数据存储方式

如果是单声道音频,采样数据按照时间的先后顺序依次存储,如果是双声道音频,则按照LRLRLRLR方式存储,每个采样点的存储方式还与大小端有关。大端如下:

另外MediaCodec解码出来的pcm是按照LRLRLRLR方式存储,但是FFmpeg解码出来的pcm存储格式很丰富(等后续学习FFmpeg时了解)。

此外即使同样是signed 16 bits 有符号,也存在packed和planar的区别。对于双声道音频来说,packed表示两个声道的数据交错存储,也就是LRLRLRLR的方式存储;planar表示两个声道的数据分开存储,也就是LLLLRRRR的方式存储。

可以看到通过MediaCodec解码出来的pcm是按照packed的方式存储的,而FFmpeg可以是任何一种。所以为了统一化处理,需要进行重采样,比如你可以采用:每个采样点按照2个字节的有符号的short类型,并且按照planar的方式存储。

重采样:
对pcm数据进行重新采样,可以改变它的声道数,采样率和采样格式。比如:原先的pcm音频数据是2个声道,44100采样率,32 bit单精度型,可以重采样为:2个声道,44100采样率,有符号short类型

本文的demo的视频和音频文件是按照采样率是44100hz, 双声道 ,16位。使用的是MediaCodec解码的音频,所以为packed类型。

混音数据处理过程

  • 所以基于以上的pcm数据的存储格式的原理,就可以进行处理了。
    用2个buffer缓存区来保存从文件读到的之前解码后的视频的pcm数据记为buffer1,另外一个为背景音乐的记为buffer2.混合后的记为buffer3.

也就是buffer1和buffer2 分别把连续的2个byte转为short,前一个字节放在低八位后面的字节放在高八位。也就是:

short temp1 = (short) ((buffer1[i] & 0xff) | (buffer1[i + 1] & 0xff) << 8);
short temp2 = (short) ((buffer2[i] & 0xff) | (buffer2[i + 1] & 0xff) << 8);
  • 然后把2个short的值相加就得到了混音的值,在相加的过程中我们可以给temp1或者temp2分别乘以一个系数就可以达到控制各自音量(原始视频和背景音乐的音量)的目的。想一想是吧!这样我们就可以调节各自音量了。系数我们也要非常注意!,注意数据的精度丢失。所以为:
// videoVolume和 bgAudioVolume取值0-100val volume1: Float = videoVolume * 1.0f / 100val volume2: Float = bgAudioVolume * 1.0f / 100temp = (int) (temp1 * volume1 + temp2 * volume2);

temp就是混音后的值。但是还有一点是2个short类型的数字相加得到的结果有可能超过short的范围[-32768 , 32767] ,所以需要控制一下最大最小值:

    if (temp > 32767) {temp = 32767;} else if (temp < -32768) {temp = -32768;}
  • 最后还原数据,我们得到了混音后的数据为int型的,我们还得把它铺开转为byte类型
buffer3[i] = (byte) (temp & 0x00ff); // 低8位
buffer3[i + 1] = (byte) ((temp & 0xFF00) >> 8 ); // 高8位

最后混音的关键代码(kotlin版)如下:

private fun mixPcm(pcm1: String,pcm2: String,mixPcm: String,videoVolume: Int,bgAudioVolume: Int
) {val volume1: Float = videoVolume * 1.0f / 100val volume2: Float = bgAudioVolume * 1.0f / 100val buffSize = 2048val buffer1 = ByteArray(buffSize)val buffer2 = ByteArray(buffSize)val buffer3 = ByteArray(buffSize)val fis1 = FileInputStream(pcm1)val fis2 = FileInputStream(pcm2)val fosMix = FileOutputStream(mixPcm)var isEnd1 = falsevar isEnd2 = falsevar temp1: Shortvar temp2: Shortvar temp: Intvar ret1 = -1var ret2 = -1while (!isEnd1 || !isEnd2) {if (!isEnd1) {ret1 = fis1.read(buffer1)isEnd1 = ret1 == -1}if (!isEnd2) {ret2 = fis2.read(buffer2)isEnd2 = ret2 == -1for (i in buffer2.indices step 2) {// java 版本清楚些
#                 // temp1 = (short) ((buffer1[i] & 0xff) | (buffer1[i + 1] & 0xff) << 8);// temp2 = (short) ((buffer2[i] & 0xff) | (buffer2[i + 1] & 0xff) << 8);//                    temp1 = PcmToWavUtil.to(buffer1[i], buffer1[i + 1])
//                    temp2 = PcmToWavUtil.to(buffer2[i], buffer2[i + 1])temp1 =((buffer1[i].toInt() and 0xff) or ((buffer1[i + 1].toInt() and 0xff) shl 8)).toShort()temp2 =((buffer2[i].toInt() and 0xff) or ((buffer2[i + 1].toInt() and 0xff) shl 8)).toShort()// 两个short变量相加 会大于shorttemp = (temp1 * volume1 + temp2 * volume2).toInt()// short类型的取值范围[-32768 ~ 32767]if (temp > 32767) {temp = 32767} else if (temp < -32768) {temp = -32768}// java 版本清楚些// buffer3[i] = (byte) (temp & 0x00ff);// buffer3[i + 1] = (byte) ((temp & 0xFF00) >> 8 );// 低八位 高八位 低八位 高八位 。。。buffer3[i] = (temp and 0x00ff).toByte()buffer3[i + 1] = (temp and 0xff00).shr(8).toByte()}fosMix.write(buffer3)}}fis1.close()fis2.close()fosMix.flush()fosMix.close()Log.d(TAG, "mixPcm:$mixPcm")
}

注意事项:
kotlin中,位运算只能是Int 和 Long类型,所以大部分我们都需要通过toInt()或者toLong()方法转为Int或Long类型。比如

(buffer1[i].toInt() and 0xff)

语法糖有时候真的很坑,还不好理解。

关于性能

个人感觉MediaCodec 单纯解码音频的解码速度比较慢,视频还不错。FFmpeg对于从视频中提取PCM较快(网上所说),等后续用FFmpeg的时候看下。

混合器MediaMuxer

在Android中我们可以使用MediaMuxer来封装编码后的视频流和音频流到mp4容器中。它仅支持一个视频轨道(track)和一个音频轨道(track)。
如果你有多个音频轨道,那么需要把多个音频轨道合为一个音频轨道一起再使用MediaMuxer。也就是为什么要混音了。

  • 创建。指定输出视频的格式和文件目录。
val mediaMuxer = MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
  • 添加轨道信息。
    创建MediaMuxer对象之后,一个比较重要的操作就是addTrack(MediaFormat format),添加媒体通道,该函数需要传入MediaFormat对象,通常从MediaExtractor或者MediaCodec中获取,比如
val videoFormat = mediaExtractor.getTrackFormat(videoIndex)
val muxerVideoTrackIndex = mediaMuxer.addTrack(videoFormat)val audioFormat = mediaExtractor.getTrackFormat(audioIndex)
val muxerAudioIndex = mediaMuxer.addTrack(audioFormat)
  • 启动。 添加完所有track后调用start方法
 mediaMuxer.start()
  • 写入音视频数据
 public void writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf,@NonNull BufferInfo bufferInfo) ;

trackIndex 视频轨道还是音频轨道
byteBuf 为数据
bufferInfo 为数据的信息。

// bufferInfo属性
info.size  数据的大小
info.flags  是否为关键帧
info.presentationTimeUs  PTS 时间戳,注意单位是 us
  • 停止写入stop和释放release
mediaMuxer.stop()
mediaMuxer.release()

源码

https://github.com/ta893115871/VideoBGAdd

文章中的源码采用kotlin,主要是自己学习下,因为工作中用不到。
用到的mp4,mp3为网络上下载,只为学习使用,如有侵权将删除。

结尾

本文介绍了视频编辑功能的混音功能。涉及的知识点也不少,比如:pcm的数据格式,音频的mp3硬解为pcm,音频的硬编码为aac,在视频如何提取指定时长的视频和音频,如何把音频和视频混合为mp4,以及对数据的位运算等等。
其实主要是原理以及如何灵活的运用。

Android音视频【十一】视频混音相关推荐

  1. Android音频编解码和混音实现

    原文链接:http://my.oschina.net/daxia/blog/636074 相关源码:https://github.com/YeDaxia/MusicPlus 认识数字音频: 在实现之前 ...

  2. FL Studio20.8中文版初学电音制作 如何制作混音?

    想要自己尝试做音乐制作人首先需要一台midi主控键盘,一套你熟悉的软件,一张音效卡(非电脑原本附的),监听耳机喇叭,以上是最基本的配备,再来-就是一条很极度漫长的路,我可以建议你 既然对电音有兴趣 那 ...

  3. 分轨混音制作|分轨混音多少钱?分轨混音母带处理服务| MZD Studios

    混音太单薄没有力量?人声像贴唱没有立体感?乐器太混不够清晰?不想太业余低端? 高端混音服务就找 MZD Studios. MZD Studios 职业混音/唱片制作:主攻欧美向/电子及重型音乐制作: ...

  4. 混音机java通用版下载_Mixer混音台下载_Mixer混音台官方下载-太平洋下载中心

    Mixer混音台是一款功能十分强大的DJ混音软件,下载使用Mixer混音台中文软件可以让您对音乐的mix混响音质等效果进行强大的操作,马上下载Mixer混音台进行DJ mix混响吧. 软件截图1 软件 ...

  5. java归一化混音_改进型归一化混音算法

    改进型归一化混音算法 linear PCM格式的音频混音 音频混音的原理:量化的语音信号的叠加等价于空气中声波的叠加. 反应到PCM音频数据上,也就是把同一个声道的数值进行简单的相加,但是这样同时会产 ...

  6. linux 混音设备,一个linux 混音播放的 /etc/asound.conf 配置

    一个linux 混音播放的 /etc/asound.conf 配置 备用. 只有相同用户才能实现设备的同时打开. pcm.!dmix { type dmix ipc_key 5678293 ipc_k ...

  7. 实时音频混音技术在视频直播中的实践应用

    作者:冼牛 转自:前端之巅 最近半年,视频直播领域中产生不少创新玩法,其中包括 K 歌直播和合唱直播.这些创新玩法都用到实时音频混音技术.今天我们来聊一下混音技术的实现,及其在创新玩法中的应用. 混音 ...

  8. 从实时音视频的微场景看混音技术

    作者|冼牛 编辑|覃云 最近半年,视频直播领域中产生不少创新玩法,其中包括 K 歌直播和合唱直播.这些创新玩法都用到实时音频混音技术.今天我们来聊一下混音技术的实现,及其在创新玩法中的应用. 混音的应 ...

  9. 实时音视频直播新玩法中的混音技术

    作者|冼牛 编辑|覃云 最近半年,视频直播领域中产生不少创新玩法,其中包括 K 歌直播和合唱直播.这些创新玩法都用到实时音频混音技术.今天我们来聊一下混音技术的实现,及其在创新玩法中的应用. 混音的应 ...

  10. 视频直播新玩法中绕不开的混音技术

    作者|冼牛 编辑|覃云 最近半年,视频直播领域中产生不少创新玩法,其中包括 K 歌直播和合唱直播.这些创新玩法都用到实时音频混音技术.今天我们来聊一下混音技术的实现,及其在创新玩法中的应用. 混音的应 ...

最新文章

  1. mysql启动warning: World-writable config file
  2. 持续集成Java覆盖率合并
  3. C++运行界面一闪而过解决
  4. 诗与远方:无题(八十九)
  5. 【Computer Organization笔记04】ALU的基本功能,1位ALU,位数扩展以及功能扩展
  6. ES6 深拷贝_JS基本数据类型和引用数据类型的区别及深浅拷贝
  7. 服务器的作用及用途,服务器的作用和用途是什么
  8. PATH=ASSA脚本学习区
  9. 禁用“微软 Windows 10 易升”
  10. 计算机软件退税,软件企业2021增值税退税
  11. 《分布式机器学习:算法、理论与实践》
  12. java8分组lambda_Java 8,Lambda:在分组列表中排序并将所有组合并到列表中
  13. Kali——绕过杀毒软件检测工具Veil-Evasion
  14. Arduino基础与常用函数
  15. linux tao环境 安装_linux编译TAO的问题,求高手指导!!!!
  16. mysql 按记录编号_告别硬编码,mysql 如何实现按某字段的不同取值进行统计
  17. unity下载模型到本地并加载
  18. 微信小程序如何实现文本换行
  19. 软件兼容性与软件兼容性测试
  20. 拼多多免单券怎么领取 拼多多免单券是真的吗

热门文章

  1. JAVA程序员代表大众车,C++程序员代表捷豹,看看各类程序员们代表着什么车
  2. 一文搞懂前端对象的深拷贝与浅拷贝
  3. Algs4-1.3.10中序表达式转为后序表达式(第二次实现)
  4. 【转载】谁动了摩卡的奶酪?
  5. MathType几个常用字体的名称
  6. android 用LruCache读取大图片并缓存(转)
  7. 前端开发 V8引擎是什么?
  8. ViT (Vision Transformer) ---- Vision Transformer
  9. cmake使用教程(五)调用opencv外部库和自己生成的库
  10. vue中使用axios发送请求(二)