简介:这是一套面向Java与移动开发学习者的原生高仿短视频APP双端源码,覆盖Android与iOS两端,适合希望深入理解短视频应用完整实现、提升音视频处理与跨平台开发能力的中高级开发者。压缩包共1127个文件,约63.2MB,以258个java源码、317个xml布局、417个png图片资源为主,另含39个so库、23个jar包及gradle构建脚本,完整呈现工程结构与依赖配置。项目涉及Android SDK、Activity与Fragment、MediaCodec、MediaPlayer、OpenGL ES、FFmpeg、AVFoundation、Objective-C、PHP 5.6、MySQL 5.5与ThinkPHP框架,并包含伪静态URL重写等后端实践。已有776人学习下载。通过研读源码,读者可掌握双端UI搭建、音视频编解码与播放、服务端接口交互及MVC设计模式,是贯通移动端与后端全流程开发的实战参考。
1. 从一份带 LiteAVSDK_UGC 的 Java 双端源码说起
短视频 App 的源码包我拆过不少,大多数要么只有 Android 端,要么 iOS 端是空壳,要么音视频模块直接调个系统播放器糊弄过去。这份「原生 Java 高仿短视频 APP 双端源码」不太一样——解压后第一眼看到的是LiteAVSDK_UGC_4.4.3774.aar和ti-sdk.aar两个体积不小的依赖包,加上gradlew.bat和一堆 Gradle 构建缓存文件(fileSnapshots.bin、localClassSetAnalysis.bin、fileHashes.bin、taskArtifacts.bin这些),说明它至少是一个真正跑过构建、编译出过 APK 的工程,不是随手丢几个 Java 文件凑数的。
它解决的核心问题是:让你在一个完整可编译的双端工程里,看到短视频从拍摄、编辑、上传到播放的整条链路是怎么用 Java(Android 端)和 Objective-C(iOS 端)串起来的,后端还配了 PHP 5.6 + MySQL 5.5 + ThinkPHP 的接口层。适合谁?适合已经会写 Android 界面、但没碰过音视频采集与推流编码的 Java 开发者,也适合想对照双端实现来理解 SDK 封装思路的移动端工程师。下面按「资源是什么 → 怎么跑起来 → 音视频链路怎么读 → 坑在哪 → 进阶怎么改」的顺序拆。
2. 工程结构与双端技术栈:先搞清楚每个模块归谁管
2.1 Android 端:Java + Android SDK + LiteAVSDK
Android 端是这份源码里最厚的一块。UI 层用 XML 布局配合 Activity 和 Fragment,列表页大概率是 RecyclerView 加自定义 Adapter,这在短视频类 App 里是标配。业务逻辑用 Java 写,网络请求、数据解析、状态管理都在这一层。真正跟音视频打交道的是LiteAVSDK_UGC_4.4.3774.aar——腾讯云短视频 SDK 的 UGC 版本,负责拍摄、剪辑、拼接、特效、推流这一整套。ti-sdk.aar通常是配套的滤镜或特效资源包。
你需要在 Android Studio 里打开工程,确认build.gradle里的compileSdkVersion、minSdkVersion跟本机 SDK 匹配。常见做法是先把 Gradle 版本对齐,再同步依赖。如果LiteAVSDK_UGC的 aar 没有正确被flatDir或libs目录引用,编译会直接报找不到TXUGCRecord之类的类。
2.2 iOS 端:Objective-C + AVFoundation + FFmpeg
iOS 端用 Objective-C 写,架构是 MVC,UI 走 UIKit。音视频处理主要靠 AVFoundation——采集用AVCaptureSession,播放用AVPlayer,编辑用AVAssetExportSession。项目正文里提到可能用到 FFmpeg 做跨平台编解码,这在需要自定义封装格式或做特殊裁剪时会用到。如果你只熟悉 Swift,读 OC 代码会有点别扭,但语法障碍不大,重点看AVCaptureVideoDataOutput的回调和CMSampleBufferRef的处理流程。
2.3 后端:PHP 5.6 + MySQL 5.5 + ThinkPHP
后端不是摆设。用户注册登录、视频列表拉取、点赞评论、视频上传后的转码回调,都走 PHP 接口。ThinkPHP 提供 MVC 结构,MySQL 存用户表和视频元数据。伪静态设置(URL 重写)是为了让接口路径看起来像静态页,对 SEO 友好,但对你调试来说,关键是找到route.php或.htaccess里的重写规则,确认 API 前缀是什么。
提示:PHP 5.6 和 MySQL 5.5 都是老版本,本地搭建建议用 phpStudy 或 Docker 拉对应镜像,别用 PHP 8 去跑,否则 ThinkPHP 的旧版本会报一堆废弃函数错误。
3. 把工程跑起来:Android 端编译与后端接口对接
3.1 Android 端 Gradle 同步与 aar 依赖配置
先把工程导入 Android Studio,别急着点 Run。第一步是检查app/build.gradle和项目根目录的build.gradle。LiteAVSDK 的 aar 通常放在app/libs/下,需要在build.gradle里声明flatDir仓库。
// app/build.gradle repositories { flatDir { dirs 'libs' // 指向 aar 所在目录 } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation(name: 'LiteAVSDK_UGC_4.4.3774', ext: 'aar') // 短视频 SDK implementation(name: 'ti-sdk', ext: 'aar') // 特效资源包 implementation 'com.android.support:recyclerview-v7:28.0.0' // 列表控件 implementation 'com.squareup.okhttp3:okhttp:3.12.0' // 网络请求 }这段配置的逻辑是:flatDir让 Gradle 去libs目录找 aar,implementation(name:..., ext: 'aar')精确引用。参数上,LiteAVSDK_UGC_4.4.3774这个版本号必须跟文件名完全一致,少一个数字都会报Failed to resolve。OkHttp 版本别升太高,3.x 跟老项目的 support 库兼容性更好。
同步成功后,如果报Manifest merger failed,多半是 aar 里的minSdkVersion跟主工程冲突。在AndroidManifest.xml的<application>标签里加tools:replace="android:theme"或tools:node="replace"能压过去。
3.2 后端接口地址替换与伪静态规则
Android 端和 iOS 端的网络层都会有一个BASE_URL或API_HOST常量。搜http://或https://找到它,换成你本地后端的地址。ThinkPHP 的入口文件一般是public/index.php,伪静态规则写在.htaccess(Apache)或 nginx 配置里。
# nginx 伪静态配置示例 location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } }这段规则的作用是:当请求的文件不存在时,把路径重写到index.php并带上s参数,ThinkPHP 再根据s参数路由到对应控制器。参数$1是捕获的完整路径。改完后用 Postman 请求http://你的IP/index.php/api/video/list,能返回 JSON 就说明后端通了。
3.3 数据库导入与初始配置
MySQL 里新建一个库,字符集选utf8mb4,导入源码包里附带的.sql文件。然后改application/database.php(ThinkPHP 3.2 是Conf/config.php)里的数据库账号密码。常见坑是 SQL 文件里用了utf8而你的库是utf8mb4,导入时中文会乱码,统一成utf8mb4就行。
4. 音视频链路拆解:从拍摄到播放的 Java 实现
4.1 拍摄与编辑:LiteAVSDK 的 TXUGCRecord 调用
Android 端的拍摄页面核心是TXUGCRecord类。它封装了摄像头采集、美颜、滤镜、分段录制。你不需要直接碰 MediaCodec,SDK 内部已经处理了硬编硬解。典型调用流程是:初始化 → 设置录制参数 → 开始录制 → 分段录制 → 停止并生成视频文件。
// 拍摄页面初始化 TXUGCRecord mRecord = TXUGCRecord.getInstance(context); TXUGCRecord.SimpleRecordParams params = new TXUGCRecord.SimpleRecordParams(); params.mRecordVideoWidth = 720; // 录制宽度 params.mRecordVideoHeight = 1280; // 录制高度 params.mVideoBitrate = 2000; // 码率 kbps params.mVideoFps = 20; // 帧率 mRecord.setRecordParams(params); // 开始录制 mRecord.startRecord(outputPath, new TXUGCRecord.ITXRecordListener() { @Override public void onRecordProgress(long milliSecond) { // 更新进度条 } @Override public void onRecordComplete(TXUGCRecord.RecordResult result) { // result.videoPath 是生成的 mp4 路径 } });参数说明:mRecordVideoWidth和mRecordVideoHeight决定输出分辨率,720×1280 是竖屏短视频的常见选择;mVideoBitrate2000kbps 在 720p 下画质和体积比较平衡,调高到 4000 文件会明显变大;mVideoFps20 帧够用,30 帧更流畅但编码压力大。startRecord的outputPath要指向 App 私有目录,别写死到/sdcard/,Android 10 以后会因权限问题失败。
4.2 播放与渲染:MediaPlayer 与 OpenGL ES 的分工
列表页的短视频播放,简单场景用MediaPlayer+SurfaceView就够了。但这份源码里出现了 OpenGL ES 的痕迹,说明它可能在视频帧上做了滤镜或水印渲染。MediaPlayer负责解码和音频输出,OpenGL ES 负责把视频帧画到GLSurfaceView上再做二次处理。
// MediaPlayer 基础播放 MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(videoUrl); mediaPlayer.setSurface(surface); // 绑定 SurfaceView 的 Surface mediaPlayer.setLooping(true); // 短视频循环播放 mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp -> mp.start());setLooping(true)是短视频列表的标配,用户滑到某个视频时自动循环。prepareAsync异步准备避免主线程卡顿。如果要用 OpenGL ES 做滤镜,就得把MediaPlayer的输出接给SurfaceTexture,再在 GL 线程里用 shader 渲染。这块代码量不小,建议先跑通基础播放,再去看滤镜部分。
4.3 上传与转码:分片上传与后端回调
视频拍完后要上传到服务器。源码里大概率用了分片上传——把 mp4 切成若干块,逐块 POST 到后端,最后合并。后端 PHP 收到文件后调 FFmpeg 转码成统一格式,再写数据库。你在UploadActivity或类似类里能找到分片逻辑。
// 分片上传伪代码 int chunkSize = 1024 * 1024; // 1MB 一片 File file = new File(videoPath); long fileSize = file.length(); int chunkCount = (int) Math.ceil((double) fileSize / chunkSize); for (int i = 0; i < chunkCount; i++) { byte[] chunk = readChunk(file, i * chunkSize, chunkSize); // POST chunk 到 /api/upload/chunk,带 fileId 和 chunkIndex } // 全部完成后调 /api/upload/merge 合并chunkSize设 1MB 是移动网络下的稳妥值,太大容易超时,太小请求次数多。每个分片要带唯一fileId和chunkIndex,后端按序合并。合并完成后后端返回视频 URL,客户端再刷新列表。
5. 避坑与排查:双端源码跑不通时先看这几条
5.1 编译报错Failed to resolve: LiteAVSDK_UGC
现象:Gradle sync 失败,提示找不到 LiteAVSDK 相关类。原因:aar 文件没放在libs目录,或者flatDir没配,或者文件名跟implementation里的名字不一致。解决:确认app/libs/下有LiteAVSDK_UGC_4.4.3774.aar,build.gradle里flatDir { dirs 'libs' }和implementation(name: 'LiteAVSDK_UGC_4.4.3774', ext: 'aar')都写对,名字大小写敏感。
5.2 录制时黑屏或报Camera permission denied
现象:进入拍摄页黑屏,Logcat 报权限拒绝。原因:Android 6.0 以上需要动态申请CAMERA和RECORD_AUDIO权限,源码里可能只写了 Manifest 没写运行时申请。解决:在拍摄 Activity 的onCreate里加ActivityCompat.requestPermissions,或者手动去系统设置里给 App 开权限。另外模拟器摄像头支持差,用真机测。
5.3 后端接口返回 404 或module not exists
现象:App 请求登录接口返回 404,或者 ThinkPHP 报模块不存在。原因:伪静态没生效,或者BASE_URL里少了index.php。解决:先直接用http://IP/index.php/api/user/login测,通了再配伪静态。nginx 的rewrite规则要放在location /里,Apache 要确认AllowOverride All已开。
5.4 视频上传后播放花屏或音画不同步
现象:上传成功但播放时画面花屏、声音对不上。原因:分片合并时顺序错了,或者转码参数跟源文件不匹配。解决:检查后端合并逻辑是否按chunkIndex排序,FFmpeg 转码命令加-vsync cfr强制恒定帧率,音频用-acodec aac -ar 44100统一采样率。
5.5 iOS 端AVCaptureSession启动崩溃
现象:iOS 端一进拍摄页就崩,控制台报AVCaptureSession startRunning相关。原因:startRunning是阻塞操作,不能在主线程调。解决:放到dispatch_queue的串行队列里执行,UI 更新再切回主线程。另外NSCameraUsageDescription和NSMicrophoneUsageDescription必须写进Info.plist,否则系统直接拒绝。
6. 进阶改造:把 LiteAVSDK 换成自研 MediaCodec 链路的思路
跑通之后,很多人会想:能不能不依赖 LiteAVSDK,自己用 MediaCodec + OpenGL ES 搭一套?可以,但工作量不小。核心是把 SDK 帮你做的三件事拆开:采集、编码、封装。
采集用Camera2+ImageReader,拿到Image后转成SurfaceTexture;编码用MediaCodec的createInputSurface模式,把 GL 渲染的输出直接喂给编码器;封装用MediaMuxer把 H.264 和 AAC 写成 mp4。这套链路的好处是可控,坏处是兼容性坑多——不同芯片的 MediaCodec 支持格式不一样,得做降级。
// MediaCodec 编码器创建(H.264) MediaFormat format = MediaFormat.createVideoFormat("video/avc", 720, 1280); format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000); format.setInteger(MediaFormat.KEY_FRAME_RATE, 20); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 关键帧间隔 1 秒 MediaCodec encoder = MediaCodec.createEncoderByType("video/avc"); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); Surface inputSurface = encoder.createInputSurface(); // 给 OpenGL 渲染 encoder.start();KEY_COLOR_FORMAT设成COLOR_FormatSurface才能用 Surface 输入模式,这是硬编的关键。KEY_I_FRAME_INTERVAL设 1 秒,方便拖动预览。createInputSurface返回的 Surface 给 GL 线程当渲染目标,GL 画完一帧,编码器就自动取走一帧。
验证方法:录一段 10 秒视频,用ffprobe看编码格式和帧率是否跟设置一致。如果花屏,多半是 GL 的eglSwapBuffers时机不对;如果音画不同步,检查音频和视频的时间戳是否都用了System.nanoTime()做基准。
我自己的习惯是:每次改完编码参数,先用 720p 录 5 秒,ffprobe确认无误再上 1080p。从那以后我每次动音视频参数都强制走一遍「短录 + ffprobe 校验」,省得后面批量转码时才发现问题。希望帮到你。
本文还有配套的精品资源,点击获取