尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android直播App开发实战:原生采集编码与RTMP推流全解析

Android直播App开发实战:原生采集编码与RTMP推流全解析 简介这是面向Android课程设计或期末大作业的直播App完整项目资料包含可运行的源代码与文档说明适合计算机相关专业在校生、教师及Android入门开发者参考也可直接作为毕业设计、课程设计或项目初期演示的基础。压缩包共49个文件整体约31.79MB文件以Vue页面组件、PHP后端接口、JSON/JS配置脚本和PNG界面素材为主另有README、LICENSE、XML/HTML等辅助文件代码按前端页面、服务端接口和构建配置分层放置便于定位和修改。项目代码经过测试运行通过功能完整能帮助读者理解直播类App从前端交互到后端数据请求的整体协作方式也可以在此基础上替换或扩展功能模块。目前已有175人浏览学习适合需要完成期末大作业、课设或希望以真实项目进阶的开发者。1. 直播 App 期末大作业为什么总卡在「能看不能播」期末大作业选直播 App 当题目的同学多数不是真想做一个直播产品而是想用「有难度、讲起来有深度」的项目把 Android 移动开发一学期的东西串起来。这个标题里的直播 app 源代码 文档说明对应的是一条完整链路Android 端采集摄像头画面硬编码成 H.264通过 RTMP 推到服务端另一端拉流播放。真正的坑不在写代码而在把采集、编码、传输、播放四段串成现场能演示的链路任何一段协议不匹配都会变成「画面卡在预览、推流没人看到」。下面用一套不依赖云厂商的本地方案把链路在 Android Studio 里跑通同时把演示时常翻车的协议、地址、权限问题一次理清。2. Android 直播 App 的架构拆解推流、拉流、信令三条线2.1 直播链路的最小闭环从摄像头到播放器直播 App 最小闭环包含三个角色推流端负责采集、编码、封装服务端负责转发和必要时转码播放端负责拉流、解码、渲染。三者之间只有一条媒体流信令是额外的一条控制线房间进出、弹幕、连麦都走信令。课程设计里信令可以简化常见做法是用一个 WebSocket 接口返回房间号和推流地址或者干脆把地址写死在配置文件中把主链路跑通再补交互。理解这个分层之后才能快速定位问题出在哪一段预览正常但对方黑屏是推流端编码或网络问题两端都通但画面花屏是 H.264 参数配置问题播放端一直 loading是地址或服务端分发问题。移动开发课程的知识点分布也就清楚了Activity 生命周期管页面、Camera2 管采集、MediaCodec 管编码、网络线程管推送这正好对应期末答辩时老师最爱问的「你负责哪一块」。2.2 协议选型为什么 RTMP HTTP-FLV 是期末作业的最优解RTMP 是推流侧最常见的协议Adobe 公开协议几乎所有开源服务端和推流库都支持基于 TCP 长连接延迟在 1~3 秒。播放端如果直接用 RTMP很多播放器内核支持不稳定而 HTTP-FLV 与 RTMP 内部的数据封装几乎一样只是换用 HTTP 分发穿透性和兼容性更好浏览器和播放器都容易处理。成熟组合是推流走 RTMP播放走 HTTP-FLV。HLS 延迟通常在 5 秒以上适合点播和公网兜底WebRTC 延迟最低但要自己搭信令、ICE、音视频协商复杂度远超期末范围。RTSP 偏向安防 NVR 场景Android 播放端支持弱演示时还得额外装软件。所以课程设计选 RTMP HTTP-FLV是成本和稳定性的平衡点。2.2.1 三种传输协议的延迟与实现成本协议典型延迟实现成本适用位置RTMP1~3 秒低推流端库多推流、局域网播放HTTP-FLV1~3 秒低播放器兼容好播放端首选HLS5~15 秒低但切片有额外延迟公网点播、网页端兜底WebRTC500ms高需要信令与 ICE连麦、低延迟场景2.3 服务端选择本地跑一个 SRS 就够交作业服务端常见做法有两个Nginx-RTMP 模块配置简单但缺少可视化的流状态确认SRS 是国内直播场景很常用的开源流媒体服务器对 RTMP 和 HTTP-FLV 的默认配置开箱即用还带 HTTP API 可以查流列表答辩时点开接口返回结果比口头说「流推上去了」更有说服力。SRS 提供 Docker 镜像一条命令就能把服务端拉起来docker run --rm -d \ --name srs-demo \ -p 1935:1935 -p 8080:8080 -p 1985:1985 \ registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5 \ ./objs/srs -c conf/docker.conf这条命令把三个端口暴露到宿主机1935 是 RTMP 入口推流地址就填rtmp://宿主机IP:1935/live/demo8080 提供 HTTP-FLV 播放地址是http://宿主机IP:8080/live/demo.flv1985 是 SRS HTTP API查流列表、看在线状态都靠它。同一个流名 demo 把推流端和播放端串起来换成别的名字就拉不到。提示Android 模拟器里访问宿主机不能写 localhost要写 10.0.2.2真机调试就写电脑的局域网 IP并保证手机和电脑在同一网段。服务端起来之后可以用curl http://localhost:1985/api/v1/streams/验证 SRS 是否在运行。返回空数组是正常状态说明服务端待命只等推流端连接。3. 用 Android Studio 跑通推流端Camera2 MediaCodec RTMP3.1 工程结构与依赖别一上来就接云厂商 SDK常见误区是选题之后直接接直播云厂商的 SDK那样开发速度快但期末答辩时老师问的原理全被封装在黑盒里源代码里几乎看不到自己的东西。课程设计阶段更值钱的方案是只用 Android Studio 原生 API 加一个开源推流内核把采集、编码、推流都放在自己代码里。我一般这样拆模块MainActivity 管页面与生命周期CameraEngine 管 Camera2 采集VideoEncoder 管 MediaCodec 编码RtmpSender 管网络推送。每个类控制在两百行以内答辩时按模块讲老师问到哪里你都能接住。依赖方面推流可以用编译好的 librtmp 库或纯 Java 的 RTMP 客户端播放端用开源的 IjkPlayer。除了这两个不引入云厂商的大 SDK保证整个工程在 Android Studio 里能独立编译运行。3.2 Android 6 动态权限与 Camera2 预览直播 App 至少要 CAMERA 和 RECORD_AUDIO 两个运行时权限缺一个就会在黑屏和无声之间横跳。Android 6 以后这类权限必须在代码里申请不能只写 Manifest。最小代码如下if (checkSelfPermission(Manifest.permission.CAMERA) ! PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.RECORD_AUDIO) ! PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO }, 100); }checkSelfPermission 逐项检查requestPermissions 是 Activity 的原生回调用户弹窗点完结果在 onRequestPermissionsResult 里返回拿到授权结果后再打开相机。请求码 100 只要和项目里其他请求码不冲突就行。这里用 OR 条件是因为部分机型对麦克风权限处理不一致分开检查更稳。预览和编码共用同一条数据流是课程设计里最划算的做法。Camera2 的 createCaptureSession 同时接收预览 Surface 和编码器的输入 Surface相机帧直接进编码器不需要在 Java 层做 YUV 拷贝和旋转处理ListSurface targets new ArrayList(); SurfaceTexture previewTexture textureView.getSurfaceTexture(); previewTexture.setDefaultBufferSize(previewW, previewH); targets.add(new Surface(previewTexture)); if (encoderSurface ! null) { targets.add(encoderSurface); } cameraDevice.createCaptureSession(targets, new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { try { CaptureRequest.Builder builder cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_RECORD); builder.addTarget(new Surface(previewTexture)); builder.addTarget(encoderSurface); session.setRepeatingRequest(builder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } // onConfigureFailed 里释放相机并提示用户 }, null);createCaptureSession 把预览 Surface 和硬编输入 Surface 同时交给相机TEMPLATE_RECORD 表示偏向录制直播不会像拍照模式那样闪帧setRepeatingRequest 持续输出帧流。编码器在这里只是一个消费者不直接看到 YUV 数据。预览用 TextureView 而不是 SurfaceView因为 TextureView 可以放在任意布局层级里后续加旋转、镜像、美颜都更方便。3.3 MediaCodec 硬编码 H.264关键参数与 Buffer 流转推流视频编码必须是 H.264FLV 的 video tag 对 AVC 的兼容性最好。MediaCodec 硬编码有两种输入模式ByteBuffer 输入和 Surface 输入直播场景强烈建议 Surface 输入相机输出直接进编码器。编码器的创建是核心代码private MediaCodec createVideoEncoder() throws IOException { MediaFormat format new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_WIDTH, 1280); format.setInteger(MediaFormat.KEY_HEIGHT, 720); format.setInteger(MediaFormat.KEY_BIT_RATE, 1_500_000); format.setInteger(MediaFormat.KEY_FRAME_RATE, 25); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_ROTATION, 90); MediaCodec codec MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); return codec; }KEY_COLOR_FORMAT 设为 COLOR_FormatSurface 之后configure 出来的 codec 要调用 createInputSurface() 拿到一个 Surface 给相机用KEY_ROTATION 90 解决竖屏直播推出去却是横画面的问题KEY_I_FRAME_INTERVAL 单位是秒直播里一般设 1播放端追帧和弱网恢复都靠关键帧。3.3.1 编码参数建议表参数建议值选型理由分辨率1280x720720p 在投影和手机上足够清晰编解码压力小码率1~2 Mbps局域网或模拟器演示足够码率再高只会更卡帧率20~25 fps25 是通用值20 能省电视觉上几乎无差I 帧间隔1~2 秒越小弱网丢帧后恢复画面越快ProfileHigh主流 Android 硬解都支持画质优于 Baseline编码输出通过 Callback 异步回调这也是推流数据的来源private byte[] spsPps null; // 编码器输出的 SPS/PPS推流时先发 codec.setCallback(new MediaCodec.Callback() { Override public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) { ByteBuffer buffer codec.getOutputBuffer(index); if ((info.flags MediaCodec.BUFFER_FLAG_CODEC_CONFIG) ! 0) { spsPps new byte[info.size]; buffer.get(spsPps); } else { byte[] frame new byte[info.size]; buffer.get(frame); rtmpSender.sendVideo(spsPps, frame, info.presentationTimeUs); } codec.releaseOutputBuffer(index, false); } // onError 和 onInputBufferAvailable 按需实现 }); codec.start();BUFFER_FLAG_CODEC_CONFIG 标记的那一帧是 SPS/PPS 等配置信息不是真正的画面数据要把它们存下来在推流开始时和每个关键帧前附带发送。普通帧直接传给 rtmpSenderpresentationTimeUs 是编码器给出的时间戳单位微秒RTMP 封装时要换算成毫秒。3.4 RTMP 推流封装把编码数据发出去RTMP 协议握手、AMF0 编码、FLV tag 封装这三件事自己从头实现至少上千行期末阶段没必要。常见做法是找一个基于 librtmp 编译好的 Android 库或者用纯 Java 的 RTMP 客户端移植版。不管你选哪一个对外接口都建议统一成四件事连接、发视频、发音频、关闭。这样换底层实现上层的编码逻辑完全不用动interface RtmpSender { fun connect(url: String, timeoutMs: Int 3000): Boolean fun sendVideo(spsPps: ByteArray?, frame: ByteArray, pts: Long) fun sendAudio(config: ByteArray?, frame: ByteArray, pts: Long) fun close() }connect 的 url 就是rtmp://宿主机IP:1935/live/demotimeout 设 3 秒避免无网环境卡死 UI 线程sendVideo 的 spsPps 与 frame 分开是因为 FLV 的 VideoTagHeader 里需要 AVCDecoderConfigurationRecord包含 SPS/PPS而普通帧只需要 NALUsendAudio 同理AAC 编码器输出的 AudioSpecificConfig 也要单独发一次。最常翻车的错误是直接把编码后的 NALU 用 Socket 发出去播放端一定花屏。RTMP 要求把 NALU 装进 FLV 的 VideoTag且 NALU 长度前缀必须是 4 字节大端。封装这层逻辑时先输出视频配置帧再输出时间戳为 0 的关键帧后面按顺序推普通帧。遇到花屏先查三件事SPS/PPS 有没有发、长度前缀是不是大端、时间戳是不是单调递增的。4. 拉流播放与「演示时最容易翻车」的三个边界问题4.1 用 IjkPlayer 拉流硬解、软解与播放地址播放端不需要自己写解码器课程设计最省心的方案是 IjkPlayer 加 HTTP-FLV。IjkPlayer 对 FLV 的支持比 ExoPlayer 默认配置更成熟硬解和软解都可以用指令切换。核心代码很短IjkMediaPlayer player new IjkMediaPlayer(); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec, 1); // 开启硬解 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, mediacodec-auto-rotate, 1); // 跟随视频旋转信息 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, fflags, nobuffer); // 关闭播放缓冲降低延迟 player.setDataSource(http://192.168.1.100:8080/live/demo.flv); player.prepareAsync();mediacodec 设为 1 是硬解设为 0 是软解mediacodec-auto-rotate 让硬解后的画面跟随编码时写入的旋转信息nobuffer 是 ffmpeg 层面的缓冲关闭开关能明显减少首帧等待。播放地址要和推流地址的流名保持一致demo 对 demo。如果卡在加载先切软解能排除是解码问题还是网络问题。提示真机调试用局域网 IP模拟器用 10.0.2.2。连不上先 ping 宿主机再确认 SRS 容器端口是否映射正确。4.2 Android 9 明文流量、相机方向与生命周期Android 9 开始默认禁止明文 HTTP 流量而 HTTP-FLV 地址是 http://直接请求会报 cleartext not permitted。最省事的解法是在项目里加一个网络安全配置允许明文流量network-security-config base-config cleartextTrafficPermittedtrue / /network-security-config然后在 AndroidManifest.xml 的 application 标签上加上android:networkSecurityConfigxml/network_security_config。这样配置之后 http 和 rtmp 都能访问演示环境完全够用不需要为给学生机升级到 HTTPS 而额外折腾。相机方向问题经常被忽略。前面在 MediaFormat 里设了 KEY_ROTATION 90这只影响编码输出的旋转信息播放端必须打开 mediacodec-auto-rotate 才能自动转正。如果推出去是横的第一反应先看播放器这个参数而不是改相机预览。生命周期是更底层的稳定性问题onPause 时不停推流、不关相机再切回来就会因为相机被占用而抛 CameraAccessException。正确的关闭顺序是先关推流再关相机Override protected void onPause() { super.onPause(); if (pusher ! null) { pusher.close(); } if (cameraDevice ! null) { cameraDevice.close(); } }先停止推流再关闭相机避免编码器还在读一个已关闭的 Surface 导致崩溃。恢复时重新打开相机并让 RtmpSender 重连。这个顺序写进文档里能体现你对移动开发生命周期不是停留在概念层面。4.3 文档说明怎么写架构图、时序图与测试记录标题带了「文档说明」这是最容易被忽视的得分点。文档最少包含三块总体架构图画推流端、服务端、播放端三栏标注每个端口和协议时序图描述 Camera2 从 onOpened 到 createCaptureSession、setRepeatingRequest、编码输出、RTMP 推流这条链路上的对象交互Android Studio 里可以直接生成测试记录写清楚不同分辨率、码率下的延迟和画面表现。测试记录建议组织成表格场景推流地址播放延迟现象720p 1.5Mbps 局域网真机rtmp://192.168.x.x/live/demo约 2s正常1080p 4Mbps 模拟器rtmp://10.0.2.2/live/demo卡顿模拟器软解性能不足720p 声音异常rtmp://宿主机IP/live/demo正常检查 RECORD_AUDIO 权限文档不应该是源码的复制粘贴而是记录你调参、定位、修正的过程。写清楚「我换了哪个参数、得到什么结果」比贴十页代码更有说服力。5. 验收清单与进阶改法从「能跑」到「能讲」的加分项5.1 用 adb 和 SRS API 验证链路是否真的通直播链路调试最怕「感觉通了但画面不动」。两个低成本验证手段第一推流过程中在电脑上执行 curl 请求 SRS 的 HTTP API看到 streams 列表里有 demo 流说明推流端和服务端的连接是通的第二用 ffprobe 直接读 HTTP-FLV 地址能读出流信息和编码参数问题就能定位到播放器curl http://192.168.1.100:1985/api/v1/streams/ ffprobe -show_streams http://192.168.1.100:8080/live/demo.flvcurl 返回的 JSON 里有 vhost、app、stream 这些字段说明 SRS 已经识别到推流端。ffprobe 输出里看 codec_name 是否等于 h264profile 是否为 High。curl 有流而 ffprobe 报错问题在播放器或网络两者都无问题在推流端或服务端。这条验证路径写进文档比截图更有说服力。5.2 三个不换架构就能拿到的加分项第一断线重连。用 ConnectivityManager 注册网络回调网络恢复后自动把 RtmpSender 重新 connectSRS 服务端天然支持重连不需要改服务端。这个回调代码量小但很能展示你对移动网络状态的理解。第二把推流地址从硬编码改成输入框。直播地址本身支持动态指定加一个 EditText 填推流 URL文档里就能写「本设计支持自定义推流地址可对接不同流媒体服务端」这一个点足以应付「扩展性」类的提问。第三用 SRS 的 HTTP API 做「在线人数」。每隔 5 秒轮询一次 streams 接口统计当前流数量作为观众数的数据来源比 UI 上写死一个数字更能经得起追问。轮询线程记得在 onDestroy 里取消避免内存泄漏。最后提醒Android 12 以上对前台服务有超时限制直播推流如果要退到后台继续运行得把 Service 的前台类型声明成 camera 并配合媒体会话。所以 RtmpSender 接口里要预留一个 state 属性把连接状态、推流状态暴露给 UI断线重连和前台服务都依赖它在文档里把这个状态机画清楚比答辩时临时解释要稳得多。本文还有配套的精品资源点击获取
返回列表