首页 > 编程知识 正文

能解码的视频播放器(安卓手机视频解码器)

时间:2023-05-05 08:04:51 阅读:81948 作者:33

如果一张照片胜过千言万语,录像呢?

语句/Izzat Bahadirov

译者/sddt

原文: 3359工程. LinkedIn.com/blog/2019 /灯光视频音频转换器-安卓

2017年,我们设立了视频共享功能,使会员可以通过LinkedIn移动APP和Web浏览器通过提要共享视频内容。 从安卓设备发布视频时,成员可以使用设备照相机的APP位置录制视频,也可以从图片库选择拍摄的视频。 上传后,视频将转换为消费者格式,并作为更新显示在订阅源中。

该功能正常启动并开始普及后,立即着手改善性能。 由于视频占用的数据量巨大,因此性能提高会带来显着的用户体验。 首先,假设用户最有可能直接从捕获的移动设备共享内容。 本文将重点介绍常见捕获参数的显示。

当时,安卓摄像机开箱后的视频录像分辨率约为720~1080p,比特率为12~17Mbps。 这与720p/5Mbps的最高格式大不相同。 这是因为实际上创建了很多字节并将其发送到后端,然后被服务器转码并丢弃。

解决这种“数据处置”问题的方法很简单。 在通过互联网发送视频之前,对设备上的视频进行转码并丢弃这些字节。 为此,需要设备上的代码转换器。 在android-transcoder上找到了开源解决方案。 此解决方案在安卓上运行基本的硬件加速视频/音频转码。 但是,当预测到需要实现的更改时,我意识到需要使用API中断进行大量改写。

我们还希望修改安卓-转换器无法处理的视频帧。 从一开始就建立了库,完成后决定与android-transcoder项目合作。 android-transcoder及其分支(由selsamman,MP4Composer-android,Transcoder进行编辑)的流行趋势是,视频/音频转码在Android媒体社区因此,LiTr诞生了。

今年秋天,开源后不久,我在Demuxed 2019大会上介绍了LiTr。 本文将概述本演示文稿,包括如何构建LiTr体系结构、用于介质转换的方法以及为什么选择MediaCodec来访问硬件编码器。 请参照这里把对话的内容录下来。

介绍

可以在安卓上使用软件或硬件编码器进行转码。 软件编码器(如ffmpeg的安卓端口)提供了几个受支持的编解码器和容器,用于执行编辑操作,如视频合并/分割、轨道合并/解复用器和帧修改但是,可能会消耗大量电池和CPU。 硬件编码器的编解码器选择有限,但性能和能效更高。

经过一些实验,得出了硬件编码器适合我们的需要和约束。 我们的用例非常简单。 降低视频分辨率和/或比特率,以减少“丢弃”多余的像素。 使用硬件编码器,可以提供实时帧速率,从而减少电池使用量。 这是移动设备用户体验的两个重要考虑事项。 在格式兼容性方面,被认为有一定的风险,但风险很低。 成员通常选择共享可以在设备上播放的视频。 这意味着可以进行解码。 此外,由于大多数安卓设备都使用H.264压缩方法录制视频,因此可以使用此编解码器对视频进行编码。

面向安卓的轻量级硬件加速视频/音频编解码器,或者只是LiTr。

媒体编解码器(媒体编解码器) )。

为了访问编码器硬件,LiTr使用安卓的媒体编码解码器API。 要使用MediaCodec,客户端必须首先请求在框架中创建实例。 例如,客户端可以告诉框架,“视频/AVC”需要解码器。 在这种情况下,如果不支持格式,则可以返回MediaCodec的新实例或空值。 创建编解码器实例后,必须设置一组参数,如分辨率、比特率和帧速率。 如果不支持所需的参数(例如,尝试解码4K视频可能会导致在不支持4K分辨率的硬件上配置失败)。 创建并配置MediaCodec实例后,可以将其启动以处理帧。

如果客户端继续通过MediaCodec将数据加载到缓冲区并接收到缓冲区,则客户端将使用缓冲队列与MediaCodec实例进行交互。

客户端将输入缓冲区从MediaCodec出队,并在可用时接收。 客户端在缓冲区中嵌入帧数据,并将其与元数据(开始索引、字节数、帧显示时间、标志)一起返回到MediaCodec。 MediaCodec处理数据。 客户端将MediaCodec输出缓冲区出队,并接收缓冲区(如果可用)。 客户端使用输出数据,将缓冲区释放到MediaCodec。

5c929?from=pc">

媒体编解码器(MediaCodec)过程示意图

重复该过程,直到处理完所有帧。客户端不拥有缓冲区,使用完缓冲区后必须将其释放回MediaCodec。否则,在某些时候,所有出队尝试将始终失败。当不再需要MediaCodec实例时,它将停止并释放它。

使用MediaCodec进行转码

要进行代码转换,我们将需要两个MediaCodec实例:一个作为解码器运行,另一个作为编码器运行。解码器使用并解码已编码的源帧。例如,视频解码器将采用H.264编码的视频帧并将其解码为像素,而音频解码器会将压缩的AAC音频帧解码为未压缩的PCM帧。然后,编码器使用已解码的帧,以生成所需目标格式的编码帧。例如,将使用视频压缩编解码器(例如H.264或VP9)对视频帧进行编码。在某些情况下,解码器的输出可以直接发送到编码器。这种情况的一个很好的例子是在不修改帧内容的情况下改变了压缩比特率(例如,在不将立体声通道合并为单声道的情况下重新压缩音频)。在其他情况下(例如调整视频大小),必须引入渲染层以将解码器输出转换为编码器输入。

在处理视频时,我们可以将MediaCodec配置为与ByteBuffer或Surface一起用作输入/输出。当需要访问原始像素时使用ByteBuffer,它通常较慢,而Surface则较快,但不提供对像素的直接访问。但是,可以使用OpenGL帧着色器修改表面像素。

LiTr将Surface模式用于视频编解码器,将ByteBuffer模式用于音频编解码器。视频渲染器使用OpenGL调整帧的大小(更改视频分辨率时)。并且由于OpenGL使我们能够绘制视频帧,因此视频渲染器支持自定义滤镜,从而允许客户端应用程序使用OpenGL着色器修改视频帧。

在ByteBuffer模式下运行编解码器时,可以执行相同的操作。除了使用OpenGL的情况外,所有渲染和帧修改都必须在软件中完成。以较低的性能为代价,这种方法允许使用软件解码器或帧内容感知逻辑(ML过滤器,超缩放等)。

LiTr结构

上面描述的代码转换过程是如何对单个轨道进行代码转换。使用MediaExtractor读取源数据,并使用MediaMuxer写入目标数据,二者均由Android媒体堆栈提供。对于每种轨道类型(视频,音频,其他),LiTr使用特定的轨道代码转换器:

视频轨道代码转换器可以调整帧大小并更改编码比特率。如有必要,它还可以使用客户端提供的 滤镜来修改帧像素。它在Surface模式下同时运行编码器和解码器编解码器,并使用OpenGL将解码器的输出渲染到编码器的输入上。音轨转码器只能更改比特率(目前)。所有所有非视频和非音频帧都使用直通轨道转码器“按原样”写出。

在进行代码转换时,LiTr会连续迭代所有轨道代码转换器,直到每个轨道代码转换器报告其已完成工作。当带有END_OF_STREAM标志的帧经过每个转码步骤时,轨道转码器认为其工作已完成。转码完成后,将发信号通知MediaMuxer最终确定目标媒体,MediaExtractor释放源媒体。

开始实践

首先,将LiTr导入您的Android应用程序:

implementation ‘com.linkedin.android.litr:litr:1.1.0’

然后,使用可以访问源/目标媒体的Context实例化MediaTransformer(主入口点类)。通常,这就是您应用的ApplicationContext。

MediaTransformermediaTransformer=new MediaTransformer(getApplicationContext());

现在,您可以转换媒体:

这里需要注意的几件事:

客户端必须提供唯一的String requestId,这是转码请求的标志。由于LiTr接受多个代码转换请求,因此需要一种方法来识别每个代码转换请求。应该从实例化MediaTranscoder时使用的上下文访问源视频URI。转码时会保留源轨道计数和顺序。视频将被转换为H.264,并以提供的文件路径保存在MP4容器中。目标视频和音频格式是设置了所有所需参数的Android MediaFormat的实例。该格式将应用于该类型的所有轨道。空格式表示该类型的轨道不会被转码,而是“原样”写出。将使用所有代码转换更新来调用侦听器:开始,进度完成,错误,取消。每个侦听器回调中都会提供一个请求令牌。 粒度是所需的进度更新数量。默认值为100(以匹配在UI中显示的百分比)。传递0将在每个帧上回调。GlFilter的可选列表将您的自定义修改应用于视频帧。

可以使用提供的代码取消正在进行的转码:

mediaTransformer.cancel(requestId);

当不再需要MediaTransformer时,必须将其释放:

mediaTransformer.release();

LiTr还在配套的“过滤器包”库中提供过滤器实现。如果要使用过滤器,请导入litr-filters库:

implementation ‘com.linkedin.android.litr:litr-filters:1.1.1'

该库中目前有两个过滤器,一个静态位图叠加层和一个帧序列动画叠加层(例如动画GIF)。我们正在努力实施更多过滤器,并欢迎做出贡献。

如果出现问题(MediaCodec初始化失败,解码器出错等),MediaTransformer将不会引发异常。相反,它将失败,并使用自定义异常调用侦听器的onError方法,然后客户端可以对其进行分析。

转换完成也可能包含详细的统计信息(跟踪元数据,转换持续时间等)。它们打算在生产环境中用于跟踪或调试目的。请注意,将来,LiTr API及其行为可能会更改,因此在这里主要将它们用于说明目的。

底层转换API

让我们退后一步,从概念上更深入地看一下转码过程。我们将看到有五个不同的步骤:

读取编码的源数据。解码编码的源数据。将解码器输出渲染到编码器输入上。编码渲染的数据。编写编码的目标数据。

每个步骤执行特定功能,并且与上一个和/或下一个步骤具有明确定义的交互。 LiTr提取了将视频转码为接口的每个步骤。我们将每个这样的接口称为“组件”。抽象为客户端提供了强大的功能,可通过插入其自己的组件实现来修改转码过程,而无需修改LiTr源代码。例如,可以实现自定义MediaSource来从Android的MediaExtractor不支持的容器中读取数据,或者自定义编码器可能会引入将代码转码为编码器硬件(例如AV1)不支持的编解码器的功能。

转码过程的逐步概述图

LiTr即开即用,提供默认的组件实现,这些实现包装了Android的MediaCodec类。要传递自定义组件实现,客户端应使用“底层” LiTr API:

由于此API为客户端提供了更多控制权,因此也更容易被破坏。客户必须确保组件可以成功地相互交互。例如,MediaSource以Decoder期望的格式生成编码的帧,或者OpenGL Renderer不与在ByteBuffer模式下运行的Decoder和/或Encoder一起使用。

对LiTr的贡献

LiTr是一个开源项目,欢迎您的贡献!只需在GitHub上,提交拉取请求或打开问题,让我们知道您想看到哪些新功能。我们将继续积极开展LiTr的开发,但可以设想其发展和演变以成为社区的努力。

致谢

非常感谢Tanaka Yuya(AKA ypresto)开拓了android-transcoder项目,该项目激发了许多惊人的Android媒体项目,包括LiTr。感谢Google的AOSP CTS团队在OpenGL中编写“表面到表面”渲染实现,该实现成为LiTr中GlRenderer的基础。向我的亲爱的同事Amita Sahasrabudhe,Long Peng和Keerthi Korrapati表示感谢,感谢他们各自的贡献和代码审查。向我们的设计师Mauroof Ahmed呐喊,为LiTr提供视觉识别的内容!

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。