MediaCodecAPI,完成音频AAC硬编,5。0异步处理,AudioRecord录音
本文代码示例采用的Kotlin语法,但是没什么高级特性
MediaExtractor:视频文件的提取器,能将视频和音频分离
MediaMuxer:音视频文件合成器,能将提取到的视频和音频合成新的视频
MediaFormat :提取器获取到的媒体格式类,保存了获取到的媒体的信息(媒体类型, 帧率等)1.设置需要权限
一.如果是本地文件,会涉及到读取和写入,需要在Manifest配置
6.0以上要在代码中动态申请,这里就省略了2.获取需要的视频文件
本文是直接通过contentResolver查询多媒体文件val cursor = contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
null, null, null, null
)
// 查询到的数据存储到了自建的Song类中,存了文件名(name)和路径(path),然后用ListView展示,
为了省事,直接使用的就是点击的文件和相邻下一个文件做为提取视频和音频的原材料 如果不想麻烦去找文件,也可以直接把视频文件放到res/raw文件夹下,raw要自己创建 然后获取文件,MediaExtractor.setDataSource支持很多方式填充
【更多音视频学习资料,点击下方链接免费领取↓↓ ,先码住不迷路~】
点击领取→音视频开发基础知识和资料包3.提取音视频/**
* 配置音视频提取器
* @param position 点击的文件下标
*/
fun configureVideoAndAudioExtractor(position: Int) {
try {
//1.设置要提取视频的文件
/**
*
* MediaExtractor反复提示初始化失败
* 1.检查文件访问权限
* 2.检查视频文件大小是否大于0!!!!!!!!!!!!!!!
* 3.最好不要用拼接路径,比如
* mVideoMediaExtractor.setDataSource(context.getFilesDir()+"xxx.mp4")
* 最好添加路径变量或完整路径地址,比如下面的变量或
* "mnt/sdcard/.../.../xxx.mp4"这种的
*/
//初始化解析器和合成器对象,合成输入的格式是mp4,
//outputVideoPath是合成后输出的路径,自己构建就好
//我的是fileDir+"video.mp4"
mMediaMuxer = MediaMuxer(outputVideoPath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
mVideoMediaExtractor = MediaExtractor()
mAudioMediaExtractor = MediaExtractor()
//songList是一个集合,存着Song类,
//设置要提取出视频的原材料文件
mVideoMediaExtractor.setDataSource(songList[position].path)
//设置要提取出音频的文件
mAudioMediaExtractor.setDataSource(songList[position].path)
//获取轨道,找到视频轨道
for (i in 0 until mVideoMediaExtractor.trackCount) {
val mediaFormat = mVideoMediaExtractor.getTrackFormat(i)
if (mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
//获取到视频轨道
mVideoMediaExtractor.selectTrack(i)
//获取添加到Muxer后生成的新的视频轨道下标
videoMuxerTrackIndex = mMediaMuxer.addTrack(mediaFormat)
//获取视频帧最大值,为了后面合成新视频,读取文件时候设定缓冲区大小
maxFrameSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
//获取视频帧率,为了后面计算获取到的文件处于的播放时间
frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE)
}
}
//找到视频文件中的音频轨道,方法和获取视频差不多
for (j in 0 until mAudioMediaExtractor.trackCount) {
val mediaFormat = mAudioMediaExtractor.getTrackFormat(j)
if (mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
//获取音轨
mAudioMediaExtractor.selectTrack(j)
//添加音轨到Muxer
audioMuxerTrackIndex = mMediaMuxer.addTrack(mediaFormat)
//获取音频最大输入,为了计算缓冲区大小
maxAudioSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
}
}
//这个方法内执行MediaMuxer的合成操作,下面会贴出来
compoundVideoAndAudioWithPermissionCheck()
} catch (e: IOException) {
Log.i("exception", e.message)
} finally {
//释放资源,切记最后要是放资源,重新添加合成文件时候要新建MediaExtractor和MediaMuxer
if (mMediaMuxer != null) {
mMediaMuxer.release()
}
if (mVideoMediaExtractor != null) {
mVideoMediaExtractor.release()
}
if (mAudioMediaExtractor != null) {
mAudioMediaExtractor.release()
}
}
}
总结一下步骤就是
1.创建MediaExtractor和MediaMuxer对象
2.为MediaExtractor对象添加需要的文件setDataSource()
3.for循环获取视频轨道和音频轨道MediaExtractor#selectTrack(),并添加到MediaMuxer中 MediaMuxer#addTrack()
4.开始准备合并4.合成音视频/**
* 合成视频和音频
*/
fun compoundVideoAndAudio() {
//1.开始合成
mMediaMuxer.start()
//2.输入提取到的视频,videoMuxerTrackIndex是之前addTrack生成的下标,
//如果为-1就是添加失败
if (-1 != videoMuxerTrackIndex) {
//描述缓冲区数据信息类,最后Muxer合成要求有的东西
val videoBufferInfo = MediaCodec.BufferInfo()
//创建缓冲区,最后Muxer合成要求有的东西
val videoByteBuffer = ByteBuffer.allocate(maxFrameSize)
while (true) {
/3./获取样本大小
val videoSampleSize = mVideoMediaExtractor.readSampleData(videoByteBuffer, 0)
if (videoSampleSize < 0) {
break
}
//4.设置样本信息
videoBufferInfo.offset = 0 //堆buffer缓冲区写入时的字节偏移
videoBufferInfo.size = videoSampleSize
videoBufferInfo.flags = mVideoMediaExtractor.sampleFlags
//读取到的文件的时间戳,单位是微秒
videoBufferInfo.presentationTimeUs += 1000 * 1000 / frameRate //每次加每帧的微秒数
//MediaMuxer写入样本数据
//videoMuxerTrackIndex 之前加入Muxer的视频轨下标
//videoByteBuffer 上面创建的Buffer对象
//videoBufferInfo 上面创建的BufferInfo对象
mMediaMuxer.writeSampleData(videoMuxerTrackIndex, videoByteBuffer, videoBufferInfo)
//5.推进到下个样本 类似快进
mVideoMediaExtractor.advance()
}
}
/**
* 合成音频,和视频类似
*/
if (-1 != audioMuxerTrackIndex) {
val audioBufferInfo = MediaCodec.BufferInfo()
val audioByteBuffer = ByteBuffer.allocate(maxAudioSize)
while (true) {
val audioSampleSize = mAudioMediaExtractor.readSampleData(audioByteBuffer, 0)
if (audioSampleSize < 0) {
break
}
audioBufferInfo.offset = 0
audioBufferInfo.size = audioSampleSize
audioBufferInfo.flags = mAudioMediaExtractor.sampleFlags
audioBufferInfo.presentationTimeUs += 1000 * 1000 / frameRate
mMediaMuxer.writeSampleData(audioMuxerTrackIndex, audioByteBuffer, audioBufferInfo)
mAudioMediaExtractor.advance()
}
}
}
}
}
这个方法是在之前的方法内调用,所以try..catch写在上面的方法中,这些步骤都可以在子线程中去做,最后可以通过系统的VideoView控件来播放outputVideoPath路径的视频,查看是否合成成功
【更多音视频学习资料,点击下方链接免费领取↓↓ ,先码住不迷路~】
点击领取→音视频开发基础知识和资料包总结流程创建Buffer对象和MediaCodec#BufferInfo对象读取样本数据,验证数据大小填写bufferInfo的信息将缓冲区内容写入Muxer调用MediaExtractor#advance(),推进到下个样本数据,循环步骤2-5释放资源
需要注意的地方!!!!!!!!!!!!!!!
1.权限获取
2.验证要提取的文件是否合法,比如大小,本人遇到MediaExtractor.setDataSource()一直无法初始化MediaExtractor,结果发现添加的视频大小为0KB
3.时间戳会影响合成后效果,需要注意计算
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
寂静岭2里的暗语太多了玩家在进入寂静岭后,遇到的第一个NPC叫安吉拉。初次见面,她声称他们自己来寂静岭寻找学生自己的mama(就是因为我们现在所说的妈妈的意思,不过游戏中教师角色配音方面并没有通过使用一
这是我见过最优雅的妈妈穿衣简单朴素有质感,不扮嫩更加体面女人在当妈妈之前,总是会将更多的心思放在自己身上,爱美的天性也会在当下完全地释放出来但是当女人当了妈妈之后,大多数都会变得忙碌起来,也很少会有时间去捣饬自己,逐渐就会变得衰老平庸普
李白一辈子没有正经工作,那他是怎么支撑生活开销和人情来往的?李白一生多有坎坷,但是其天生我才必有用,千金散尽还复来的豪言壮语,让人情不自禁的感受到李白的气魄和视金钱如粪土的人生态度。不过从这里我们也可以看出李白也是有钱的,可是当我们稍微了解
李少莉正式回单位工作了,那些揣测的的人打脸了吗?这段时间网上关于李少莉的讨论,还是持续发酵,持续深扒,推到风口浪尖,但是聪明的是她沉默是金!这也许就是她大格局,能走到今天的位置!就因为发布会带一个不知道真假的耳钉,丝巾,让很多网
吴京与金鸡奖大佬聚会!高端海鲜摆满一桌,张涵予抽雪茄太霸气第35届金鸡奖,刚落下帷幕。就有网友在社交平台上,晒出了一则著名导演吴京,跟圈内大佬聚餐的视频。引发了网友们的热烈讨论。从视频中可以看到,参加聚会的,除了吴京外。还有徐克张涵予于冬
郭晶晶罕见参加贵妇聚会,没一个背名牌包,却暴露真名媛生活最近,向来低调的郭晶晶罕见出现在一个香港顶级名媛聚会上。当天郭晶晶的打扮依旧朴素干净,身穿的红色碎花长裙,之前接孩子和多次参加活动都穿过。唯一比较抢镜头的是她手上戴的红宝石钻戒,可
南京小孩冲突家长打人事件的一点思考近日,南京两个五岁小孩在幼儿园里发生冲突,家长上门掌掴孩子,推倒老人的事件冲上热搜,并引发全网热议。我想这件事情之所以引起大家普遍共鸣,是因为这样的事情,每个养育孩子的父母曾经遇到
一件懊悔不已的事前几天的一个下午,带孙女茜茜到楼下广场去玩,下楼后孙女蹬着她的滑板车,我跟在后面,到了建在小区一侧供儿童玩乐的滑滑梯处,茜茜玩了一会儿之后,我就和她离开,向着家的方向走去。只见孙女
宫外孕胎停我的二胎经历2016年1月1日开放的二胎政策,在这之前身边的人已经有了二胎。看到别人身边软软糯糯的小姑娘,心生羡慕,与老公商量打算要二胎。于是停下了避孕措施,打算顺其自然,结果一年时间也没有动
你的孩子离牛娃,只差这套宝典!我不允许你还不知道这套牛娃启蒙手册,大家请看目录,包括一年级到六年级各个年龄段都要掌握的能力。送自己一朵花更有如何进行英语启蒙,312岁英语规划路线,超干货,史上最全解析!送自己一朵花幼小衔接怎么做
大家来评评理,这孩子该不该生啊?刚看到一位妈妈说的故事,我给大家讲述讲述01hr生活陷入两难,这孩子该不该生?我和我老公是二婚,一胎是女儿,如今两岁10个月了,意外又怀孕了,打了去,太可惜了,生下吧又受罪!我们结
智者问答如何面对恐惧?尼欲望很强大,正是欲望导致了轮回。没有欲望的地方也就不会有轮回。问那恐惧呢?尼欲望属于过去,恐惧属于未来。回忆起过去的痛苦恐惧于痛苦的复发,从而就会对未来感到焦虑。问也有些恐惧是对
灯塔夜读丨遇事修性,遇人修心1hr有人说生活就是个缓慢受锤的过程。在这一番千锤百打中,有些人在自我放弃中不断沉沦,有些人一路高歌前进。生活波澜,命运起伏,有些挫折难以预料,但我们可以选择以何种姿态相迎。在人情
原来我们相伴的日子屈指可数寂静的清晨,深深地思念骤起,然而,我所有的思念和祝福都已经送不出去,只有回忆还在,思念更深。01hr记得,有一年我回老家,上午10点到家,下午4点离家返回市区,中间外出将近三个小时
流浪的风载不动雨的叹息蒲公英悄然地独自伫立清纯含羞的处子让风儿偷窃了她的春色洒向山谷河床原野和麦田春天来了,源自你的骨血,而我去找寻你的灵魂向天空森林岩石和海洋跋涉的好艰辛蓦然回首有一只小黄鸟正在林梢婉
刘晓庆大晒泳装照!70岁仍有超模身段和少女颜,王晓玉真娶到宝了在不同的年龄阶段,女性的思想和认知都会的得到提升,尤其是对于自身的穿衣打扮当中,有的女人或许会觉得需要保守一点,才显得足够得体合适,但其实,女性的魅力从来都不是保守和性感来决定的,
人间不老即神仙为何能将养老尊老写入中华民族基因在阅读此文前,诚邀您点击一下关注,既方便您进行讨论与分享,又给您带来不一样的参与感,感谢您的支持。引言礼记王制云凡养老,有虞氏以燕礼,夏后氏以飨礼,殷商人以食礼,周人修而兼用之。纵
汪明荃赵雅芝两大不老女神同框,一个优雅婆婆,一个娇艳似少女爱美之心,女性皆有。因此,有不少女性即使已经上了年纪,但仍然会大力去追求属于自己的美,利用穿搭来让自己显得更加年轻,打造出不老女神的形象。而有些女性则与此不同,她们更倾向于通过穿搭
长沙出境游喧嚣回归2月8日晚,长沙黄花国际机场,前往越南芽庄的游客排队进入边检通道。图片均由湖南日报全媒体记者辜鹏博摄湖南日报全媒体记者彭可心2月8日晚8时许,长沙黄花国际机场T2航站楼出发层灯火通
澳门新濠影汇住在饭店就超好玩!在澳门机场周边的凼仔区,其中的新濠影汇新濠天地酒店,以水舞间表演蝙蝠侠夜神飞驰影汇之星8字摩天轮澳门最大VR娱乐体验及各种适合小朋友们的娱乐设施,加上购物品牌很多且有优惠,吃饭的餐
主持人刘美希应邀参加缅甸驻华使馆举办的缅甸独立75周年招待会2023年2月8日,主持人刘美希应邀参加缅甸联邦共和国驻华大使馆在京举办的缅甸独立(75)周年招待会。主持人刘美希柬埔寨,文莱,老挝,蒙古国,尼泊尔等国驻华大使,中国外交部部长助理
黎溪5000亩油菜花开等你打卡油菜花开遍地黄,风和日丽金波漾。初春时节,会理市黎溪粮油现代农业园区核心区种植的5000亩油菜花花开正艳,构成了春天里一道亮丽的风景线,吸引不少游客前来赏花踏春。远远望去,黄灿灿的