尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ExoPlayer Transformer 视频处理实战:5步从零搭建视频剪辑导出引擎

ExoPlayer Transformer 视频处理实战:5步从零搭建视频剪辑导出引擎 ExoPlayer Transformer 视频处理实战5步从零搭建视频剪辑导出引擎【免费下载链接】ExoPlayerAn extensible media player for Android项目地址: https://gitcode.com/gh_mirrors/exop/ExoPlayer产品经理又来了用户录完视频想裁出 15 秒精华套一层胶片滤镜再盖上自己的水印一键导出发朋友圈。你翻遍 Android 自带的 MediaPlayer只会发现一个残酷事实——它只会播不会剪更别提导出。想要 ExoPlayer 视频处理能力又不想从零去写 MediaCodec、OpenGL、MP4 封装这一大摊底层活ExoPlayer 生态里的 Transformer 模块正是为这个场景准备的。它是播放器的离线双胞胎把解码 → 处理 → 编码 → 封装整条流水线封装成几个类。这篇文章我们就沿着先跑通、再叠加、后打磨的路线亲手把一套视频剪辑导出引擎从 0 搭到 1。全程代码都可在仓库的demos/transformer/找到对应实现。第一步装好管道5 行代码跑通第一次导出先别急着想滤镜任何剪辑功能都建立在能导出之上。Transformer 的思路很好理解普通播放器把帧送到屏幕而 Transformer 把帧送到编码器再写进文件。中间的解码端完全复用 ExoPlayer 的组件所以你能处理的所有格式它几乎都能导出。先加依赖implementation com.google.android.exoplayer:exoplayer-transformer:2.19.1 implementation com.google.android.exoplayer:exoplayer-effect:2.19.1然后是核心导出代码Transformer transformer new Transformer.Builder(context) .addListener(new Transformer.Listener() { Override public void onCompleted(Composition composition, ExportResult result) { // 导出完成result 里带有输出时长、文件大小等信息 } Override public void onError(Composition composition, ExportResult result, ExportException e) { // 导出失败e.errorCode 会告诉你具体原因 } }) .build(); EditedMediaItem item new EditedMediaItem.Builder(MediaItem.fromUri(videoUri)).build(); Composition composition new Composition.Builder(new EditedMediaItemSequence(item)).build(); transformer.start(composition, new File(context.getCacheDir(), output.mp4).getAbsolutePath());这段代码做了三件事构建导出器、把素材包装成可编辑条目、启动导出。EditedMediaItem是素材的包装盒之后所有裁剪、滤镜都会塞进这个盒子里。跑通后你会在cacheDir下得到一个重编码的 mp4——虽然它和原视频长得一模一样但整条管道已经打通了。管道通了可它只会原封不动地洗一遍。用户要的是剪辑不是复制。所以下一步我们让视频听话。第二步让视频听话——裁剪、拼接与旋转需求很朴素把 30 秒的视频只留中间 15 秒。MediaItem本身就支持裁剪配置这和你播放时设置起止位置是同一套 APIMediaItem mediaItem new MediaItem.Builder() .setUri(videoUri) .setClippingConfiguration( new MediaItem.ClippingConfiguration.Builder() .setStartPositionMs(5_000) .setEndPositionMs(20_000) .build()) .build();为什么要用 MediaItem 而不是手写时间轴因为裁剪在解码前生效省掉了后面整段无用帧的处理开销比导出后再切快得多。再进阶一点把两段素材拼成一个视频。只需把多个EditedMediaItem放进同一个EditedMediaItemSequenceEditedMediaItemSequence sequence new EditedMediaItemSequence(ImmutableList.of( new EditedMediaItem.Builder(clipA).build(), new EditedMediaItem.Builder(clipB).build())); Composition composition new Composition.Builder(sequence).build();Transformer 会按顺序播放并导出它们段与段之间无缝衔接——这正是短视频拼接功能的最小实现。当然直接拼会很生硬想让画面旋转、缩放交给ScaleAndRotateTransformationEffects effects new Effects( ImmutableList.of(), // 音频处理器先留空 ImmutableList.of(new ScaleAndRotateTransformation.Builder() .setRotationDegrees(90f) .setScale(0.5f, 0.5f) .build())); EditedMediaItem item new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();旋转后的效果就像这样——素材库里的测试帧在rotate90处理后整个横了过来到这里视频已经能剪、能拼、能转用户终于能产出作品了。但还差点意思——嗯还差一层妆。第三步给视频上妆——滤镜与文字水印滤镜的本质是逐帧改变像素颜色。Transformer 内置了一批开箱即用的效果类灰度、反色、对比度、HSL 调节全是现成的ListEffect videoEffects new ArrayList(); videoEffects.add(RgbFilter.createGrayscaleFilter()); // 灰度滤镜 videoEffects.add(RgbFilter.createInvertedFilter()); // 反色滤镜 videoEffects.add(new HslAdjustment.Builder() .adjustSaturation(1.2f) // 饱和度 20% .adjustLightness(0.1f) .build());加一个效果只需要往列表里 add 一行多个效果会按顺序串成一条链逐帧执行。看一组实际效果左边是原始画面右边是同帧经过灰度处理后的结果如果想让画面更有层次Contrast控制对比度、RgbAdjustment单独调节 RGB 三通道组合使用就能做出冷暖色调——这些类的源码都在library/effect/src/main/java/com/google/android/exoplayer2/effect/下读起来比想象中容易。滤镜之外水印是短视频的刚需。用TextOverlay加文字、BitmapOverlay加图片再用OverlayEffect把多个叠加层打包SpannableString watermark new SpannableString(My First Edit); watermark.setSpan(new ForegroundColorSpan(Color.WHITE), 0, watermark.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); OverlayEffect overlayEffect new OverlayEffect( TextOverlay.createStaticTextOverlay( watermark, new OverlaySettings.Builder().setAlpha(0.8f).build())); videoEffects.add(overlayEffect); EditedMediaItem item new EditedMediaItem.Builder(mediaItem) .setEffects(new Effects(ImmutableList.of(), videoEffects)) .build();OverlaySettings里的矩阵参数可以控制水印的位置、缩放和透明度这是从能加水印到水印放得好看的关键。叠加文字后的输出长这样功能齐全了可真的上架后导出黑屏、卡在 99%、闪退这些野生问题会接踵而来。最后一步我们把体验打磨到能交付。第四步把导出体验打磨到可用——进度、回退与避坑清单先解决不知道导到哪了。Transformer 用ProgressHolder提供进度注意它需要你在主线程定时轮询ProgressHolder holder new ProgressHolder(); Handler mainHandler new Handler(Looper.getMainLooper()); mainHandler.postDelayed(new Runnable() { Override public void run() { if (transformer.getProgress(holder) ! Transformer.PROGRESS_STATE_NOT_STARTED) { progressBar.setProgress(holder.progress); // 0 ~ 100 mainHandler.postDelayed(this, 500); // 每 500ms 刷新一次 } } }, 500);每 500ms 轮询一次对 UI 线程压力极小用户却能实时看到进度条在动体感提升巨大。再解决设备编码器不给力。安卓碎片化的直接后果是这台手机能导出的格式换一台就报错。打开编码回退开关让 Transformer 在首选编码器失败时自动降级Transformer transformer new Transformer.Builder(context) .setEncoderFactory(new DefaultEncoderFactory.Builder(context) .setEnableFallback(true) .build()) .build();这行配置能把某机型导出必崩这类事故率降下一大截。最后对照这张避坑清单自查一遍坑表现对策导出中再次调用 start抛IllegalStateException用布尔标记锁住入口输出文件指向已存在文件导出失败或行为异常先删除旧文件再写入getCacheDir()主线程频繁忙轮询进度掉帧卡顿用 Handler 定时 500ms 轮询低端机导出超大分辨率慢且易 OOM加Presentation.createForHeight(720)压缩输出Android 13 读取相册视频权限异常申请READ_MEDIA_VIDEO输出走私有目录⚠️ 特别提醒Presentation.createForHeight(720)是控制输出分辨率的利器——把 1080p 压到 720p 后编码数据量直接降到原来的约四分之一导出速度和成功率都会有肉眼可见的提升非常适合默认导出场景。而调试期如果怀疑滤镜或画面出了问题Transformer.Builder上还留了setDebugViewProvider钩子可以实时预览处理中的每一帧demos/transformer/TransformerActivity.java里就有完整用法。进阶方向从能用到专业到这里你已经拥有一个能裁剪、拼接、加滤镜、加水印并导出 MP4 的剪辑引擎了。往下走还有三条值得探索的路动态效果仓库demos/transformer/里的MatrixTransformationFactory实现了 3D 旋转、缩放推近等转场PeriodicVignetteShaderProgram展示如何手写 GLSL 做周期性暗角它们是通往自定义特效的样板。慢动作与变速EditedMediaItem.Builder的setFlattenForSlowMotion配合音频处理器如SonicAudioProcessor可以玩出变速效果。HDR 内容tone_map_pq_to_sdr.png这类测试图告诉你Transformer 连 HDR 到 SDR 的色调映射都管了新项目请直接关注 media3 的迁移方向。看完这一篇你的下一版 App 或许就敢在产品评审会上接导出剪辑这个需求了。如果文章对你有帮助欢迎留言说说你最想实现的视频功能——下一期我们聊聊如何用 GLSL 手写一个抖音式实时特效敬请期待。【免费下载链接】ExoPlayerAn extensible media player for Android项目地址: https://gitcode.com/gh_mirrors/exop/ExoPlayer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表