这是关于安卓音视频的一个系列文章。大家可以从这里随意跳跃:
《安卓音视频》
《安卓音视频——音频模块》
《安卓音视频——编解码》
摘要:多媒体视频不光包括了音频部分,还有图像部分。一个视频文件的录制和播放,伴随着音视频的编解码过程,而编解码也是多媒体开发中很重要的一个知识点。上2篇文章我们对安卓音频的知识点进行了梳理。在这篇文章中,我会基于之前整理的内容,同时重点围绕多媒体视频来叙述。
1.容器(封装)格式与编码方式 视频是现在电脑中多媒体系统中的重要一环。为了适应储存视频的需要,人们设定了不同的视频文件格式来把视频和音频放在一个文件中,以方便同时回放。一个视频文件实际上是在一个文件里面包含了编码过的视频轨道、编码过的音频轨道,还有各种文件描述信息。
1.1 编码格式 编码格式就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件存在的方法。音视频编码格式分为视频编码格式和音频编码格式。
视频编码 的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。不同编码方法的区别主要是压缩算法的不同。视频编码的目的主要是压缩数据体积。
视频中常用的编码标准有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等格式,都可以说是一种视频容器。视频容器的格式会关系到这个视频文件的可扩展性。
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 的作用了。它们的顺序大概如下:
3. 安卓 MP4 视频文件的编解码举例 以最多见的MP4为例,简单整理一下安卓开发中的视频编解码流程。下图可以看作是MP4文件的录制与播放流程。我将从这2个图对其展开叙述。
视频录制流程:
视频播放流程:
应该是能够看得懂我画的是什么,要是看不懂那就过,往下看。
3.1 MP4 文件编码录制 先来看一下MP4文件录制流程的简单框图,为了不影响看图,我把图再贴一下在下面。从图中可以看出,MP4录制的过程包含了3个重要部分:
在声音的录制、图像的录制中就分别涉及到了音频、视频的编码。图中音频数据需要被编码成AAC;图像数据需要被编码成H264。然后通过安卓中的 MediaMuxer 进行音视频的合并,合并中同步音频和图像2者的时间戳,这样一个MP4文件就诞生了。
关于MP4的录制,重点也就是 音频数据编码到AAC 、图像数据编码到H264 、以及音视频数据合并成MP4 这3块内容。本篇文章重点讲解音视频的编解码。编码使用到的编码器 为MediaCodec,对此我已做好了封装。一个录制MP4的过程,我们有这3个线程。分别是 AudioEncodecThread;VideoEncodecThread;EGLMediaThread(预览)。以下是编码初始化代码。
1 2 3 4 5 6 7 8 9 10 11 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。这样编码器会按照我们所传参数分别对音频、视频编码进行初始化。以下是分别对音频、视频编码器进行初始化代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 的内容。
3.1.1 音频数据编码到AAC 音频数据编码到AAC,过程是需要将录音机得到的音频PCM数据实时传入 MediaCodec,由MediaCodec完成音频数据编码得到AAC的过程,音频数据被编码为AAC,我们再将音频的AAC数据传入到 MediaMuxer。于此同时,视频数据的编码以及写入MediaMuxer也在同步进行。音频编码线程的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 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<YorkBaseMediaEncoder> encoder; private boolean isExit; private MediaCodec audioEncodec; private MediaCodec.BufferInfo bufferInfo; private MediaMuxer mediaMuxer; private int audioTrackIndex = -1 ; long pts; public AudioEncodecThread (WeakReference<YorkBaseMediaEncoder> 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 ){ return 0 ; } return time; } public void exit () { isExit = true ; } }
3.1.2 图像数据编码到H264 图像数据编码到H264,原理是将Camera得到的图像数据实时传入 MediaCodec,由 MediaCodec 进行编码得到H264数据,当数据被编码得到了H264数据,我们再将H264数据传入到 MediaMuxer。图像数据编码线程的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 static class VideoEncodecThread extends Thread { private WeakReference<YorkBaseMediaEncoder> encoder; private boolean isExit; private MediaCodec videoEncodec; private MediaCodec.BufferInfo videoBufferinfo; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1 ; public VideoEncodecThread (WeakReference<YorkBaseMediaEncoder> 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 ){ return 0 ; } return time; } public void exit () { isExit = true ; } }
3.1.3 音视频数据合并成MP4 1 mediaMuxer = new MediaMuxer (savePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
如上,在初始化时,已经设置了视频的文件保存路径 savePath, 以及其输出格式 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 。经过上面2个线程的同时写入。此时的 savePath 路径的MP4文件中已经有数据了。 使用 MediaMuxer 合并音视频过程有一个注意点,那就是保证音频视频时间戳的对应,否则将导致音视频不同步,或者程序报错。
音视频数据合并成MP4主要就是上面代码中的:
1 mediaMuxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo);
和
1 mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, videoBufferinfo);
这2步,分别将已经编码好的音频轨道数据、和视频轨道数据传入了MediaMuxer。MediaMuxer进行音视频合并得到的也就是我们想要的.mp4文件。
完整代码:YorkBaseMediaEncoder.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 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 <YorkBaseMediaEncoder>(this )); videoEncodecThread = new VideoEncodecThread (new WeakReference <YorkBaseMediaEncoder>(this )); audioEncodecThread = new AudioEncodecThread (new WeakReference <YorkBaseMediaEncoder>(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<YorkBaseMediaEncoder> 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<YorkBaseMediaEncoder> 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<YorkBaseMediaEncoder> encoder; private boolean isExit; private MediaCodec videoEncodec; private MediaCodec.BufferInfo videoBufferinfo; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1 ; public VideoEncodecThread (WeakReference<YorkBaseMediaEncoder> 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 ){ return 0 ; } return time; } public void exit () { isExit = true ; } } static class AudioEncodecThread extends Thread { private WeakReference<YorkBaseMediaEncoder> encoder; private boolean isExit; private MediaCodec audioEncodec; private MediaCodec.BufferInfo bufferInfo; private MediaMuxer mediaMuxer; private int audioTrackIndex = -1 ; long pts; public AudioEncodecThread (WeakReference<YorkBaseMediaEncoder> 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 ){ return 0 ; } return time; } public void exit () { isExit = true ; } } public interface OnMediaInfoListener { void onMediaRecordStart () ; void onMediaTime (int times) ; void onMediaRecordComple () ; } }
以上就是一个.MP4文件的录制过程
3.2 MP4 文件解码播放 关于MP4 文件的播放,先再看一下MP4文件播放流程的简单框图:
图中MP4播放的过程主要有以下流程:
FFmpeg 解码得到 Avpacket
音频重采样 Avframe 得到 PCM
OpenSL ES 播放 PCM 扬声器发出声音
视频硬/软解码得到图像数据
OpenGL 渲染 YUV 数据在屏幕上显示
之所以采用这种方式播放MP4文件,是因为这种方式更能体现出一个视频文件播放的每一个具体步骤。我们还可以了解音频解码、重采样;视频的硬解码、软解码过程,同时也能加深对安卓音视频开发的理解。
关于FFmpeg的使用,本篇文章里不会对它的每一个步骤细讲,之后我会将这部分内容单独开一篇文章来叙述,在这篇文章中,我们会涉及到FFmpeg的编译、集成、使用,以及音视频播放器SDK的封装等,重点也是在 NDK开发。有兴趣的盆友可以看一下。
预告一下,播放器中会有这些基本功能,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 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;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; } 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 = 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); } 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" ); } } } 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) { } mediaFormat = null ; info = null ; } } 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(); } 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) ; private native long n_get_current () ; private native long n_get_duration () ; }
好了,以上就是音视频编解码的内容整理,更多的内容可以看一下我的后续文章。感谢观看