1.ijkplayer源码分析 视频解码流程
2.深入剖析-ijkplayer框架音视频开发
3.FFmpeg example 使用硬件加速解码视频
4.Android 视频硬件编码
5.音视频探索(6):浅析MediaCodec工作原理
6.从 ExoPlayer 源码分析视频无法播放问题
ijkplayer源码分析 视频解码流程
深入ijkplayer源码,源码本文聚焦视频解码流程。源码在video_thread中,源码我们首先审视IJKFF_Pipenode结构体,源码定义于ff_ffpipenode.h和ff_ffpipenode.c。源码pipenode封装软解与硬解功能,源码unity3d愤怒的小鸟源码初始化流程在stream_component_open中启动,源码调用pipeline.ffpipeline_open_video_decoder实现。源码
在视频解码流程中,源码视频帧处理在video_thread线程下进行。源码从packet_queue读取视频packet,源码然后通过软/硬解码,源码最终将解码结果放入frame_queue。源码软解通过ffpipenode_ffplay_vdec.c实现,源码硬解则在ffpipenode_android_mediacodec_vdec.c中执行。源码不论软解还是硬解,解码后的结果均被引导至ff_ffplay.c#queue_picture进行队列化,准备渲染。
对于LinuxC++音视频开发者,学习资源尤为关键。免费音视频开发资料、视频、学习路线图以及面试题,涵盖C/C++、Linux、FFmpeg、WebRTC、RTMP、NDK和Android音视频流媒体高级开发,免费提供给有需求者。学习交流君羊群,点击加入即可获取资料。
最后,渲染流程在stream_open方法中启动,创建video_refresh_thread线程。此线程从frame_queue中读取视频帧,进行音视频同步后,完成渲染。此环节聚焦渲染流程,音视频同步细节暂不展开。
深入剖析-ijkplayer框架音视频开发
随着互联网技术的迅猛发展,移动设备上的视频播放需求日益增长,催生了一系列开源和闭源播放器。这些播放器的功能虽然强大,兼容性也颇优,但其基本模块通常包括事务处理、高端产品展示源码数据接收和解复用、音视频解码以及渲染。以下是一个简化的基本框架图。
在众多播放器项目中,我们选择了ijkplayer进行源码分析。ijkplayer是一款基于FFPlay的轻量级Android/iOS视频播放器,支持跨平台,API易于集成,编译配置可裁剪,方便控制安装包大小。本文基于ijkplayer的k0.7.6版本,重点分析其C语言实现的核心代码,以iOS平台为例,Android平台实现类似,具体请读者自行研究。
ijkplayer的主要目录结构如下:tool(初始化项目工程脚本)、config(编译ffmpeg使用的配置文件)、extra(存放编译ijkplayer所需的依赖源文件,如ffmpeg、openssl等)、ijkmedia(核心代码)、ijkplayer(播放器数据下载及解码相关)、ijksdl(音视频数据渲染相关)、ios(iOS平台上的上层接口封装以及平台相关方法)、android(android平台上的上层接口封装以及平台相关方法)。iOS和Android平台在功能实现上的主要差异在于视频硬件解码和音视频渲染。
ijkplayer的初始化流程包括创建播放器对象,打开ijkplayer/ios/IJKMediaDemo/IJKMediaDemo.xcodeproj工程,在IJKMoviePlayerViewController类中viewDidLoad方法中创建了IJKFFMoviePlayerController对象,即iOS平台上的播放器对象。
ijkplayer的初始化方法具体实现如下:创建了IjkMediaPlayer结构体实例_mediaPlayer,主要完成了以下三个动作:创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分;至此,ijkplayer播放器初始化的相关流程已经完成。
ijkplayer实际上是基于ffplay.c实现的,本章节将以该文件为主线,从数据接收、音视频解码、音视频渲染及同步这三大方面进行讲解,要求读者具备基本的ffmpeg知识。
当外部调用prepareToPlay启动播放后,ijkplayer内部最终会调用到ffplay.c中的stream_open方法,该方法是启动播放器的入口函数,在此会设置player选项,打开audio output,qq暗雷源码最重要的是调用stream_open方法。
从代码中可以看出,stream_open主要做了以下几件事情:创建上下文结构体,设置中断函数,打开文件,探测媒体类型,打开视频、音频解码器,读取媒体数据,将音视频数据分别送入相应的queue中,重复读取和送入数据步骤。
ijkplayer在视频解码上支持软解和硬解两种方式,可在播放前配置优先使用的解码方式,播放过程中不可切换。iOS平台上硬解使用VideoToolbox,Android平台上使用MediaCodec。ijkplayer中的音频解码只支持软解,暂不支持硬解。
ijkplayer中Android平台使用OpenSL ES或AudioTrack输出音频,iOS平台使用AudioQueue输出音频。audio output节点在ffp_prepare_async_l方法中被创建。
iOS平台上采用OpenGL渲染解码后的YUV图像,渲染线程为video_refresh_thread,最后渲染图像的方法为video_image_display2。
对于播放器来说,音视频同步是一个关键点,同时也是一个难点。通常音视频同步的解决方案就是选择一个参考时钟,播放时读取音视频帧上的时间戳,同时参考当前时钟参考时钟上的时间来安排播放。
ijkplayer支持的事件比较多,具体定义在ijkplayer/ijkmedia/ijkplayer/ff_ffmsg.h中。在播放器底层上报事件时,实际上就是将待发送的消息放入消息队列,另外有一个线程会不断从队列中取出消息,上报给外部。
本文只是粗略的分析了ijkplayer的关键代码部分,平台相关的解码、渲染以及用户事务处理部分,都没有具体分析到,大家可以参考代码自行分析。
FFmpeg example 使用硬件加速解码视频
FFmpeg的hw_decode.c示例详细展示了如何利用硬件加速技术来提升视频解码效率。首先,我们探讨一下不同平台的硬件加速选项。在Mac上,贵州智慧农业源码主要依赖videotoolbox进行加速,而在其他平台上,如Windows和Linux,FFmpeg支持CUDA、OpenCL、MediaCodec和Vulkan等多种方式,为开发者提供了丰富的选择。
深入到源代码层面,执行这个示例程序,可以看到关键步骤是如何调用并配置这些硬件加速器的。在这里,开发者需要明确指定希望输出的像素格式,以确保解码后的数据与目标设备兼容。
在main函数中,开发者会明确指定硬件加速器的目标像素格式,这一步骤至关重要,因为它指导解码器如何从GPU传输数据到CPU,从而优化解码性能。
总的来说,FFmpeg的hw_decode.c示例不仅仅是一个编程示例,更是硬件加速技术在音视频处理中的实际应用展示。通过理解并利用这些技术,开发者可以显著提升视频处理的效率和质量。如果你对C++音视频开发感兴趣,可以参考免费的资源包,包括技术视频和详细教程,如FFmpeg、WebRTC、RTMP等技术的学习资料,这些都可以通过指定的交流群组获取,群号是,点击加入即可获取免费的学习资料。
Android 视频硬件编码
本文将深入探讨 Android 平台上 WebRTC 如何利用 MediaCodec 对视频数据进行编码,并分析 webrtc native 与 java 之间的流程交互。
首先,我们将回顾 Android 的 MediaCodec 概念及其基本使用,然后深入源码进行详细分析。
MediaCodec 是 Android 提供的一个处理音频和视频数据的底层 API,支持编码和解码过程。自 Android 4.1(API )起引入,通常与 MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、python无线密码源码Image、Surface 等一起使用。
在 WebRTC 中,VP8 编码器是优先使用的,因此要分析 Android 上硬件编码的流程,需要先支持 H 硬件编码。
MediaCodec 是 Android 中处理音视频编解码的关键组件。了解其基本概念和用法有助于构建高效、稳定的媒体应用程序。
在 WebRTC 中,如何使用硬件编码器?通过 DefaultVideoEncoderFactory 默认编码器,内部实现是使用硬件编码器。在 DefaultVideoEncoderFactory 中看到 createEncoder 函数,内部是实例化 HardwareVideoEncoder 的地方。
WebRTC 如何将数据送入编码器?WebRTC 使用 VideoEncoder 接口进行视频编码,该接口定义了一个用于编码视频帧的方法:encode(VideoFrame frame, EncodeInfo info)。WebRTC 提供了一个名为 HardwareVideoEncoder 的类,该类实现了 VideoEncoder 接口,并使用 MediaCodec 对视频帧进行编码。
在 HardwareVideoEncoder 类中,WebRTC 将 VideoFrame 对象转换为与 MediaCodec 关联的 Surface 的纹理。这是通过使用 EglBase 类创建一个 EGL 环境,并使用该环境将 VideoFrame 的纹理绘制到 Surface 上来实现的。
WebRTC 如何获取编码后的数据?在 HardwareVideoEncoder 类中,使用 MediaCodec 同步模式获取编码后的数据。当数据可用时,会调用 callback.onEncodedFrame(encodedImage, new CodecSpecificInfo()); 方法,然后将编码后的帧传递给 WebRTC 引擎。
WebRTC 如何做码流控制?WebRTC 的码流控制包括拥塞控制和比特率自适应两个主要方面。当比特率发生变化时,WebRTC 会调用 VideoEncoder.setRateAllocation() 方法来通知更新比特率。
本文深入剖析了 WebRTC 在 Android 平台上如何使用 MediaCodec 对视频数据进行编码,以及整个编码过程中 webrtc native 与 java 的流程交互。希望本文能帮助读者更好地理解 WebRTC Android 编码技术。
音视频探索(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中。
从 ExoPlayer 源码分析视频无法播放问题
面对项目中出现的视频无法播放问题,我们在ExoPlayer三方库中发现了Decoder init failed的常见错误,即(ERROR_CODE_DECODER_INIT_FAILED)。在Google搜索未果后,我们决定深入源码以寻找问题根源。最终,通过源码分析,我们找到了问题所在并找到了解决方案,希望能为遇到类似问题的读者提供帮助。
对比应用,我们发现使用ExoPlayer播放动态壁纸在多个机型上均能正常工作,这有助于排除机型因素。随后,我们引入ExoPlayer库并创建了一个简单的Demo,测试对比后发现,虽然在特定机型上可以播放网络视频链接,但无法播放我们的视频链接。这提示我们可能是在视频格式上存在问题。
在源码分析中,我们发现MediaCodecVideoRenderer抛出的ExoPlaybackException是问题的关键。从调用栈关系可以看出,问题最终归咎于MediaCodecRenderer的maybeInitCodecWithFallback()方法。深入源码分析后,我们发现initCodec()方法调用时出现了异常,进一步导致了DecoderInitializationException。异常信息与日志显示一致,我们继续追踪initCodec()的逻辑。
通过断点调试,我们发现逻辑最终到达了DefaultMediaCodecAdapterFactory的createAdapter()方法,进一步跟进到SynchronousMediaCodecAdapter.Factory中的createAdapter()方法,最终调用了MediaCodec的configure()方法,导致异常。从源码中可以看出,无论逻辑是否执行到特定的if条件,最终都会调用到MediaCodec方法,因此无需关注if逻辑。
我们意识到最终调用的是C/C++代码,通常在Android端遇到此类异常时似乎无能为力。然而,我们从另一个角度思考问题,即在能够播放视频的机型和无法播放的机型之间是否存在参数差异。通过逐步回溯排查MediaCodecInfo对象的值,我们最终发现了关键逻辑代码。
分析后,我们得知首先通过getAvailableCodecInfos()方法获取一组可用解码器列表,然后通过逻辑判断将列表中的所有解码器或第一个添加到队列availableCodecInfos中。接下来,通过while循环不断从availableCodecInfos队列中取出第一个解码器进行初始化尝试,直到找到成功初始化的解码器为止。
从代码注释中,我们了解到enableDecoderFallback参数的含义,设置为true可能导致性能降低(软解性能不如硬解),但默认情况下优先初始化硬解。通过设置setEnableDecoderFallback(true),问题得以解决,从而实现了视频的正常播放。
集成响度均衡遇到播放卡顿问题研究
集成响度均衡后,虽然整体效果显著,但在灰度测试阶段遇到了播放卡顿的问题,尤其是在低配置设备上。用户反馈切换音量大差异的节目时,播放会出现间歇性卡顿,尤其是在倍速播放时更为明显。问题核心在于AudioProcessor处理音频数据的速度超过了设备能实时播放的速率,特别是在处理时间限制下,如0.s或0.s的音频块。 为解决这个问题,尝试了以下策略:提前处理音频数据,但ExoPlayer的源码表明MediaCodec解码后数据直接输入AudioSink,无法缓冲。
在解码前处理数据,考虑重编码,但会增加处理时间,且涉及重编码、缓存管理和切换响度均衡的数据处理,复杂且不优先考虑。
移除java层的1s缓冲,以为能解决卡顿,但实际并未解决问题,因为卡顿与AudioTrack的缓冲大小有关。
进一步的分析揭示了卡顿的根源在于AudioProcessor处理数据的耗时超过了AudioTrack的实际缓冲限制,这导致了播放时的阻塞。为防止卡顿,关键在于:在java层设置足够的缓冲,例如1s,以适应不同设备的处理速度差异。
调整AudioTrack的缓冲大小,提高数据处理的上限,如将其设置为1s,以降低卡顿概率。
因此,解决播放卡顿的关键在于优化数据处理流程和缓冲策略,以适应设备性能和音频处理需求。