Media|安卓音视频整理(二)—— 音视频编解码
这是关于安卓音视频的一个系列文章,大家可以从这里随意跳跃:
0.安卓音视频整理
1.安卓音视频整理(一)—— 音频模块
2.安卓音视频整理(二)—— 音视频编解码
3.安卓音视频整理(三)—— 图像模块
4.安卓音视频整理(四)—— 音视频播放器
5.安卓音视频整理(五)—— MediaSDK的封装
摘要:多媒体视频不光包括了音频部分,还有图像部分。一个视频文件的录制和播放,伴随着音视频的编解码过程,而编解码也是多媒体开发中很重要的一个知识点。上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)
扩展名:.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。
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音频码流的封装格式之一。
2.1.2 AAC文件处理流程
- (1) 判断文件格式,确定为ADIF或ADTS
- (2) 若为ADIF,解ADIF头信息,跳至第6步。
- (3) 若为ADTS,寻找同步头。
- (4) 解ADTS帧头信息。
- (5) 若有错误检测,进行错误检测。
- (6) 解块信息。
- (7) 解元素信息。
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个重要部分:
- 声音的录制
- 图像的录制
- 声音与图像的合并
关于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 的内容。
文章图片
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文件播放流程的简单框图:
文章图片
图中MP4播放的过程主要有以下流程:
- FFmpeg 解码得到 Avpacket
- 音频重采样 Avframe 得到 PCM
- OpenSL ES 播放 PCM 扬声器发出声音
- 视频硬/软解码得到图像数据
- OpenGL 渲染 YUV 数据在屏幕上显示
关于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|安卓音视频整理(二)—— 音视频编解码】
推荐阅读
- MediaRecorder前后摄像头同时录像
- 「按键精灵安卓版」关于全分辨率脚本的一些理解(非游戏app)
- 打死都不会用安卓手机(试用两天之后(嗯,真香!))
- 笔记|如何在Windows11安装安卓子系统()
- android|android today上下卡片,【精品文档】关于计算机专业大学生安卓系统有关的外文文献翻译成品(基于Android(安卓)的考勤管理系统(中英文双语对照)
- 音视频开发|音视频开发 三(渲染图片纹理)
- Qt|Qt 使用QMediaPlayer播放MP3
- 安卓软键盘
- 5G芯片发热功耗测试(华为、高通和MediaTek,谁拥有最冷的核心())
- 安卓、常见错误、规整