1.音视频开发(三):AudioTrack播放PCM音频
2.深入理解Android内容简介
3.集成响度均衡遇到播放卡顿问题研究
4.wav音频怎么去掉header播放
5.Android音视频十二使用OpenSLES和AudioTrack进行播放PCM
6.android åºå±å¯ä»¥è°ç¨mediaplayer.hå
音视频开发(三):AudioTrack播放PCM音频
音视频开发系列
音视频开发(一):三种方式绘制
音视频开发(二):AudioRecord录制PCM音频
音视频开发(三):AudioTrack播放PCM音频
在Android开发中,源码声音处理是源码不可忽视的一个方面。在前两篇文章中,源码我们分别介绍了使用三种方法绘制和如何使用AudioRecord录制PCM音频。源码本篇将聚焦于如何利用AudioTrack来播放PCM音频。源码
一、源码汽车租赁网源码MediaPlayer与AudioTrack
Android SDK提供了MediaPlayer与AudioTrack两种API用于播放声音。源码其中,源码AudioTrack更专注于管理与播放单一音频资源,源码能够将PCM音频数据传输到音频接收器,源码支持播放源码流和wav格式的源码音频。而MediaPlayer则可以播放多种格式的源码音频文件,如mp3、源码aac等,源码这是源码因为它在framework层创建了对应的音频解码器。
尽管MediaPlayer功能更全面,但AudioTrack在特定场景下依然有其独特的价值,尤其是在视频编辑、音频混合等应用中。例如,在“剪映”等软件中,用户可以添加多个音轨,与Audition软件类似,这些功能的实现都需要深入理解AudioTrack。
二、AudioTrack简介
AudioTrack提供了丰富的API,包括构造方法、操作、状态管理等。构造方法中的参数包括采样率、声道数、音频格式、缓冲区大小等,与AudioRecord的构造方法相似。其中,streamType参数定义了音频流类型,mode参数则分为MODE_STATIC(静态缓冲)与MODE_STREAM(流式缓冲)。
在AudioTrack的使用中,需要关注的API主要有write、play、pause、stop和release等操作。asp程序源码配置此外,状态管理方面,AudioTrack提供了两个关键状态:是否已初始化以及当前播放状态。
三、实现与问题解决
在实际应用中,使用AudioTrack播放PCM音频时,可能会遇到一些问题。例如,在stream模式下快速点击可能会导致声音重叠,解决方法是在触发播放前先停止和释放audioTrack,然后在写入数据线程中做好状态判断。对于如何监听播放进度,AudioTrack并未提供像MediaPlayer那样的丰富回调机制,但可以通过自定义监听器实现一定的控制。
静态模式下有时无法播放,可能是因为数据加载或释放流程不当。在stream模式中,出现IllegalStateException: Unable to retrieve AudioTrack pointer for write()的异常,通常是由于在播放状态不正确的情况下执行write操作导致。解决这类问题的关键在于正确管理audioTrack的状态。
四、实践与收获
通过学习与实践AudioTrack,我们对音频处理有了更深入的理解。此外,了解其内部机制对于优化音频播放性能、解决实际问题具有重要意义。未来,我们将探讨视频采集与处理,期待与大家在视频领域交流学习。
深入理解Android内容简介
深入理解Android系统,可以从《深入理解Android(卷1)》这本书开始。这本书以一种情境化的形式,对Android的源代码进行详尽剖析,覆盖了Framework层、Native层和Application层。其内容全面,每部分代码分析都力求深入,旨在满足实际应用开发的需求,书中涵盖的知识点对Android开发者来说至关重要。
书共分为章,第一章为预备知识,辅助充值系统源码介绍了阅读所需的系统架构理解及源码阅读技巧;第二章详细解析了MediaScanner,讲解了Android中的核心JNI技术;第三章分析了init进程,揭示了Zygote启动和属性服务工作原理的底层过程;第四章深入剖析了Zygote、SystemServer等关键进程,涉及Android启动速度、HeapSize调整和Watchdog的工作原理等话题。
第五章讲解了Android系统中的重要类,如sp、wp、RefBase、Thread等,以及Java中的Handler和Looper类,掌握这些内容有助于后续代码分析的顺利进行;第六章以MediaServer为核心,全面剖析了Binder,揭示其核心机制;第七章深入研究Audio系统,包括AudioTrack、AudioFlinger和AudioPolicyService的工作原理;第八章讲解Surface系统的实现原理,涉及Surface与Activity、SurfaceFlinger的关系及数据传输流程;第九章对Vold和Rild进行深入分析,还探讨了Phone设计优化的问题;最后,第十章详细阐述了MediaScanner在多媒体系统中的作用。
这本书适合有一定Android开发基础的工程师阅读,通过阅读,读者能更深入地理解Android系统,为实际开发中的挑战提供强大支持。
集成响度均衡遇到播放卡顿问题研究
集成响度均衡后,虽然整体效果显著,但在灰度测试阶段遇到了播放卡顿的问题,尤其是在低配置设备上。用户反馈切换音量大差异的节目时,播放会出现间歇性卡顿,尤其是在倍速播放时更为明显。问题核心在于AudioProcessor处理音频数据的速度超过了设备能实时播放的速率,特别是在处理时间限制下,如0.s或0.s的音频块。 为解决这个问题,尝试了以下策略:提前处理音频数据,但ExoPlayer的源码表明MediaCodec解码后数据直接输入AudioSink,无法缓冲。
在解码前处理数据,考虑重编码,etc 挖矿源码但会增加处理时间,且涉及重编码、缓存管理和切换响度均衡的数据处理,复杂且不优先考虑。
移除java层的1s缓冲,以为能解决卡顿,但实际并未解决问题,因为卡顿与AudioTrack的缓冲大小有关。
进一步的分析揭示了卡顿的根源在于AudioProcessor处理数据的耗时超过了AudioTrack的实际缓冲限制,这导致了播放时的阻塞。为防止卡顿,关键在于:在java层设置足够的缓冲,例如1s,以适应不同设备的处理速度差异。
调整AudioTrack的缓冲大小,提高数据处理的上限,如将其设置为1s,以降低卡顿概率。
因此,解决播放卡顿的关键在于优化数据处理流程和缓冲策略,以适应设备性能和音频处理需求。wav音频怎么去掉header播放
WAV格式的细节在互联网上都可以找到,你仅仅需要在Google上搜索下。但是,遗憾的是,我并没有搜索到一个很好的Java库来读取WAV文件,而且可以移植到Android下。因此,我自己写了一些简单的代码。
下面这个方法就是如何读取一个WAV文件的头部:
private static final String RIFF_HEADER = "RIFF";
private static final String WAVE_HEADER = "WAVE";
private static final String FMT_HEADER = "fmt ";
private static final String DATA_HEADER = "data";
private static final int HEADER_SIZE = ;
private static final String CHARSET = "ASCII";
/* ... */
public static WavInfo readHeader(InputStream wavStream) throws IOException,
DecoderException {
ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN);
wavStream.read(buffer.array(), buffer.arrayOffset(), buffer.capacity());
buffer.rewind();
buffer.position(buffer.position() + );
int format = buffer.getShort();
checkFormat(format == 1, "Unsupported encoding: " + format); // 1 means
// Linear
// PCM
int channels = buffer.getShort();
checkFormat(channels == 1 || channels == 2, "Unsupported channels: "
+ channels);
int rate = buffer.getInt();
checkFormat(rate <= && rate >= , "Unsupported rate: " + rate);
buffer.position(buffer.position() + 6);
int bits = buffer.getShort();
checkFormat(bits == , "Unsupported bits: " + bits);
int dataSize = 0;
while (buffer.getInt() != 0x) { // "data" marker
Log.d(TAG, "Skipping non-data chunk");
int size = buffer.getInt();
wavStream.skip(size);
buffer.rewind();
wavStream.read(buffer.array(), buffer.arrayOffset(), 8);
buffer.rewind();
}
dataSize = buffer.getInt();
checkFormat(dataSize > 0, "wrong datasize: " + dataSize);
return new WavInfo(new FormatSpec(rate, channels == 2), dataSize);
}
上面的代码中,缺少的部分应该是显而易见的。正如你所看到的,仅仅支持位,但在你可以修改代码以支持8位(AudioTrack不支持任何其他分辨率的)。
下面这个方法,则是用来读取文件剩余的部分 – 音频数据。
public static byte[] readWavPcm(WavInfo info, InputStream stream)
throws IOException {
byte[] data = new byte[info.getDataSize()];
stream.read(data, 0, data.length);
return data;
}
我们读取的WavInfo结构体,包含采样率,分辨率和声道数已经足够让我们去播放我们读取的音频了。
如果我们不需要将全部音频数据一次性放入内存中,我们可以使用一个InputStream,windows源码安装php一点一点地读取。
Android音视频十二使用OpenSLES和AudioTrack进行播放PCM
Android中播放PCM数据的两种方法:AudioTrack和OpenSLES。AudioTrack适用于本地播放pcm文件或解码音频流,API简单,适用于场景较为固定;OpenSLES则常用于音频/视频播放器,利用c层直接调用API,减少java-jni反射开销,功能更强大,适合动态处理和复杂播放场景。 AudioTrack的步骤包括:指定采样率(如Hz),声道数(单/双声道)和采样位数(/8位)创建AudioTrack。若数据参数变化,需销毁重建。write方法用于写入pcm数据,同步操作,需在播放前调用play()。播放进度管理需自行处理。 OpenSLES则需先链接系统so库,创建引擎和混音器。配置音频信息后,创建播放器,设置状态,通过回调函数不断注入音频数据。音量和声道控制通过相应的接口实现,提供了更精细的控制选项。完整的源码可以在这里查看。android åºå±å¯ä»¥è°ç¨mediaplayer.hå
ããå®æ¹æ¯æ²¡æå¨NDKéæä¾ä½¿ç¨mediaplayerçæ¥å£çï¼ä½å¦æä½ ä¸å®è¦è¿æ ·åï¼ä¹æ¯æå¯è½çï¼æ路就æ¯ï¼
ãã1ï¼ä»Androidæºç ä¸æ¾å°mediaplayer.h以ålibmediaplayer.soï¼åºååçæµåºè¯¥æ¯è¿æ ·ï¼
ãã2ï¼ç¨soæ¥çå·¥å ·ï¼å¾å°libmediaplayer.soéé¢çå½æ°æ¥å£
ãã3ï¼å¨èªå·±çNDKä¸include mediaplayer.hï¼è¿ä¸ªå¤´æ件å¯ä»¥copyå°èªå·±çå·¥ç¨æ¥ï¼ç¨dllopenå½æ°æå¼libmediaplayer.soè·åéé¢çæ¥å£ï¼åèandroidæºä»£ç 使ç¨mediaplayerææ¾é³è§é¢ã
ããæèªå·±ç¨è¿æ¤æ¹æ³å¨NDKä¸è°ç¨AudioTrackï¼ä¹æ¯java sdkéçä¸ä¸ªç±»ï¼æ¥ææ¾é³é¢ï¼ä½ä¹æ¯æ缺é·çï¼ä¸åçæ¬çAndroidï¼.hæ.soéçæ¥å£æå¯è½æååï¼æä»¥å ¼å®¹æ§åå¨é®é¢ï¼éè¦å¯¹ä¸åçæ¬çæ¥å£é½è¦æ£æµä¸éã
ããé¢å¤è¯ï¼å¨NDKéææ¾é³è§é¢æ£è§çåæ³ï¼æ¯è°ç¨NDKå®æ¹æä¾ç模åOpenSLæOpenALï¼å ·ä½è§NDKææ¡£åexample
WebRTC 实现 Android 传屏 demo
WebRTC是一项实时通讯技术,允许网络应用或站点在浏览器之间不借助中间媒介实现点对点的连接,传输视频流、音频流或其他任意数据。在Android平台上集成WebRTC框架,可以实现强大的音视频传输功能,只需简洁的代码即可。
为了实现Android平台的WebRTC demo,并实现两端局域网传屏功能,我们需要搭建相关环境和配置。首先,通过导入WebRTC官方的aar包,方便地引入官方提供的so与java层sdk代码。若需要自行编译aar,可以参考官方源码中的脚本生成本地aar或发布到maven仓库。
在使用PeerConnectionFactory之前,必须进行全局初始化与资源加载,通过调用静态方法initialize()并传入InitializationOptions进行配置。完成初始化后,可以创建PeerConnectionFactory实例,这个工厂类在后续的音视频采集、编解码等操作中扮演重要角色。通过Builder模式进行初始化,方便设置编解码器。
创建PeerConnection对象是点对点连接的起点,它可以从远端获取音视频流等数据。在创建之前,可以通过RTCConfiguration对连接进行详细配置。同时,通过Factory创建音视频数据源,如AudioSource和VideoSource,但数据来源需在外部实现,如从音频设备捕获或通过特定接口获取视频流。
对于视频流,WebRTC提供了多种获取方式,如ScreenCapturerAndroid、CameraCapturer和FileVideoCapturer。通过startCapture()方法开始获取数据,并通过CapturerObserver进行回调。创建VideoTrack和AudioTrack后,通过PeerConnection添加到连接中,以便生成包含相应媒体信息的SDP。
建立信令服务器是交换SDP信息的关键步骤。对于局域网场景,可直接使用java Socket实现TCPChannelClient。在进行媒体协商前,需要交换编解码器、传输协议等信息,确保双方支持后,连接才能成功建立。
媒体协商过程包括创建Offer和Answer SDP,由呼叫方发起Offer请求,被呼叫方接收并创建Answer回复。当协商成功后,PeerConnection会尝试进行连接,优先选择host方式实现内网间连通。
在点对点连接建立后,开始获取音视频流数据。通过onAddStream()回调接收远端的MediaStream对象,其中包含AudioTracks与VideoTracks。通过addSink()与SurfaceViewRenderer绑定,展示远端视频流。
在WebRTC中,JavaAudioDeviceModule用于实现音视频的录制与播放,底层采用AudioRecord和AudioTrack。创建PeerConnectionFactory时设置AudioDeviceModule即可。
DataChannel专门用于传输音视频流外的数据,如实时文字聊天、文件传输等。通过DataChannel.send()方法相互发送数据,onMessage()回调获取远端发送的数据。DataChannel的创建方式包括In-band与Out-of-band协商,后者在绑定ID一致时更为简便。
音视频探索(6):浅析MediaCodec工作原理
MediaCodec类是Android平台用于访问低层多媒体编/解码器的接口,它是Android多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack等工具配合使用,可以处理多种常见的音视频格式,包括H.、H.、AAC、3gp等。MediaCodec的工作原理是通过输入/输出缓存区同步或异步处理数据。客户端首先将要编解码的数据写入编解码器的输入缓存区,并提交给编解码器。编解码器处理后,数据转存到输出缓存区,同时收回客户端对输入缓存区的所有权。然后,客户端从编解码器的输出缓存区读取编码好的数据进行处理,读取完毕后编解码器收回客户端对输出缓存区的所有权。这一过程不断重复,直至编码器停止工作或异常退出。
在整个MediaCodec的使用过程中,会经历配置、启动、数据处理、停止、释放等步骤,对应的状态包括停止(Stopped)、执行(Executing)以及释放(Released),而Stopped状态又细分为未初始化(Uninitialized)、配置(Configured)、异常(Error),Executing状态细分为读写数据(Flushed)、运行(Running)和流结束(End-of-Stream)。当MediaCodec被创建后,它会处于未初始化状态,待设置好配置信息并调用start()方法启动后,它会进入运行状态,并可以进行数据读写操作。若在运行过程中出现错误,MediaCodec会进入Stopped状态。此时,使用reset方法来重置编解码器是必要的,否则MediaCodec所持有的资源最终会被释放。如果MediaCodec正常完成使用,可以向编解码器发送EOS指令,同时调用stop和release方法来终止编解码器的使用。
MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,这两个方法需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式有:image/jpeg、audio/amr、video/3gpp、video/h、video/avc等。此外,MediaCodec还提供了createByCodecName (String name)方法,可以使用组件的具体名称来创建编解码器,但这种方法的使用相对繁琐,且官方建议最好配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。我们也可以使用MediaCodecList对传入的minmeType参数进行判断,以匹配出MediaCodec对该mineType类型的编解码器是否支持。例如,指定MIME类型为“video/avc”时,可以使用如下代码来创建H.编码器:
java
MediaCodecInfo.CodecCapabilities capabilities = MediaCodecList.getCodecCapabilities("video/avc");
if (capabilities != null) {
MediaCodec codec = MediaCodec.createByCodecName(capabilities.getName());
}
配置和启动编解码器使用MediaCodec的configure方法。这个方法首先提取MediaFormat存储的数据map,然后调用本地方法native_configure实现配置工作。在配置时,需要传入format、surface、crypto、flags参数。format是一个MediaFormat实例,它以“key-value”键值对的形式存储多媒体数据格式信息;surface用于指定解码器的数据源;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。对于H.编码器的配置,可以使用createVideoFormat("video/avc", , )方法创建“video/avc”类型的编码器的MediaFormat对象,并需要指定视频数据的宽高。如果处理音频数据,则可以调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)方法。
配置完毕后,通过调用MediaCodec的start()方法启动编码器,并调用本地方法ByteBuffer[] getBuffers(input)开辟一系列输入、输出缓存区。start()方法的源码如下:
java
native_start();
ByteBuffer[] buffers = getBuffers(input);
MediaCodec支持同步(synchronous)和异步(asynchronous)两种编解码模式。同步模式下,编解码器的数据输入和输出是同步的,只有当输出数据处理完毕时,编解码器才会接收下一次输入数据。而异步模式下,输入和输出数据是异步的,编解码器不会等待输出数据处理完毕就接收下一次输入数据。这里主要介绍同步编解码模式,因为它更常用。当编解码器启动后,它会拥有输入和输出缓存区,但是这些缓存区暂时无法使用,需要通过MediaCodec的dequeueInputBuffer/dequeueOutputBuffer方法获取输入输出缓存区的授权,并通过返回的ID来操作这些缓存区。下面是一个官方提供的示例代码:
java
for (;;) {
ByteBuffer[] buffers = codec.dequeueInputBuffer();
if (buffers != null) {
// 处理输入缓存区
}
ByteBuffer[] outputBuffers = codec.dequeueOutputBuffer(new MediaCodec.BufferInfo(), );
if (outputBuffers != null) {
// 处理输出缓存区
}
}
获取编解码器的输入缓存区并写入数据。首先调用MediaCodec的dequeueInputBuffer(long timeoutUs)方法从编码器的输入缓存区集合中获取一个输入缓存区,并返回该缓存区的下标index。接着调用MediaCodec的getInputBuffer(int index),该方法返回缓存区的ByteBuffer,并将获得的ByteBuffer对象及其index存储到BufferMap对象中,以便在输入结束后释放缓存区并交还给编解码器。然后,在获得输入缓冲区后,将数据填入并使用queueInputBuffer将其提交到编解码器中处理,同时释放输入缓存区交还给编解码器。queueInputBuffer的源码如下:
java
native_queueInputBuffer(index, offset, size, presentationTimeUs, flags);
获取编解码器的输出缓存区并读出数据。与获取输入缓存区类似,MediaCodec提供了dequeueOutputBuffer和getOutputBuffer方法来获取输出缓存区。但是,在调用dequeueOutputBuffer时,还需要传入一个MediaCodec.BufferInfo对象,它记录了编解码好的数据在输出缓存区中的偏移量和大小。当调用本地方法native_dequeueOutputBuffer返回INFO_OUTPUT_BUFFERS_CHANGED时,会调用cacheBuffers方法重新获取一组输出缓存区。这意味着在使用getOutputBuffers方法(API 后被弃用,使用getOutputBuffer(index)代替)来获取输出缓存区时,需要在调用dequeueOutputBuffer时判断返回值,如果返回值为MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED,则需要重新获取输出缓存区集合。此外,还需要判断dequeueOutputBuffer的其他两个返回值:MediaCodec.INFO_TRY_AGAIN_LATER、MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,以处理获取缓存区超时或输出数据格式改变的情况。最后,当输出缓存区的数据被处理完毕后,通过调用MediaCodec的releaseOutputBuffer释放输出缓存区,交还给编解码器。releaseOutputBuffer方法接收两个参数:Index、render,其中Index为输出缓存区索引,render表示当配置编码器时指定了surface,那么应该置为true,输出缓存区的数据将被传递到surface中。