ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android视频录制核心:MediaRecorder setVideoSource深度解析

Android视频录制核心:MediaRecorder setVideoSource深度解析 1. 开篇Android视频采集这条链路到底卡在哪做Android多媒体开发的人大概率都有过这种经历需求很简单——打开相机录一段视频保存到本地。听起来像个Hello World级别的功能但真正动手以后你会发现摄像头、Surface、MediaCodec、MediaRecorder这几个组件之间的调用关系能把一个经验丰富的应用层开发折腾到怀疑人生。特别是当你从Camera1迁移到Camera2再从Camera2走向CameraX底层的时序、缓冲区的流转、Surface的格式要求每一层都暗藏玄机。这篇文章要聊的setVideoSource就是这条链路里最容易被忽略、却又决定全局的一环。它是MediaRecorder录制视频的“源头入口”告诉系统“我要从哪里拿视频数据”。这个调用本身只有一个参数看起来人畜无害但它背后牵扯到Camera2的预览Surface注册、CameraX的用例绑定、MediaCodec输入Surface的格式协商以及整个音视频时间戳的同步基准。用一句话概括setVideoSource选错了后面你的所有录制配置都是空中楼阁。这篇文章适合两类人一类是正在做Android相机录制、视频流处理需要在MediaRecorder和Camera2/CameraX之间打通链路的工程师另一类是面试前想搞懂MediaRecorder内部机制、不想停留在“调用几个API就行”层面的进阶开发者。我会从调用时序、源码级流转、实战参数、踩坑记录四个维度展开尽量把这条链路拆到“读完就能照着做”的程度。2. 整体设计思路为什么必须单独调用setVideoSource2.1 一次录制行为涉及多少个子系统先理清一个概念MediaRecorder并不是一个“录制器”它更像一个状态机驱动的管道调度器。一次完整的视频录制至少涉及以下参与者数据源Video Source通常是Camera2的CaptureSession输出Surface或者CameraX的PreviewSurface / VideoCapture Surface。编码器Video EncoderMediaCodec负责把YUV/RGB原始帧压缩成H.264/H.265/AV1等格式。封装器MuxerMediaCodec产出的编码数据由MediaRecorder内部的MP4/WebM封装器混合音视频轨写入文件。状态机State MachineMediaRecorder内部维护了Initial、Initialized、DataSourceConfigured、Prepared、Recording等状态任何一步错误都会抛出异常。setVideoSource就是“DataSourceConfigured”这个状态的前置条件。你的数据源类型摄像头、屏幕录制、虚拟合成决定了内部编码器对输入Surface的格式要求、帧率控制策略、时间戳来源。没有这一步MediaRecorder压根不知道从哪里取数据后续的setOutputFormat、setEncoder、prepare都会变成无源之水。2.2 一次错误示范为什么不能只setOutputFormat就prepare我在社区里见过不少新手这样写mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setVideoSize(1920, 1080); mediaRecorder.setVideoFrameRate(30); mediaRecorder.setOutputFile(filePath); mediaRecorder.prepare();这段代码跑起来会直接崩或者录出来的文件是0字节。原因在于MediaRecorder在prepare时会检查自己的状态是否已经是DataSourceConfigured。setOutputFormat和setVideoEncoder只是告诉封装器和编码器“我要用什么格式”但没有告诉系统“我的数据从哪来”。对于视频录制MediaRecorder唯一合法的数据源头就是setVideoSource指定。省略这一步状态机直接卡在Initializedprepare必然抛出IllegalStateException。2.3 API 33以后的行为变化与设计演进Android 13API 33之后系统对MediaRecorder的权限和数据源校验更严格。比如录制屏幕必须使用MediaProjection的VirtualDisplay作为数据源不能再用摄像头。申请相机权限时如果目标SDK大于等于33还需要细分CAMERA和RECORD_AUDIO两个运行时权限的分离弹窗。Android 14API 34进一步强化了对麦克风并发采集的限制——如果其他应用正在占用麦克风你的MediaRecorder可能拿不到音频源。这些变化叠加在一起意味着你不能再把setVideoSource当成“固定写法”而是要根据业务场景动态选择数据源。比如同一个App里有扫码和录像两个功能扫码用CameraX的ImageAnalysis录像用MediaRecorder的Surface两套链路如果共用Camera对象就必须考虑Camera的reopen和Surface的重新注册这就到了文章后面实战部分的重点。2.4 setVideoSource的参数到底有哪些MediaRecorder.VideoSource是一个int常量集合常用值如下常量值适用场景CAMERA1传统的Camera/Camera2预览Surface作为输入SURFACE2输入为Surface常见于CameraX的SurfaceProvider或MediaCodec的输入SurfaceDISPLAY3屏幕录制需要配合VirtualDisplayMIC5麦克风作为音频源视频源不使用DEFAULT0使用默认源实际等价于CAMERA这里必须强调一个关键点从Camera2开始系统不再建议把原始的Camera设备直接绑定到MediaRecorder而是推荐你把Camera2的CaptureSession输出Surface指向一个MediaRecorder创建的Surface或者通过CameraX的VideoCapture来管理。原因在于Camera2的架构里CameraDevice和CaptureSession是多对多的关系MediaRecorder如果直接操作Camera设备会绕开Camera2的流管理机制导致预览和录制无法并存甚至崩溃。3. setVideoSource调用流程的完整拆解3.1 状态机视角下的合法调用顺序MediaRecorder官方定义的状态转移如下简化版Initial → Initialized → DataSourceConfigured → Prepared → Recording → ReleasedsetVideoSource的作用正是把状态从Initialized推进到DataSourceConfigured配合setAudioSource可以同时配置音频源。在源码层面MediaRecorder.setVideoSource()最终会调用native层的setVideoSource(int video_source)接着设置一个VIDEO_SOURCE键值对到MediaRecorder内部的MediaFormat相关结构里。真正的校验发生在prepare()阶段系统会检查source是否有效、surface是否已注册、权限是否齐全。值得注意的是setVideoSource必须在setOutputFormat之前调用。一旦调用setOutputFormat内部状态就会进入DataSourceConfigured之后的阶段此时你不能再回头修改数据源否则会收到RuntimeException: setVideoSource called in an invalid state: 2。3.2 Camera2链路下setVideoSource的完整时序现在拆解一条最典型的链路Camera2 MediaRecorder录制视频。第一步创建MediaRecorder实例并设置数据源MediaRecorder mediaRecorder new MediaRecorder(context); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setVideoEncodingBitRate(10_000_000); mediaRecorder.setVideoFrameRate(30); mediaRecorder.setVideoSize(1280, 720); // 注意这个尺寸必须和Camera2采集尺寸匹配 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mediaRecorder.setOutputFile(videoFile); mediaRecorder.prepare(); Surface recordSurface mediaRecorder.getSurface(); // 关键获取MediaRecorder内部的输入Surface注意这里我故意没有用VideoSource.CAMERA而是用SURFACE。原因很简单Camera2要求你把输出Target传给CaptureSession而CaptureSession只能接受你自己创建的Surface。MediaRecorder.getSurface()返回的就是一个可用于Camera2输出的Surface这个Surface内部连接的是MediaCodec的输入端口。你把它作为CaptureSession的Target之一Camera2每次出帧就会通过这个Surface把数据送进MediaCodec。这样摄像头和MediaRecorder就解耦了——Camera2管采集MediaRecorder管编码封装。第二步构建Camera2的CaptureRequest并添加TargetCaptureRequest.Builder requestBuilder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); requestBuilder.addTarget(previewSurface); // 预览Surface requestBuilder.addTarget(recordSurface); // 录制Surface就是MediaRecorder.getSurface() cameraCaptureSession.setRepeatingRequest(requestBuilder.build(), null, null);这里有一个极大的坑Camera2的CaptureRequest要求所有Target的尺寸必须一致或者严格遵循对应Surface的BufferQueue格式限制。如果你在MediaRecorder里设了setVideoSize(1280, 720)那么你通过Camera2配置的ImageReader或SurfaceTexture也必须是1280x720否则会出现“stream configuration failed”的异常。第三步stop和release。mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release();stop之后必须立刻reset否则下次录制时MediaRecorder还停留在Recording状态setVideoSource会直接抛错。3.3 CameraX链路下的封装差异CameraX的VideoCapture内部已经封装了MediaRecorder所以大多数情况下你不需要手动调用setVideoSource。但如果你在自定义实现里需要更精细的控制比如拿到MediaRecorder的Surface做二次处理CameraX也提供了getSurfaceProvider()相关的接口。CameraX的封装逻辑是VideoCapture默认使用VideoSource.SURFACE并且它会用自己的SurfaceProvider向底层预览用例申请一个Surface然后把这个Surface传给MediaRecorder。这里面需要留意的是CameraX对分辨率的处理是用例内部自行协商的和你在MediaRecorder中调用的setVideoSize不一定完全一致。如果你在CameraX中设置的分辨率和MediaRecorder的配置冲突最后看到的效果可能是录出来的视频比例不对或者画面被裁切。我的建议是优先使用CameraX的VideoCapture自带的setTargetResolution不要手动去调MediaRecorder的setVideoSize。只有当你使用Camera2时才需要完全手动配置这两者。3.4 Surface格式与缓冲区流转的核心机制讲到这里需要补充一点ColorFormat相关的知识。MediaCodec的输入Surface在底层是一个InputSurface它对应的GraphicBuffer格式通常是COLOR_FormatYUV420Flexible即NV21/YV12等YUV420家族的统称。Camera2输出的Buffer格式默认是YUV_420_888两者是兼容的。但如果你在MediaRecorder之前插入了一个ImageReader做帧处理就必须确认ImageReader的输出格式是ImageFormat.YUV_420_888否则MediaCodec不认识你给它的数据。缓冲区流转的整体链路是这样的Camera2 HAL层产生帧 → 帧进入Camera2的BufferQueue → BufferQueue输出给CaptureSession的TargetSurface即MediaRecorder的输入Surface → MediaCodec从输入Surface拿到原始帧 → 编码为H.264 → 封装进MP4。在这一整条链路中时间戳是贯穿始终的锚点。MediaCodec输入Surface要求时间戳单调递增Camera2底层的天然时间戳一般是基于System.nanoTime()而MediaRecorder内部的Muxer会把所有轨道的起始时间对齐到0。如果某个帧的时间戳跳变或倒转封装出来的视频会出现超长黑帧、音画不同步等问题。这里提示一个排查技巧如果你发现录出来的视频开头有1-2秒黑屏基本就是首帧时间戳和音频起始时间戳相差过大导致的。解决方案是在录音开始前先让Camera2输出几帧作为预热再调用mediaRecorder.start()。CameraX内部其实也做了类似的预卷处理所以用CameraX出现首帧黑屏的概率更低。4. 实战代码一版可直接落地的Camera2 MediaRecorder录制实现4.1 环境与权限准备先列一下必备条件项目compileSdk至少35targetSdk建议35或36真机建议Android 12以上测试。权限声明CAMERARECORD_AUDIO如果targetSdk 33还需要在运行时申请POST_NOTIFICATIONS部分国产ROM录制时会弹通知实际上MediaRecorder本身不需要通知权限但某些ROM上录屏功能需要摄像头录制一般不需要。硬件要求真机测试不能用模拟器因为模拟器的Camera2 HAL对Surface格式支持不完整。4.2 完整可运行的配置代码下面给出一份我在实际项目中抽出来的核心代码已去掉业务无关部分包含Camera2打开、MediaRecorder创建、录制开始/停止的完整流程。public class CameraRecorderManager { private CameraDevice cameraDevice; private CameraCaptureSession captureSession; private CaptureRequest.Builder recordRequestBuilder; private MediaRecorder mediaRecorder; private Surface recordSurface; private Surface previewSurface; private String videoFilePath; private Size videoSize new Size(1280, 720); public void startRecording(Context context, String filePath) { this.videoFilePath filePath; // 1. 初始化MediaRecorder setupMediaRecorder(context); // 2. 打开Camera并配置Target openCamera(); // 3. 开始录制 mediaRecorder.start(); } private void setupMediaRecorder(Context context) { mediaRecorder new MediaRecorder(context); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setVideoEncodingBitRate(10_000_000); mediaRecorder.setVideoFrameRate(30); mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight()); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mediaRecorder.setAudioEncodingBitRate(128_000); mediaRecorder.setAudioSamplingRate(44_100); mediaRecorder.setOutputFile(videoFilePath); try { mediaRecorder.prepare(); recordSurface mediaRecorder.getSurface(); } catch (IOException e) { throw new RuntimeException(MediaRecorder prepare failed, e); } } private void openCamera() { CameraManager manager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); try { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) ! PackageManager.PERMISSION_GRANTED) { return; } String cameraId manager.getCameraIdList()[0]; CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // 选择合适的输出尺寸务必匹配videoSize Size[] outputSizes map.getOutputSizes(SurfaceTexture.class); Size matchedSize chooseOptimalSize(outputSizes, videoSize.getWidth(), videoSize.getHeight()); manager.openCamera(cameraId, new CameraDevice.StateCallback() { Override public void onOpened(CameraDevice camera) { cameraDevice camera; createCaptureSession(); } Override public void onDisconnected(CameraDevice camera) { camera.close(); } Override public void onError(CameraDevice camera, int error) { camera.close(); } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } private void createCaptureSession() { try { SurfaceTexture texture new SurfaceTexture(10); texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight()); previewSurface new Surface(texture); recordRequestBuilder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); recordRequestBuilder.addTarget(previewSurface); recordRequestBuilder.addTarget(recordSurface); ListSurface surfaces Arrays.asList(previewSurface, recordSurface); cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { Override public void onConfigured(CameraCaptureSession session) { captureSession session; try { captureSession.setRepeatingRequest(recordRequestBuilder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } Override public void onConfigureFailed(CameraCaptureSession session) { // 常见的失败原因Surface尺寸不匹配 / Surface已经释放 / 权限不足 } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } public void stopRecording() { if (mediaRecorder ! null) { try { mediaRecorder.stop(); } catch (RuntimeException e) { // stop抛错通常是因为数据源没有输出帧或录制时长太短 } mediaRecorder.reset(); mediaRecorder.release(); mediaRecorder null; } if (captureSession ! null) { captureSession.close(); captureSession null; } if (cameraDevice ! null) { cameraDevice.close(); cameraDevice null; } if (recordSurface ! null) { recordSurface.release(); recordSurface null; } } }4.3 关键参数选择的背后逻辑上面这段代码里有几个参数是多数教程不会解释的我单独提一下码率设为10Mbps这个数值不是随便定的。1280x72030fps的H.264视频在普通画面下如室内访谈、桌面操作8~12Mbps是画质和文件体积之间比较均衡的区间。如果码率太低运动画面会出现大量马赛克太高了的话文件体积线性增长但人眼感知力提升有限。音频采样率设为44.1kHz这是AAC编码器最通用的采样率对绝大多数播放器和剪辑软件兼容性最好。48kHz虽然也是标准但在部分老设备的硬件编码器上会有微妙的音高偏移问题。10号SurfaceTexture纹理ID这个值只要不冲突即可Camera2并不会真的渲染到这个纹理上它只是一个占位绑定。TEMPLATE_RECORD这个模板会尽量让硬件优化预览录制的并发场景保证帧率和曝光策略偏向视频录制而不是拍照。如果这里误用TEMPLATE_PREVIEW录制中可能会出现在低光环境下帧率骤降的问题。4.4 旋转角度与分辨率匹配问题很多时候你录出来的视频是倒的或者比例不对。原因在于Camera2传感器的天然方向是横屏而手机屏幕通常是竖屏需要通过SENSOR_ORIENTATION做旋转处理。int sensorOrientation characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // 后置摄像头旋转90度前置摄像头旋转270度还要考虑镜像 mediaRecorder.setOrientationHint(sensorOrientation);setOrientationHint的作用是告诉Muxer在写入MP4时带上旋转矩阵播放器会依据这个矩阵自动旋转画面。注意这个调用必须在setOutputFormat之后、prepare之前执行。如果漏掉这个设置视频文件在PC播放器里是横的但在手机上又可能是竖的极其容易造成线上反馈“视频方向不对”。分辨率匹配方面我提供一个通用排查顺序先检查MediaRecorder.setVideoSize的宽高是否和你Camera2选择的输出Surface一致再检查previewSurface和recordSurface是否都是同一比例最后检查SurfaceTexture.setDefaultBufferSize是否与目标一致。三步都没问题分辨率相关的报错基本能杜绝。5. 常见问题与排查技巧实录5.1 问题速查表现象根本原因解决方案prepare时抛出IllegalStateExceptionsetVideoSource与setOutputFormat顺序错误或重复调用setVideoSource重新创建MediaRecorder实例严格按状态机顺序调用录制文件0字节setVideoSource没有调用或Camera2的Target没有包含recordSurface确认MediaRecorder.getSurface()已被加入CaptureRequest录到一半崩溃CameraAccessException CAMERA_IN_USE相机被其他应用占用或Target Surface尺寸不匹配检查权限和分辨率必要时释放相机再重开stop时抛出RuntimeException录制时长太短或数据源没有输出帧捕获异常并做兜底处理不要直接崩溃视频画面变形MediaRecorder.setVideoSize与Camera2实际输出尺寸比例不一致统一视频尺寸与Surface尺寸必要时用setOrientationHint调整音画不同步音频源启动时机晚于视频源或时间戳跳变在MediaRecorder.start前先预热Camera输出或改用CameraX预览正常但录制黑屏recordSurface未正确加入CaptureSession或MediaCodec输入Surface格式不支持检查CaptureSession的Surface列表是否同时包含预览和录制Surface5.2 最隐蔽的坑重复录制时必须重新获取Surface我在实际项目中踩过最深的一个坑是MediaRecorder进入Recording状态后再stop、reset虽然可以重新prepare但getSurface()返回的Surface在底层已经和旧的MediaCodec实例绑定了。如果你复用旧Surface对象去重新配置CaptureSession会有概率出现“BufferQueue已经被废弃”的崩溃日志——这个崩溃通常在Camera2的onConfigureFailed里体现而不是直接抛出Java层异常。正确的做法是每次录制开始前都重新new一个MediaRecorder实例在prepare后重新getSurface()。即使资源开销稍大一些也比排查那种不确定的底层崩溃省时间。另一个隐蔽问题是MediaRecorder在stop之后编码器产生的最后一帧数据可能还没flush完。此时立刻读取文件大小可能是0或偏小。解决思路是在stop返回后不要立刻关闭文件流而是等500ms左右再对文件进行操作。如果有更严格的需求可以考虑在stop回调后使用MediaExtractor轮询文件是否已经有了可读的轨道信息。5.3 权限与后台限制的实战提醒Android 11API 30以后如果App在后台调用MediaRecorder录制会抛出SecurityException。即便是前台Service如果相机权限不是“仅前台使用”在某些OEM ROM上也会录不出来。测试时建议将权限设为“仅使用期间允许”并且确保录制过程中App保持在前台或持有前台Service。另外要注意麦克风权限的独立弹窗Android 13以上摄像头和麦克风权限是分开的用户可能在系统设置里只关掉其中一个。初始化MediaRecorder前应当同时检查CAMERA和RECORD_AUDIO两个权限。缺少麦克风权限时setAudioSource不会立即崩溃但prepare后start时可能找不到音频采集设备录制出来的文件没有音轨。5.4 进程级异常时的清理策略在一些特殊场景里例如录制过程中被系统杀掉进程或内存不足导致录制线程崩溃MediaRecorder持有的Camera和编码器可能没有被正确释放导致下一次启动时摄像头设备一直处于占用状态。建议做一个简单的守护策略在Application的onCreate里注册一个ActivityLifecycleCallbacks监听进程被杀前的状态同时在每次录制前清理一次残留的MediaRecorder和CameraDevice引用。如果出现“Camera is being used after CameraDevice.close()”错误多半是异步回调晚于close时机此时可以把所有Camera2回调里的操作都放入主线程Handler保证与关闭动作串行。6. 进阶扩展除了MediaRecorder还有哪些录制方案可选6.1 MediaRecorder vs MediaCodec MediaMuxerMediaRecorder的优势是封装度高、API简单、内部自带音视频同步逻辑劣势是灵活性差很难插入滤镜、特效、水印。MediaCodec MediaMuxer则允许你完全控制编码器配置、帧处理管道、YUV/RGB转换等。选择建议如果想快速实现“点击录制、保存MP4”优先MediaRecorder。如果需要实时美颜、滤镜、AI特效或者要在编码前做图像处理必须选用MediaCodec Surface组合。如果需要低延迟直播流推送MediaCodec RTMP推流是主流路径MediaRecorder在这种场景下不合适。6.2 CameraX VideoCapture的封装价值CameraX在MediaRecorder之上做了一层非常友好的封装内部处理了分辨率协商、旋转、预卷、生命周期绑定等大量细节。如果你不需要在Camera2级别做精细化控制用CameraX能把开发成本降低一半以上。但CameraX有一个限制它的VideoCapture目前对多路输出同时预览、录制、分析有性能要求低端设备上同时开三个用例可能会掉帧。如果你在项目里遇到这种情况可以降级到Camera2的TEMPLATE_RECORD把预览和录制Surface合并成两个Target再用ImageReader做帧分析这样对硬件的压力会小一些。6.3 子流程协作多媒体采集与脚本自动化如何打通文章标题提到的热词里有一条“影刀在python中调用其他子流程”虽然影刀是RPA工具不直接参与Android录制但这个思路可以迁移到端侧自动化测试场景中。比如你在做相机录制功能的自动化回归测试时可以用Python脚本调用Appium或UIAutomator来触发App内的录制按钮再通过MediaMetadataRetriever读取录制产物参数校验分辨率、码率、时长是否符合预期。这样整个验证链路就跑通了录制入口用UI自动化触发录出的文件由多媒体API校验省去人工一遍遍点击和肉眼检查的时间。参考这一思路我在实际项目中搭过一套简易的图像录制自动验证脚本核心流程是Python通过ADB启动App并点击录制按钮App内部调用MediaRecorder录制10秒视频ADB拉取视频文件到本地Python调用FFprobe解析视频流和音频流参数将参数与预先配置的期望值进行比对输出测试报告。这其实就体现了“主流程 子流程”的调度思想主流程是录制功能本身子流程是验证、上报、统计。做多媒体开发时不要只盯着单个API把外部调用链也纳入考虑整个项目的质量和效率都会上一个台阶。7. 我的实操体会踩了这么多坑之后我最大的体会是MediaRecorder这条链路一点都不“高级”它只是把复杂藏在了底层你没摸清内部规律的时候系统会通过异常、崩溃、黑屏来反复提醒你。但只要理解了状态机、Surface流转、时间戳协同这三点绝大多数问题都能快速定位。真的别一上来就贴代码先想清楚每一步发生的时机和对象比多写几百行代码都管用。最后再分享一个小技巧调试录制问题时把MediaRecorder的详细日志打开会非常有帮助。可以通过adb shell setprop log.tag.MediaRecorder DEBUG开启日志配合Android Studio的Logcat过滤你会看到prepare()内部做了哪些校验、start()时音频源和视频源是否都处于Ready状态、编码器实际使用的码率是多少。这些信息比我上面写的任何一条排查建议都更具体几乎是一步到位的定位方式。
返回列表