Media|安卓音视频整理(二)—— 音视频编解码

这是关于安卓音视频的一个系列文章,大家可以从这里随意跳跃:
0.安卓音视频整理
1.安卓音视频整理(一)—— 音频模块
2.安卓音视频整理(二)—— 音视频编解码
3.安卓音视频整理(三)—— 图像模块
4.安卓音视频整理(四)—— 音视频播放器
5.安卓音视频整理(五)—— MediaSDK的封装

摘要:多媒体视频不光包括了音频部分,还有图像部分。一个视频文件的录制和播放,伴随着音视频的编解码过程,而编解码也是多媒体开发中很重要的一个知识点。上2篇文章我们对安卓音频的知识点进行了梳理。在这篇文章中,我会基于之前整理的内容,同时重点围绕多媒体视频来叙述。
1.容器(封装)格式与编码方式 视频是现在电脑中多媒体系统中的重要一环。为了适应储存视频的需要,人们设定了不同的视频文件格式来把视频和音频放在一个文件中,以方便同时回放。一个视频文件实际上是在一个文件里面包含了编码过的视频轨道、编码过的音频轨道,还有各种文件描述信息。
1.1 编码格式 编码格式就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件存在的方法。音视频编码格式分为视频编码格式和音频编码格式。
视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。不同编码方法的区别主要是压缩算法的不同。视频编码的目的主要是压缩数据体积。
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

视频中常用的编码标准有H.26X系列,MPEG系列,移动端使用最多的是MP4视频文件,主要使用H.264 AVC编码格式。
有关视频编码的原理可以看一看 雷霄骅 的这篇文章:视频压缩编码和音频压缩编码的基本原理
音频编码的主要作用是将音频采样数据(PCM 等)压缩成为音频码流,从而降低音频的数据量,偏于存储和传输,跟视频编码的作用类似。
音频中常用的编码格式有以下:
① WAV
PCM(脉冲编码调制)是 Pulse Code Modulation 的缩写。WAV 编码的一种实现(有多种实现方式,但是都不会进行压缩操作)就是在 PCM 数据格式的前面加上 44 字节,分别用来描述 PCM 的采样率、声道数、数据格式等信息。
特点:音质非常好,大量软件都支持。
适用场合:多媒体开发的中间文件、保存音乐和音效素材。
② MP3(有损)
MP3 具有不错的压缩比,使用 LAME 编码(MP3 编码格式的一种实现)的中高码率的 MP3 文件,听感上非常接近源 WAV 文件,当然在不同的应用场景下,应该调整合适的参数以达到最好的效果。
特点:音质在 128Kbit/s 以上表现还不错,压缩比比较高,大量软件和硬件都支持,兼容性好。
适用场合:高比特率下对兼容性有要求的音乐欣赏。
③ AAC(有损)
AAC 是新一代的音频有损压缩技术,它通过一些附加的编码技术(比如 PS、SBR 等),衍生出了 LC-AAC、HE-AAC、HE-AAC v2 三种主要的编码格式。
LC-AAC 是比较传统的 AAC,相对而言,其主要应用于中高码率场景的编码(≥80Kbit/s);
HE-AAC(相当于AAC+SBR)主要应用于中低码率场景的编码(≤80Kbit/s);
而新近推出的 HE-AAC v2(相当于AAC+SBR+PS)主要应用于低码率场景的编码(≤48Kbit/s)。事实上大部分编码器都设置为 ≤48Kbit/s 自动启用 PS 技术,而 >48Kbit/s 则不加PS,相当于普通的 HE-AAC。
特点:在小于 128Kbit/s 的码率下表现优异,并且多用于视频中的音频编码。
适用场合:128Kbit/s 以下的音频编码,多用于视频中音频轨的编码。
④ Ogg(有损)
Ogg 是一种非常有潜力的编码,在各种码率下都有比较优秀的表现,尤其是在中低码率场景下。Ogg 除了音质好之外,还是完全免费的,这为 Ogg 获得更多的支持打好了基础。Ogg 有着非常出色的算法,可以用更小的码率达到更好的音质,128Kbit/s 的 Ogg 比 192Kbit/s 甚至更高码率的 MP3 还要出色。但目前因为还没有媒体服务软件的支持,因此基于 Ogg 的数字广播还无法实现。Ogg 目前受支持的情况还不够好,无论是软件上的还是硬件上的支持,都无法和 MP3 相提并论。
特点:可以用比 MP3 更小的码率实现比 MP3 更好的音质,高中低码率下均有良好的表现,兼容性不够好,流媒体特性不支持。
适用场合:语音聊天的音频消息场景。
⑤ APE(无损)
APE 是流行的数字音乐无损压缩格式之一,因出现较早,在全世界特别是中国大陆有着广泛的用户群。与 MP3 这类有损压缩格式不可逆转地删除(人耳听力不敏感的)数据以缩减源文件体积不同,APE 这类无损压缩格式,是以更精炼的记录方式来缩减体积,还原后数据与源文件一样,从而保证了文件的完整性。
APE 由软件 Monkey’s audio 压制得到,开发者为 Matthew T. Ashland,源代码开放,因其界面上有只 “猴子” 标志而出名。相较同类文件格式 FLAC,ape 有查错能力但不提供纠错功能,以保证文件的无损和纯正;其另一个特色是压缩率约为 55%,比 FLAC 高,体积大概为原 CD 的一半,便于存储。
APE 作为一种无损压缩音频格式,通过 Monkey’s Audio 这个软件可以将庞大的 WAV 音频文件压缩为 APE,,体积虽然变小了,但音质和原来一样。通过 Monkey’s Audio 解压缩还原以后得到的 WAV 文件可以做到与压缩前的源文件完全一致。所以 APE 被誉为“无损音频压缩格式”,Monkey’'s Audio 被誉为“无损音频压缩软件”。
简单来讲,APE 压缩与 WinZip 或 WinRAR 这类专业数据压缩软件压缩原理类似,只是 APE 等无损压缩数字音乐之后的 APE 音频文件是可以直接被播放的。APE 的压缩速率是动态的,压缩时只压缩可被压缩部分,不能被压缩的部分还是会保留下来。
⑥ FLAC(无损)
FLAC 中文可解释为无损音频压缩编码。FLAC 是一套著名的自由音频压缩编码,其特点是无损压缩。不同于其他有损压缩编码如 MP3 及 AAC,它不会破坏任何原有的音频资讯,所以可以还原音乐光盘音质。2012 年以来它已被很多软件及硬件音频产品(如 CD 等)所支持.
FLAC 与 MP3 不同,MP3 是音频压缩编码,但 FLAC 是无损压缩,也就是说音频以 FLAC 编码压缩后不会丢失任何信息,将 FLAC 件还原为 WAV 文件后,与压缩前的 WAV 文件内容相同。这种压缩与 ZIP 的方式类似,但 FLAC 的压缩比率大于 ZIP 和 RAR,因为 FLAC 是专门针对 PCM 音频的特点设计的压缩方式。而且可以使用播放器直接播放 FLAC 压缩的文件,就象通常播放你的 MP3 文件一样(近几年已经有许多汽车播放器和家用音响设备支持 FLAC,在 FLAC 的网站上你可以找到这些设备厂家的链接)。
有关音频编码的内容可以看一看这篇文章:常见音频编码格式解析
1.2 容器(封装)格式 我们知道,视频文件需要同时包含音频数据和图像数据,有时还会加入一些其它的数据,例如字幕。这些数据可能会被不同的程序或者硬件处理,但是当它们要进行传输或者存储的时候,这三种数据通常是被封装在一起的,这个存放封装数据的东西,就可以称为 容器,也就是我们日常所知道的 视频 文件格式。例如常见的*.mpg, *.avi, *.mov, *.mp4, *.rm, *.ogg, *.tta等格式,都可以说是一种视频容器。视频容器的格式会关系到这个视频文件的可扩展性。
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

2. 音视频 MPEG 介绍
MPEG 是Moving Picture Experts Group的简称。这个名字本来的含义是指一个研究视频和音频编码标准的小组。现在我们所说的MPEG泛指又该小组制定的一系列视频编码标准。该小组于 1988年组成,至今已经制定了MPEG-1、MPEG-2、MPEG-3、MPEG-4、MPEG-7等多个标准,MPEG-21正在制定中。
2.1 AAC音频 AAC是高级音频编码(Advanced Audio Coding)的缩写,出现于1997年,最初是基于MPEG-2的音频编码技术。由Fraunhofer IIS、Dolby Laboratories、AT&T、Sony等公司共同开发,目的是取代MP3格式。
AAC是新一代的音频有损压缩技术,它通过一些附加的编码技术(比如PS,SBR等),衍生出了 LC-AAC, HE-AAC , HE-AACv2 三种主要的编码。
  • LC-AAC 就是比较传统的AAC,相对而言,主要用于中高码率(>=80Kbps);
  • HE-AAC (相当于AAC+SBR)主要用于中低码(<=80Kbps);
  • HE-AACv2 (相当于AAC+SBR+PS)为最新推出,主要用于低码率(<=48Kbps)
目前使用最多的是 LC-AAC 和 HE-AAC (适合低码率)。流行的Nero AAC编码程序只支持LC,HE,HEv2这三种规格,编码后的AAC音频,规格显示都是LC。HE其实就是AAC(LC)+SBR技术,HEv2就是AAC(LC)+SBR+PS技术;事实上大部分编码器设成<=48Kbps自动启用PS技术,而>48Kbps就不加PS,就相当于普通的HE-AAC。
扩展名:.m4a, .m4b, .m4p, .m4v, .m4r, .3gp, .mp4, .aac
互联网媒体类型:audio/aac, audio/aacp, audio/3gpp, audio/3gpp2,audio/mp4, audio/MP4A-LATM, audio/mpeg4-generic
格式:有损数据压缩
延伸自:MPEG-2 音频
标准:ISO/IEC 13818-7(MPEG-2第7部), ISO/IEC 14496-3(MPEG-4第3部)
AAC格式的主要扩展名有三种:
  • AAC - 使用MPEG-2 Audio Transport Stream( ADTS,参见MPEG-2 )容器,区别于使用MPEG-4容器的MP4/M4A格式,属于传统的AAC编码(FAAC默认的封装,但FAAC亦可输出 MPEG-4 封装的AAC)
  • MP4 - 使用了MPEG-4 Part 14(第14部分)的简化版即3GPP Media Release 6 Basic (3gp6,参见3GP ) 进行封装的AAC编码(Nero AAC 编码器仅能输出MPEG-4封装的AAC);
  • M4A - 为了区别纯音频MP4文件和包含视频的MP4文件而由苹果(Apple)公司使用的扩展名,Apple iTunes 对纯音频MP4文件采用了".M4A"命名。M4A的本质和音频MP4相同,故音频MP4文件亦可直接更改扩展名为M4A。
作为一种高压缩比的音频压缩算法,AAC压缩比通常为18:1,也有资料说为20:1,远胜mp3; 在音质方面,由于采用多声道,和使用低复杂性的描述方式,使其比几乎所有的传统编码方式在同规格的情况下更胜一筹。不过直到2006年, 使用这一格式储存音乐的并不多。
2.1.1 AAC音频文件格式
AAC的音频文件格式有 ADIF & ADTS & LATM 。
  • ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
  • ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。
  • LATM 的全称为“Low-overhead MPEG-4 Audio TransportMultiplex”(低开销音频传输复用),是MPEG-4 AAC制定的一种高效率的码流传输方式,MPEG-2 TS流也采用LATM作为AAC音频码流的封装格式之一。
简单的说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是ADTS格式的音频流。
2.1.2 AAC文件处理流程
  • (1) 判断文件格式,确定为ADIF或ADTS
  • (2) 若为ADIF,解ADIF头信息,跳至第6步。
  • (3) 若为ADTS,寻找同步头。
  • (4) 解ADTS帧头信息。
  • (5) 若有错误检测,进行错误检测。
  • (6) 解块信息。
  • (7) 解元素信息。
2.2 H.26x介绍 H.26x 有H.261,H.262,H.263, H.263v2以及H.264,H.261基本上已经不再使用。这里重点讲一下H.264。
H.264/AVC 是一种视频高压缩技术,同时称为MPEG-4 AVC,或MPEG-4 Part10。 H.264 可工作于多种速率,广泛应用于Internet/intranet上的多媒体流服务、视频点播、可视游戏、低码率移动多媒体通信 (视频 手机等)、交互式多媒体应用、实时多媒体监控、数字电视与演播电视和虚拟视频会议等,大有在上述领域一统天下的趋势,有非常广泛的开发和应用前景。H.264集中体现了当今国际视频编码解码技术的最新成果。在相同的重建图像质量下,H.264比其他视频压缩编码具有更高的压缩比、 更好的IP和无线网络信道适应性。
H.264 标准分为三档:基本档次;主要档次(可用于SDTV、HDTV和DVD等);以及扩展档次(用于网络的视频流)
2.2.1 序列
在H264中,图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是I帧图像。H.264引入IDR图像是为了解码的重同步,当解码器解码到IDR图像时,立即将参考帧队列清空,将已编码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会,IDR图像之后的图像永远不会使用IDR之前的图片的数据来编码。
一个序列就是一段内容差异不太大的图像编码后生产的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以遍一个I镇,然后一个P帧,B帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3,4个P帧。
一个 I 帧可以不依赖其他帧就解码出一幅完整的图像,而 P 帧、B 帧不行。P 帧需要依赖视频流中排在它前面的帧才能解码出图像。B 帧则需要依赖视频流中排在它前面或后面的帧才能解码出图像。
2.2.2 DTS & PTS
DTS:解码时间戳,这个事件戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
PTS:显示时间戳,这个事件戳用来告诉播放器该什么时候显示这一帧的数据。
当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,解码顺序和播放顺序就不一致了。
比如一个视频中,帧的显示顺序是:I B B P,现在我们需要在解码 B 帧时知道 P 帧中信息,因此这几帧在视频流中的顺序可能是:I P B B,这时候就体现出每帧都有 DTS 和 PTS 的作用了。它们的顺序大概如下:
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

3. 安卓 MP4 视频文件的编解码举例 以最多见的MP4为例,简单整理一下安卓开发中的视频编解码流程。下图可以看作是MP4文件的录制与播放流程。我将从这2个图对其展开叙述。
视频录制流程:
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

视频播放流程:
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

应该是能够看得懂我画的是什么,要是看不懂那就过,往下看。
3.1 MP4 文件编码录制 先来看一下MP4文件录制流程的简单框图,为了不影响看图,我把图再贴一下在下面。从图中可以看出,MP4录制的过程包含了3个重要部分:
  • 声音的录制
  • 图像的录制
  • 声音与图像的合并
在声音的录制、图像的录制中就分别涉及到了音频、视频的编码。图中音频数据需要被编码成AAC;图像数据需要被编码成H264。然后通过安卓中的 MediaMuxer 进行音视频的合并,合并中同步音频和图像2者的时间戳,这样一个MP4文件就诞生了。
关于MP4的录制,重点也就是 音频数据编码到AAC、图像数据编码到H264、以及音视频数据合并成MP4 这3块内容。安卓获取声音数据的方法我已经在上一篇文章《安卓音视频整理(一)—— 音频模块》里讲过了,而图像数据的获取我会在下一篇文章《安卓音视频整理(三)—— 图像模块》里讲解。本篇文章重点讲解音视频的编解码。编码使用到的编码器 为MediaCodec,对此我已做好了封装。一个录制MP4的过程,我们有这3个线程。分别是 AudioEncodecThread;VideoEncodecThread;EGLMediaThread(预览)。以下是编码初始化代码。
private void initMediaEncodec(String savePath, int width, int height, int sampleRate, int channelCount) { try { LogAAR.e("initMediaEncodec:-->savePath="+savePath+",size: w * h ="+width+"*"+height+",sampleRate="+sampleRate+",channelCount="+channelCount); mediaMuxer = new MediaMuxer(savePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); initVideoEncodec(MediaFormat.MIMETYPE_VIDEO_AVC, width, height); initAudioEncodec(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount); } catch (IOException e) { e.printStackTrace(); } }

代码中,初始化时已经将MP4需要的编码格式传进编码器,音频和视频分别是MediaFormat.MIMETYPE_AUDIO_AA 和 MediaFormat.MIMETYPE_VIDEO_AVC。这样编码器会按照我们所传参数分别对音频、视频编码进行初始化。以下是分别对音频、视频编码器进行初始化代码。
private void initVideoEncodec(String mimeType, int width, int height) { try { videoBufferinfo = new MediaCodec.BufferInfo(); videoFormat = MediaFormat.createVideoFormat(mimeType, width, height); videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 4); videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); videoEncodec = MediaCodec.createEncoderByType(mimeType); videoEncodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = videoEncodec.createInputSurface(); } catch (IOException e) { e.printStackTrace(); videoEncodec = null; videoFormat = null; videoBufferinfo = null; }}private void initAudioEncodec(String mimeType, int sampleRate, int channelCount) { try { audioBufferinfo = new MediaCodec.BufferInfo(); audioFormat = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount); audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, sampleRate*channelCount*16); audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 96000); audioEncodec = MediaCodec.createEncoderByType(mimeType); audioEncodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (IOException e) { e.printStackTrace(); audioBufferinfo = null; audioFormat = null; audioEncodec = null; } }

接下来分开讲 音频数据编码到AAC、图像数据编码到H264、以及音视频数据合并成MP4 的内容。
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

3.1.1 音频数据编码到AAC
音频数据编码到AAC,过程是需要将录音机得到的音频PCM数据实时传入 MediaCodec,由MediaCodec完成音频数据编码得到AAC的过程,音频数据被编码为AAC,我们再将音频的AAC数据传入到 MediaMuxer。于此同时,视频数据的编码以及写入MediaMuxer也在同步进行。音频编码线程的代码如下:
public void putPCMData(byte[] buffer, int size) { if (audioEncodecThread != null && !audioEncodecThread.isExit && buffer != null && size > 0) { if (audioEncodec!=null){ try { int inputBufferindex = audioEncodec.dequeueInputBuffer(0); if (inputBufferindex >= 0) { ByteBuffer byteBuffer = audioEncodec.getInputBuffers()[inputBufferindex]; byteBuffer.clear(); byteBuffer.put(buffer); long result = System.nanoTime() ; long time = (result - audioEncodecThread.baseTimeStamp ) / 1000; audioEncodec.queueInputBuffer(inputBufferindex, 0, size, time, 0); } }catch (Exception e){ e.printStackTrace(); } } } } static class AudioEncodecThread extends Thread {private WeakReference encoder; private boolean isExit; private MediaCodec audioEncodec; private MediaCodec.BufferInfo bufferInfo; private MediaMuxer mediaMuxer; private int audioTrackIndex = -1; long pts; public AudioEncodecThread(WeakReference encoder) { this.encoder = encoder; audioEncodec = encoder.get().audioEncodec; bufferInfo = encoder.get().audioBufferinfo; mediaMuxer = encoder.get().mediaMuxer; audioTrackIndex = -1; }@Override public void run() { super.run(); pts = 0; isExit = false; audioEncodec.start(); while (true) { if (isExit) { audioEncodec.stop(); audioEncodec.release(); audioEncodec = null; encoder.get().audioExit = true; if (encoder.get().videoExit) { mediaMuxer.stop(); mediaMuxer.release(); mediaMuxer = null; } break; } if (audioEncodec==null){ isExit=true; break; } int outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (mediaMuxer != null) { audioTrackIndex = mediaMuxer.addTrack(audioEncodec.getOutputFormat()); if (encoder.get().videoEncodecThread.videoTrackIndex != -1) { mediaMuxer.start(); encoder.get().encodecStart = true; } } } else { while (outputBufferIndex >= 0) { if (encoder.get().encodecStart) { ByteBuffer outputBuffer = audioEncodec.getOutputBuffers()[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); bufferInfo.presentationTimeUs = getPTSUs(); mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo); } audioEncodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0); } } } } private long baseTimeStamp = -1; private long getPTSUs() { long result = System.nanoTime() ; long time = (result - baseTimeStamp ) / 1000; if (time < 0){ return0; } return time; } public void exit() { isExit = true; } }

3.1.2 图像数据编码到H264
图像数据编码到H264,原理是将Camera得到的图像数据实时传入 MediaCodec,由 MediaCodec 进行编码得到H264数据,当数据被编码得到了H264数据,我们再将H264数据传入到 MediaMuxer。图像数据编码线程的代码如下:
static class VideoEncodecThread extends Thread { private WeakReference encoder; private boolean isExit; private MediaCodec videoEncodec; private MediaCodec.BufferInfo videoBufferinfo; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1; public VideoEncodecThread(WeakReference encoder) { this.encoder = encoder; videoEncodec = encoder.get().videoEncodec; videoBufferinfo = encoder.get().videoBufferinfo; mediaMuxer = encoder.get().mediaMuxer; videoTrackIndex = -1; }@Override public void run() { super.run(); videoTrackIndex = -1; isExit = false; videoEncodec.start(); while (true) { if (isExit) { videoEncodec.stop(); videoEncodec.release(); videoEncodec = null; encoder.get().videoExit = true; if (encoder.get().audioExit) { mediaMuxer.stop(); mediaMuxer.release(); mediaMuxer = null; } if (encoder.get()!=null&&encoder.get().onMediaInfoListener!=null){ encoder.get().onMediaInfoListener.onMediaRecordComple(); } break; } if (videoEncodec==null){ isExit=true; break; } int outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0); if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { videoTrackIndex = mediaMuxer.addTrack(videoEncodec.getOutputFormat()); if (encoder.get().audioEncodecThread.audioTrackIndex != -1) { mediaMuxer.start(); encoder.get().encodecStart = true; } } else { while (outputBufferIndex >= 0) { if (encoder.get().encodecStart) { ByteBuffer outputBuffer = videoEncodec.getOutputBuffers()[outputBufferIndex]; outputBuffer.position(videoBufferinfo.offset); outputBuffer.limit(videoBufferinfo.offset + videoBufferinfo.size); videoBufferinfo.presentationTimeUs = getPTSUs(); mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, videoBufferinfo); if (encoder.get()!=null&&encoder.get().onMediaInfoListener != null) { encoder.get().onMediaInfoListener.onMediaTime((int) (videoBufferinfo.presentationTimeUs / 1000)); } } videoEncodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0); } } } } private long baseTimeStamp = -1; private long getPTSUs() { long result = System.nanoTime() ; long time = (result - baseTimeStamp ) / 1000; if (time < 0){ return0; } return time; }public void exit() { isExit = true; }}

3.1.3 音视频数据合并成MP4
mediaMuxer = new MediaMuxer(savePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

如上,在初始化时,已经设置了视频的文件保存路径 savePath, 以及其输出格式 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 。经过上面2个线程的同时写入。此时的 savePath 路径的MP4文件中已经有数据了。
使用 MediaMuxer 合并音视频过程有一个注意点,那就是保证音频视频时间戳的对应,否则将导致音视频不同步,或者程序报错。
音视频数据合并成MP4主要就是上面代码中的:
mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo);


mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, videoBufferinfo);

这2步,分别将已经编码好的音频轨道数据、和视频轨道数据传入了MediaMuxer。MediaMuxer进行音视频合并得到的也就是我们想要的.mp4文件。
完整代码:YorkBaseMediaEncoder.java
package com.york.opensdk.opengl.encodec; import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; import android.view.Surface; import com.york.opensdk.opengl.egl.EglHelper; import com.york.opensdk.opengl.egl.YorkEGLSurfaceView; import com.york.opensdk.utils.LogAAR; import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import javax.microedition.khronos.egl.EGLContext; public abstract class YorkBaseMediaEncoder {private Surface surface; private EGLContext eglContext; private int width; private int height; private MediaCodec videoEncodec; private MediaFormat videoFormat; private MediaCodec.BufferInfo videoBufferinfo; private MediaCodec audioEncodec; private MediaFormat audioFormat; private MediaCodec.BufferInfo audioBufferinfo; private MediaMuxer mediaMuxer; private boolean encodecStart; private boolean audioExit; private boolean videoExit; private yorkEGLMediaThread yorkEGLMediaThread; private VideoEncodecThread videoEncodecThread; private AudioEncodecThread audioEncodecThread; private YorkEGLSurfaceView.YorkGLRender yorkGLRender; public final static int RENDERMODE_WHEN_DIRTY = 0; public final static int RENDERMODE_CONTINUOUSLY = 1; private int mRenderMode = RENDERMODE_CONTINUOUSLY; private OnMediaInfoListener onMediaInfoListener; public YorkBaseMediaEncoder(Context context) { }public void setRender(YorkEGLSurfaceView.YorkGLRender yorkGLRender) { this.yorkGLRender = yorkGLRender; }public void setmRenderMode(int mRenderMode) { if (yorkGLRender == null) { throw new RuntimeException("must set render before"); } this.mRenderMode = mRenderMode; }public void setOnMediaInfoListener(OnMediaInfoListener onMediaInfoListener) { this.onMediaInfoListener = onMediaInfoListener; }public void initEncodec(EGLContext eglContext, String savePath, int width, int height, int sampleRate, int channelCount) { this.width = width; this.height = height; this.eglContext = eglContext; initMediaEncodec(savePath, width, height, sampleRate, channelCount); }public void startRecord() { if (surface != null && eglContext != null) { audioExit = false; videoExit = false; encodecStart = false; yorkEGLMediaThread = new yorkEGLMediaThread(new WeakReference(this)); videoEncodecThread = new VideoEncodecThread(new WeakReference(this)); audioEncodecThread = new AudioEncodecThread(new WeakReference(this)); audioEncodecThread.baseTimeStamp=System.nanoTime(); videoEncodecThread.baseTimeStamp=System.nanoTime(); yorkEGLMediaThread.isCreate = true; yorkEGLMediaThread.isChange = true; yorkEGLMediaThread.start(); videoEncodecThread.start(); audioEncodecThread.start(); if (onMediaInfoListener!=null){ onMediaInfoListener.onMediaRecordStart(); } } }public void stopRecord() { if (yorkEGLMediaThread != null && videoEncodecThread != null && audioEncodecThread != null) { videoEncodecThread.exit(); audioEncodecThread.exit(); yorkEGLMediaThread.onDestory(); audioEncodecThread.baseTimeStamp=-1; videoEncodecThread.baseTimeStamp=-1; videoEncodecThread = null; yorkEGLMediaThread = null; audioEncodecThread = null; } }private void initMediaEncodec(String savePath, int width, int height, int sampleRate, int channelCount) { try { LogAAR.e("initMediaEncodec:-->savePath="+savePath+",size: w * h ="+width+"*"+height+",sampleRate="+sampleRate+",channelCount="+channelCount); mediaMuxer = new MediaMuxer(savePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); initVideoEncodec(MediaFormat.MIMETYPE_VIDEO_AVC, width, height); initAudioEncodec(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount); } catch (IOException e) { e.printStackTrace(); } }private void initVideoEncodec(String mimeType, int width, int height) { try { videoBufferinfo = new MediaCodec.BufferInfo(); videoFormat = MediaFormat.createVideoFormat(mimeType, width, height); videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 4); videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); videoEncodec = MediaCodec.createEncoderByType(mimeType); videoEncodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); surface = videoEncodec.createInputSurface(); } catch (IOException e) { e.printStackTrace(); videoEncodec = null; videoFormat = null; videoBufferinfo = null; }}private void initAudioEncodec(String mimeType, int sampleRate, int channelCount) { try { audioBufferinfo = new MediaCodec.BufferInfo(); audioFormat = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount); audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, sampleRate*channelCount*16); audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 96000); audioEncodec = MediaCodec.createEncoderByType(mimeType); audioEncodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (IOException e) { e.printStackTrace(); audioBufferinfo = null; audioFormat = null; audioEncodec = null; } }public void putPCMData(byte[] buffer, int size) { if (audioEncodecThread != null && !audioEncodecThread.isExit && buffer != null && size > 0) { if (audioEncodec!=null){ try { int inputBufferindex = audioEncodec.dequeueInputBuffer(0); if (inputBufferindex >= 0) { ByteBuffer byteBuffer = audioEncodec.getInputBuffers()[inputBufferindex]; byteBuffer.clear(); byteBuffer.put(buffer); long result = System.nanoTime() ; long time = (result - audioEncodecThread.baseTimeStamp ) / 1000; audioEncodec.queueInputBuffer(inputBufferindex, 0, size, time, 0); } }catch (Exception e){ e.printStackTrace(); } } } }static class yorkEGLMediaThread extends Thread { private WeakReference encoder; private EglHelper eglHelper; private Object object; private boolean isExit = false; private boolean isCreate = false; private boolean isChange = false; private boolean isStart = false; public yorkEGLMediaThread(WeakReference encoder) { this.encoder = encoder; }@Override public void run() { super.run(); isExit = false; isStart = false; object = new Object(); eglHelper = new EglHelper(); eglHelper.initEgl(encoder.get().surface, encoder.get().eglContext); while (true) { if (isExit) { release(); break; }if (isStart) { if (encoder.get().mRenderMode == RENDERMODE_WHEN_DIRTY) { synchronized (object) { try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } else if (encoder.get().mRenderMode == RENDERMODE_CONTINUOUSLY) { try { Thread.sleep(1000 / 60); } catch (InterruptedException e) { e.printStackTrace(); } } else { throw new RuntimeException("mRenderMode is wrong value"); } } onCreate(); onChange(encoder.get().width, encoder.get().height); onDraw(); isStart = true; }}private void onCreate() { if (isCreate && encoder.get().yorkGLRender != null) { isCreate = false; encoder.get().yorkGLRender.onSurfaceCreated(); } }private void onChange(int width, int height) { if (isChange && encoder.get().yorkGLRender != null) { isChange = false; encoder.get().yorkGLRender.onSurfaceChanged(width, height); } }private void onDraw() { if (encoder.get().yorkGLRender != null && eglHelper != null) { encoder.get().yorkGLRender.onDrawFrame(); if (!isStart) { encoder.get().yorkGLRender.onDrawFrame(); } eglHelper.swapBuffers(); } }private void requestRender() { if (object != null) { synchronized (object) { object.notifyAll(); } } }public void onDestory() { isExit = true; requestRender(); }public void release() { if (eglHelper != null) { eglHelper.destoryEgl(); eglHelper = null; object = null; encoder = null; } } }static class VideoEncodecThread extends Thread { private WeakReference encoder; private boolean isExit; private MediaCodec videoEncodec; private MediaCodec.BufferInfo videoBufferinfo; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1; public VideoEncodecThread(WeakReference encoder) { this.encoder = encoder; videoEncodec = encoder.get().videoEncodec; videoBufferinfo = encoder.get().videoBufferinfo; mediaMuxer = encoder.get().mediaMuxer; videoTrackIndex = -1; }@Override public void run() { super.run(); videoTrackIndex = -1; isExit = false; videoEncodec.start(); while (true) { if (isExit) { videoEncodec.stop(); videoEncodec.release(); videoEncodec = null; encoder.get().videoExit = true; if (encoder.get().audioExit) { mediaMuxer.stop(); mediaMuxer.release(); mediaMuxer = null; } if (encoder.get()!=null&&encoder.get().onMediaInfoListener!=null){ encoder.get().onMediaInfoListener.onMediaRecordComple(); } break; } if (videoEncodec==null){ isExit=true; break; } int outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0); if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { videoTrackIndex = mediaMuxer.addTrack(videoEncodec.getOutputFormat()); if (encoder.get().audioEncodecThread.audioTrackIndex != -1) { mediaMuxer.start(); encoder.get().encodecStart = true; } } else { while (outputBufferIndex >= 0) { if (encoder.get().encodecStart) { ByteBuffer outputBuffer = videoEncodec.getOutputBuffers()[outputBufferIndex]; outputBuffer.position(videoBufferinfo.offset); outputBuffer.limit(videoBufferinfo.offset + videoBufferinfo.size); videoBufferinfo.presentationTimeUs = getPTSUs(); mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, videoBufferinfo); if (encoder.get()!=null&&encoder.get().onMediaInfoListener != null) { encoder.get().onMediaInfoListener.onMediaTime((int) (videoBufferinfo.presentationTimeUs / 1000)); } } videoEncodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = videoEncodec.dequeueOutputBuffer(videoBufferinfo, 0); } } } } private long baseTimeStamp = -1; private long getPTSUs() { long result = System.nanoTime() ; long time = (result - baseTimeStamp ) / 1000; if (time < 0){ return0; } return time; }public void exit() { isExit = true; }}static class AudioEncodecThread extends Thread {private WeakReference encoder; private boolean isExit; private MediaCodec audioEncodec; private MediaCodec.BufferInfo bufferInfo; private MediaMuxer mediaMuxer; private int audioTrackIndex = -1; long pts; public AudioEncodecThread(WeakReference encoder) { this.encoder = encoder; audioEncodec = encoder.get().audioEncodec; bufferInfo = encoder.get().audioBufferinfo; mediaMuxer = encoder.get().mediaMuxer; audioTrackIndex = -1; }@Override public void run() { super.run(); pts = 0; isExit = false; audioEncodec.start(); while (true) { if (isExit) { audioEncodec.stop(); audioEncodec.release(); audioEncodec = null; encoder.get().audioExit = true; if (encoder.get().videoExit) { mediaMuxer.stop(); mediaMuxer.release(); mediaMuxer = null; } break; } if (audioEncodec==null){ isExit=true; break; } int outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (mediaMuxer != null) { audioTrackIndex = mediaMuxer.addTrack(audioEncodec.getOutputFormat()); if (encoder.get().videoEncodecThread.videoTrackIndex != -1) { mediaMuxer.start(); encoder.get().encodecStart = true; } } } else { while (outputBufferIndex >= 0) { if (encoder.get().encodecStart) { ByteBuffer outputBuffer = audioEncodec.getOutputBuffers()[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); bufferInfo.presentationTimeUs = getPTSUs(); mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo); } audioEncodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = audioEncodec.dequeueOutputBuffer(bufferInfo, 0); } } } } private long baseTimeStamp = -1; private long getPTSUs() { long result = System.nanoTime() ; long time = (result - baseTimeStamp ) / 1000; if (time < 0){ return0; } return time; } public void exit() { isExit = true; } }public interface OnMediaInfoListener { void onMediaRecordStart(); void onMediaTime(int times); void onMediaRecordComple(); }}

以上就是一个.MP4文件的录制过程
3.2 MP4 文件解码播放 关于MP4 文件的播放,先再看一下MP4文件播放流程的简单框图:
Media|安卓音视频整理(二)—— 音视频编解码
文章图片

图中MP4播放的过程主要有以下流程:
  • FFmpeg 解码得到 Avpacket
  • 音频重采样 Avframe 得到 PCM
  • OpenSL ES 播放 PCM 扬声器发出声音
  • 视频硬/软解码得到图像数据
  • OpenGL 渲染 YUV 数据在屏幕上显示
之所以采用这种方式播放MP4文件,是因为这种方式更能体现出一个视频文件播放的每一个具体步骤。我们还可以了解音频解码、重采样;视频的硬解码、软解码过程,同时也能加深对安卓音视频开发的理解。
关于FFmpeg的使用,本篇文章里不会对它的每一个步骤细讲,之后我会将这部分内容单独开一篇文章《安卓音视频整理(四)—— 音视频播放器》来叙述,在这篇文章中,我们会涉及到FFmpeg的编译、集成、使用,以及音视频播放器SDK的封装等,重点也是在 NDK开发。有兴趣的盆友可以看一下。
预告一下,播放器中会有这些基本功能,代码如下:
package com.york.opensdk.media; import android.media.MediaCodec; import android.media.MediaFormat; import android.text.TextUtils; import android.view.Surface; import com.york.opensdk.listner.PlayerObservable; import com.york.opensdk.listner.PlayerOnFileCodecListener; import com.york.opensdk.listner.PlayerOnParpareListener; import com.york.opensdk.listner.PlayerStatusListener; import com.york.opensdk.utils.BitConverter; import com.york.opensdk.utils.VideoSupportUitl; import com.york.opensdk.utils.LogAAR; import com.york.opensdk.utils.ToolUtils; import com.york.opensdk.opengl.videoplay.YorkGLSurfacePlayView; import com.york.opensdk.opengl.videoplay.YorkRender; import java.nio.ByteBuffer; import java.util.Observable; import java.util.Observer; /** * Created by 86158 on 2019/8/3. * 支持音频和视频播放 */public class OpenPlayer implements Observer {private String source; private boolean playNext = false; private PlayerStatusListener mPlayerStatusListener; private PlayerOnParpareListener mPlayerOnParpareListener; private PlayerOnFileCodecListener mPlayerOnFileCodecListener; private YorkGLSurfacePlayView mGLSurfacePlayView; private MediaFormat mediaFormat; private MediaCodec mediaCodec; private Surface surface; private MediaCodec.BufferInfo info; private PlayerObservable mPlayerObservable; public final int PLAY_STOP = 1; public final int PLAY_PAUSE = 2; public final int PLAYING = 3; public OpenPlayer() { mPlayerObservable = new PlayerObservable(); mPlayerObservable.addObserver(this); }@Override public void update(Observable o, Object arg) {}static { System.loadLibrary("player_lib"); System.loadLibrary("avcodec-57"); System.loadLibrary("avformat-57"); System.loadLibrary("avutil-55"); System.loadLibrary("swresample-2"); System.loadLibrary("swscale-4"); }public void setYorkGLSurfaceView(YorkGLSurfacePlayView yorkGLSurfacePlayView) { this.mGLSurfacePlayView = yorkGLSurfacePlayView; if (mGLSurfacePlayView != null) { mGLSurfacePlayView.getWlRender().setOnSurfaceCreateListener(new YorkRender.OnSurfaceCreateListener() { @Override public void onSurfaceCreate(Surface mSurface) { if (surface == null) { surface = mSurface; LogAAR.d("onSurfaceCreate"); } } }); } }public YorkGLSurfacePlayView getGLSurfacePlayView() { return mGLSurfacePlayView; }public void setPlayerStatusListener(PlayerStatusListener mPlayerStatusListener) { this.mPlayerStatusListener = mPlayerStatusListener; }public void setOpenPlayOnParpare(PlayerOnParpareListener mPlayerOnParpareListener) { this.mPlayerOnParpareListener = mPlayerOnParpareListener; }public void setPlayerOnFileCodecListener(PlayerOnFileCodecListener mPlayerOnFileCodecListener) { this.mPlayerOnFileCodecListener = mPlayerOnFileCodecListener; }public PlayerObservable getPlayerObservable() { return mPlayerObservable; }/*********************************************** player callback ************************************************/private void onCallParpared() { LogAAR.i("onParpared"); if (mPlayerStatusListener != null) { mPlayerStatusListener.onParpared(); } if (mPlayerOnParpareListener != null) { mPlayerOnParpareListener.OnParpare(); } mPlayerObservable.onParpare(); }private void onCallLoad(boolean load) { if (mPlayerStatusListener != null) { mPlayerStatusListener.onLoad(load); } }private void onCallComplete(boolean isDoStop) { LogAAR.i("onComplete ->isDoStop=" + isDoStop); if (mPlayerStatusListener != null) { mPlayerStatusListener.onOnComplete(isDoStop); } mPlayerObservable.onOnComplete(isDoStop); }private void onCallPlayValumeDB(int chenell, int db, int left_db, int right_db) { if (mPlayerStatusListener != null) { mPlayerStatusListener.onPlayValumeDB(chenell, db, left_db, right_db); } }private void onCallError(int cord, String msg) { LogAAR.e("cord = " + cord + ",msg = " + msg); if (mPlayerStatusListener != null) { mPlayerStatusListener.onError(cord, msg); } mPlayerObservable.onError(); }private void onCallNext() { if (playNext) { playNext = false; parparePlay(); } }private void onAudioData(byte[] buffer, int chenell, int size) {byte[] leftData = https://www.it610.com/article/new byte[buffer.length]; byte[] rightData = new byte[buffer.length]; for (int i = 0; i < buffer.length / 2; i++) { if (i % 2 == 0) { System.arraycopy(buffer, i * 2, leftData, i, 2); } else { System.arraycopy(buffer, i * 2, rightData, i - 1, 2); } } short[] left = BitConverter.toShorts(leftData); short[] right = BitConverter.toShorts(rightData); short[] midle = BitConverter.toShorts(buffer); int left_db = (int) ToolUtils.getDB(left, size); int right_db = (int) ToolUtils.getDB(right, size); int midle_db = (int) ToolUtils.getDB(midle, size); if (mPlayerStatusListener != null) { mPlayerStatusListener.onPcmData(buffer, size); mPlayerStatusListener.onPlayValumeDB(chenell, midle_db, left_db, right_db); if ( getPlayStates() == PLAYING){ mPlayerObservable.onTimeInfo(n_get_current(),n_get_duration()); } } }private void onPCMdown() { LogAAR.i("onPCMdown"); if (mPlayerOnFileCodecListener != null) { mPlayerOnFileCodecListener.onPcmDown(); } n_release_decode(); }private void onDecodePosition(double pos) { if (mPlayerOnFileCodecListener != null) { mPlayerOnFileCodecListener.onDecodePosition(pos); } }public void onCallRenderYUV(int width, int height, byte[] y, byte[] u, byte[] v) { if (mGLSurfacePlayView != null) { mGLSurfacePlayView.getWlRender().setRenderType(YorkRender.RENDER_YUV); mGLSurfacePlayView.setYUVData(width, height, y, u, v); } }public boolean onCallIsSupportMediaCodec(String ffcodecname) { return VideoSupportUitl.isSupportCodec(ffcodecname); }/** * 初始化MediaCodec,供底层用 * * @param codecName * @param width * @param height * @param csd_0 * @param csd_1 */ public void initMediaCodec(String codecName, int width, int height, byte[] csd_0, byte[] csd_1) { if (surface != null) { try { mGLSurfacePlayView.getWlRender().setRenderType(YorkRender.RENDER_MEDIACODEC); String mime = VideoSupportUitl.findVideoCodecName(codecName); mediaFormat = MediaFormat.createVideoFormat(mime, width, height); mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height); mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(csd_0)); mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(csd_1)); LogAAR.d(mediaFormat.toString()); mediaCodec = MediaCodec.createDecoderByType(mime); info = new MediaCodec.BufferInfo(); mediaCodec.configure(mediaFormat, surface, null, 0); mediaCodec.start(); } catch (Exception e) { e.printStackTrace(); } } else { if (mPlayerStatusListener != null) { onCallError(2001, "surface is null"); } } }/** * 供底层用 * * @param datasize * @param data */ public void decodeAVPacket(int datasize, byte[] data) { if (surface != null && datasize > 0 && data != null && mediaCodec != null) {int intputBufferIndex = mediaCodec.dequeueInputBuffer(10); if (intputBufferIndex >= 0) { ByteBuffer byteBuffer = mediaCodec.getInputBuffers()[intputBufferIndex]; byteBuffer.clear(); byteBuffer.put(data); mediaCodec.queueInputBuffer(intputBufferIndex, 0, datasize, 0, 0); } if (info == null) { info = new MediaCodec.BufferInfo(); } int outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 10); while (outputBufferIndex >= 0) { if (mediaCodec != null) { mediaCodec.releaseOutputBuffer(outputBufferIndex, true); outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 10); } } } }private void releaseMediacodec() { if (mediaCodec != null) { try { mediaCodec.flush(); mediaCodec.stop(); mediaCodec.release(); } catch (Exception e) { //e.printStackTrace(); } mediaFormat = null; info = null; }}/********************************************** player method *************************************************/ public void setSource(String source) { LogAAR.d("setSource:source="+source); stopPlay(); this.source = source; }public void parparePlay() { if (TextUtils.isEmpty(source)) { LogAAR.e("source not be null"); return; } LogAAR.d("parparePlay"); n_parpare_play(source); }public void startPlay() { if (TextUtils.isEmpty(source)) { LogAAR.e("source not be null"); return; } LogAAR.d("startPlay"); n_start_play(); mPlayerObservable.onPlaying(); }public void seekPlay(double secd) { n_seek_play(secd); }public void pausePlay() { if (getPlayStates() == 3) { LogAAR.d("pausePlay"); n_pause_play(); if (mPlayerStatusListener != null) { mPlayerStatusListener.onPause(true); } } mPlayerObservable.onPause(); }public void resumePlay() { LogAAR.d("resumePlay"); n_resume_play(); if (mPlayerStatusListener != null) { mPlayerStatusListener.onPause(false); } mPlayerObservable.onPlaying(); }public void stopPlay() { LogAAR.d("stopPlay"); if (!TextUtils.isEmpty(source)) { n_stop_play(); releaseMediacodec(); } }public void playNext(String url) { LogAAR.d("playNext: url="+url); this.source = url; playNext = true; stopPlay(); }public void setVolume(int percent) { LogAAR.d("setVolume: percent="+percent); if (percent >= 0 && percent <= 100) { n_volume(percent); } }public void setMute(int mute) { LogAAR.d("setMute: mute="+mute); n_mute(mute); }public void setPitch(float pitch) { LogAAR.d("setPitch: pitch="+pitch); n_pitch(pitch); }public void setSpeed(float speed) { LogAAR.d("setSpeed: speed="+speed); n_speed(speed); }public long getDuration() { return n_get_duration(); }public long getCurrent() { return n_get_current(); }/** * @return 1.stop; 2.pause; 3.playing; */ public int getPlayStates() { int states=n_get_play_states(); return states; }public void decodeFile(String source) { n_decode_audio(source); }public void setRecordTempPath(String path) { n_set_sava_dir(path); }public void release() { stopPlay(); n_release(); if (mediaCodec != null) { mediaCodec = null; } mPlayerObservable.deleteObserver(this); mPlayerStatusListener = null; mPlayerOnParpareListener = null; mPlayerOnFileCodecListener = null; mGLSurfacePlayView = null; }/********************************** 音视频 ****************************************/private native void n_parpare_play(String url); private native void n_start_play(); private native void n_pause_play(); private native void n_resume_play(); private native void n_seek_play(double secd); private native int n_duration(); private native void n_stop_play(); private native void n_set_sava_dir(String path); private native void n_release(); private native void n_release_decode(); private native void n_decode_audio(String path); private native int n_get_play_states(); private native void n_volume(int percent); private native void n_mute(int mute); private native void n_pitch(float pitch); private native void n_speed(float speed); // 视频0.5--2.0倍,音频0--5倍都可以private native long n_get_current(); private native long n_get_duration(); }

好了,以上就是音视频编解码的内容整理,更多的内容可以看一下我的后续文章。感谢观看


上一篇:1.安卓音视频整理(一)—— 音频模块
下一篇:3.安卓音视频整理(三)—— 图像模块
个人网站:www.yorkyue.com
【Media|安卓音视频整理(二)—— 音视频编解码】

    推荐阅读