ARTICLE DETAIL

资讯详情

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

Android Mpeg4Writer深度剖析:MP4容器格式与录制原理

Android Mpeg4Writer深度剖析:MP4容器格式与录制原理 做Android音视频开发的这十来年里我被问得最多的一个问题不是怎么调通一个播放器而是同一个mp4文件为什么在手机自带播放器里播得好好的拷到电脑上却提示损坏又或者明明录了30分钟的视频播放器显示的总时长只有3分钟拖动进度条直接卡死。这些问题十有八九都出在对MP4容器格式的理解不够尤其是写文件的那一层——在Android系统里这个角色叫Mpeg4Writer。这篇内容围绕Android多媒体录制展开重点拆解MP4格式的底层结构和Mpeg4Writer这个核心写入器的设计思路。适合三类人看一是应用层开发想彻底搞明白MediaRecorder和MediaMuxer背后的文件到底怎么写出来的二是做Android framework定制需要改录制逻辑或者定位录制文件损坏问题的三是刚入门音视频想知道m3u8转mp4、直播录制这些工具背后依赖的容器原理的同学。我会结合自己实际调试录制的经验把代码里看不到的细节一并说清楚。1. MP4格式核心设计解析1.1 为什么Android录制首选MP4先讲一个最基础的问题录像是视频编码器和音频编码器共同工作的结果编码器输出的是H.264裸码流和AAC裸码流这两者本身不携带时间信息、不包含画面尺寸、也没有索引结构。如果直接把这两路裸流写进同一个文件播放器根本不知道哪一帧对应哪个时间点也没法快速定位到第10分钟的画面。MP4在这里扮演的是“容器”的角色相当于一个带目录的文件柜。视频流、音频流、字幕、时间戳、编码参数全部被装进这个柜子里并且按规则排列好。Android录制系统选择MP4作为默认封装格式核心原因有三点第一MP4是ISO/IEC 14496-12定义的国际标准几乎所有平台、播放器、剪辑软件都支持兼容性极佳第二MP4的box结构支持流式播放和随机定位moov box里保存了完整的采样索引播放器可以秒开、快进第三它对metadata的承载能力很强录制时间、旋转角度、厂商信息都能写进去。市场上有大量m3u8转mp4的免费工具原理也在这里。m3u8是HLS流媒体协议的分片索引它指向的是一堆ts片段每个ts里封装的是同一份H.264/AAC码流。转成mp4时绝大多数工具并不是重新编码而是把ts里的裸流拆出来按照MP4的box规则重新封装一次这样得到的文件体积几乎不变却获得了mp4的通用性和可编辑性。1.2 MP4的Box结构拆解MP4文件没有“段”的概念它是由一个个box也叫atom串起来的。每一个box都遵循同样的头部格式前4个字节是size表示整个box的字节数包含头自身紧接着4个字节是type用四字符码标识box类型比如ftyp、moov、mdat。如果你的size字段值是1说明后面还有一个8字节的大尺寸扩展字段用于超大文件如果type是uuid说明这是一个自定义扩展box。可以说解析MP4文件就是在递归地读取box遇到容器box就钻进去继续解析子box。关键box必须烂熟于心Box名称作用备注ftyp文件类型与兼容性声明位于文件开头声明major_brand和兼容brand列表moov元数据容器存放所有轨道信息、时长、采样索引mdat媒体数据实体存放所有编码后的音视频帧体积最大free / skip占位或填充常常被用于预留空间录制容器也用它做对齐mvex扩展元数据如果视频是fragmented MP4会有mvex和moofmoov是整个文件的灵魂。打开它以后能看到mvhd电影头记录整体时长和timescale、trak轨道容器。每个trak里面又包含tkhd轨道头记录轨道ID、宽高、mdia媒体信息容器。mdia往下是minf媒体信息、stbl采样表而stbl恰恰决定了一个MP4文件是否“健康”。stbl里最常见的几个box值得记一下stsd采样描述记录编码格式、分辨率、声道数、编码器配置信息比如H.264的avcC就在这里里面封装了SPS和PPS。stts时间戳到采样序号的映射表播放器靠它计算每个采样应该显示的时间。stss同步采样表记录哪些是关键帧I帧。没有它播放器就不知道快进时该从哪一帧开始解码。stsz每个采样的字节大小用于定位和解码。stco / stco64chunk的偏移地址指每个chunk在文件中的物理位置。stsc采样到chunk的映射关系。之前有人问我为什么同一个mp4文件VLC能打开Windows Media Player打不开换了个播放器又能打开了大概率就是stbl里的某些表写得不严谨解析器宽容度不同导致播放结果天差地别。这也是Mpeg4Writer这类写入器必须严格按规范生成每个子box的原因。1.3 moov前置与文件可播性的关系很多刚接触MP4的人会忽略一个细节moov在文件里的物理位置。MP4规范并没有强制规定moov必须放在文件开头它可以出现在mdat之前也可以出现在文件末尾。但如果moov在文件末尾播放器要获取文件时长和采样索引就必须先读完整文件这对本地文件还算勉强对在线播放就是灾难——你得等整个文件下载完才能开始播放。所以流媒体场景下moov必须前置。Android的Mpeg4Writer在正常录制时会做两手准备一是在文件开头预留空闲空间二是在stop之后重新整理文件布局保证moov位于mdat之前。这样录制出来的文件能被系统播放器秒开。但大家反过来想一下如果录制过程中进程被杀、断电、拔存储卡moov就根本没有被正确写入或者写入了一半。这时候文件里很可能只有一个巨大的mdat没有任何索引播放器探测不到时长自然就认为文件损坏了。我用一个不太严谨但很好理解的类比mp4文件就像一本没有目录就装订成册的书moov就是目录。目录放在开头翻开书就能找到第几章在第几页目录放在末尾你至少要翻到最后一页才能开始阅读如果装订时目录被撕掉了这本书基本等于废纸。平时我们做录制开发最怕的就是“文件半残”——数据都有就是打不开只能靠ffmpeg硬修。2. Mpeg4Writer核心原理分析2.1 Mpeg4Writer在Android框架中的定位Mpeg4Writer是一个继承了MediaWriter的C类实现在frameworks/av/media/libstagefright/mpeg4writer/目录下。它在Android音视频录制链路中的位置刚好处于StagefrightRecorder和底层编码器之间。完整调用链一般是这样应用层创建的MediaRecorder通过JNI进入Native层由StagefrightRecorder负责编排整个录制流程。StagefrightRecorder会为视频源、音频源分别创建编码器节点然后把这些编码后的media source交给Mpeg4Writer由它统一接收并写入MP4文件。在Android 7.0之后应用层可以直接用MediaMuxer这个Java接口完成同样的事情但MediaMuxer内部封装的下层实现依然还是MPEG4Writer这套写文件逻辑只是API形态变了。所以你虽然平时在应用层写的是MediaRecorder或MediaMuxer但真正决定mp4文件格式质量、决定moov能不能正确生成、决定音视频轨道参数怎么落盘的是Mpeg4Writer。定位录制文件问题绕不开这一层。Mpeg4Writer暴露给上层的关键接口就四个addSource()添加一路媒体源start()启动写入线程stop()停止写入并生成moovpause()暂停录制。看起来很简单但内部的麻烦事远比想象得多。2.2 从addSource到Track建立调用addSource时Mpeg4Writer并不会立刻开始写数据它只做一件事把MediaSource保存下来并通过getFormat()拿到该路的元数据。这些元数据以KeyValueMap形式存储里面包含MIME类型比如video/avc对应H.264audio/mp4a-latm对应AAC、宽高、比特率、帧率、采样率、声道数等。拿到元数据后Mpeg4Writer会在内部为这路源建立一个StoredTrack对象。StoredTrack保存了源、格式、轨道ID以及后续要写入的所有chunk数据。需要注意的是addSource本身是有顺序的第一路添加的源会成为track 0第二路是track 1。在MP4规范里每路track要有唯一的track_IDMpeg4Writer内部用的是从1开始的自增ID但写进tkhd时会再做一次映射保证ID在文件内唯一。这里我要强调一个实际操作中的经验addSource的先后顺序会直接影响最终文件里轨道的排列但不会影响播放器默认选择哪条音轨。真正决定播放器默认音轨的是tkhd里的alternate_group和mdis里的语言字段。在定制ROM时如果想让视频默认播放英文字幕而非中文字幕要改的是这些字段而不是调整addSource的顺序。StoredTrack真正开始收集数据是在start()之后。每一路source都会有自己的抓取线程不断从编码器读取编码后的MediaBuffer再封装成MediaChunk存入列表。MediaChunk是Mpeg4Writer内部定义的结构体记录样本的偏移、大小、时间戳等关键信息。2.3 写入流程的线程模型先看start()触发后的整体线程模型。Mpeg4Writer会启动一个主写入线程同时为每一路StoredTrack启动一个抓取线程。主写入线程负责从各路的chunk列表里取数据写出抓取线程负责从编码器源读取数据并填充chunk列表。抓取线程的核心逻辑可以理解为从MediaSource读取一帧编码数据得到一个MediaBuffer。解析MediaBuffer里的时间戳信息包括pts和dts。判断这一帧是否是关键帧如果是记录到同步帧表对应stss。把buffer数据拷贝进新的MediaChunk并记录其在文件中的偏移。主写入线程要做的事更多它需要为每个track维护已写入偏移协调多路数据的交错布局。MP4规范里多个track的数据是以chunk为单位交错存放的比如视频chunk、音频chunk交替出现。交错粒度直接影响seek精度和流式播放体验。Mpeg4Writer默认会参考各track的“chunk时长”来控制交错保证每隔一段较短时间就能同时拿到音视频数据。stop()的过程比start()复杂得多。Mpeg4Writer必须等所有抓取线程退出把剩余chunk全部flush到文件然后开始构建moov。构建moov的原材料正是前面收集的采样表、同步帧表、chunk偏移和时间戳表。全部生成后还要把这坨元数据搬到文件前部并对文件内所有偏移做修正。这也是为什么一个正常录制完成的mp4可能比裸流总和还要大几十KB——多出来的就是moov和各类辅助box。2.4 PTS/DTS与采样时序音视频同步的根源在于时间戳。Mpeg4Writer内部统一使用microsecond微秒作为时间戳单位在写入stts时再根据mvhd的timescale换算。先说视频。H.264编码器有可能输出B帧也就是双向预测帧。B帧的显示顺序和解码顺序不一致所以必须区分pts展示时间戳和dts解码时间戳。Android上常用的编码器如果设置为Baseline Profile不会输出B帧dts和pts数值是一致的但High Profile编码器允许输出B帧这时候写入stts用的顺序必须是dtsstss标记的同步帧必须是解码顺序下的关键帧。如果你手动做过MP4封装最容易翻车的点就在这里——把带B帧的H.264按显示顺序写入播放器解码必然花屏或者卡顿。音频的时间戳相对简单。AAC编码器的输入是PCM每帧固定包含1024个采样所以一帧AAC的时长是1024 / sampleRate 秒。比如48kHz采样率每帧时长约21.33毫秒。Mpeg4Writer计算音频PTS的基准逻辑不是从0开始的。第一个音频采样通常在时间轴上有一个偏移这个偏移往往来自编码器内部的delay或者媒体源启动时刻的差异正确处理它才能保证音画同步。还有一个必须知道的点是CSDCodec Specific Data。对视频轨Mpeg4Writer需要通过format里的kKeyCSD拿到SPS和PPS封装成avcC box对音频轨需要拿到AudioSpecificConfig封装成esds box。如果这些配置信息缺失或顺序错误播放器可能能播画面却不出声或者干脆识别不了编码格式。3. 实操Android录制MP4的完整链路3.1 MediaRecorder的常用参数与底层映射在应用层写MediaRecorder时我们设置的每一项其实都会映射到Mpeg4Writer的元数据里。举个例子你调用setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)时底层会创建Mpeg4Writer而不是OggWriter或者WebmWriter。你调用setVideoEncoder(MediaRecorder.VideoEncoder.H264)时底层会配置一个MediaCodec编码器并把这个编码器的输出作为Mpeg4Writer的视频源。setVideoSize()决定了编码器输出的分辨率同时也决定了stsd里tkhd中的宽高字段。setVideoFrameRate()设置帧率会进入编码器的输入参数和stts的timescale计算。setVideoEncodingBitRate()设置码率影响编码器码控但不直接影响容器结构。有一个参数特别值得注意color format。MediaRecorder在底层会把输入surface的像素格式转成编码器支持的格式最常见的转换目标是OMX_COLOR_FormatYUV420SemiPlanar也就是NV12。如果你直接从Camera拿YUV做硬编格式匹配不上的话编码器直接返回错误。Mpeg4Writer对颜色格式没有感知它只负责封装编码器输出的H.264码流但颜色格式错了你连编码器输出都拿不到问题会提前暴露。用MediaMuxer手动录制时流程也类似。你先用MediaCodec创建编码器在输出格式变化时通过getOutputFormat拿到csd-0和csd-1调用MediaMuxer.addTrack()添加轨道时传入这些信息后续通过writeSampleData把编码后的buffer写进去。这套流程在Java层看起来和Mpeg4Writer毫无关系但实际上MediaMuxer内部会把这些调用翻译成Mpeg4Writer的操作。3.2 关键帧间隔与H.264码流适配录制过程中影响文件可用性的一个隐藏参数是关键帧间隔。MediaRecorder没有直接的setKeyFrameInterval接口它是通过setVideoFrameRate和内部编码器配置间接控制的。底层编码器的I帧间隔通常由i-frame-interval或者gop-size参数决定常见默认值是1秒到2秒一个I帧。I帧为什么重要它直接决定了两个应用场景一是播放器seek的精度seek到某个时间点后播放器需要定位到最近的I帧开始解码I帧间隔越大seek后画面恢复越快还是越慢其实是大间隔会导致seek后要解码更多帧才能显示目标画面肉眼感觉就是卡顿二是录制文件的容错性如果文件损坏了一部分从最近的I帧开始仍然能恢复部分画面I帧越少坏一段就黑一片。Mpeg4Writer在写入时会在stss中记录每个I帧的偏移。这需要编码器输出的MediaBuffer带有IS_SYNC_FRAME标志。如果你自己写了一个MediaSource塞给Mpeg4Writer却没有正确标记同步帧生成的stss就会是空的很多播放器会直接判定文件无法seek甚至无法播放。这个坑我建议任何做framework定制的人都记住同步帧标记是MediaSource输出的基本义务不是Mpeg4Writer的兜底能力。音频这边AAC裸流最常见的封装形态是ADTS每帧前面带有7字节头。Mpeg4Writer要求的是原始AAC格式AudioSpecificConfig 裸数据所以如果你从AudioRecord采集后自己做了ADTS封装再塞给Mpeg4Writer它会把ADTS头当音频数据写进去播放时就会有吱吱的爆音或者速率异常。正确的做法是在编码器外面拆掉ADTS头或者直接使用MediaCodec的AAC输出格式。3.3 录制完成的MP4文件如何快速自检排查所有mp4问题时我第一个动作永远是看文件结构和元数据。这里给一套我自己常用的自检流程。先用ffprobe看基本信息ffprobe -v trace -show_format recorded.mp4重点关注format里的duration、start_time、bit_rate、nb_streams。如果duration和你的录制时长对不上moov多半有问题。再看streams里的codec_name、profile、width、height、sample_rate、channels、time_base。如果视频轨道是high profile确认编码器是否支持B帧而容器是否带dts就变得关键。然后看box布局推荐用mp4dump或者hexdump手动校验mp4dump recorded.mp4检查文件开头是不是ftypftyp之后是不是moovmoov后面才是mdat。如果moov跑到文件尾部说明录制过程可能中断过或者写入器用了兼容模式。再进moov里确认trak数量、stts/stss是否为空。stss为空是seek失效的经典原因。线上产品如果不好装ffprobe也可以用Android自带的MediaMetadataRetriever获取时长和宽高再用MediaExtractor的track count验证轨道数量。不过这些接口都是解析器层面的东西某些损坏文件在解析器里能读到部分数据和用ffprobe检查box结构得到的结果会有差异所以定位问题我优先用ffprobe。4. 录制质量与性能优化的四个关键点4.1 分辨率、帧率、码率怎么搭配录制配置没有万能参数但有一个大致的参考坐标系。分辨率决定单帧数据量帧率决定每秒处理多少帧码率决定编码器允许用多少bit来表达这些信息。三者失衡的结果要么是画质糊要么是文件膨胀要么是编码器跟不上导致掉帧。我这里给出一个基于个人项目经验的参考表压制场景和录制场景基本通用分辨率建议帧率H.264码率推荐范围主要使用场景480p (720x480)25-30fps1.5-2.5 Mbps低端机录像、监控720p (1280x720)30fps4-6 Mbps手机录像、直播备录1080p (1920x1080)30fps8-12 Mbps主流短视频、vlog4K (3840x2160)30fps35-60 Mbps专业相机、高端旗舰机有一个容易被忽略的坑编码器对分辨率有对齐要求。绝大多数硬件H.264编码器要求宽高至少是2的倍数某些还要求16的倍数。Android的MediaCodec在configure时如果传入奇数宽高很多设备会直接抛异常或者输出亮度偏移的画面。Mpeg4Writer对宽高本身不做合法性检查它只会把值写进stsd所以你在上层不处理好对齐文件照样能生成画面却是花的。帧率的选择也影响文件时长精度。MP4的mvhd timescale默认是1000如果你录制的实际帧率是29.97fps这类非整数帧率时长计算会产生微小的累计误差。录制非常长比如2小时的视频时这种误差会被放大seek到最后几秒可能出现偏差。解决思路是在自定义写入器时根据视频帧率设置更合理的timescale比如90000就是视频领域很常用的折中值。4.2 音视频同步与时钟基准我先给出一个最基本的规律视频PTS由编码器输入的时间戳决定音频PTS由采样累积时长计算得到两者最终都要落到同一个时间基准上。用MediaCodec做视频编码时每帧的presentationTimeUs需要你自己传入。如果你是从Camera来的帧通常会取系统时钟或者统一的技术启动时间作为基准。用AudioRecord录制时每读到一帧PCM会计算出这一帧的PTS frameIndex * 1024 * 1e6 / sampleRate。只要编码和封装端不额外引入偏移最终文件和直播流的表现都会正常。最容易出问题的是两个场景一是录制过程中切摄像头或暂停恢复时间戳基准被重新校准导致文件里出现时间倒退或断层。Mpeg4Writer在pause/resume时走了一套相对复杂的逻辑目的就是平滑这条时间线。二是视频源本身自带负偏移比如某些设备编码器输出的缓冲延迟导致第一帧PTS是负值。如果你不做处理直接写进mp4播放器解析出来的start_time就是负数很多播放器会显示时长异常或者干脆不播。另外注意音频和视频轨道的交错写入会影响音画同步的“稳健度”。如果音频chunk集中写在文件末尾播放器读取时会频繁在文件头尾之间跳转弱网或磁盘慢的机器就会出现声音卡顿。Mpeg4Writer内部有chunk交错逻辑但如果你用MediaMuxer乱序写sample得到的结果可能就是播放器在音频上卡顿。务必保证writeSampleData的调用顺序和时间戳单调递增。4.3 Buffer与内存管理Mpeg4Writer内部使用MediaChunk收集数据每个Chunk保存一帧或一组样本的容量、偏移和时间戳。录制过程中内存占用取决于MediaChunk列表的长度也就是编码输出和磁盘写入之间的追赶速度。如果设备磁盘写入慢而编码器输出快MediaChunk列表会不断膨胀极端情况下OOM。Mpeg4Writer代码里做了几层保护一是限制单个track的chunk数量超过阈值后会主动阻塞读取线程让编码器暂停输出二是控制交错写入时每轮写入的样本数避免一次性把整个GOP都攒在内存里。应用层使用MediaMuxer时对应的策略是控制writeSampleData的节奏不要在编码器回调里一次性把所有缓冲都发出去。这里还要提醒一句关于文件大小限制的问题。录制类应用几乎都有“单个文件不超过4GB”的需求这来自FAT32文件系统限制再加上MP4的stco偏移字段本身是32位的。虽然stco64扩展可以解决偏移问题但很多老播放器不支持。Mpeg4Writer在录制过程中支持设置最大文件大小达到阈值后会停止写入并触发stop回调。如果你的应用做长时录制不考虑这个限制文件超过4GB后要么写入失败要么播放器不认。4.4 降低功耗与CPU占用录制视频是手机里功耗最大的场景之一。Mpeg4Writer本身不参与编码计算但它的写入策略会影响CPU和存储系统的压力。最实用的优化路径是使用Surface输入模式。Camera传Surface给编码器时数据在图形管线内部流转不需要把YUV从GPU拷贝到CPU再传回编码器省掉了一次完整的内存拷贝。Mpeg4Writer在收到编码后的码流后尽量直接引用buffer而不做深拷贝也能减少内存带宽消耗。编码器的参数对功耗影响很大。帧率和码率设置过高编码器的计算负载直线上升发热和掉帧跟着就来。在定制录制方案时可以考虑限制录制时的CPU大核使用或者通过Camera2的StreamingRequest把帧率限制在合理范围。另外H.264的level设置如果过高很多解码器和编码器需要更高功耗来维持吞吐一般1080p30选择Level 4.0即可4K30才需要Level 5.1以上。5. 常见问题与排查技巧实录5.1 问题速查表前面写的都是原理这一节直接落到排查层面。下面表格里的每一条都是我在实际项目里遇到过的真实问题。现象可能原因排查思路处理方案录制完成文件播放器不认moov缺失或损坏录制中断ffprobe查box结构查看文件尾部是否有moov用ffmpeg -c copy重封装或应用层重录视频时长显示错误mvhd timescale设置不对或PTS异常跳跃检查start_time、duration拉流看时间戳自定义写入器重新计算timescale画面花屏/绿屏分辨率未对齐、SPS/PPS丢失、csd顺序错误检查stsd里的avcC配置比对编码器输出csd按编码器要求对齐宽高修复csd注入没有声音但画面正常AAC配置错误、ADTS头未剥离、音频轨道PTS异常看esds的AudioSpecificConfig检查音频PTS正确剥离ADTS重新封装音频轨道快进seek卡顿或黑屏stss为空或关键帧标记错误检查stss表是否有数据在MediaSource层正确标记SYNC_FRAME录到一半文件大小异常4GB偏移限制、stco溢出、写入线程中断用ffprobe看轨道的总大小和偏移启用stco64或限制单文件大小做分段音画不同步越来越严重音频和视频时钟基准不一致拉取录制流比较两路PTS的差值统一时间基准检查pause/resume逻辑5.2 排查工具与日志定位Android的MediaRecorder和MediaMuxer在运行时会输出详细的日志。在跑framework调试版本或者带verbose的系统版本时你能在logcat里看到Mpeg4Writer打出的track信息包括每路的MIME、宽高、码率。开日志的命令是adb logcat -v threadtime | grep -E Mpeg4Writer|StagefrightRecorder|MediaCodec如果怀疑是编码器输出异常可以同时抓MediaCodec的buffer信息。很多录制问题其实发生在编码阶段不是封装阶段但表面上看起来像mp4文件坏了。对于native crash建议抓取tombstone或者用debuggerd导出调用栈。Mpeg4Writer的崩溃经常集中在stop阶段因为这时要处理大量chunk和偏移计算一旦有野指针就会挂。复现时最好开着adb shell setprop debug.debuggerd.wait_for_gdb 1这样能拿到比较完整的native栈信息。5.3 两个实测案例复盘案例一某项目录制10分钟视频播放器显示时长只有3分钟。用ffprobe看发现mdat里明明有足够的数据量但stts表只记录了一小段。最终定位是stop()被提前触发抓取线程还没有把全部chunk送进写入队列moov就已经生成了。简单说就是录制线程和停止线程之间出现了竞态条件。修复方式是保证stop前完整flush所有MediaSource的MediaBuffer再通知Mpeg4Writer停止而不是直接掐断source。案例二某ROM录制1080p高帧率视频在转码平台处理后音画不同步。用时间戳分析工具拉取两路PTS发现视频轨第一帧PTS是3000us音频轨第一帧PTS是0us而整个录制过程中这个差值一直保持。这个偏移来自编码器的初始缓冲造成音画表现上差了一帧的观感。修复方案是给视频轨统一增加一个初始偏移校正让它和音频轨从同一根时间轴上起算。这些案例都说明一个共性Mpeg4Writer只是把上层提供的时间戳和编码数据规整地放进MP4容器如果上游数据本身就带病它不会替你纠正。做录制开发对时间戳的敬畏要放在第一位。我个人在实际操作中的体会是排查MP4录制问题最忌讳“看文件能不能播”这个单一判断标准。能播只能说明播放器宽容并不代表文件规范。真正要管的是box结构、时间戳曲线、关键帧间隔、csd完整性这几件事。如果你准备长期搞Android音视频建议把ffprobe、mp4dump、MediaExtractor这几个工具用熟再对照Mpeg4Writer源码把每个box的生成逻辑读一遍很多问题当场就能定位不用靠猜。
返回列表