Android 多媒体之音频

在开发上,习惯的将音频、视频功能的使用称之为多媒体,实际上如果讲的宽泛一些的话,相机的使用,比如拍照,录制视频等,也可以划分到多媒体的范畴里面。

从本节课开始,我们就来看看Android中多媒体的API使用和具体的功能。

本篇文章我们先从音频开发聊起。

零、音频开发场景、内容和基本概念

在说音频开发之前,我们可以先想一想自己琢磨一下,哪些应用场景会用到音频开发。主要的应用场景大致包括:

  • 音频播放器
  • 录音机
  • 语音电话
  • 音视频监控应用
  • 音视频直播应用
  • 音频编辑/处理软件
  • 蓝牙耳机/音箱
  • ... ...

如果我们要成系统的学习多媒体和音视频的开发,大致会有涉及到哪些方面的知识呢,归纳来看主要有一下几个方面的内容:

  • 音频采集/播放:已有音频如何播放;如何采集一段音频;
  • 音频算法处理:主要包括去噪、静音检测、回声消除、音效处理、功放/增强、混音/分离,等等
  • 音频的编解码和格式转换:不同格式之间的转码操作
  • 音频传输协议的开发:主要包括SIP,A2DP、AVRCP,等等

另外,如果要进行音频开发,需要了解一些音频的概念作为前置知识,一些常见的概念如下所示:

  • SampleRate:采样率,每秒采集声音的数量,它用赫兹(Hz)来表示。采样频率越高,音频质量越好。常用的音频采样频率有:8kHz、16kHz、44.1kHz、48kHz 等。
  • Channel:声道数,表示声音录制时的音源数量或回放时相应的扬声器数量。常用的是单声道(Mono)和双声道(Stereo)。要记住这两个词:Stereo和Mono。
  • BitDepth:采样精度,每个采样点用多少数据量表示,它以位(Bit)为单位。位数越多,表示得就越精细,声音质量自然就越好,当然数据量也越大。常见的位宽是:8bit 或者 16bit。
  • BitRate:比特率,每秒音频占用的比特数量,单位是 bps(Bit Per Second),比特率越高,压缩比越小,声音质量越好,音频体积也越大。

一、音频播放

说到音视频多媒体,首先就有一个概念叫:媒体格式。也就是我们常说的不同格式的音视频文件。在Android这个开放系统平台中,支持的媒体格式还是很丰富的,详细内容如下:

音频格式和编解码器

总结来说,Android中常见的音频压缩格式有:MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a,AMR,等等。

1.1 音频的播放

1.1.1 MediaPlayer

首先认识两个基础的概念和API:

  • MediaPlayer:用于播放声音和视频的主要 API。Android 多媒体框架支持播放各种常见媒体类型,可以轻松地将音频、视频和图片集成到应用中。可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。
  • AudioManager:该类API用于管理设备上的音频源和音频输出。

另外需要说一下,MediaPlayerl除了能够获取、解码以及播放音频和视频,而且只需很简单的设置即可以外。它还支持多种不同的媒体源,比如:

  • 本地资源:即res目录下的音频资源。
  • URI:比如可能是通过Content Provider解析到的某个资源URI
  • 网络:通过网络,获取流式传输数据进行播放。

使用步骤

  • 1、初始化MediaPlayer对象
  • 2、准备播放工作:准备工作主要是音频数据源的获取或者是音频数据的解码操作等,该过程属于耗时操作,因此需要在工作线程中进行。
  • 3、音频状态管理:在准备工作过后,可以对音频进行播放、暂停等操作。同时需要注意的是MediaPlayer是有状态的,包括:Idle、Initialized、Prepared、Started、Paused、PlaybackCompleted等状态。当在进行状态的切换时,需要注意几个点:
    • ① Started(开始)/Paused(暂停)到Stopped(停止)是单向转换,无法再从Stopped直接转换到Started,需要经历Prepared重新装载才可以重新播放。
    • ② Initialized(初始化)状态需要装载数据才可以进行start()播放,但是如果使用prepareAsync()方法异步准备,需要等待准备完成再开始播放,这里需要使用一个回调方法:setOnPreparedListener(),它会在异步装载完成后调用。
    • ③ End(结束)状态是游离在其他状态之外的,在任何状态皆可切换,一般在不需要继续使用MediaPlayer的时候,才会使用release()回收资源。
    • ④ Error(错误)状态是游离在其他状态之外的,只有在MediaPlayer发生错误的时候才会转换。为了保持应用的用户体验,通常会监听setOnErrorListener()回调方法,它会在MediaPlayer发生错误的时候被回调。

注意事项

  • 1、使用Service播放音频。在使用MediaPlayer播放音频流时,推荐使用一个Service来承载MediaPlayer,而不是直接在Activity里使用。
  • 2、使用唤醒锁。Android系统的功耗设计里,为了节约电池消耗,如果设备处于睡眠状态,系统将试图降低或者关闭一些没设备必须的特性,包括CPU和Wifi硬件。如果是一个后台播放音乐的应用,降低CPU可能导致在后台运行的时候干扰音频的正常播放,关闭Wifi将可能导致网络音频流的获取出现错误。因此为了保证功能的正常使用,我们必须阻止系统关闭服务。可以使用wake locks(唤醒锁),它会告诉系统你正在使用某些功能,这样就可以一直保持该功能处于唤醒状态,即使锁屏无操作也能继续使用。这个锁会在paused和stoped状态下释放。

1.1.2 SoundPool

如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:

  • 1、延时时间较长,且资源占用率高。
  • 2、不支持多个音频同时播放。

Android中除了MediaPlayer播放音频之外还提供了SoundPool来播放音效,SoundPool使用音效池的概念来管理多个短促的音效,例如它可以开始就加载20个音效,以后在程序中按音效的ID进行播放。SoundPool的特点和使用长江如下:

  • 1、主要用于播放一些较短的声音片段。
  • 2、SoundPool对CPU资源占用量低和反应延迟小。
  • 3、SoundPool还支持自行设置声音的品质、音量、 播放比率等参数。

SoundPool的API说明如下:

  • 1、SoundPool(int maxStreams, int streamType, int srcQuality):指定它总共支持多少个声音(也就是池的大小)、声音的品质。该方法属于5.0以下版本使用。
  • 2、SoundPool.Builder:从5.0版本开始使用的是SoundPool.Builder模式。
  • 3、load(Context context, int resld, int priority):从 resld 所对应的资源加载声音。
  • 4、load(FileDescriptor fd, long offset, long length, int priority):加载 fd 所对应的文件的offset开始、长度为length的声音。
  • 5、load(AssetFileDescriptor afd, int priority):从afd 所对应的文件中加载声音。
  • 6、load(String path, int priority):从path 对应的文件去加载声音。
  • 说明:4个load方法中都有一个priority参数,该参数目前还没有任何作用,Android建议将该 参数设为1,保持和未来的兼容性。
  • play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate):指定播放哪个声音,2和3参数的意思是音量,priority指定播放的优先级,数值越大优先级越高;loop用于指定是否循环,0不循环,-1位循环;rate指定播放的比率,可选值为0.5 - 2 ,1为正常比率。

1.1.3 AudioTrack

AudioTrack属于更偏底层的音频播放,在Android的framework层有MediaPlayerService,其内部就是使用了AudioTrack。AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有:精炼、高效的优点。因此,对于AutioTrack可以总结如下:

  • 使用场景:更适合实时产生播放数据的情况,如加密的音频,MediaPlayer是束手无策的,AudioTrack可以处理。
  • 要求:AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,如果要播需放其它格式音频,需要相应的解码器,这也是AudioTrack用的比较少的原因,原因在于需要程序开发者自己解码音频。
  • 播放模式:
    • ① AudioTrack播放音频有两种播放模式,一种是静态模式,即加载的数据和资源可以直接全部加载完毕,加载方式简单,效率也比较高。但是如果数据量很大,往往不适合;
    • ② 流模式和网络上播放视频是类似的,即数据是按照一定规律不断地传递给接收方的。音频文件过大 音频属性要求高,比如采样率高、深度大的数据;另外如果音频数据是实时产生的,这种情况就只能用流模式。

使用AudioTrack公有三个步骤:

共有三个步骤:

  1. 构建AudioTrack对象,并且把PCM的参数传到对象里面
  2. 调用start
  3. 调用write。

另外,其实AudioTrack以外,还有一个Audio系统,在该系统中主要包含三个核心的API,分别是:

  • AudioManager:主要是用来管理Audio系统的。
  • AudioTrack:主要是用来播放声音。
  • AudioRecord:主要是用来录音。

1.1.4 RingtoneManager

Ringtone为铃声、通知和其他类似声音提供快速播放的方法,该种方式播放音频时,还会涉及到一个核心的管理类”RingtoneManager”,该类作为管理类提供系统铃声列表检索方法,并且RingtoneManager可以生成Ringtone实例。具体的Ringtone的使用步骤和相关的方法如下所示:

  • 1、获取Ringtone对象实例:
//1.通过铃声uri获取
static Ringtone getRingtone(Context context, Uri ringtoneUri)
​
//2.通过铃声检索位置获取
Ringtone getRingtone(int position)
  • 2、RingtoneManager中重要的方法:
1. // 两个构造方法
(Activity activity)
RingtoneManager(Context context)
​
2. // 获取指定声音类型(铃声、通知、闹铃等)的默认声音的Uri
static Uri getDefaultUri(int type)
​
3. // 获取系统所有Ringtone的cursor
Cursor getCursor()
​
4. // 获取cursor指定位置的Ringtone uri
Uri getRingtoneUri(int position)
​
5. // 判断指定Uri是否为默认铃声
static boolean isDefault(Uri ringtoneUri)
​
6. //获取指定uri的所属类型
static int getDefaultType(Uri defaultRingtoneUri)
​
7. //将指定Uri设置为指定声音类型的默认声音
static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)
  
8、//播放
void play() 
9、//停止播放
void stop()

1.1.5 音频及音效的播放总结

经过如上几种音效的播放方式的讲解,我们可以对音效的播放做简单的总结如下所示:

  • 1.对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合。
  • 2.声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool。
  • 3.播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,却播放延迟较小。
  • 4、AudioTrack直接支持WAV和PCM,其他音频需要解码成PCM格式才能播放。 .jet的音频比较少见(有的游戏中在使用),可使用专门的播放器JetPlayer播放。
  • 5.对于系统类声音的播放和操作,Ringtone更适合。

二、音频的采集

手机一般都有麦克风和摄像头,而Android系统就可以利用这些硬件来录制音视频了。为了增加对录制音视频的支持,Android系统提供了一个MediaRecorder的类。

与MediaPlayer类非常相似MediaRecorder也有它自己的状态图,MediaRecorder的各个状态介绍如下:

  • Initial:初始化状态。使用new()方法创建MediaRecorder对象或者调用了reset()方法时,该MediaRecorder对象处于Initial状态。
  • Initialized:已初始化状态,在Initial状态调用setAudioSource()或setVideoSource()方法进入该状态。在这个状态可以通过setOutputFormat()方法设置输出格式,此时MediaRecorder转换为DataSourceConfigured状态。另外,通过reset()重新进入Initial状态。
  • DataSourceConfigured:数据源配置状态,这期间可以设定编码方式、输出文件、屏幕旋转、预览显示等等。可以在Initialized状态通过setOutputFormat()方法进入该状态。可以通过prepare()方法到达Prepared状态。
  • Prepared:就绪状态,在DataSourceConfigured状态通过prepare()方法进入该状态。可以通过start()进入录制状态。另外,可以通过reset()方法回到Initialized状态。
  • Recording:录制状态,通过调用start()方法进入该状态。另外,它可以通过stop()方法或reset()方法回到Initial状态。
  • Released:释放状态,可以通过在Initial状态调用release()方法来进入这个状态,这时将会释放所有和MediaRecorder对象绑定的资源。
  • Error:错误状态,当错误发生的时候进入这个状态,它可以通过reset()方法进入Initial状态。

需要说明的是,与MediaPlayer相似,使用MediaRecorder录音录像时需要严格遵守状态函数调用的先后顺序,在不同的状态调用不同的函数,否则会出现异常。如上的文字描述可以转换为如下状态图:

三、Android中多音视频编解码

音视频的原始数据非常庞大,难以存储和传输。要解决音视频数据的存储和传输问题,需要做如下处理:

  • 音视频编码:即对数据进行压缩,音视频数据压缩技术就是音视频编码。编码的目的就是在最小图像或音频信息丢失情况下得到最大的压缩。
  • 音视频解码:解码是相对编码的,其目的是最大限度的还原原始图像或声音信息。
  • 编解码的作用:编解码的意义就是便于数据传输和存储。

而我们知道音视频编解码格式非常多(h264、h265、vp8、vp9、aac、opus……),实现每种编解码都需要引入外部库,导致项目臃肿、包体积过大且运行性能差。

因此Google提出了一套标准,这就是MediaCodec。具体来说,了解MediaCodec可以从以下几个方面来说:

  • 定义:MediaCodec是Google公司专门为Android开发者和芯片厂商搭建的一套用于调用硬件编解码器组件的统一接口,全部遵循该接口规范即可简单的使用,主要的目的在于统一标准。
  • 特点:与常规编解码库相比,MediaCodec具有非常明显的优势,它速度快、效率高、CPU占用率低、内存小、节省包体积。使用MediaCodec可以解决项目臃肿、减小包体积和提升编解码性能。

关于MediaCodec的工作原理,可以参见下图所示:

工作步骤如下所示:

  • MediaCodec处理输入数据后生成输出数据。通过异步方式处理数据,并使用一组输入和输出缓冲区。
  • 输入端:请求一个空的输入缓冲区,用数据填充它并将其发送到编解码器进行处理。
  • 输出端:编解码器处理完数据并将其转换到一个空的输出缓冲区。最后,请求一个已填满的输出缓冲区,使用它的内容并将其释放回编解码器。

可以操作的数据类型

MediaCodec可以对三种数据进行操作,分别是:

  • 编码数据
  • 原始音频数据
  • 原始视频数据

MediaCodec的状态管理

MediaCodec存在三种状态:停止(stoped)、执行(executing)、释放(released)。

  • 停止状态:包含三个子状态:配置(configured)、未初始化(uninitialized)、错误(error)
  • 执行状态:包含三个子状态:刷新(flushed)、运行(running)、结束流(end-of-stream)

MediaCodec 发展

Android系统中关于MediaCodec的介绍,可以参考Android的官方网站提供的信息:https://developer.android.google.cn/reference/kotlin/android/media/MediaCodec

MediaCodec 是在 Android 4.1版本(API16 )中出现并可用的,它提供了一种极其原始的接口。MediaCodec类同时存在 Java和C++层中,但是只有前者是公共访问方法。

在Android 4.3 (API18)中,MediaCodec被扩展为通过 Surface 提供输入的方法(通过 createInputSurface方法),允许来自于相机的预览或者是经过OpenGL ES呈现。在该版本中,MediaCodec是第一个过了CTS测试的版本。所谓的CTS,全称是Compatibility Test Suite,主要是google推出的一种设备兼容性测试规范,用来保证不同设备一致的用户体验的规范。

除此之外,4.3版本还引入了 MediaMuxer。MediaMuxer允许将AVC编解码器(原始H.264基本流)的输出转换为.MP4格式,可以和音频流一起转码也可以单独转换。

音视频编辑

MediaCodec通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用,几乎可以实现大部分音视频相关功能。主要的操作步骤如下所示:

  • 1、初始化。
  • 2、MediaExtractor:提取音视频编码数据,MediaExtractor用于对音视频文件解封装,提取出已编码的媒体数据。
  • 3、MediaCodec:使用解码器进行解码。
  • 4、处理:对音视频进行处理。
  • 5、编码:使用MediaCodec编码器对音视频数据编码。
  • 6、合成:MediaMuxer合成音视频文件。MediaMuxer用于封装编码后的音视频数据,目前支持MP4、Webm和3GP文件作为输出。
  • 7、 释放资源。

代码中的体现如下:

- createEncoderByType/createDecoderByType
- configure
- start
- while(true) {
    - dequeueInputBuffer
    - queueInputBuffer
    - dequeueOutputBuffer
    - releaseOutputBuffer
}
- stop
- release

使用MediaCodec编码音频

  • 初始化MediaCodec对象,如下所示:
private static MediaCodec createAudioEncoder() throws IOException {
   MediaCodec codec = MediaCodec.createEncoderByType(AUDIO_MIME);
   MediaFormat format = new MediaFormat();
   format.setString(MediaFormat.KEY_MIME, AUDIO_MIME);
   format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
   format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
   format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
   format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
   codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
   return codec;
 }
  • 读取PCM数据,执行编码操作。
...
while (!sawOutputEOS) {
                if (!sawInputEOS) {
                    inputBufIndex = audioEncoder.dequeueInputBuffer(10_000);
                    if (inputBufIndex >= 0) {
                        ByteBuffer inputBuffer = audioInputBuffers[inputBufIndex];
                        //先清空缓冲区
                        inputBuffer.clear();
                        int bufferSize = inputBuffer.remaining();
                        if (bufferSize != rawInputBytes.length) {
                            rawInputBytes = new byte[bufferSize];
                        }
                        //读取
                        readRawAudioCount = fisRawAudio.read(rawInputBytes);
                        //判断是否到文件的末尾
                        if (readRawAudioCount == -1) {
                            readRawAudioEOS = true;
                        }
                        if (readRawAudioEOS) {
                            audioEncoder.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            sawInputEOS = true;
                        } else {
                            //放入缓冲区
                            inputBuffer.put(rawInputBytes, 0, readRawAudioCount);
                            rawAudioSize += readRawAudioCount;
                            //放入编码队列
                            audioEncoder.queueInputBuffer(inputBufIndex, 0, readRawAudioCount, audioTimeUs, 0);
                            audioTimeUs = (long) (1_000_000 * ((float) rawAudioSize / AUDIO_BYTES_PER_SAMPLE));
                        }
                    }
                }
​
                //输出端
                outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, 10_000);
                if (outputBufIndex >= 0) {
                    // Simply ignore codec config buffers.
                    if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                        Log.i(TAG, "audio encoder: codec config buffer");
                        audioEncoder.releaseOutputBuffer(outputBufIndex, false);
                        continue;
                    }
                    if (outBufferInfo.size != 0) {
                        ByteBuffer outBuffer = audioOutputBuffers[outputBufIndex];
                        outBuffer.position(outBufferInfo.offset);
                        outBuffer.limit(outBufferInfo.offset + outBufferInfo.size);
                        //Log.v(TAG, String.format(" writing audio sample : size=%s , presentationTimeUs=%s", outBufferInfo.size, outBufferInfo.presentationTimeUs));
                        if (lastAudioPresentationTimeUs <= outBufferInfo.presentationTimeUs) {
                            lastAudioPresentationTimeUs = outBufferInfo.presentationTimeUs;
                            int outBufSize = outBufferInfo.size;
                            int outPacketSize = outBufSize + 7;
                            outBuffer.position(outBufferInfo.offset);
                            outBuffer.limit(outBufferInfo.offset + outBufSize);
                            byte[] outData = new byte[outPacketSize];
                            addADTStoPacket(outData, outPacketSize);
                            outBuffer.get(outData, 7, outBufSize);
                            fosAccAudio.write(outData, 0, outData.length);
                            //Log.v(TAG, outData.length + " bytes written.");
                        } else {
                            Log.e(TAG, "error sample! its presentationTimeUs should not lower than before.");
                        }
                    }
                    audioEncoder.releaseOutputBuffer(outputBufIndex, false);
                    if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        sawOutputEOS = true;
                    }
                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    audioOutputBuffers = audioEncoder.getOutputBuffers();
                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat audioFormat = audioEncoder.getOutputFormat();
                    Log.i(TAG, "format change : " + audioFormat);
                }
            }
...

以上是MediaCodec的编码执行操作。如果是解码,与编码过程相反即可完成。

总结

  • 优点:MediaCodec是Android重要的底层多媒体组件,合理使用MediaCodec可以实现播放器、直播、视频编辑、视频录制、视频通话、视频会议等几乎所有音视频相关的编解码功能,且与常规编解码库相比拥有绝对的性能优势。
  • 不足:MediaCodec也存在一些缺点,兼容性、稳定性都比较差,开发过程中会经常遇到机型、版本等适配问题,这些都可以通过适配合理解决。

四、音频NDK API开发

如果遇到一些要求更高的项目开发,对音频有高性能的需求,比如说:所需的不仅仅是简单的声音播放或录制功能。它们需要响应式实时系统行为。一些典型用例如:音频合成器、电子鼓、音乐学习应用、DJ 混音、音效、视频/音频会议等这类要求特别高的需求时。就要从更深层次的底层来提供功能支持,这里就会用到NDK开发。

首先来了解一下NDK,全称是Native Development Kit,翻译为原生开发工具包,主要的作用是可以让开发者在Android应用中利用C和c++代码的工具,可用以从自己的源代码构建,或者利用现有的预构建库。

本部分的内容可以在如下的Android官方网站中进行查看和学习:https://developer.android.google.cn/ndk/guides/audio

Android官方给提供了如下选择:

  • OpenSL ES:全称为Open Sound Library for Embedded Systems,嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API,为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现方法,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。
  • AAudio:在 Android 8.0 版本后引入的音频库 , 该音频库需要使用C语言在Native层进行调用 , 属于NDK开发范畴 。AAudio是OpenSL ES 库的轻量级实现,同样具有低延迟 , 高性能的特点。需要特别注意的是,AAudio作为一款定位为轻量级的音频库,只提供写入音频流进行发音的功能 , 不负责音频设备管理 , 文件 I / O , 音频编解码 等操作 ;
    • 音频输入:从话筒 , 耳机等音频输入设备中 , 使用AAudio音频流采集音频数据 , 读取性能高 , 低延迟 。
    • 音频输出:将音频流写入到 AAudio,以极高性能方式将音频流输出到发音设备中 。
  • Oboe:该库是基于AAudio封装的一个开源库,在github上有开源的地址,链接如下:https://github.com/google/oboe 该库与AAudio是使用C++编写的适用于Android开发的高效率的音频开发,依然属于NDK开发的范畴。Google官方推荐使用该库。

五、音频算法的开源库

FFmpeg:路人皆知

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源程序。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

只要是做音视频开发的开发者,几乎没有不知道FFmpeg库的。在github上可以找到FFmpeg的主页地址如下:https://github.com/FFmpeg/FFmpeg 官方网站的地址是:https://ffmpeg.org/

其中包含的库主要包括:

  • libavcodec:音/视频编码库。
  • libavformat:音视频格式的生成和解析等操作。
  • libavutil:公共的工具函数。

该程序最初在Linux平台上开发和使用,目前在windows、mac上均可以使用。

在Android中使用FFmpeg

如果需要在Android中使用FFmpeg,需要进行集成。需要经过几个步骤:

  • 编译:首先要下载FFmpeg,并进行编译,编译出Android中需要的文件。
  • 将编译后的内容集成到Android项目中。
  • 测试并调用集成的FFmpeg中的方法。

Speex

Speex主要是针对语音的开源免费,无专利保护的一种音频压缩格式,是专门为码率在2-44kbps的语音压缩而设计。Speex的特点主要包括:

  • 窄带(8kHz),宽带(16kHz)和超宽带(32kHz)压缩于同一位流
  • 可变比特率(VBR)
  • 非连续传输(DTX)
  • 感官回声消除(AEC)
  • 噪音屏蔽

Slik

Slik算法主要的作用是实现语音和音频的编解码,其主要的特点是:

  • 支持4种采样率:8KHz、12KHz、16KHz、24KHz;三种复杂度:低、中、高。
  • 编码码率在 6~40kbps。
  • 提供了定点C代码,非常有利于向ARM、DSP移植和优化。

六、总结

本篇文档,我们用很长的篇幅介绍了多媒体开发中的音频功能的开发和使用,在具体的开发和应用中,重点应该放在对整体知识的理解和架构的梳理上,不要拘泥于某个API的使用,参数的作用等。归根到底,不同的实现方案,不同的解决方案最终的落脚点和代码操作步骤几乎是相同的。再次回顾总结我们本篇内容:

  • 音频的播放:MediaPlayer、SoundPool、AudioTrack、RingtoneManager
  • 音频的采集:MediaRecorder
  • 音频格式的转换:MediaCodec
  • 底层库的支持和使用:OpenSL ES、AAudio、Oboe
  • 开源库的了解和介绍:FFmpeg
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值